datocms-react-ui 0.3.26 → 0.3.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/dist/cjs/FieldHint/styles.module.css.json +1 -1
  2. package/dist/esm/FieldHint/styles.module.css.json +1 -1
  3. package/package.json +4 -3
  4. package/src/Button/index.tsx +173 -0
  5. package/src/Button/styles.module.css +149 -0
  6. package/src/Button/styles.module.css.json +1 -0
  7. package/src/ButtonGroup/Button/index.tsx +40 -0
  8. package/src/ButtonGroup/Button/styles.module.css +72 -0
  9. package/src/ButtonGroup/Button/styles.module.css.json +1 -0
  10. package/src/ButtonGroup/Group/index.tsx +31 -0
  11. package/src/ButtonGroup/Group/styles.module.css +6 -0
  12. package/src/ButtonGroup/Group/styles.module.css.json +1 -0
  13. package/src/ButtonGroup/index.ts +4 -0
  14. package/src/Canvas/index.tsx +556 -0
  15. package/src/Canvas/styles.module.css +75 -0
  16. package/src/Canvas/styles.module.css.json +1 -0
  17. package/src/ContextInspector/index.tsx +316 -0
  18. package/src/ContextInspector/styles.module.css +90 -0
  19. package/src/ContextInspector/styles.module.css.json +1 -0
  20. package/src/Dropdown/Dropdown.tsx +171 -0
  21. package/src/Dropdown/DropdownContext.tsx +10 -0
  22. package/src/Dropdown/Group.tsx +16 -0
  23. package/src/Dropdown/Menu.tsx +351 -0
  24. package/src/Dropdown/MenuContext.tsx +18 -0
  25. package/src/Dropdown/Option.tsx +148 -0
  26. package/src/Dropdown/OptionAction.tsx +42 -0
  27. package/src/Dropdown/Portal.tsx +46 -0
  28. package/src/Dropdown/Separator.tsx +13 -0
  29. package/src/Dropdown/Text.tsx +8 -0
  30. package/src/Dropdown/index.tsx +26 -0
  31. package/src/Dropdown/styles.module.css +331 -0
  32. package/src/Dropdown/styles.module.css.json +1 -0
  33. package/src/FieldError/index.tsx +10 -0
  34. package/src/FieldError/styles.module.css +6 -0
  35. package/src/FieldError/styles.module.css.json +1 -0
  36. package/src/FieldGroup/index.tsx +25 -0
  37. package/src/FieldGroup/styles.module.css +12 -0
  38. package/src/FieldGroup/styles.module.css.json +1 -0
  39. package/src/FieldHint/index.tsx +10 -0
  40. package/src/FieldHint/styles.module.css +14 -0
  41. package/src/FieldHint/styles.module.css.json +1 -0
  42. package/src/Form/index.tsx +145 -0
  43. package/src/Form/styles.module.css +19 -0
  44. package/src/Form/styles.module.css.json +1 -0
  45. package/src/FormLabel/index.tsx +36 -0
  46. package/src/FormLabel/styles.module.css +31 -0
  47. package/src/FormLabel/styles.module.css.json +1 -0
  48. package/src/Section/index.tsx +104 -0
  49. package/src/Section/styles.module.css +100 -0
  50. package/src/Section/styles.module.css.json +1 -0
  51. package/src/SelectField/index.tsx +244 -0
  52. package/src/SelectInput/index.tsx +233 -0
  53. package/src/SidebarPanel/index.tsx +110 -0
  54. package/src/SidebarPanel/styles.module.css +49 -0
  55. package/src/SidebarPanel/styles.module.css.json +1 -0
  56. package/src/Spinner/index.tsx +68 -0
  57. package/src/Spinner/styles.module.css +31 -0
  58. package/src/Spinner/styles.module.css.json +1 -0
  59. package/src/SwitchField/index.tsx +67 -0
  60. package/src/SwitchField/styles.module.css +25 -0
  61. package/src/SwitchField/styles.module.css.json +1 -0
  62. package/src/SwitchInput/index.tsx +74 -0
  63. package/src/SwitchInput/styles.module.css +100 -0
  64. package/src/SwitchInput/styles.module.css.json +1 -0
  65. package/src/TextField/index.tsx +58 -0
  66. package/src/TextField/styles.module.css +0 -0
  67. package/src/TextField/styles.module.css.json +1 -0
  68. package/src/TextInput/index.tsx +73 -0
  69. package/src/TextInput/styles.module.css +52 -0
  70. package/src/TextInput/styles.module.css.json +1 -0
  71. package/src/Toolbar/Button/index.tsx +32 -0
  72. package/src/Toolbar/Button/styles.module.css +43 -0
  73. package/src/Toolbar/Button/styles.module.css.json +1 -0
  74. package/src/Toolbar/Stack/index.tsx +33 -0
  75. package/src/Toolbar/Stack/styles.module.css +18 -0
  76. package/src/Toolbar/Stack/styles.module.css.json +1 -0
  77. package/src/Toolbar/Title/index.tsx +17 -0
  78. package/src/Toolbar/Title/styles.module.css +12 -0
  79. package/src/Toolbar/Title/styles.module.css.json +1 -0
  80. package/src/Toolbar/Toolbar/index.tsx +112 -0
  81. package/src/Toolbar/Toolbar/styles.module.css +15 -0
  82. package/src/Toolbar/Toolbar/styles.module.css.json +1 -0
  83. package/src/Toolbar/index.ts +8 -0
  84. package/src/base.css +89 -0
  85. package/src/generateStyleFromCtx/index.ts +25 -0
  86. package/src/global.css +23 -0
  87. package/src/icons.tsx +108 -0
  88. package/src/index.ts +23 -0
  89. package/src/mergeRefs/index.ts +8 -0
  90. package/src/useClickOutside/index.ts +30 -0
  91. package/src/useMediaQuery/index.ts +185 -0
  92. package/styles.css +1 -1
