@basic-ui/core 0.0.29 → 0.0.33

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 (130) hide show
  1. package/build/cjs/index.js +90 -71
  2. package/build/cjs/index.js.map +1 -1
  3. package/build/esm/FocusLock/useFocusLock.js +21 -7
  4. package/build/esm/FocusLock/useFocusLock.js.map +1 -1
  5. package/build/esm/Menu/MenuList.js +7 -5
  6. package/build/esm/Menu/MenuList.js.map +1 -1
  7. package/build/esm/Tooltip/Tooltip.d.ts +1 -0
  8. package/build/esm/Tooltip/Tooltip.js +10 -3
  9. package/build/esm/Tooltip/Tooltip.js.map +1 -1
  10. package/build/esm/Tooltip/stateMachine.d.ts +17 -19
  11. package/build/esm/Tooltip/stateMachine.js +45 -49
  12. package/build/esm/Tooltip/stateMachine.js.map +1 -1
  13. package/build/esm/Tooltip/useTooltip.js +9 -9
  14. package/build/esm/Tooltip/useTooltip.js.map +1 -1
  15. package/build/tsconfig.tsbuildinfo +384 -89
  16. package/package.json +5 -5
  17. package/src/Accordion/Accordion.story.tsx +72 -0
  18. package/src/Accordion/Accordion.tsx +51 -0
  19. package/src/Accordion/AccordionBody.tsx +53 -0
  20. package/src/Accordion/AccordionHeader.tsx +165 -0
  21. package/src/Accordion/AccordionItem.tsx +43 -0
  22. package/src/Accordion/context.ts +35 -0
  23. package/src/Accordion/index.ts +4 -0
  24. package/src/Accordion/scopeQuery.ts +7 -0
  25. package/src/Accordion/styles.css +21 -0
  26. package/src/CheckBox/CheckBox.tsx +41 -0
  27. package/src/CheckBox/index.ts +1 -0
  28. package/src/ComboBox/ComboBox.story.tsx +118 -0
  29. package/src/ComboBox/Combobox.tsx +153 -0
  30. package/src/ComboBox/ComboboxButton.tsx +60 -0
  31. package/src/ComboBox/ComboboxInput.tsx +178 -0
  32. package/src/ComboBox/ComboboxLabel.tsx +32 -0
  33. package/src/ComboBox/ComboboxList.tsx +47 -0
  34. package/src/ComboBox/ComboboxOption.tsx +107 -0
  35. package/src/ComboBox/ComboboxPopover.tsx +58 -0
  36. package/src/ComboBox/cities.ts +23194 -0
  37. package/src/ComboBox/context.ts +33 -0
  38. package/src/ComboBox/hooks.tsx +428 -0
  39. package/src/ComboBox/index.ts +8 -0
  40. package/src/ComboBox/makeHash.ts +19 -0
  41. package/src/ComboBox/scopeQuery.ts +6 -0
  42. package/src/ComboBox/styles.css +30 -0
  43. package/src/FocusLock/FocusLock.tsx +59 -0
  44. package/src/FocusLock/index.ts +1 -0
  45. package/src/FocusLock/tabUtils.ts +28 -0
  46. package/src/FocusLock/useFocusLock.ts +61 -0
  47. package/src/List/List.story.tsx +17 -0
  48. package/src/List/List.tsx +17 -0
  49. package/src/List/ListItem.tsx +23 -0
  50. package/src/List/context.ts +19 -0
  51. package/src/List/index.ts +2 -0
  52. package/src/Menu/.gitkeep +0 -0
  53. package/src/Menu/Menu.story.tsx +158 -0
  54. package/src/Menu/Menu.tsx +60 -0
  55. package/src/Menu/MenuButton.tsx +83 -0
  56. package/src/Menu/MenuItem.tsx +83 -0
  57. package/src/Menu/MenuList.tsx +201 -0
  58. package/src/Menu/MenuPopover.tsx +25 -0
  59. package/src/Menu/context.ts +32 -0
  60. package/src/Menu/index.ts +5 -0
  61. package/src/Menu/scope.ts +7 -0
  62. package/src/Menu/styles.css +42 -0
  63. package/src/Modal/Modal.story.tsx +242 -0
  64. package/src/Modal/Modal.tsx +42 -0
  65. package/src/Modal/ModalBackdrop.tsx +72 -0
  66. package/src/Modal/NavDrawer.story.tsx +157 -0
  67. package/src/Modal/index.ts +2 -0
  68. package/src/Modal/styles.css +46 -0
  69. package/src/Popover/.gitkeep +0 -0
  70. package/src/Popper/Popper.story.tsx +267 -0
  71. package/src/Popper/Popper.tsx +149 -0
  72. package/src/Popper/PopperArrow.tsx +36 -0
  73. package/src/Popper/context.ts +9 -0
  74. package/src/Popper/index.ts +3 -0
  75. package/src/Popper/styles.css +60 -0
  76. package/src/Portal/Portal.tsx +20 -0
  77. package/src/Portal/index.ts +1 -0
  78. package/src/RadioButton/RadioButton.story.tsx +73 -0
  79. package/src/RadioButton/RadioButton.tsx +48 -0
  80. package/src/RadioButton/RadioGroup.tsx +56 -0
  81. package/src/RadioButton/context.ts +19 -0
  82. package/src/RadioButton/index.ts +2 -0
  83. package/src/SkipNav/SkipNav.tsx +16 -0
  84. package/src/SkipNav/index.tsx +1 -0
  85. package/src/Spinner/Spinner.story.tsx +30 -0
  86. package/src/Spinner/Spinner.tsx +112 -0
  87. package/src/Spinner/SpinnerButton.tsx +48 -0
  88. package/src/Spinner/context.ts +21 -0
  89. package/src/Spinner/index.ts +2 -0
  90. package/src/Spinner/styles.css +23 -0
  91. package/src/Tabs/Tab.story.tsx +78 -0
  92. package/src/Tabs/Tab.tsx +131 -0
  93. package/src/Tabs/TabList.tsx +63 -0
  94. package/src/Tabs/TabPanel.tsx +52 -0
  95. package/src/Tabs/TabPanels.tsx +30 -0
  96. package/src/Tabs/Tabs.tsx +47 -0
  97. package/src/Tabs/context.ts +30 -0
  98. package/src/Tabs/index.tsx +5 -0
  99. package/src/Tabs/scopeQuery.ts +6 -0
  100. package/src/Tabs/styles.css +0 -0
  101. package/src/Tooltip/.gitkeep +0 -0
  102. package/src/Tooltip/Tooltip.story.tsx +59 -0
  103. package/src/Tooltip/Tooltip.tsx +48 -0
  104. package/src/Tooltip/index.ts +1 -0
  105. package/src/Tooltip/stateMachine.ts +196 -0
  106. package/src/Tooltip/styles.css +17 -0
  107. package/src/Tooltip/useTooltip.ts +128 -0
  108. package/src/hooks/index.ts +14 -0
  109. package/src/hooks/useAutoFocus.ts +13 -0
  110. package/src/hooks/useChildrenCounter.ts +50 -0
  111. package/src/hooks/useControlledState.ts +37 -0
  112. package/src/hooks/useFocusReturn.ts +23 -0
  113. package/src/hooks/useFocusState.ts +28 -0
  114. package/src/hooks/useGestureHandlers.ts +217 -0
  115. package/src/hooks/useId.ts +18 -0
  116. package/src/hooks/useMeasure.ts +33 -0
  117. package/src/hooks/useOnClickOutside.ts +32 -0
  118. package/src/hooks/useOnKeyDown.ts +18 -0
  119. package/src/hooks/useReducerMachine.ts +59 -0
  120. package/src/hooks/useRemoveBodyScroll.ts +37 -0
  121. package/src/hooks/useScope.ts +51 -0
  122. package/src/hooks/useThrottle.ts +19 -0
  123. package/src/index.ts +19 -0
  124. package/src/utils/assignRef.ts +27 -0
  125. package/src/utils/clamp.ts +3 -0
  126. package/src/utils/createSubscription.ts +16 -0
  127. package/src/utils/getCircularIndex.ts +7 -0
  128. package/src/utils/index.ts +4 -0
  129. package/src/utils/rubberBandClamp.ts +25 -0
  130. package/src/utils/wrapEvent.ts +20 -0
