mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) move client code to core
Summary: This moves all client code to core, and makes minimal fix-ups to get grist and grist-core to compile correctly. The client works in core, but I'm leaving clean-up around the build and bundles to follow-up. Test Plan: existing tests pass; server-dev bundle looks sane Reviewers: dsagal Reviewed By: dsagal Differential Revision: https://phab.getgrist.com/D2627
This commit is contained in:
145
app/client/ui2018/search.ts
Normal file
145
app/client/ui2018/search.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* Search icon that expands to a search bar and collapse on 'x' or blur.
|
||||
* Takes a `SearchModel` that controls the search behavior.
|
||||
*/
|
||||
import { createGroup } from 'app/client/components/commands';
|
||||
import { reportError } from 'app/client/models/AppModel';
|
||||
import { SearchModel } from 'app/client/models/SearchModel';
|
||||
import { cssHoverCircle, cssTopBarBtn } from 'app/client/ui/TopBarCss';
|
||||
import { colors } from 'app/client/ui2018/cssVars';
|
||||
import { icon } from 'app/client/ui2018/icons';
|
||||
import { dom, input, styled } from 'grainjs';
|
||||
import { noTestId, TestId } from 'grainjs';
|
||||
import debounce = require('lodash/debounce');
|
||||
|
||||
export * from 'app/client/models/SearchModel';
|
||||
|
||||
const EXPAND_TIME = .5;
|
||||
|
||||
const searchWrapper = styled('div', `
|
||||
display: flex;
|
||||
flex: 0 0 initial;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid transparent;
|
||||
padding: 16px;
|
||||
width: 50px;
|
||||
height: 100%;
|
||||
max-height: 50px;
|
||||
transition: width 0.4s;
|
||||
&-expand {
|
||||
width: 100%;
|
||||
border: 1px solid grey;
|
||||
}
|
||||
`);
|
||||
|
||||
const expandedSearch = styled('div', `
|
||||
display: flex;
|
||||
flex-grow: 0;
|
||||
align-items: center;
|
||||
width: 0;
|
||||
opacity: 0;
|
||||
transition: width ${EXPAND_TIME}s, opacity ${EXPAND_TIME / 2}s ${EXPAND_TIME / 2}s;
|
||||
.${searchWrapper.className}-expand > & {
|
||||
width: auto;
|
||||
flex-grow: 1;
|
||||
opacity: 1;
|
||||
}
|
||||
`);
|
||||
|
||||
const searchInput = styled(input, `
|
||||
outline: none;
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
padding-left: 4px;
|
||||
box-sizing: border-box;
|
||||
width: 0;
|
||||
transition: width ${EXPAND_TIME}s;
|
||||
.${searchWrapper.className}-expand & {
|
||||
width: 100%;
|
||||
}
|
||||
`);
|
||||
|
||||
const cssArrowBtn = styled('div', `
|
||||
font-size: 14px;
|
||||
padding: 3px;
|
||||
cursor: pointer;
|
||||
margin: 2px;
|
||||
visibility: hidden;
|
||||
|
||||
&.disabled {
|
||||
color: ${colors.darkGrey};
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.${searchWrapper.className}-expand & {
|
||||
visibility: visible;
|
||||
}
|
||||
`);
|
||||
|
||||
const cssCloseBtn = styled(icon, `
|
||||
cursor: pointer;
|
||||
`);
|
||||
|
||||
export function searchBar(model: SearchModel, testId: TestId = noTestId) {
|
||||
const commandGroup = createGroup({
|
||||
find: () => { inputElem.focus(); inputElem.select(); },
|
||||
// On Mac, Firefox has a default behaviour witch causes to close the search bar on Cmd+g and
|
||||
// Cmd+shirt+G. Returning false is a Mousetrap convenience which prevents that.
|
||||
findNext: () => {model.findNext().catch(reportError); return false; },
|
||||
findPrev: () => {model.findPrev().catch(reportError); return false; },
|
||||
}, null, true);
|
||||
|
||||
const toggleMenu = debounce((_value?: boolean) => {
|
||||
model.isOpen.set(_value === undefined ? !model.isOpen.get() : _value);
|
||||
}, 100);
|
||||
const inputElem = searchInput(model.value, {onInput: true},
|
||||
{type: 'text', placeholder: 'Search in document'},
|
||||
dom.on('blur', () => toggleMenu(false)),
|
||||
dom.onKeyDown({
|
||||
Enter: () => model.findNext(),
|
||||
Escape: () => toggleMenu(false),
|
||||
// Catch both Tab and Shift+Tab to prevent focus entering unrelated editable label.
|
||||
Tab: () => toggleMenu(false),
|
||||
}),
|
||||
dom.on('focus', () => toggleMenu(true)),
|
||||
commandGroup.attach(),
|
||||
);
|
||||
|
||||
// Releases focus when closing the search bar, otherwise users could keep typing in without
|
||||
// noticing.
|
||||
const lis = model.isOpen.addListener(val => val || inputElem.blur());
|
||||
|
||||
return searchWrapper(
|
||||
testId('wrapper'),
|
||||
searchWrapper.cls('-expand', model.isOpen),
|
||||
dom.autoDispose(commandGroup),
|
||||
dom.autoDispose(lis),
|
||||
cssHoverCircle(
|
||||
cssTopBarBtn('Search',
|
||||
testId('icon'),
|
||||
dom.on('click', () => inputElem.focus())
|
||||
)
|
||||
),
|
||||
expandedSearch(
|
||||
testId('input'),
|
||||
inputElem,
|
||||
cssArrowBtn('\u2329',
|
||||
testId('prev'),
|
||||
// Prevent focus from being stolen from the input
|
||||
dom.on('mousedown', (event) => event.preventDefault()),
|
||||
dom.on('click', () => model.findPrev()),
|
||||
dom.cls('disabled', model.noMatch)),
|
||||
cssArrowBtn('\u232A',
|
||||
testId('next'),
|
||||
// Prevent focus from being stolen from the input
|
||||
dom.on('mousedown', (event) => event.preventDefault()),
|
||||
dom.on('click', () => model.findNext()),
|
||||
dom.cls('disabled', model.noMatch)),
|
||||
cssCloseBtn('CrossSmall',
|
||||
testId('close'),
|
||||
dom.on('click', () => toggleMenu(false)))
|
||||
)
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user