@@ -0,0 +1,351 @@
1
+ import React, {
2
+ createRef,
3
+ SyntheticEvent,
4
+ useCallback,
5
+ useContext,
6
+ useEffect,
7
+ useRef,
8
+ useState,
9
+ } from 'react';
10
+ import scrollIntoView from 'scroll-into-view-if-needed';
11
+ import { useInView } from 'react-intersection-observer';
12
+ import { Portal } from './Portal';
13
+ import { useMediaQuery } from '../useMediaQuery';
14
+ import { DropdownContext } from './DropdownContext';
15
+ import { Group } from './Group';
16
+ import { MenuContext } from './MenuContext';
17
+ import { Option } from './Option';
18
+ import s from './styles.module.css.json';
19
+ import { mergeRefs } from '../mergeRefs';
20
+ import classNames from 'classnames';
21
+
22
+ const MenuMobileContainer = ({
23
+ children,
24
+ }: {
25
+ children: React.ReactNode;
26
+ }): JSX.Element => (
27
+ <Portal>
28
+ <div
29
+ className={s['Dropdown__menu__mobile-container']}
30
+ style={{ zIndex: 1000 }}
31
+ >
32
+ <div className={s['Modal__backdrop']} />
33
+ {children}
34
+ </div>
35
+ </Portal>
36
+ );
37
+
38
+ const MenuDesktopContainer = React.forwardRef<
39
+ HTMLDivElement,
40
+ { children: React.ReactNode }
41
+ >(({ children }, ref) => (
42
+ <Portal>
43
+ <div
44
+ className={s['Dropdown__menu-container']}
45
+ style={{ zIndex: 1000 }}
46
+ ref={ref}
47
+ >
48
+ {children}
49
+ </div>
50
+ </Portal>
51
+ ));
52
+
53
+ function getAbsoluteHeight(el: HTMLElement) {
54
+ const styles = window.getComputedStyle(el);
55
+ const margin = parseFloat(styles.marginTop) + parseFloat(styles.marginBottom);
56
+ return Math.ceil(el.offsetHeight + margin);
57
+ }
58
+
59
+ function getAbsoluteWidth(el: HTMLElement) {
60
+ const styles = window.getComputedStyle(el);
61
+ const margin = parseFloat(styles.marginLeft) + parseFloat(styles.marginRight);
62
+ return Math.ceil(el.offsetWidth + margin);
63
+ }
64
+
65
+ function setPosition(
66
+ panel: HTMLElement,
67
+ parent: HTMLElement,
68
+ alignment: 'left' | 'right',
69
+ ) {
70
+ const rect = parent.getBoundingClientRect();
71
+ const height = getAbsoluteHeight(panel);
72
+ const parentWidth = getAbsoluteWidth(parent);
73
+ const width = getAbsoluteWidth(panel);
74
+
75
+ if (alignment === 'left') {
76
+ // eslint-disable-next-line no-param-reassign
77
+ panel.style.left = `${rect.left + window.pageXOffset}px`;
78
+ } else {
79
+ // eslint-disable-next-line no-param-reassign
80
+ panel.style.left = `${
81
+ rect.left + window.pageXOffset + parentWidth - width
82
+ }px`;
83
+ }
84
+
85
+ const windowHeight = Math.max(
86
+ document.documentElement.clientHeight || 0,
87
+ window.innerHeight || 0,
88
+ );
89
+
90
+ const menu = panel.querySelector<HTMLElement>('.' + s['Dropdown__menu']);
91
+
92
+ if (!menu) {
93
+ return;
94
+ }
95
+
96
+ const styles = window.getComputedStyle(menu);
97
+ const marginTop = parseFloat(styles.marginTop);
98
+
99
+ const fitsBelow = rect.bottom + height <= windowHeight;
100
+ const fitsAbove = rect.top - height > 0;
101
+
102
+ if (!fitsBelow && fitsAbove) {
103
+ // eslint-disable-next-line no-param-reassign
104
+ panel.style.top = `${rect.bottom - height}px`;
105
+ } else if (fitsBelow) {
106
+ // eslint-disable-next-line no-param-reassign
107
+ panel.style.top = `${rect.bottom}px`;
108
+ } else {
109
+ const spaceAbove = rect.top;
110
+ const spaceBelow = windowHeight - rect.bottom;
111
+
112
+ if (spaceBelow > spaceAbove) {
113
+ // eslint-disable-next-line no-param-reassign
114
+ panel.style.top = `${rect.bottom}px`;
115
+ menu.style.maxHeight = `${windowHeight - rect.bottom - marginTop - 10}px`;
116
+ } else {
117
+ // eslint-disable-next-line no-param-reassign
118
+ panel.style.top = `0px`;
119
+ menu.style.maxHeight = `${rect.top - marginTop}px`;
120
+ }
121
+ }
122
+
123
+ // eslint-disable-next-line no-param-reassign
124
+ panel.style.visibility = 'visible';
125
+ }
126
+
127
+ export type MenuProps = {
128
+ children: React.ReactNode;
129
+ alignment?: 'left' | 'right';
130
+ };
131
+
132
+ export const Menu = ({
133
+ children,
134
+ alignment = 'left',
135
+ }: MenuProps): JSX.Element => {
136
+ const { closeMenu } = useContext(DropdownContext);
137
+
138
+ const childrenArray = React.Children.toArray(children);
139
+ const [searchTerm, setSearchTerm] = useState<string>('');
140
+ const [options, setOptions] = useState<
141
+ Array<{
142
+ id: string;
143
+ handler?: (e: SyntheticEvent) => void;
144
+ }>
145
+ >([]);
146
+
147
+ const handleChange = useCallback(
148
+ (e) => {
149
+ setSearchTerm(e.target.value);
150
+ },
151
+ [setSearchTerm],
152
+ );
153
+
154
+ const anyGroup = childrenArray.some(
155
+ (child) =>
156
+ typeof child === 'object' && 'type' in child && child.type === Group,
157
+ );
158
+
159
+ const { matches } = useMediaQuery('(max-width: 1024px)');
160
+
161
+ const addOption = useCallback(
162
+ (id: string) => {
163
+ setOptions((old) => [...old, { id }]);
164
+
165
+ return () => {
166
+ setOptions((old) => old.filter((x) => x.id !== id));
167
+ };
168
+ },
169
+ [setOptions],
170
+ );
171
+
172
+ const setClickHandlerForOption = useCallback(
173
+ (id: string, handler: (e: SyntheticEvent) => void) => {
174
+ setOptions((old) =>
175
+ old.map((x) => (x.id !== id ? x : { ...x, handler })),
176
+ );
177
+ },
178
+ [setOptions],
179
+ );
180
+
181
+ const contentRef = useRef<HTMLDivElement>(null);
182
+
183
+ const handleKeyDown = useCallback(
184
+ (event: React.KeyboardEvent<HTMLDivElement>) => {
185
+ if (event.key === 'Escape') {
186
+ event.preventDefault();
187
+ event.stopPropagation();
188
+ closeMenu();
189
+ }
190
+
191
+ if (['ArrowDown', 'ArrowUp'].includes(event.key)) {
192
+ event.preventDefault();
193
+ event.stopPropagation();
194
+
195
+ if (!contentRef.current) {
196
+ return;
197
+ }
198
+
199
+ const delta = event.key === 'ArrowUp' ? -1 : 1;
200
+
201
+ const selectedOption = contentRef.current.querySelector(
202
+ '.' + s['Dropdown__menu__option--is-selected'],
203
+ );
204
+
205
+ let nextOption;
206
+
207
+ if (!selectedOption) {
208
+ nextOption = contentRef.current.querySelector(
209
+ '.' + s['Dropdown__menu__option'],
210
+ );
211
+ } else {
212
+ const elements = Array.from(
213
+ contentRef.current.querySelectorAll(
214
+ '.' + s['Dropdown__menu__option'],
215
+ ),
216
+ );
217
+ const index = elements.findIndex((el) => el === selectedOption);
218
+ const nextIndex =
219
+ index + delta < 0
220
+ ? elements.length - 1
221
+ : (index + delta) % elements.length;
222
+ nextOption = elements[nextIndex];
223
+ selectedOption.classList.remove(
224
+ s['Dropdown__menu__option--is-selected'],
225
+ );
226
+ }
227
+
228
+ if (nextOption) {
229
+ nextOption.classList.add(s['Dropdown__menu__option--is-selected']);
230
+
231
+ scrollIntoView(nextOption, {
232
+ scrollMode: 'if-needed',
233
+ behavior: 'auto',
234
+ });
235
+ }
236
+ }
237
+
238
+ if (event.key === 'Enter') {
239
+ event.preventDefault();
240
+ event.stopPropagation();
241
+
242
+ if (!contentRef.current) {
243
+ return;
244
+ }
245
+
246
+ const selectedOption = contentRef.current.querySelector(
247
+ '.' + s['Dropdown__menu__option--is-selected'],
248
+ );
249
+
250
+ if (selectedOption) {
251
+ const id = (selectedOption as HTMLButtonElement).dataset.optionId;
252
+ const option = options.find((x) => x.id === id);
253
+ if (option && option.handler) {
254
+ option.handler(event);
255
+ }
256
+ }
257
+ }
258
+ },
259
+ [options],
260
+ );
261
+
262
+ const [observerRef, inView, entry] = useInView({
263
+ threshold: 0,
264
+ rootMargin: '0px 0px 0px 0px',
265
+ triggerOnce: false,
266
+ });
267
+
268
+ const parentRef = createRef<HTMLDivElement>();
269
+ const menuRef = createRef<HTMLDivElement>();
270
+
271
+ const reposition = useCallback(() => {
272
+ if (menuRef.current && parentRef.current) {
273
+ setPosition(menuRef.current, parentRef.current, alignment);
274
+ }
275
+ }, [menuRef, parentRef, alignment]);
276
+
277
+ useEffect(() => {
278
+ reposition();
279
+ }, [inView, entry?.intersectionRatio]);
280
+
281
+ useEffect(() => {
282
+ if (menuRef.current && parentRef.current) {
283
+ const resizeObserver = new ResizeObserver(reposition);
284
+ resizeObserver.observe(menuRef.current);
285
+ resizeObserver.observe(parentRef.current);
286
+
287
+ return () => {
288
+ resizeObserver.disconnect();
289
+ };
290
+ }
291
+
292
+ return undefined;
293
+ }, [menuRef, reposition]);
294
+
295
+ useEffect(() => {
296
+ window.addEventListener('resize', reposition);
297
+ return () => window.removeEventListener('resize', reposition);
298
+ }, [reposition]);
299
+
300
+ const Wrapper = matches ? MenuMobileContainer : MenuDesktopContainer;
301
+
302
+ return (
303
+ <>
304
+ <div
305
+ ref={mergeRefs(observerRef, parentRef)}
306
+ className={s['Dropdown__spacer']}
307
+ />
308
+ <Wrapper ref={menuRef}>
309
+ <div
310
+ className={classNames(s['Dropdown__menu'], {
311
+ [s['Dropdown__menu--fullscreen']]: matches,
312
+ })}
313
+ >
314
+ {options.length > 5 && (
315
+ <div className={s['Dropdown__menu__search']}>
316
+ <input
317
+ type="text"
318
+ value={searchTerm || ''}
319
+ onChange={handleChange}
320
+ onKeyDown={handleKeyDown}
321
+ placeholder="Search..."
322
+ autoFocus
323
+ className={s['Dropdown__menu__search__input']}
324
+ />
325
+ </div>
326
+ )}
327
+ <MenuContext.Provider
328
+ value={{ searchTerm, addOption, setClickHandlerForOption }}
329
+ >
330
+ <div className={s['Dropdown__menu__inner']} ref={contentRef}>
331
+ {anyGroup ? (
332
+ children
333
+ ) : (
334
+ <div className={s['Dropdown__menu__group__content']}>
335
+ {children}
336
+ </div>
337
+ )}
338
+ </div>
339
+ </MenuContext.Provider>
340
+ </div>
341
+ {matches ? (
342
+ <div>
343
+ <div className={s['Dropdown__menu__inner']}>
344
+ <Option closeMenuOnClick>Close</Option>
345
+ </div>
346
+ </div>
347
+ ) : null}
348
+ </Wrapper>
349
+ </>
350
+ );
351
+ };
@@ -0,0 +1,18 @@
1
+ import { createContext, SyntheticEvent } from 'react';
2
+
3
+ type Context = {
4
+ searchTerm: string;
5
+ addOption: (id: string) => void;
6
+ setClickHandlerForOption: (
7
+ id: string,
8
+ handler: (e: SyntheticEvent) => void,
9
+ ) => void;
10
+ };
11
+
12
+ export const MenuContext = createContext<Context>({
13
+ searchTerm: '',
14
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
15
+ addOption: () => {},
16
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
17
+ setClickHandlerForOption: () => {},
18
+ });
@@ -0,0 +1,148 @@
1
+ import classNames from 'classnames';
2
+ import React, {
3
+ useCallback,
4
+ useContext,
5
+ useEffect,
6
+ useMemo,
7
+ useRef,
8
+ useState,
9
+ } from 'react';
10
+ import { useCtx } from '..';
11
+ import { DropdownContext } from './DropdownContext';
12
+ import { MenuContext } from './MenuContext';
13
+ import { OptionAction } from './OptionAction';
14
+ import s from './styles.module.css.json';
15
+
16
+ export type OptionProps = {
17
+ onClick?: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
18
+ to?: string;
19
+ active?: boolean;
20
+ red?: boolean;
21
+ valid?: boolean;
22
+ invalid?: boolean;
23
+ children: React.ReactNode;
24
+ disabled?: boolean;
25
+ closeMenuOnClick?: boolean;
26
+ };
27
+
28
+ export const Option = ({
29
+ onClick,
30
+ to,
31
+ active,
32
+ red,
33
+ valid,
34
+ invalid,
35
+ children,
36
+ disabled,
37
+ closeMenuOnClick,
38
+ }: OptionProps): JSX.Element | null => {
39
+ const ctx = useCtx();
40
+
41
+ const id = useMemo(() => new Date().getTime().toString(36), []);
42
+
43
+ const { searchTerm, addOption, setClickHandlerForOption } = useContext(
44
+ MenuContext,
45
+ );
46
+
47
+ const childrenArray = React.Children.toArray(children);
48
+
49
+ const buttons = childrenArray.filter(
50
+ (child) =>
51
+ typeof child === 'object' &&
52
+ 'type' in child &&
53
+ child.type === OptionAction,
54
+ );
55
+
56
+ const notButtons = childrenArray.filter(
57
+ (child) =>
58
+ typeof child !== 'object' ||
59
+ !('type' in child) ||
60
+ child.type !== OptionAction,
61
+ );
62
+
63
+ const contentRef = useRef<HTMLButtonElement>(null);
64
+ const [innerText, setInnerText] = useState<string>('');
65
+
66
+ useEffect(() => {
67
+ if (contentRef.current) {
68
+ setInnerText(contentRef.current.innerText);
69
+ }
70
+ });
71
+
72
+ const { closeMenu } = useContext(DropdownContext);
73
+
74
+ const handleClick = useCallback(
75
+ (e) => {
76
+ if (!onClick && !to) {
77
+ return;
78
+ }
79
+
80
+ e.preventDefault();
81
+ e.stopPropagation();
82
+
83
+ if (disabled) {
84
+ return;
85
+ }
86
+
87
+ if (closeMenuOnClick ?? true) {
88
+ closeMenu();
89
+ }
90
+
91
+ if (to) {
92
+ ctx?.navigateTo(to);
93
+ }
94
+
95
+ if (onClick) {
96
+ onClick(e);
97
+ }
98
+ },
99
+ [closeMenuOnClick, closeMenu, onClick, to],
100
+ );
101
+
102
+ useEffect(() => {
103
+ return addOption(id);
104
+ }, [id]);
105
+
106
+ useEffect(() => {
107
+ return setClickHandlerForOption(id, handleClick);
108
+ }, [id, handleClick, setClickHandlerForOption]);
109
+
110
+ if (
111
+ innerText &&
112
+ searchTerm &&
113
+ !innerText.toLowerCase().includes(searchTerm.toLowerCase())
114
+ ) {
115
+ return null;
116
+ }
117
+
118
+ const content = (
119
+ <>
120
+ <button
121
+ type="button"
122
+ className={s['Dropdown__menu__option__content']}
123
+ ref={contentRef}
124
+ onClick={handleClick}
125
+ >
126
+ {notButtons}
127
+ </button>
128
+ {buttons.length > 0 && (
129
+ <div className={s['Dropdown__menu__option__icons']}>{buttons}</div>
130
+ )}
131
+ </>
132
+ );
133
+
134
+ return (
135
+ <div
136
+ className={classNames(s['Dropdown__menu__option'], {
137
+ [s['Dropdown__menu__option--is-active']]: active,
138
+ [s['Dropdown__menu__option--is-disabled']]: disabled,
139
+ [s['Dropdown__menu__option--is-valid']]: valid,
140
+ [s['Dropdown__menu__option--is-invalid']]: invalid,
141
+ [s['Dropdown__menu__option--is-dangerous']]: red,
142
+ })}
143
+ data-option-id={id}
144
+ >
145
+ {content}
146
+ </div>
147
+ );
148
+ };
@@ -0,0 +1,42 @@
1
+ import classNames from 'classnames';
2
+ import React, { ReactNode } from 'react';
3
+ import { DropdownContext } from './DropdownContext';
4
+ import s from './styles.module.css.json';
5
+
6
+ export type OptionActionProps = {
7
+ icon: ReactNode;
8
+ red?: boolean;
9
+ onClick: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
10
+ closeMenuOnClick?: boolean;
11
+ };
12
+
13
+ export const OptionAction = ({
14
+ icon,
15
+ red,
16
+ onClick,
17
+ closeMenuOnClick,
18
+ }: OptionActionProps): JSX.Element => (
19
+ <DropdownContext.Consumer>
20
+ {(context) => (
21
+ <button
22
+ type="button"
23
+ className={classNames(s['Dropdown__menu__option__icon'], {
24
+ [s['Dropdown__menu__option__icon--delete']]: red,
25
+ })}
26
+ onClick={(e) => {
27
+ e.preventDefault();
28
+ e.stopPropagation();
29
+
30
+ if (closeMenuOnClick ?? true) {
31
+ context.closeMenu();
32
+ }
33
+ onClick(e);
34
+ }}
35
+ >
36
+ {icon}
37
+ </button>
38
+ )}
39
+ </DropdownContext.Consumer>
40
+ );
41
+
42
+ OptionAction.displayName = 'DropdownOptionAction';
@@ -0,0 +1,46 @@
1
+ import ReactDOM from 'react-dom';
2
+ import React, { ReactNode, useEffect, useRef } from 'react';
3
+ import { useCtx } from '..';
4
+ import s from '../Canvas/styles.module.css.json';
5
+ import { generateStyleFromCtx } from '../generateStyleFromCtx';
6
+ import classNames from 'classnames';
7
+
8
+ export function Portal({
9
+ children,
10
+ }: {
11
+ children: ReactNode;
12
+ }): React.ReactPortal | null {
13
+ const ctx = useCtx();
14
+
15
+ const elRef = useRef(
16
+ typeof document === 'undefined' ? null : document.createElement('div'),
17
+ );
18
+
19
+ useEffect(() => {
20
+ if (!elRef.current || !document.body) {
21
+ return;
22
+ }
23
+
24
+ document.body.appendChild(elRef.current);
25
+
26
+ return () => {
27
+ if (elRef.current) {
28
+ document.body.removeChild(elRef.current);
29
+ }
30
+ };
31
+ }, []);
32
+
33
+ if (!elRef.current) {
34
+ return null;
35
+ }
36
+
37
+ return ReactDOM.createPortal(
38
+ <div
39
+ className={classNames(s['themeVariables'], s['canvas'])}
40
+ style={generateStyleFromCtx(ctx)}
41
+ >
42
+ {children}
43
+ </div>,
44
+ elRef.current,
45
+ );
46
+ }
@@ -0,0 +1,13 @@
1
+ import React, { useContext } from 'react';
2
+ import { MenuContext } from './MenuContext';
3
+ import s from './styles.module.css.json';
4
+
5
+ export const Separator = (): JSX.Element | null => {
6
+ const { searchTerm } = useContext(MenuContext);
7
+
8
+ if (searchTerm) {
9
+ return null;
10
+ }
11
+
12
+ return <div className={s['Dropdown__menu__separator']} />;
13
+ };
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import s from './styles.module.css.json';
3
+
4
+ export type TextProps = { children: React.ReactNode };
5
+
6
+ export function Text({ children }: TextProps): JSX.Element {
7
+ return <div className={s['Dropdown__menu__text']}>{children}</div>;
8
+ }
@@ -0,0 +1,26 @@
1
+ import { Dropdown, DropdownProps } from './Dropdown';
2
+ import { Group, GroupProps } from './Group';
3
+ import { Menu, MenuProps } from './Menu';
4
+ import { Option, OptionProps } from './Option';
5
+ import { OptionAction, OptionActionProps } from './OptionAction';
6
+ import { Separator } from './Separator';
7
+ import { Text, TextProps } from './Text';
8
+
9
+ export {
10
+ Dropdown,
11
+ Group as DropdownGroup,
12
+ Menu as DropdownMenu,
13
+ Option as DropdownOption,
14
+ OptionAction as DropdownOptionAction,
15
+ Separator as DropdownSeparator,
16
+ Text as DropdownText,
17
+ };
18
+
19
+ export type {
20
+ DropdownProps,
21
+ GroupProps as DropdownGroupProps,
22
+ MenuProps as DropdownMenuProps,
23
+ OptionProps as DropdownOptionProps,
24
+ OptionActionProps as DropdownOptionActionProps,
25
+ TextProps as DropdownTextProps,
26
+ };