@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,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,3 @@
1
+ export * from './Popper';
2
+ export * from './PopperArrow';
3
+ export * from './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
+ );