Neutrino-UI
Neutrino-UI
Simple Select
1type OptionItem = {
2 id: string | number;
3 value: string | number;
4};
5
6export interface ISimpleSelectProps extends Omit<React.HTMLProps<HTMLDivElement>, 'value' | 'onSelect'> {
7 hasError?: boolean;
8 value?: string | number;
9 selectInputStyles?: SerializedStyles;
10 optionsListStyles?: SerializedStyles;
11 optionStyles?: SerializedStyles;
12 options?: OptionItem[];
13 onSelect: (event?: React.PointerEvent<HTMLLIElement>) => void;
14}
1import {SimpleSelect} from 'neutrino-ui';
2
3const optionsList = [
4 {id: -1, value: 'All items'},
5 {id: 1, value: 'Item 1'},
6 {id: 2, value: 'Item 2'},
7 {id: 3, value: 'Item 3'},
8 {id: 4, value: 'Item 4'},
9 {id: 5, value: 'Item 5'},
10 {id: 6, value: 'Item 6'},
11 {id: 7, value: 'Item 7'},
12 {id: 8, value: 'Item 8'},
13 {id: 9, value: 'Item 9'},
14 {id: 10, value: 'Item 10'},
15 {id: 11, value: 'Item 11'},
16 {id: 12, value: 'Item 12'},
17];
18
19<SimpleSelect
20 options={optionsList}
21 css={{width: 300}}
22 value={selected}
23 onSelect={handleItemClick}
24 selectInputStyles={css({borderRadius: 8})}
25 optionsListStyles={css({borderRadius: 8, height: 300, overflow: 'auto'})}
26 optionStyles={css({color: 'green'})}
27/>
Themed Simple Select
1import {ThemeProvider} from 'emotion-theming';
2import {createTheme, SimpleSelect} from 'neutrino-ui';
3
4const theme = createTheme({
5 colors: {
6 pageElementsColors: {
7 formElements: '#325b72',
8 selectedItem: '#293676',
9 border: '#0FC0FC',
10 },
11 textColors: {
12 text: '#fff',
13 },
14 },
15 typography: {
16 span: {
17 color: '#fff',
18 fontSize: '14px',
19 },
20 },
21});
22
23const optionsList = [
24 {id: -1, value: 'All items'},
25 {id: 1, value: 'Item 1'},
26 {id: 2, value: 'Item 2'},
27 {id: 3, value: 'Item 3'},
28 {id: 4, value: 'Item 4'},
29 {id: 5, value: 'Item 5'},
30 {id: 6, value: 'Item 6'},
31 {id: 7, value: 'Item 7'},
32 {id: 8, value: 'Item 8'},
33 {id: 9, value: 'Item 9'},
34 {id: 10, value: 'Item 10'},
35 {id: 11, value: 'Item 11'},
36 {id: 12, value: 'Item 12'},
37];
38
39<ThemeProvider theme={theme}>
40 <SimpleSelect
41 options={optionsList}
42 css={{width: 300}}
43 value={selected}
44 onSelect={handleItemClick}
45 selectInputStyles={css({borderRadius: 8})}
46 optionsListStyles={css({borderRadius: 8, height: 300, overflow: 'auto'})}
47 />
48</ThemeProvider>
MultiSelect
Prefix: All items
1import React from 'react';
2import {css} from '@emotion/core';
3import styled from '@emotion/styled';
4import {
5 ArrowIcon,
6 Combobox,
7 useCombobox,
8 useTheme,
9 Span,
10 Dropdown,
11} from 'neutrino-ui';
12
13type OptionItem = {
14 id: string | number;
15 value: string | number;
16};
17
18type Props = {
19 options?: OptionItem[];
20 value?: number[];
21 onSelect: (values: number[]) => void;
22};
23
24const TextBox = styled.div'
25 display: flex;
26 flex-flow: row nowrap;
27 justify-content: flex-start;
28 align-items: center;
29 width: 100%;
30 height: 48px;
31 padding: 12px 16px;
32';
33
34
35function SelectBox({children, styles}: any) {
36 const {handleToggle, isOpen} = useToggle();
37 const {colors} = useTheme();
38 const baseCss = css({
39 border: ' 1px $ {isOpen ? colors.pageElementsColors.activeBorder : colors.pageElementsColors.border} solid ',
40 '&:hover': {cursor: 'pointer', borderColor: colors.pageElementsColors.activeBorder},
41 color: colors.textColors.text,
42 backgroundColor: colors.pageElementsColors.formElements,
43 });
44
45 return (
46 <TextBox onClick={handleToggle} css={[baseCss, styles]}>
47 {children}
48 <ToggleArrowIcon />
49 </TextBox>
50 );
51}
52
53function Select({options, value, onSelect}: Props) {
54 const [selectedChecks, setCheck] = React.useState<number[]>(value);
55 const [selectRect, setSelectRect] = React.useState(null);
56 const theme = useTheme();
57 const mSelectRef = React.useRef<HTMLDivElement>(null);
58 const optionsRef = React.useRef<HTMLDivElement>(null);
59 const {isOpen, handleClose} = useToggle();
60
61 const listBaseCss = css({
62 margin: 0,
63 padding: 0,
64 listStyle: 'none',
65 border: ' 1px $ {theme.colors.pageElementsColors.border} solid ',
66 boxSizing: 'border-box',
67 backgroundColor: theme.colors.pageElementsColors.formElements,
68 width: '100%',
69 height: 200,
70 overflow: 'auto',
71 });
72
73 const optionBaseCss = css({
74 padding: '8px 16px',
75 borderBottom: '1px #ccc solid',
76 margin: 0,
77 color: theme.colors.textColors.text,
78 fontSize: 14,
79 cursor: 'pointer',
80 });
81
82 const handleSelectItem = React.useCallback(
83 (event: React.MouseEvent<HTMLLIElement>) => {
84 const {value} = event.currentTarget;
85 if (selectedChecks.includes(value)) {
86 const updatedChecks = selectedChecks.filter(c => c !== value);
87 setCheck(updatedChecks);
88 } else {
89 setCheck(prev => [...prev, value]);
90 }
91 },
92 [selectedChecks],
93 );
94
95 const applySelect = React.useCallback(() => {
96 onSelect(selectedChecks);
97 handleClose();
98 }, [handleClose, onSelect, selectedChecks]);
99
100 const isSelected = React.useCallback((checkId: number = -1) => selectedChecks.includes(checkId), [
101 selectedChecks,
102 ]);
103
104 React.useEffect(() => {
105 const handleClickOutside = (e: PointerEvent) => {
106 if (e.target instanceof HTMLElement && isOpen) {
107 const optionsList = optionsRef?.current;
108 const selectInput = mSelectRef?.current;
109 if (optionsList?.contains(e.target) || selectInput?.contains(e.target)) {
110 return;
111 }
112 handleClose();
113 }
114 };
115
116 const handleScroll = (e: PointerEvent) =>
117 window.requestAnimationFrame(() => {
118 if (e.target instanceof HTMLElement && isOpen) {
119 const optionsList = optionsRef?.current;
120 if (optionsList?.contains(e.target)) {
121 return;
122 }
123 setSelectRect(mSelectRef?.current.getBoundingClientRect());
124 }
125 });
126
127 if (isOpen) {
128 setSelectRect(mSelectRef?.current.getBoundingClientRect());
129 document.addEventListener('click', handleClickOutside);
130 window.addEventListener('scroll', handleScroll, true);
131 }
132 return () => {
133 document.removeEventListener('click', handleClickOutside);
134 window.removeEventListener('scroll', handleScroll, true);
135 };
136 }, [handleClose, isOpen]);
137
138 return (
139 <div css={{position: 'relative', width: 300}} ref={mSelectRef}>
140 <SelectBox>
141 <Span>Prefix: </Span>
142 <Span>{selectedChecks.length > 0 ? ' ($ {selectedChecks.length}) ' : 'All items'}</Span>
143 </SelectBox>
144 <Dropdown isOpen={isOpen} ref={optionsRef} parentBound={isOpen ? selectRect : undefined}>
145 <div css={listBaseCss}>
146 <ul>
147 {options?.map(option => {
148 return (
149 <li
150 key={option.id}
151 value={option.id}
152 css={[
153 optionBaseCss,
154 css({
155 backgroundColor: isSelected(Number(option.id))
156 ? theme.colors.pageElementsColors.selectedItem
157 : 'transparent',
158 }),
159 ]}
160 onClick={handleSelectItem}
161 >
162 {option.value}
163 </li>
164 );
165 })}
166 </ul>
167 <button onClick={applySelect}>Apply</button>
168 </div>
169 </Dropdown>
170 </div>
171 );
172}
173
174export function MultiSelect(props: Props) {
175 return (
176 <ToggleProvider>
177 <Select {...props} />
178 </ToggleProvider>
179 );
180}
181
182// Somewhere in app...
183<MultiSelect options={optionsList} value={items} onSelect={handleMultiSelect} />