@@ -0,0 +1,32 @@
1
+ import { createContext, useContext, MutableRefObject } from 'react';
2
+
3
+ export type ItemObject = { text: string; value: any; id: string | undefined };
4
+
5
+ // MenuRoot
6
+ export interface MenuContextProps {
7
+ buttonRef: MutableRefObject<HTMLButtonElement | null>;
8
+ menuListIdRef: MutableRefObject<undefined | string>;
9
+ openWithArrowKeyRef: MutableRefObject<string | null>;
10
+ onChange?: (
11
+ e:
12
+ | React.KeyboardEvent<HTMLElement>
13
+ | React.MouseEvent<HTMLElement>
14
+ | React.PointerEvent<HTMLElement>,
15
+ isOpen: boolean
16
+ ) => void;
17
+ open: boolean;
18
+ }
19
+
20
+ const menuContext = createContext<MenuContextProps>(null as any);
21
+ export const { Provider: MenuProvider } = menuContext;
22
+ export const useMenuContext = () => useContext(menuContext);
23
+
24
+ // MenuList
25
+ export interface MenuListContextProps {
26
+ navigationItem: HTMLElement | undefined;
27
+ onNavigate: undefined | ((idx: HTMLElement) => void);
28
+ }
29
+
30
+ const menuListContext = createContext<MenuListContextProps>(null as any);
31
+ export const MenuListProvider = menuListContext.Provider;
32
+ export const useMenuListContext = () => useContext(menuListContext);
@@ -0,0 +1,5 @@
1
+ export * from './Menu';
2
+ export * from './MenuButton';
3
+ export * from './MenuItem';
4
+ export * from './MenuList';
5
+ export * from './MenuPopover';
@@ -0,0 +1,7 @@
1
+ export function queryScope(type: string, props: Record<string, unknown>) {
2
+ return (
3
+ props['data-menu-item'] === '' &&
4
+ props['data-disabled'] !== '' &&
5
+ props['data-disabled'] !== true
6
+ );
7
+ }
@@ -0,0 +1,42 @@
1
+ [data-menu-item] {
2
+ padding: 8px;
3
+ list-style: none;
4
+ cursor: pointer;
5
+ }
6
+
7
+ [data-menu-item][data-highlighted] {
8
+ background-color: #e7e7e7;
9
+ }
10
+
11
+ [data-menu-item][data-highlighted]:hover {
12
+ background-color: #d7d7d7;
13
+ }
14
+
15
+ [data-menu-item]:hover {
16
+ background-color: #f3f3f3;
17
+ }
18
+
19
+ [data-menu-item][data-disabled] {
20
+ background-color: #ffffff;
21
+ color: #777;
22
+ cursor: not-allowed;
23
+ }
24
+
25
+ [data-menu-list] {
26
+ margin: 0;
27
+ padding: 0;
28
+ box-shadow: 0px 2px 6px hsla(0, 0%, 0%, 0.15);
29
+ border-radius: 3px;
30
+ }
31
+
32
+ [data-popper-placement='top'] [data-menu-list] {
33
+ transform-origin: bottom center;
34
+ }
35
+
36
+ [data-popper-placement='bottom'] [data-menu-list] {
37
+ transform-origin: top center;
38
+ }
39
+
40
+ [data-menu-list]:focus {
41
+ outline: none;
42
+ }
@@ -0,0 +1,242 @@
1
+ import { useState } from 'react';
2
+ import { storiesOf } from '@storybook/react';
3
+ import { useSpring, animated } from 'react-spring';
4
+ import { Modal, ModalBackdrop } from './';
5
+ import { Portal } from '../Portal';
6
+ import './styles.css';
7
+
8
+ const stories = storiesOf('Components/Modal', module);
9
+
10
+ const LoremIpsum = ({ numOfParagraphs = 20 }) => {
11
+ const content = [];
12
+
13
+ for (let i = 0; i < numOfParagraphs; i++) {
14
+ content.push(
15
+ <p key={`paragraph_${i}`}>
16
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse
17
+ vestibulum sapien nec mauris placerat, et facilisis massa placerat.
18
+ Curabitur sagittis condimentum lectus vel aliquam. Sed ultrices, metus
19
+ sed suscipit venenatis, metus lectus vestibulum neque, eget sodales
20
+ justo sapien in ante. In a neque mollis, volutpat est a, vehicula lacus.
21
+ Praesent lectus justo, tempor a laoreet in, consectetur nec sapien.
22
+ Phasellus non venenatis erat. Maecenas eget mi sodales, euismod tortor
23
+ vitae, ultricies quam. Sed varius nunc id tincidunt porttitor. Morbi
24
+ lectus massa, malesuada at lorem ut, semper volutpat orci. Donec mauris
25
+ eros, faucibus ut egestas a, ultricies vel tortor. Phasellus mi lectus,
26
+ consectetur a risus at, faucibus porttitor ligula. Maecenas felis eros,
27
+ porttitor vel placerat eget, malesuada ac leo. Cras a eros id dui
28
+ porttitor ullamcorper sit amet quis lorem.
29
+ </p>
30
+ );
31
+ }
32
+
33
+ return <>{content}</>;
34
+ };
35
+
36
+ const Wrapper = ({ children }) => {
37
+ return (
38
+ <div style={{ maxWidth: 900, margin: '0 auto' }}>
39
+ <div
40
+ style={{
41
+ boxSizing: 'border-box',
42
+ display: 'flex',
43
+ alignItems: 'flex-start',
44
+ padding: '96px 48px',
45
+ justifyContent: 'space-around',
46
+ width: '100%',
47
+ position: 'relative',
48
+ }}
49
+ >
50
+ <a href="#" onClick={(e) => e.preventDefault()}>
51
+ Link
52
+ </a>
53
+ <div style={{ minHeight: 120, width: 100 }}>{children}</div>
54
+ <a href="#" onClick={(e) => e.preventDefault()}>
55
+ Link
56
+ </a>
57
+ </div>
58
+ <div>
59
+ <LoremIpsum />
60
+ </div>
61
+ <div
62
+ style={{
63
+ boxSizing: 'border-box',
64
+ display: 'flex',
65
+ alignItems: 'flex-start',
66
+ padding: '96px 48px',
67
+ justifyContent: 'space-around',
68
+ width: '100%',
69
+ position: 'relative',
70
+ }}
71
+ >
72
+ <a href="#" onClick={(e) => e.preventDefault()}>
73
+ Link
74
+ </a>
75
+ <div style={{ minHeight: 120, width: 100 }}>{children}</div>
76
+ <a href="#" onClick={(e) => e.preventDefault()}>
77
+ Link
78
+ </a>
79
+ </div>
80
+ </div>
81
+ );
82
+ };
83
+
84
+ const SimpleModalControlled = () => {
85
+ const [open, setOpen] = useState(false);
86
+
87
+ const handleClose = () => {
88
+ setOpen(false);
89
+ };
90
+
91
+ return (
92
+ <>
93
+ <button onClick={() => setOpen(!open)}>Open modal</button>
94
+ {open && (
95
+ <Portal>
96
+ <ModalBackdrop onClose={handleClose}>
97
+ <Modal trapFocus={true}>
98
+ <button>Start button</button>
99
+ <LoremIpsum />
100
+ <button>End button</button>
101
+ </Modal>
102
+ </ModalBackdrop>
103
+ </Portal>
104
+ )}
105
+ </>
106
+ );
107
+ };
108
+
109
+ const NestedModalControlled = () => {
110
+ const [open, setOpen] = useState(false);
111
+ const [open2, setOpen2] = useState(false);
112
+
113
+ return (
114
+ <>
115
+ <button onClick={() => setOpen(!open)}>Open modal</button>
116
+ {open && (
117
+ <Portal>
118
+ <ModalBackdrop onClose={() => setOpen(false)}>
119
+ <Modal trapFocus={true}>
120
+ <button onClick={() => setOpen2(true)}>Open another modal</button>
121
+ {open2 && (
122
+ <Portal>
123
+ <ModalBackdrop onClose={() => setOpen2(false)}>
124
+ <Modal trapFocus={true}>
125
+ <LoremIpsum numOfParagraphs={1} />
126
+ <button onClick={() => setOpen2(false)}>Close</button>
127
+ </Modal>
128
+ </ModalBackdrop>
129
+ </Portal>
130
+ )}
131
+ <LoremIpsum numOfParagraphs={1} />
132
+ <button onClick={() => setOpen(false)}>Close</button>
133
+ </Modal>
134
+ </ModalBackdrop>
135
+ </Portal>
136
+ )}
137
+ </>
138
+ );
139
+ };
140
+
141
+ const SimpleModalControlledAnimated = () => {
142
+ const [open, setOpen] = useState(false);
143
+ const [pointerEvents, setPointerEvents] = useState('none');
144
+ const [{ scale, opacity }, set] = useSpring(() => ({
145
+ scale: 0.97,
146
+ opacity: 0,
147
+ }));
148
+
149
+ const handleClose = () => {
150
+ const t = Date.now();
151
+ setPointerEvents('none');
152
+ set({
153
+ scale: 0.97,
154
+ opacity: 0,
155
+ config: {
156
+ mass: 1,
157
+ tension: 1600,
158
+ friction: 50,
159
+ clamp: true,
160
+ },
161
+ onRest: () => {
162
+ console.log('Close in: ', Date.now() - t);
163
+ setOpen(false);
164
+ },
165
+ });
166
+ };
167
+
168
+ const handleOpen = () => {
169
+ const t = Date.now();
170
+ setOpen(true);
171
+ setPointerEvents('auto');
172
+ set({
173
+ scale: 1,
174
+ opacity: 1,
175
+ config: {
176
+ mass: 1,
177
+ tension: 1050,
178
+ friction: 50,
179
+ clamp: true,
180
+ },
181
+ onRest: () => {
182
+ console.log('Opened in: ', Date.now() - t);
183
+ },
184
+ });
185
+ };
186
+
187
+ return (
188
+ <>
189
+ <button onClick={handleOpen}>Open modal</button>
190
+ {open && (
191
+ <Portal>
192
+ <animated.div
193
+ style={{
194
+ opacity,
195
+ position: 'fixed',
196
+ boxSizing: 'border-box',
197
+ top: '0',
198
+ left: '0',
199
+ right: '0',
200
+ bottom: '0',
201
+ backgroundColor: 'rgba(0, 0, 0, 0.3)',
202
+ pointerEvents: 'none',
203
+ }}
204
+ />
205
+ <ModalBackdrop onClose={handleClose} style={{ pointerEvents }}>
206
+ <Modal
207
+ as={animated.div}
208
+ style={{
209
+ transform: scale.to((x: number) => `scale(${x}, ${x * x})`),
210
+ opacity,
211
+ transformOrigin: 'center top',
212
+ }}
213
+ >
214
+ <button>Start button</button>
215
+ Hello world
216
+ <LoremIpsum />
217
+ <button>End button</button>
218
+ </Modal>
219
+ </ModalBackdrop>
220
+ </Portal>
221
+ )}
222
+ </>
223
+ );
224
+ };
225
+
226
+ stories.add('Simple modal, controlled', () => (
227
+ <Wrapper>
228
+ <SimpleModalControlled />
229
+ </Wrapper>
230
+ ));
231
+
232
+ stories.add('Nested modal, controlled', () => (
233
+ <Wrapper>
234
+ <NestedModalControlled />
235
+ </Wrapper>
236
+ ));
237
+
238
+ stories.add('Simple modal, controlled, animated', () => (
239
+ <Wrapper>
240
+ <SimpleModalControlledAnimated />
241
+ </Wrapper>
242
+ ));
@@ -0,0 +1,42 @@
1
+ import { ReactNode, forwardRef, useRef } from 'react';
2
+ import type * as React from 'react';
3
+ import { useAutoFocus, useFocusReturn, useRemoveBodyScroll } from '../hooks';
4
+ import { FocusLock } from '../FocusLock';
5
+ import { assignMultipleRefs } from '../utils';
6
+
7
+ export interface ModalProps extends React.HTMLAttributes<HTMLDivElement> {
8
+ as?: React.ElementType<any>;
9
+ innerAs?: React.ElementType<any>;
10
+ children?: ReactNode;
11
+ style?: React.CSSProperties;
12
+ trapFocus?: boolean;
13
+ }
14
+
15
+ export const Modal = forwardRef<HTMLDivElement, ModalProps>(
16
+ (
17
+ { as: Comp = 'div', children, trapFocus = true, style = {}, ...otherProps },
18
+ ref
19
+ ) => {
20
+ const modalRef = useRef<HTMLDivElement>(null);
21
+
22
+ useFocusReturn(trapFocus);
23
+ useRemoveBodyScroll(trapFocus);
24
+ useAutoFocus(trapFocus, modalRef);
25
+
26
+ return (
27
+ <FocusLock childRef={modalRef} enabled={trapFocus}>
28
+ <Comp
29
+ ref={assignMultipleRefs(ref, modalRef)}
30
+ data-modal-container=""
31
+ role="dialog"
32
+ aria-modal="true"
33
+ style={style}
34
+ tabIndex={0}
35
+ {...otherProps}
36
+ >
37
+ {children}
38
+ </Comp>
39
+ </FocusLock>
40
+ );
41
+ }
42
+ );
@@ -0,0 +1,72 @@
1
+ import { useRef, forwardRef } from 'react';
2
+ import type * as React from 'react';
3
+ import { assignMultipleRefs } from '../utils/assignRef';
4
+ import { wrapEvent } from '../utils/wrapEvent';
5
+
6
+ export interface ModalBackdropProps
7
+ extends React.HTMLAttributes<HTMLDivElement> {
8
+ as?: React.ElementType<any>;
9
+ innerAs?: React.ElementType<any>;
10
+ onClose?: () => void;
11
+ style?: React.CSSProperties;
12
+ disableCloseOnClick?: boolean;
13
+ disableEscapeKeyDown?: boolean;
14
+ }
15
+
16
+ export const ModalBackdrop = forwardRef<HTMLDivElement, ModalBackdropProps>(
17
+ (
18
+ {
19
+ as: Comp = 'div',
20
+ onClose,
21
+ onClick,
22
+ onMouseDown,
23
+ onKeyDown,
24
+ disableCloseOnClick = false,
25
+ disableEscapeKeyDown = false,
26
+ ...otherProps
27
+ },
28
+ forwardedRef
29
+ ) => {
30
+ const ref = useRef();
31
+ const mouseDownTargetRef = useRef<EventTarget | null>(null);
32
+
33
+ const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
34
+ // Ignore the events not coming from the "backdrop"
35
+ // We don't want to close the dialog when clicking the dialog content.
36
+ if (e.target !== e.currentTarget) {
37
+ return;
38
+ }
39
+
40
+ // Make sure the event starts and ends on the same DOM element.
41
+ if (e.target !== mouseDownTargetRef.current) {
42
+ return;
43
+ }
44
+
45
+ mouseDownTargetRef.current = null;
46
+ !disableCloseOnClick && onClose?.();
47
+ e.stopPropagation();
48
+ };
49
+
50
+ const handleMouseDown = (e: React.MouseEvent<HTMLDivElement>) => {
51
+ mouseDownTargetRef.current = e.target;
52
+ };
53
+
54
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
55
+ if (e.key === 'Escape') {
56
+ !disableEscapeKeyDown && onClose?.();
57
+ e.stopPropagation();
58
+ }
59
+ };
60
+
61
+ return (
62
+ <Comp
63
+ ref={assignMultipleRefs(ref, forwardedRef)}
64
+ data-modal-root=""
65
+ onClick={wrapEvent(onClick, handleClick)}
66
+ onMouseDown={wrapEvent(onMouseDown, handleMouseDown)}
67
+ onKeyDown={wrapEvent(onKeyDown, handleKeyDown)}
68
+ {...otherProps}
69
+ />
70
+ );
71
+ }
72
+ );
@@ -0,0 +1,157 @@
1
+ import { useState } from 'react';
2
+ import { storiesOf } from '@storybook/react';
3
+ import { useSpring, animated } from 'react-spring';
4
+ import { Modal, ModalBackdrop } from './';
5
+ import { Portal } from '../Portal';
6
+ import './styles.css';
7
+
8
+ const stories = storiesOf('Components/Modal/NavDrawer', module);
9
+
10
+ const LoremIpsum = ({ numOfParagraphs = 20 }) => {
11
+ const content = [];
12
+
13
+ for (let i = 0; i < numOfParagraphs; i++) {
14
+ content.push(
15
+ <p key={`paragraph_${i}`}>
16
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse
17
+ vestibulum sapien nec mauris placerat, et facilisis massa placerat.
18
+ Curabitur sagittis condimentum lectus vel aliquam. Sed ultrices, metus
19
+ sed suscipit venenatis, metus lectus vestibulum neque, eget sodales
20
+ justo sapien in ante. In a neque mollis, volutpat est a, vehicula lacus.
21
+ Praesent lectus justo, tempor a laoreet in, consectetur nec sapien.
22
+ Phasellus non venenatis erat. Maecenas eget mi sodales, euismod tortor
23
+ vitae, ultricies quam. Sed varius nunc id tincidunt porttitor. Morbi
24
+ lectus massa, malesuada at lorem ut, semper volutpat orci. Donec mauris
25
+ eros, faucibus ut egestas a, ultricies vel tortor. Phasellus mi lectus,
26
+ consectetur a risus at, faucibus porttitor ligula. Maecenas felis eros,
27
+ porttitor vel placerat eget, malesuada ac leo. Cras a eros id dui
28
+ porttitor ullamcorper sit amet quis lorem.
29
+ </p>
30
+ );
31
+ }
32
+
33
+ return <>{content}</>;
34
+ };
35
+
36
+ const Wrapper = ({ children }) => {
37
+ return (
38
+ <div style={{ maxWidth: 900, margin: '0 auto' }}>
39
+ <div
40
+ style={{
41
+ boxSizing: 'border-box',
42
+ display: 'flex',
43
+ alignItems: 'flex-start',
44
+ padding: '96px 48px',
45
+ justifyContent: 'space-around',
46
+ width: '100%',
47
+ position: 'relative',
48
+ }}
49
+ >
50
+ <a href="#" onClick={(e) => e.preventDefault()}>
51
+ Link
52
+ </a>
53
+ <div style={{ minHeight: 120, width: 100 }}>{children}</div>
54
+ <a href="#" onClick={(e) => e.preventDefault()}>
55
+ Link
56
+ </a>
57
+ </div>
58
+ <div>
59
+ <LoremIpsum />
60
+ </div>
61
+ </div>
62
+ );
63
+ };
64
+
65
+ const SimpleModalControlledAnimated = () => {
66
+ const [open, setOpen] = useState(false);
67
+ const [pointerEvents, setPointerEvents] = useState<PointerEventsProperty>(
68
+ 'none'
69
+ );
70
+ const [{ transformX, opacity }, set] = useSpring(() => ({
71
+ transformX: -120,
72
+ opacity: 0,
73
+ }));
74
+
75
+ const handleClose = () => {
76
+ const t = Date.now();
77
+ setPointerEvents('none');
78
+ set({
79
+ transformX: -120,
80
+ opacity: 0,
81
+ config: {
82
+ mass: 1,
83
+ tension: 820,
84
+ friction: 50,
85
+ clamp: true,
86
+ },
87
+ onRest: () => {
88
+ console.log('Close in: ', Date.now() - t);
89
+ setOpen(false);
90
+ },
91
+ });
92
+ };
93
+
94
+ const handleOpen = () => {
95
+ const t = Date.now();
96
+ setOpen(true);
97
+ setPointerEvents('auto');
98
+ set({
99
+ transformX: 0,
100
+ opacity: 1,
101
+ config: {
102
+ mass: 1,
103
+ tension: 770,
104
+ friction: 50,
105
+ clamp: true,
106
+ },
107
+ onRest: () => {
108
+ console.log('Opened in: ', Date.now() - t);
109
+ },
110
+ });
111
+ };
112
+
113
+ const trans = (x: number) => `translateX(${x}%)`;
114
+
115
+ return (
116
+ <>
117
+ <button onClick={handleOpen}>Open modal</button>
118
+ {open && (
119
+ <Portal>
120
+ <animated.div
121
+ style={{
122
+ opacity,
123
+ position: 'fixed',
124
+ boxSizing: 'border-box',
125
+ top: '0',
126
+ left: '0',
127
+ right: '0',
128
+ bottom: '0',
129
+ backgroundColor: 'rgba(0, 0, 0, 0.3)',
130
+ pointerEvents: 'none',
131
+ }}
132
+ />
133
+ <ModalBackdrop onClose={handleClose} style={{ pointerEvents }}>
134
+ <Modal
135
+ as={animated.div}
136
+ className="nav-drawer-left"
137
+ style={{
138
+ transform: transformX.to(trans),
139
+ opacity,
140
+ }}
141
+ >
142
+ <button>This is a cool button</button>
143
+ Hello world
144
+ <LoremIpsum />
145
+ </Modal>
146
+ </ModalBackdrop>
147
+ </Portal>
148
+ )}
149
+ </>
150
+ );
151
+ };
152
+
153
+ stories.add('NavDrawer, controlled, animated', () => (
154
+ <Wrapper>
155
+ <SimpleModalControlledAnimated />
156
+ </Wrapper>
157
+ ));
@@ -0,0 +1,2 @@
1
+ export * from './Modal';
2
+ export * from './ModalBackdrop';
@@ -0,0 +1,46 @@
1
+ [data-modal-root] {
2
+ position: fixed;
3
+ box-sizing: border-box;
4
+ top: 0;
5
+ left: 0;
6
+ right: 0;
7
+ bottom: 0;
8
+ overflow-y: auto;
9
+ }
10
+
11
+ [data-modal-container] {
12
+ position: relative;
13
+ left: 0;
14
+ right: 0;
15
+ margin: 96px auto;
16
+ width: 100%;
17
+ max-width: 600px;
18
+ background-color: #fff;
19
+ box-shadow: 0px 2px 6px hsla(0, 0%, 0%, 0.15);
20
+ border-radius: 3px;
21
+ overflow-y: auto;
22
+ }
23
+
24
+ [data-modal-backdrop] {
25
+ position: fixed;
26
+ box-sizing: border-box;
27
+ top: 0;
28
+ left: 0;
29
+ right: 0;
30
+ bottom: 0;
31
+ background-color: rgba(0, 0, 0, 0.3);
32
+ pointer-events: none;
33
+ }
34
+
35
+ .nav-drawer-left {
36
+ position: relative;
37
+ left: 0;
38
+ right: 0;
39
+ margin: 0;
40
+ width: 260px;
41
+ background-color: #fff;
42
+ box-shadow: 0px 2px 6px hsla(0, 0%, 0%, 0.15);
43
+ border-radius: 0px;
44
+ max-height: 100%;
45
+ overflow-y: auto;
46
+ }
File without changes