@basic-ui/core 0.0.31 → 0.0.34
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.
- package/build/cjs/index.js +113 -71
- package/build/cjs/index.js.map +1 -1
- package/build/esm/FocusLock/useFocusLock.js +21 -7
- package/build/esm/FocusLock/useFocusLock.js.map +1 -1
- package/build/esm/Tooltip/stateMachine.d.ts +17 -19
- package/build/esm/Tooltip/stateMachine.js +45 -49
- package/build/esm/Tooltip/stateMachine.js.map +1 -1
- package/build/esm/Tooltip/useTooltip.js +9 -9
- package/build/esm/Tooltip/useTooltip.js.map +1 -1
- package/build/esm/hooks/useGestureHandlers.d.ts +2 -0
- package/build/esm/hooks/useGestureHandlers.js +39 -7
- package/build/esm/hooks/useGestureHandlers.js.map +1 -1
- package/build/tsconfig.tsbuildinfo +383 -88
- package/package.json +6 -6
- package/src/Accordion/Accordion.story.tsx +72 -0
- package/src/Accordion/Accordion.tsx +51 -0
- package/src/Accordion/AccordionBody.tsx +53 -0
- package/src/Accordion/AccordionHeader.tsx +165 -0
- package/src/Accordion/AccordionItem.tsx +43 -0
- package/src/Accordion/context.ts +35 -0
- package/src/Accordion/index.ts +4 -0
- package/src/Accordion/scopeQuery.ts +7 -0
- package/src/Accordion/styles.css +21 -0
- package/src/CheckBox/CheckBox.tsx +41 -0
- package/src/CheckBox/index.ts +1 -0
- package/src/ComboBox/ComboBox.story.tsx +118 -0
- package/src/ComboBox/Combobox.tsx +153 -0
- package/src/ComboBox/ComboboxButton.tsx +60 -0
- package/src/ComboBox/ComboboxInput.tsx +178 -0
- package/src/ComboBox/ComboboxLabel.tsx +32 -0
- package/src/ComboBox/ComboboxList.tsx +47 -0
- package/src/ComboBox/ComboboxOption.tsx +107 -0
- package/src/ComboBox/ComboboxPopover.tsx +58 -0
- package/src/ComboBox/cities.ts +23194 -0
- package/src/ComboBox/context.ts +33 -0
- package/src/ComboBox/hooks.tsx +428 -0
- package/src/ComboBox/index.ts +8 -0
- package/src/ComboBox/makeHash.ts +19 -0
- package/src/ComboBox/scopeQuery.ts +6 -0
- package/src/ComboBox/styles.css +30 -0
- package/src/FocusLock/FocusLock.tsx +59 -0
- package/src/FocusLock/index.ts +1 -0
- package/src/FocusLock/tabUtils.ts +28 -0
- package/src/FocusLock/useFocusLock.ts +61 -0
- package/src/List/List.story.tsx +17 -0
- package/src/List/List.tsx +17 -0
- package/src/List/ListItem.tsx +23 -0
- package/src/List/context.ts +19 -0
- package/src/List/index.ts +2 -0
- package/src/Menu/.gitkeep +0 -0
- package/src/Menu/Menu.story.tsx +158 -0
- package/src/Menu/Menu.tsx +60 -0
- package/src/Menu/MenuButton.tsx +83 -0
- package/src/Menu/MenuItem.tsx +83 -0
- package/src/Menu/MenuList.tsx +201 -0
- package/src/Menu/MenuPopover.tsx +25 -0
- package/src/Menu/context.ts +32 -0
- package/src/Menu/index.ts +5 -0
- package/src/Menu/scope.ts +7 -0
- package/src/Menu/styles.css +42 -0
- package/src/Modal/Modal.story.tsx +242 -0
- package/src/Modal/Modal.tsx +42 -0
- package/src/Modal/ModalBackdrop.tsx +72 -0
- package/src/Modal/NavDrawer.story.tsx +157 -0
- package/src/Modal/index.ts +2 -0
- package/src/Modal/styles.css +46 -0
- package/src/Popover/.gitkeep +0 -0
- package/src/Popper/Popper.story.tsx +267 -0
- package/src/Popper/Popper.tsx +149 -0
- package/src/Popper/PopperArrow.tsx +36 -0
- package/src/Popper/context.ts +9 -0
- package/src/Popper/index.ts +3 -0
- package/src/Popper/styles.css +60 -0
- package/src/Portal/Portal.tsx +20 -0
- package/src/Portal/index.ts +1 -0
- package/src/RadioButton/RadioButton.story.tsx +73 -0
- package/src/RadioButton/RadioButton.tsx +48 -0
- package/src/RadioButton/RadioGroup.tsx +56 -0
- package/src/RadioButton/context.ts +19 -0
- package/src/RadioButton/index.ts +2 -0
- package/src/SkipNav/SkipNav.tsx +16 -0
- package/src/SkipNav/index.tsx +1 -0
- package/src/Spinner/Spinner.story.tsx +30 -0
- package/src/Spinner/Spinner.tsx +112 -0
- package/src/Spinner/SpinnerButton.tsx +48 -0
- package/src/Spinner/context.ts +21 -0
- package/src/Spinner/index.ts +2 -0
- package/src/Spinner/styles.css +23 -0
- package/src/Tabs/Tab.story.tsx +78 -0
- package/src/Tabs/Tab.tsx +131 -0
- package/src/Tabs/TabList.tsx +63 -0
- package/src/Tabs/TabPanel.tsx +52 -0
- package/src/Tabs/TabPanels.tsx +30 -0
- package/src/Tabs/Tabs.tsx +47 -0
- package/src/Tabs/context.ts +30 -0
- package/src/Tabs/index.tsx +5 -0
- package/src/Tabs/scopeQuery.ts +6 -0
- package/src/Tabs/styles.css +0 -0
- package/src/Tooltip/.gitkeep +0 -0
- package/src/Tooltip/Tooltip.story.tsx +59 -0
- package/src/Tooltip/Tooltip.tsx +48 -0
- package/src/Tooltip/index.ts +1 -0
- package/src/Tooltip/stateMachine.ts +196 -0
- package/src/Tooltip/styles.css +17 -0
- package/src/Tooltip/useTooltip.ts +128 -0
- package/src/hooks/index.ts +14 -0
- package/src/hooks/useAutoFocus.ts +13 -0
- package/src/hooks/useChildrenCounter.ts +50 -0
- package/src/hooks/useControlledState.ts +37 -0
- package/src/hooks/useFocusReturn.ts +23 -0
- package/src/hooks/useFocusState.ts +28 -0
- package/src/hooks/useGestureHandlers.ts +253 -0
- package/src/hooks/useId.ts +18 -0
- package/src/hooks/useMeasure.ts +33 -0
- package/src/hooks/useOnClickOutside.ts +32 -0
- package/src/hooks/useOnKeyDown.ts +18 -0
- package/src/hooks/useReducerMachine.ts +59 -0
- package/src/hooks/useRemoveBodyScroll.ts +37 -0
- package/src/hooks/useScope.ts +51 -0
- package/src/hooks/useThrottle.ts +19 -0
- package/src/index.ts +19 -0
- package/src/utils/assignRef.ts +27 -0
- package/src/utils/clamp.ts +3 -0
- package/src/utils/createSubscription.ts +16 -0
- package/src/utils/getCircularIndex.ts +7 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/rubberBandClamp.ts +25 -0
- package/src/utils/wrapEvent.ts +20 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { useRef, useState, useEffect } from 'react';
|
|
2
|
+
import { useSpring, animated } from 'react-spring';
|
|
3
|
+
import { storiesOf } from '@storybook/react';
|
|
4
|
+
import { Popper } from './Popper';
|
|
5
|
+
import { PopperArrow } from './PopperArrow';
|
|
6
|
+
import './styles.css';
|
|
7
|
+
|
|
8
|
+
const stories = storiesOf('Components/Popper', module);
|
|
9
|
+
|
|
10
|
+
const noop = () => {
|
|
11
|
+
// noop function to be used on onRest, as a cleanup
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const placements = [
|
|
15
|
+
'bottom',
|
|
16
|
+
'bottom-start',
|
|
17
|
+
'bottom-end',
|
|
18
|
+
'top',
|
|
19
|
+
'top-start',
|
|
20
|
+
'top-end',
|
|
21
|
+
'right',
|
|
22
|
+
'right-start',
|
|
23
|
+
'right-end',
|
|
24
|
+
'left',
|
|
25
|
+
'left-start',
|
|
26
|
+
'left-end',
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const Tooltip = ({ children, open, placement, anchorEl, showArrow }) => {
|
|
30
|
+
const [visible, setVisible] = useState(open);
|
|
31
|
+
const [animationProps, set] = useSpring(() => ({
|
|
32
|
+
opacity: open ? 1 : 0,
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
const onRest = () => {
|
|
36
|
+
setVisible(false);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (open) {
|
|
41
|
+
setVisible(true);
|
|
42
|
+
set({ opacity: 1, onRest: noop, delay: 0 });
|
|
43
|
+
} else {
|
|
44
|
+
set({ opacity: 0, onRest, delay: 0 });
|
|
45
|
+
}
|
|
46
|
+
}, [open, set]);
|
|
47
|
+
|
|
48
|
+
if (!visible) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<Popper
|
|
54
|
+
anchorEl={anchorEl}
|
|
55
|
+
placement={placement}
|
|
56
|
+
usePortal={true}
|
|
57
|
+
distance={showArrow ? 8 : 0}
|
|
58
|
+
skidding={0}
|
|
59
|
+
style={{ zIndex: 1100 }}
|
|
60
|
+
>
|
|
61
|
+
<animated.div style={animationProps}>
|
|
62
|
+
<div
|
|
63
|
+
style={{
|
|
64
|
+
backgroundColor: '#fff',
|
|
65
|
+
boxShadow: '0 0 4px 1px rgba(187,187,187,0.7)',
|
|
66
|
+
padding: 8,
|
|
67
|
+
}}
|
|
68
|
+
>
|
|
69
|
+
{children}
|
|
70
|
+
</div>
|
|
71
|
+
{showArrow && <PopperArrow color="#f00" />}
|
|
72
|
+
</animated.div>
|
|
73
|
+
</Popper>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const PopperExample = ({ showAll = false, showArrow = false }) => {
|
|
78
|
+
const ref = useRef<any>(null);
|
|
79
|
+
const [open, setOpen] = useState(true);
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<div
|
|
83
|
+
style={{
|
|
84
|
+
display: 'flex',
|
|
85
|
+
alignItems: 'center',
|
|
86
|
+
width: '100%',
|
|
87
|
+
minHeight: 2900,
|
|
88
|
+
backgroundColor: '#f5f5f5',
|
|
89
|
+
padding: 120,
|
|
90
|
+
}}
|
|
91
|
+
>
|
|
92
|
+
<div
|
|
93
|
+
ref={ref}
|
|
94
|
+
style={{
|
|
95
|
+
width: 350,
|
|
96
|
+
height: 350,
|
|
97
|
+
minWidth: 350,
|
|
98
|
+
minHeight: 350,
|
|
99
|
+
display: 'flex',
|
|
100
|
+
alignItems: 'center',
|
|
101
|
+
justifyContent: 'center',
|
|
102
|
+
backgroundColor: '#c5afae',
|
|
103
|
+
}}
|
|
104
|
+
onMouseEnter={() => {
|
|
105
|
+
setOpen(true);
|
|
106
|
+
}}
|
|
107
|
+
onMouseLeave={() => {
|
|
108
|
+
setOpen(true);
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
Cool div
|
|
112
|
+
</div>
|
|
113
|
+
{showAll ? (
|
|
114
|
+
placements.map((placement) => (
|
|
115
|
+
<Tooltip
|
|
116
|
+
anchorEl={ref}
|
|
117
|
+
key={`placement_${placement}`}
|
|
118
|
+
open={open}
|
|
119
|
+
showArrow={showArrow}
|
|
120
|
+
placement={placement}
|
|
121
|
+
>
|
|
122
|
+
{placement}
|
|
123
|
+
</Tooltip>
|
|
124
|
+
))
|
|
125
|
+
) : (
|
|
126
|
+
<Tooltip
|
|
127
|
+
anchorEl={ref}
|
|
128
|
+
open={open}
|
|
129
|
+
showArrow={showArrow}
|
|
130
|
+
placement="right-start"
|
|
131
|
+
>
|
|
132
|
+
right-start
|
|
133
|
+
</Tooltip>
|
|
134
|
+
)}
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
function Menu() {
|
|
140
|
+
const ref = useRef<any>(null);
|
|
141
|
+
return (
|
|
142
|
+
<>
|
|
143
|
+
<div
|
|
144
|
+
ref={ref}
|
|
145
|
+
style={{
|
|
146
|
+
width: 64,
|
|
147
|
+
textAlign: 'center',
|
|
148
|
+
height: 32,
|
|
149
|
+
display: 'flex',
|
|
150
|
+
backgroundColor: '#cc8888',
|
|
151
|
+
}}
|
|
152
|
+
>
|
|
153
|
+
Menu
|
|
154
|
+
</div>
|
|
155
|
+
<Tooltip anchorEl={ref} open={true} showArrow={true} placement="bottom">
|
|
156
|
+
Some slightly bigger tooltip
|
|
157
|
+
</Tooltip>
|
|
158
|
+
</>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const PopperFixedExample = ({ showAll = false, showArrow = false }) => {
|
|
163
|
+
const ref = useRef<any>(null);
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<>
|
|
167
|
+
<div
|
|
168
|
+
style={{
|
|
169
|
+
position: 'fixed',
|
|
170
|
+
left: 0,
|
|
171
|
+
maxWidth: 230,
|
|
172
|
+
right: 230,
|
|
173
|
+
top: 0,
|
|
174
|
+
bottom: 0,
|
|
175
|
+
display: 'flex',
|
|
176
|
+
backgroundColor: '#f5f5f5',
|
|
177
|
+
overflow: 'hidden',
|
|
178
|
+
justifyContent: 'flex-end',
|
|
179
|
+
zIndex: 1000,
|
|
180
|
+
}}
|
|
181
|
+
>
|
|
182
|
+
<Menu />
|
|
183
|
+
</div>
|
|
184
|
+
<div style={{ paddingLeft: 560 }}>
|
|
185
|
+
<Menu />
|
|
186
|
+
</div>
|
|
187
|
+
</>
|
|
188
|
+
);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const JumpingPopper = () => {
|
|
192
|
+
const [currentPopperIndex, setCurrentPopperIndex] = useState(0);
|
|
193
|
+
const ref1 = useRef();
|
|
194
|
+
const ref2 = useRef();
|
|
195
|
+
const ref3 = useRef();
|
|
196
|
+
const allRefs = [ref1, ref2, ref3];
|
|
197
|
+
|
|
198
|
+
useEffect(() => {
|
|
199
|
+
const handler = setInterval(() => {
|
|
200
|
+
setCurrentPopperIndex((i) => i + 1);
|
|
201
|
+
}, 1000);
|
|
202
|
+
|
|
203
|
+
return () => {
|
|
204
|
+
clearInterval(handler);
|
|
205
|
+
};
|
|
206
|
+
}, []);
|
|
207
|
+
|
|
208
|
+
const currentPopperRef = allRefs[currentPopperIndex % allRefs.length];
|
|
209
|
+
|
|
210
|
+
return (
|
|
211
|
+
<>
|
|
212
|
+
<div>
|
|
213
|
+
<button ref={ref1} style={{ margin: 25 }}>
|
|
214
|
+
Button 1
|
|
215
|
+
</button>
|
|
216
|
+
<button ref={ref2} style={{ margin: 25 }}>
|
|
217
|
+
Button 2
|
|
218
|
+
</button>
|
|
219
|
+
<button ref={ref3} style={{ margin: 25 }}>
|
|
220
|
+
Button 3
|
|
221
|
+
</button>
|
|
222
|
+
</div>
|
|
223
|
+
{currentPopperRef && (
|
|
224
|
+
<Popper anchorEl={currentPopperRef}>Hello Popper</Popper>
|
|
225
|
+
)}
|
|
226
|
+
</>
|
|
227
|
+
);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const RerenderingPopper = () => {
|
|
231
|
+
const [value, setValue] = useState('');
|
|
232
|
+
const ref = useRef<HTMLButtonElement>(null);
|
|
233
|
+
return (
|
|
234
|
+
<div style={{ margin: 100 }}>
|
|
235
|
+
<button ref={ref}>Buy now</button>
|
|
236
|
+
<Popper anchorEl={ref}>
|
|
237
|
+
<div
|
|
238
|
+
style={{
|
|
239
|
+
padding: 24,
|
|
240
|
+
boxShadow: '0px 2px 10px rgba(0,0,0,0.12)',
|
|
241
|
+
backgroundColor: 'white',
|
|
242
|
+
}}
|
|
243
|
+
>
|
|
244
|
+
<input value={value} onChange={(e) => setValue(e.target.value)} />
|
|
245
|
+
</div>
|
|
246
|
+
</Popper>
|
|
247
|
+
</div>
|
|
248
|
+
);
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
stories.add('Single point', () => <PopperExample />);
|
|
252
|
+
|
|
253
|
+
stories.add('All points', () => <PopperExample showAll={true} />);
|
|
254
|
+
|
|
255
|
+
stories.add('Single point, with arrow', () => (
|
|
256
|
+
<PopperExample showArrow={true} />
|
|
257
|
+
));
|
|
258
|
+
|
|
259
|
+
stories.add('All points, with arrow', () => (
|
|
260
|
+
<PopperExample showAll={true} showArrow={true} />
|
|
261
|
+
));
|
|
262
|
+
|
|
263
|
+
stories.add('Fixed popper, all points', () => (
|
|
264
|
+
<PopperFixedExample showAll={true} />
|
|
265
|
+
));
|
|
266
|
+
stories.add('Re-rendering popper', () => <RerenderingPopper />);
|
|
267
|
+
stories.add('Jumping popper', () => <JumpingPopper />);
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import {
|
|
2
|
+
forwardRef,
|
|
3
|
+
useRef,
|
|
4
|
+
useEffect,
|
|
5
|
+
useLayoutEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
memo,
|
|
8
|
+
} from 'react';
|
|
9
|
+
import type * as React from 'react';
|
|
10
|
+
import { assignMultipleRefs } from '../utils/assignRef';
|
|
11
|
+
import { PopperProvider, PopperContextProps } from './context';
|
|
12
|
+
import { Portal } from '../Portal';
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
Placement,
|
|
16
|
+
Modifier,
|
|
17
|
+
PositioningStrategy,
|
|
18
|
+
Instance,
|
|
19
|
+
Rect,
|
|
20
|
+
createPopper,
|
|
21
|
+
} from '@popperjs/core';
|
|
22
|
+
import type { OffsetModifier } from '@popperjs/core/lib/modifiers/offset';
|
|
23
|
+
import type { ArrowModifier } from '@popperjs/core/lib/modifiers/arrow';
|
|
24
|
+
|
|
25
|
+
const useEnhancedEffect =
|
|
26
|
+
typeof window !== 'undefined' ? useLayoutEffect : useEffect;
|
|
27
|
+
|
|
28
|
+
export type OffsetsFunction = (arg0: {
|
|
29
|
+
popper: Rect;
|
|
30
|
+
reference: Rect;
|
|
31
|
+
placement: Placement;
|
|
32
|
+
}) => [number | null | undefined, number | null | undefined];
|
|
33
|
+
|
|
34
|
+
export interface PopperProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
35
|
+
as?: React.ElementType<any>;
|
|
36
|
+
innerAs?: React.ElementType<any>;
|
|
37
|
+
anchorEl: React.RefObject<HTMLElement>;
|
|
38
|
+
children?: React.ReactNode;
|
|
39
|
+
placement?: Placement;
|
|
40
|
+
strategy?: PositioningStrategy;
|
|
41
|
+
modifiers?: Array<Partial<Modifier<any, any>>>;
|
|
42
|
+
usePortal?: boolean;
|
|
43
|
+
portalSelector?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Displaces the popper along the reference element.
|
|
46
|
+
*/
|
|
47
|
+
skidding?: number;
|
|
48
|
+
/**
|
|
49
|
+
* Displaces the popper away from, or toward, the reference element in the direction of its placement. A positive number displaces it further away, while a negative number lets it overlap the reference.
|
|
50
|
+
*/
|
|
51
|
+
distance?: number;
|
|
52
|
+
/**
|
|
53
|
+
* An optional function that must return a pair of [skidding, padding]. Useful for doing things like, displace the popper by 100%.
|
|
54
|
+
*/
|
|
55
|
+
offsetFn?: OffsetsFunction;
|
|
56
|
+
/**
|
|
57
|
+
* If you don't want the arrow to reach the very edge of the popper (this is common if your popper has rounded corners using border-radius), you can apply some padding to it.
|
|
58
|
+
*/
|
|
59
|
+
arrowPadding?: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const emptyModifiers: Array<Partial<Modifier<any, any>>> = [];
|
|
63
|
+
export const Popper = memo(
|
|
64
|
+
forwardRef<HTMLDivElement, PopperProps>(function Popper(
|
|
65
|
+
{
|
|
66
|
+
placement = 'bottom',
|
|
67
|
+
strategy = 'absolute',
|
|
68
|
+
as: Comp = 'div',
|
|
69
|
+
innerAs,
|
|
70
|
+
anchorEl,
|
|
71
|
+
children,
|
|
72
|
+
modifiers = emptyModifiers,
|
|
73
|
+
usePortal = false,
|
|
74
|
+
style = {},
|
|
75
|
+
portalSelector = 'body',
|
|
76
|
+
distance = 0,
|
|
77
|
+
skidding = 0,
|
|
78
|
+
arrowPadding = 5,
|
|
79
|
+
offsetFn,
|
|
80
|
+
...props
|
|
81
|
+
},
|
|
82
|
+
forwardedRef
|
|
83
|
+
) {
|
|
84
|
+
const arrowRef = useRef<HTMLSpanElement>(null);
|
|
85
|
+
|
|
86
|
+
const popperRef = useRef<HTMLDivElement | null>(null);
|
|
87
|
+
const popperEngineInstance = useRef<null | Instance>(null);
|
|
88
|
+
|
|
89
|
+
const defaultModifiers: Array<Partial<Modifier<any, any>>> = useMemo(() => {
|
|
90
|
+
const arrowModifier: Omit<ArrowModifier, 'enabled' | 'fn' | 'phase'> = {
|
|
91
|
+
name: 'arrow',
|
|
92
|
+
options: {
|
|
93
|
+
element: '[data-popper-arrow]',
|
|
94
|
+
padding: arrowPadding,
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
const offsetModifier: Omit<OffsetModifier, 'enabled' | 'fn' | 'phase'> = {
|
|
98
|
+
name: 'offset',
|
|
99
|
+
options: {
|
|
100
|
+
offset: offsetFn ?? [skidding, distance],
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
return [arrowModifier, offsetModifier];
|
|
105
|
+
}, [arrowPadding, distance, skidding, offsetFn]);
|
|
106
|
+
|
|
107
|
+
useEnhancedEffect(() => {
|
|
108
|
+
if (anchorEl && anchorEl.current && popperRef.current) {
|
|
109
|
+
popperEngineInstance.current = createPopper(
|
|
110
|
+
anchorEl.current,
|
|
111
|
+
popperRef.current,
|
|
112
|
+
{
|
|
113
|
+
placement,
|
|
114
|
+
strategy,
|
|
115
|
+
modifiers: [...defaultModifiers, ...modifiers],
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return () => {
|
|
121
|
+
popperEngineInstance.current && popperEngineInstance.current.destroy();
|
|
122
|
+
popperEngineInstance.current = null;
|
|
123
|
+
};
|
|
124
|
+
}, [anchorEl, modifiers, placement, strategy, defaultModifiers]);
|
|
125
|
+
|
|
126
|
+
const contextValue: PopperContextProps = {
|
|
127
|
+
arrowRef,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const ret = (
|
|
131
|
+
<PopperProvider value={contextValue}>
|
|
132
|
+
<Comp
|
|
133
|
+
{...props}
|
|
134
|
+
as={innerAs}
|
|
135
|
+
ref={assignMultipleRefs(popperRef, forwardedRef)}
|
|
136
|
+
style={{ position: 'fixed', left: -5000, top: -5000, ...style }}
|
|
137
|
+
>
|
|
138
|
+
{children}
|
|
139
|
+
</Comp>
|
|
140
|
+
</PopperProvider>
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
if (usePortal) {
|
|
144
|
+
return <Portal selector={portalSelector}>{ret}</Portal>;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return ret;
|
|
148
|
+
})
|
|
149
|
+
);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import type * as React from 'react';
|
|
3
|
+
import { usePopperContext } from './context';
|
|
4
|
+
import { assignRef } from '../utils/assignRef';
|
|
5
|
+
|
|
6
|
+
export interface PopperArrowProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
as?: React.ElementType<any>;
|
|
8
|
+
innerAs?: React.ElementType<any>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const PopperArrow = forwardRef<HTMLDivElement, PopperArrowProps>(
|
|
12
|
+
function PopperArrow({ as: Comp = 'div', ...props }, ref) {
|
|
13
|
+
const ctx = usePopperContext();
|
|
14
|
+
|
|
15
|
+
if (ctx === null) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<Comp
|
|
21
|
+
{...props}
|
|
22
|
+
ref={(node: HTMLDivElement | null) => {
|
|
23
|
+
if (node && ctx.arrowRef.current && ctx.arrowRef.current !== node) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
'You can only render one <PopperArrow /> per <Popper> component'
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
assignRef(ref, node);
|
|
30
|
+
assignRef(ctx.arrowRef, node);
|
|
31
|
+
}}
|
|
32
|
+
data-popper-arrow=""
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { createContext, MutableRefObject, useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface PopperContextProps {
|
|
4
|
+
arrowRef: MutableRefObject<HTMLSpanElement | null>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const context = createContext<PopperContextProps | null>(null);
|
|
8
|
+
export const PopperProvider = context.Provider;
|
|
9
|
+
export const usePopperContext = () => useContext(context);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
[data-popper-arrow] {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
position: absolute;
|
|
4
|
+
width: 10px;
|
|
5
|
+
height: 10px;
|
|
6
|
+
}
|
|
7
|
+
[data-popper-arrow]::before {
|
|
8
|
+
/* Apply shadows as a pseudo element */
|
|
9
|
+
content: '';
|
|
10
|
+
background-color: #fff;
|
|
11
|
+
width: 100%;
|
|
12
|
+
height: 100%;
|
|
13
|
+
position: absolute;
|
|
14
|
+
z-index: -1;
|
|
15
|
+
box-shadow: 2px 2px 2px 0 rgba(187, 187, 187, 0.6);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* Popper on top */
|
|
19
|
+
[data-popper-placement^='top'] {
|
|
20
|
+
transform-origin: bottom center;
|
|
21
|
+
}
|
|
22
|
+
[data-popper-placement^='top'] [data-popper-arrow] {
|
|
23
|
+
bottom: -5px;
|
|
24
|
+
}
|
|
25
|
+
[data-popper-placement^='top'] [data-popper-arrow]::before {
|
|
26
|
+
transform: rotate(45deg);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Popper on bottom */
|
|
30
|
+
[data-popper-placement^='bottom'] {
|
|
31
|
+
transform-origin: top center;
|
|
32
|
+
}
|
|
33
|
+
[data-popper-placement^='bottom'] [data-popper-arrow] {
|
|
34
|
+
top: -5px;
|
|
35
|
+
}
|
|
36
|
+
[data-popper-placement^='bottom'] [data-popper-arrow]::before {
|
|
37
|
+
transform: rotate(-135deg);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* Popper on right */
|
|
41
|
+
[data-popper-placement^='right'] {
|
|
42
|
+
transform-origin: left center;
|
|
43
|
+
}
|
|
44
|
+
[data-popper-placement^='right'] [data-popper-arrow] {
|
|
45
|
+
left: -5px;
|
|
46
|
+
}
|
|
47
|
+
[data-popper-placement^='right'] [data-popper-arrow]::before {
|
|
48
|
+
transform: rotate(135deg);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Popper on left */
|
|
52
|
+
[data-popper-placement^='left'] {
|
|
53
|
+
transform-origin: right center;
|
|
54
|
+
}
|
|
55
|
+
[data-popper-placement^='left'] [data-popper-arrow] {
|
|
56
|
+
right: -5px;
|
|
57
|
+
}
|
|
58
|
+
[data-popper-placement^='left'] [data-popper-arrow]::before {
|
|
59
|
+
transform: rotate(-45deg);
|
|
60
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { FC, ReactNode } from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
3
|
+
|
|
4
|
+
export interface PortalProps {
|
|
5
|
+
children?: ReactNode;
|
|
6
|
+
selector?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const Portal: FC<PortalProps> = ({ children, selector = 'body' }) => {
|
|
10
|
+
if (typeof window === 'undefined') {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const dom = document.querySelector(selector);
|
|
15
|
+
if (dom) {
|
|
16
|
+
return createPortal(<div data-portal="">{children}</div>, dom);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return null;
|
|
20
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Portal';
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { RadioButton, RadioGroup } from './';
|
|
3
|
+
import { storiesOf } from '@storybook/react';
|
|
4
|
+
// import './styles.css';
|
|
5
|
+
|
|
6
|
+
const Uncontrolled = (props: { initialValue?: string }) => {
|
|
7
|
+
return (
|
|
8
|
+
<RadioGroup defaultValue={props.initialValue}>
|
|
9
|
+
<label>
|
|
10
|
+
<RadioButton value="highly agree" />
|
|
11
|
+
Highly agree
|
|
12
|
+
</label>
|
|
13
|
+
<label>
|
|
14
|
+
<RadioButton value="agree" />
|
|
15
|
+
Agree
|
|
16
|
+
</label>
|
|
17
|
+
<label>
|
|
18
|
+
<RadioButton value="neutral" />
|
|
19
|
+
Neutral
|
|
20
|
+
</label>
|
|
21
|
+
<label>
|
|
22
|
+
<RadioButton value="disagree" />
|
|
23
|
+
Disagree
|
|
24
|
+
</label>
|
|
25
|
+
<label>
|
|
26
|
+
<RadioButton value="highly disagree" />
|
|
27
|
+
Highly disagree
|
|
28
|
+
</label>
|
|
29
|
+
</RadioGroup>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const Controlled = () => {
|
|
34
|
+
const [value, setValue] = useState(undefined);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<RadioGroup
|
|
38
|
+
value={value}
|
|
39
|
+
onChange={(e, value) => {
|
|
40
|
+
setValue(value);
|
|
41
|
+
}}
|
|
42
|
+
>
|
|
43
|
+
<label>
|
|
44
|
+
<RadioButton value="highly agree" />
|
|
45
|
+
Highly agree
|
|
46
|
+
</label>
|
|
47
|
+
<label>
|
|
48
|
+
<RadioButton value="agree" />
|
|
49
|
+
Agree
|
|
50
|
+
</label>
|
|
51
|
+
<label>
|
|
52
|
+
<RadioButton value="neutral" />
|
|
53
|
+
Neutral
|
|
54
|
+
</label>
|
|
55
|
+
<label>
|
|
56
|
+
<RadioButton value="disagree" />
|
|
57
|
+
Disagree
|
|
58
|
+
</label>
|
|
59
|
+
<label>
|
|
60
|
+
<RadioButton value="highly disagree" />
|
|
61
|
+
Highly disagree
|
|
62
|
+
</label>
|
|
63
|
+
</RadioGroup>
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const stories = storiesOf('Components/RadioButton', module);
|
|
68
|
+
|
|
69
|
+
stories.add('uncontrolled', () => <Uncontrolled />);
|
|
70
|
+
stories.add('uncontrolled, initial value set', () => (
|
|
71
|
+
<Uncontrolled initialValue={'highly disagree'} />
|
|
72
|
+
));
|
|
73
|
+
stories.add('controlled', () => <Controlled />);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import type * as React from 'react';
|
|
3
|
+
import { RadioValue, useRadioGroupContext } from './context';
|
|
4
|
+
import { wrapEvent } from '../utils';
|
|
5
|
+
|
|
6
|
+
export interface RadioButtonProps
|
|
7
|
+
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value'> {
|
|
8
|
+
as?: React.ElementType<any>;
|
|
9
|
+
innerAs?: React.ElementType<any>;
|
|
10
|
+
children?: React.ReactNode;
|
|
11
|
+
checked?: boolean;
|
|
12
|
+
value: RadioValue;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const RadioButton = forwardRef<HTMLInputElement, RadioButtonProps>(
|
|
16
|
+
function RadioButton(props, forwardedRef) {
|
|
17
|
+
const {
|
|
18
|
+
as: Comp = 'input',
|
|
19
|
+
value: valueProp,
|
|
20
|
+
onChange: onChangeProp,
|
|
21
|
+
checked: checkedProp,
|
|
22
|
+
name: nameProp,
|
|
23
|
+
...otherProps
|
|
24
|
+
} = props;
|
|
25
|
+
const radioGroupContext = useRadioGroupContext();
|
|
26
|
+
|
|
27
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
28
|
+
radioGroupContext?.onChange?.(e, valueProp);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const checked = radioGroupContext
|
|
32
|
+
? radioGroupContext.value === valueProp
|
|
33
|
+
: checkedProp;
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Comp
|
|
37
|
+
ref={forwardedRef}
|
|
38
|
+
type="radio"
|
|
39
|
+
checked={checked}
|
|
40
|
+
aria-checked={String(checked)}
|
|
41
|
+
name={radioGroupContext ? radioGroupContext.name : nameProp}
|
|
42
|
+
onChange={wrapEvent(onChangeProp, handleChange)}
|
|
43
|
+
value={valueProp}
|
|
44
|
+
{...otherProps}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
);
|