@a-type/ui 6.1.16 → 6.1.19

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 (47) hide show
  1. package/dist/components/button/ConfirmedButton.js +2 -2
  2. package/dist/components/button/ConfirmedButton.js.map +1 -1
  3. package/dist/components/datePicker/DatePicker.d.ts +1 -1
  4. package/dist/components/datePicker/DatePicker.stories.d.ts +1 -1
  5. package/dist/components/datePicker/DateRangePicker.d.ts +1 -1
  6. package/dist/components/datePicker/DateRangePicker.js +15 -1
  7. package/dist/components/datePicker/DateRangePicker.js.map +1 -1
  8. package/dist/components/dialog/Dialog.d.ts +20 -34
  9. package/dist/components/dialog/Dialog.js +47 -208
  10. package/dist/components/dialog/Dialog.js.map +1 -1
  11. package/dist/components/dialog/Dialog.module.css +10 -82
  12. package/dist/components/dialog/Dialog.stories.js +8 -7
  13. package/dist/components/dialog/Dialog.stories.js.map +1 -1
  14. package/dist/components/drawer/Drawer.d.ts +24 -0
  15. package/dist/components/drawer/Drawer.js +101 -0
  16. package/dist/components/drawer/Drawer.js.map +1 -0
  17. package/dist/components/drawer/Drawer.module.css +236 -0
  18. package/dist/components/drawer/Drawer.stories.d.ts +7 -0
  19. package/dist/components/drawer/Drawer.stories.js +28 -0
  20. package/dist/components/drawer/Drawer.stories.js.map +1 -0
  21. package/dist/components/drawer/index.d.ts +1 -0
  22. package/dist/components/drawer/index.js +2 -0
  23. package/dist/components/drawer/index.js.map +1 -0
  24. package/dist/components/globalErrorFallback/GlobalErrorFallback.d.ts +1 -1
  25. package/dist/components/globalErrorFallback/GlobalErrorFallback.js +2 -1
  26. package/dist/components/globalErrorFallback/GlobalErrorFallback.js.map +1 -1
  27. package/dist/components/index.d.ts +1 -0
  28. package/dist/components/index.js +1 -0
  29. package/dist/components/index.js.map +1 -1
  30. package/dist/components/scrollArea/ScrollArea.module.css +1 -0
  31. package/dist/hooks/withContextVariant.d.ts +8 -0
  32. package/dist/hooks/withContextVariant.js +21 -0
  33. package/dist/hooks/withContextVariant.js.map +1 -0
  34. package/package.json +3 -2
  35. package/src/components/button/ConfirmedButton.tsx +9 -16
  36. package/src/components/datePicker/DateRangePicker.tsx +18 -1
  37. package/src/components/dialog/Dialog.module.css +10 -82
  38. package/src/components/dialog/Dialog.stories.tsx +45 -51
  39. package/src/components/dialog/Dialog.tsx +98 -332
  40. package/src/components/drawer/Drawer.module.css +235 -0
  41. package/src/components/drawer/Drawer.stories.tsx +109 -0
  42. package/src/components/drawer/Drawer.tsx +189 -0
  43. package/src/components/drawer/index.ts +1 -0
  44. package/src/components/globalErrorFallback/GlobalErrorFallback.tsx +2 -1
  45. package/src/components/index.ts +1 -0
  46. package/src/components/scrollArea/ScrollArea.module.css +1 -0
  47. package/src/hooks/withContextVariant.tsx +24 -0
@@ -0,0 +1,235 @@
1
+ .overlay {
2
+ --bleed: 3rem;
3
+ --swipe-opacity: 0.2;
4
+
5
+ position: fixed;
6
+ min-height: 100dvh;
7
+ inset: 0;
8
+
9
+ @apply --mx-bg(var(--m-gray-ink));
10
+ opacity: calc(
11
+ 0.2 + var(--swipe-opacity) * (1 - var(--drawer-swipe-progress, 0))
12
+ );
13
+
14
+ border-top: var(--m-lw) solid var(--m-gray);
15
+
16
+ transition: all var(--m-dur-long) var(--m-ease);
17
+
18
+ backdrop-filter: blur(2px);
19
+
20
+ &[data-starting-style],
21
+ &[data-ending-style] {
22
+ opacity: 0;
23
+ }
24
+
25
+ &[data-swiping] {
26
+ transition-duration: 0ms;
27
+ }
28
+
29
+ &[data-ending-style] {
30
+ transition-duration: calc(var(--drawer-swipe-strength) * var(--m-dur-long));
31
+ }
32
+ }
33
+
34
+ .viewport {
35
+ --bleed: 3rem;
36
+
37
+ position: fixed;
38
+ inset: 0;
39
+ display: flex;
40
+ align-items: flex-end;
41
+ justify-content: center;
42
+ touch-action: none;
43
+
44
+ &::after {
45
+ content: '';
46
+ position: fixed;
47
+ inset-inline: 0;
48
+ bottom: 0;
49
+ height: var(--bleed);
50
+ @apply --mx-bg(var(--m-surface-ambient-bg));
51
+ pointer-events: none;
52
+ }
53
+
54
+ &[data-closed]::after {
55
+ opacity: 0;
56
+ }
57
+
58
+ /* While swiping, the popup's own bleed covers the bottom edge, so hide the
59
+ fixed fill — otherwise it paints white over the backdrop once the popup is
60
+ dragged far enough down. */
61
+ &:has([data-swiping])::after {
62
+ opacity: 0;
63
+ }
64
+ }
65
+
66
+ .popup {
67
+ --bleed: 3rem;
68
+ --top-gap: 1rem;
69
+
70
+ box-sizing: border-box;
71
+ position: relative;
72
+ z-index: 1;
73
+ max-width: unset;
74
+ width: 100%;
75
+ max-height: calc(100% - var(--top-gap) + var(--bleed));
76
+ height: calc(100% - var(--top-gap) + var(--bleed));
77
+ margin-bottom: calc(-1 * var(--bleed));
78
+ padding-bottom: max(
79
+ 0px,
80
+ calc(var(--drawer-snap-point-offset) + var(--drawer-swipe-movement-y))
81
+ );
82
+
83
+ display: flex;
84
+ flex-direction: column;
85
+ align-items: stretch;
86
+
87
+ @apply --mx-bg(var(--m-surface-ambient-bg));
88
+ @apply --mx-borderColor(var(--m-gray-heavy));
89
+
90
+ touch-action: none;
91
+
92
+ overflow: visible;
93
+
94
+ transition:
95
+ transform var(--m-dur-long) var(--m-ease),
96
+ box-shadow var(--m-dur-long) var(--m-ease),
97
+ padding-bottom var(--m-dur-long) var(--m-ease);
98
+
99
+ --shadow-dir: -1;
100
+ @apply --mx-shadow;
101
+ --mx-shadow-value: 0 calc(var(--shadow-dir, 1) * var(--m-shadow-lg-y))
102
+ var(--m-shadow-lg-blur) var(--m-shadow-lg-spread) var(--m-shadow-lg-color);
103
+
104
+ border-radius: var(--m-surface-rd) var(--m-surface-rd) 0 0;
105
+ border-bottom-width: 0;
106
+
107
+ transform: translateY(
108
+ calc(
109
+ var(--drawer-swipe-movement-y) + var(--drawer-snap-point-offset) -
110
+ var(--bleed)
111
+ )
112
+ );
113
+ will-change: transform;
114
+
115
+ /* Disable transitions while swiping so movement is immediate */
116
+ &[data-swiping] {
117
+ transition: none;
118
+ user-select: none;
119
+ }
120
+
121
+ &[data-starting-style],
122
+ &[data-ending-style] {
123
+ transform: translateY(calc(100% - var(--bleed)));
124
+ padding-bottom: 0;
125
+ box-shadow: 0 0 0 transparent;
126
+ }
127
+
128
+ &[data-ending-style] {
129
+ transition-duration: calc(var(--drawer-swipe-strength) * var(--m-dur-long));
130
+ }
131
+
132
+ &::after {
133
+ content: '';
134
+ position: absolute;
135
+ inset-inline: 0;
136
+ top: 100%;
137
+ height: var(--bleed);
138
+ background-color: inherit;
139
+ pointer-events: none;
140
+ }
141
+ }
142
+
143
+ .contentScrollArea {
144
+ flex: 1 1 auto;
145
+ min-height: 0;
146
+ overscroll-behavior: contain;
147
+ touch-action: auto;
148
+ overflow-y: auto;
149
+ padding: var(--m-surface-p);
150
+ display: flex;
151
+ flex-direction: column;
152
+ gap: var(--m-sp-md);
153
+ }
154
+
155
+ .swipeHandle {
156
+ position: relative;
157
+ display: flex;
158
+ align-items: center;
159
+ justify-content: center;
160
+ width: 100%;
161
+ cursor: grab;
162
+ touch-action: none;
163
+ padding-block: var(--m-sp-lg);
164
+ user-select: none;
165
+ flex-shrink: 0;
166
+ }
167
+
168
+ .swipeHandleBar {
169
+ height: --fn-literal(4px);
170
+ width: 20%;
171
+ @apply --mx-bg(var(--m-gray));
172
+ border-radius: var(--m-rd-lg);
173
+ }
174
+
175
+ .close {
176
+ position: absolute;
177
+ right: var(--m-sp-sm);
178
+ top: var(--m-sp-sm);
179
+ z-index: 101;
180
+ display: flex;
181
+ }
182
+
183
+ .title {
184
+ @apply --mx-fg(var(--m-gray-ink));
185
+ font-size: var(--m-prose-primary-size);
186
+ font-weight: var(--m-prose-primary-weight);
187
+ line-height: var(--m-prose-primary-lineHeight);
188
+ margin-block-end: var(--m-sp-xs);
189
+ margin-block-start: 0;
190
+ }
191
+
192
+ .description {
193
+ @apply --mx-fg(var(--m-gray-heavy));
194
+ font-size: var(--m-prose-ambient-size);
195
+ line-height: var(--m-prose-ambient-lineHeight);
196
+ margin: 0;
197
+ margin-block-end: var(--m-sp-xs);
198
+ }
199
+
200
+ .actions {
201
+ position: sticky;
202
+ bottom: 0;
203
+ z-index: 100;
204
+ display: flex;
205
+ flex-direction: row;
206
+ flex-wrap: wrap;
207
+ align-items: center;
208
+ justify-content: end;
209
+ align-self: end;
210
+ @apply --mx-shadow;
211
+ --mx-shadow-value: 0 calc(-1 * var(--m-shadow-md-y)) 16px
212
+ var(--m-shadow-md-spread) var(--m-gray-paper);
213
+ @apply --mx-bg(var(--m-surface-ambient-bg));
214
+ @apply --mx-bg-faded(50%);
215
+ margin-block-start: var(--m-sp-md);
216
+ gap: var(--m-sp-sm);
217
+ border-radius: var(--m-surface-rd);
218
+ transition: bottom var(--m-dur) var(--m-ease);
219
+
220
+ /*
221
+ Hide the actions when the keyboard is open.
222
+ Done by moving them downward behind it.
223
+ This both fixes a bug with the positioning, and
224
+ gives the user more room to see.
225
+ */
226
+ transform: translateY(calc(2 * var(--drawer-keyboard-inset, 0px)));
227
+ /*
228
+ Same but with modern CSS if()
229
+ */
230
+ @supports (
231
+ display: if(style(--drawer-keyboard-inset > 0px): none; else: flex;)
232
+ ) {
233
+ display: if(style(--drawer-keyboard-inset > 0px): none; else: flex;);
234
+ }
235
+ }
@@ -0,0 +1,109 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { Button } from '../button/index.js';
3
+ import { Input } from '../input/Input.js';
4
+ import { H1, P } from '../typography/index.js';
5
+ import { Drawer } from './Drawer.js';
6
+
7
+ const meta: any = {
8
+ title: 'Components/Drawer',
9
+ component: Drawer,
10
+ argTypes: {},
11
+ parameters: {
12
+ controls: { expanded: true },
13
+ },
14
+ } satisfies Meta<typeof Drawer>;
15
+
16
+ export default meta;
17
+
18
+ type Story = StoryObj<typeof Drawer>;
19
+
20
+ function DummyContent() {
21
+ return (
22
+ <div style={{ display: 'flex', flexDirection: 'column' }}>
23
+ <H1>Some content</H1>
24
+ <P>
25
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam at
26
+ porttitor sem. Aliquam erat volutpat. Donec fermentum tortor eget ligula
27
+ accumsan, sit amet ullamcorper nunc ultricies. Nulla facilisi.
28
+ </P>
29
+ <Input placeholder="Type something..." />
30
+ <P>
31
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam at
32
+ porttitor sem. Aliquam erat volutpat. Donec fermentum tortor eget ligula
33
+ accumsan, sit amet ullamcorper nunc ultricies. Nulla facilisi.
34
+ </P>
35
+ <Input placeholder="Type something..." />
36
+ <P>
37
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam at
38
+ porttitor sem. Aliquam erat volutpat. Donec fermentum tortor eget ligula
39
+ accumsan, sit amet ullamcorper nunc ultricies. Nulla facilisi. Lorem
40
+ ipsum dolor sit amet, consectetur adipiscing elit. Aliquam at porttitor
41
+ sem. Aliquam erat volutpat. Donec fermentum tortor eget ligula accumsan,
42
+ sit amet ullamcorper nunc ultricies. Nulla facilisi.
43
+ </P>
44
+ <Input placeholder="Type something..." />
45
+ <P>
46
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam at
47
+ porttitor sem. Aliquam erat volutpat. Donec fermentum tortor eget ligula
48
+ accumsan, sit amet ullamcorper nunc ultricies. Nulla facilisi. Lorem
49
+ ipsum dolor sit amet, consectetur adipiscing elit. Aliquam at porttitor
50
+ sem. Aliquam erat volutpat. Donec fermentum tortor eget ligula accumsan,
51
+ sit amet ullamcorper nunc ultricies. Nulla facilisi.
52
+ </P>
53
+ <Input placeholder="Type something..." />
54
+ <P>
55
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam at
56
+ porttitor sem. Aliquam erat volutpat. Donec fermentum tortor eget ligula
57
+ accumsan, sit amet ullamcorper nunc ultricies. Nulla facilisi. Lorem
58
+ ipsum dolor sit amet, consectetur adipiscing elit. Aliquam at porttitor
59
+ sem. Aliquam erat volutpat. Donec fermentum tortor eget ligula accumsan,
60
+ sit amet ullamcorper nunc ultricies. Nulla facilisi.
61
+ </P>
62
+ <Input placeholder="Type something..." />
63
+ <P>
64
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam at
65
+ porttitor sem. Aliquam erat volutpat. Donec fermentum tortor eget ligula
66
+ accumsan, sit amet ullamcorper nunc ultricies. Nulla facilisi. Lorem
67
+ ipsum dolor sit amet, consectetur adipiscing elit. Aliquam at porttitor
68
+ sem. Aliquam erat volutpat. Donec fermentum tortor eget ligula accumsan,
69
+ sit amet ullamcorper nunc ultricies. Nulla facilisi.
70
+ </P>
71
+ <Input placeholder="Type something..." />
72
+ </div>
73
+ );
74
+ }
75
+
76
+ export const Default: Story = {
77
+ args: {
78
+ children: (
79
+ <>
80
+ <Drawer.Trigger render={<Button />}>Open</Drawer.Trigger>
81
+ <Drawer.Content>
82
+ <Drawer.Title>Hello world</Drawer.Title>
83
+ <DummyContent />
84
+ <Drawer.Actions>
85
+ <Drawer.Close />
86
+ <Button emphasis="primary">Accept</Button>
87
+ </Drawer.Actions>
88
+ </Drawer.Content>
89
+ </>
90
+ ),
91
+ },
92
+ };
93
+
94
+ export const NoDefaultClose: Story = {
95
+ args: {
96
+ children: (
97
+ <>
98
+ <Drawer.Trigger render={<Button />}>Open</Drawer.Trigger>
99
+ <Drawer.Content disableDefaultClose>
100
+ <Drawer.Title>No default close</Drawer.Title>
101
+ <DummyContent />
102
+ <Drawer.Actions>
103
+ <Drawer.Close />
104
+ </Drawer.Actions>
105
+ </Drawer.Content>
106
+ </>
107
+ ),
108
+ },
109
+ };
@@ -0,0 +1,189 @@
1
+ import {
2
+ Drawer as BaseDrawer,
3
+ DrawerCloseProps,
4
+ DrawerPopupProps,
5
+ DrawerRootProps,
6
+ DrawerTriggerProps,
7
+ } from '@base-ui/react/drawer';
8
+ import clsx from 'clsx';
9
+ import { ComponentPropsWithoutRef, Ref, useCallback, useRef } from 'react';
10
+ import useMergedRef from '../../hooks/useMergedRef.js';
11
+ import { withClassName } from '../../hooks/withClassName.js';
12
+ import { Box } from '../box/Box.js';
13
+ import { Button } from '../button/index.js';
14
+ import { Icon } from '../icon/Icon.js';
15
+ import { useParticles } from '../particles/ParticleContext.js';
16
+ import cls from './Drawer.module.css';
17
+
18
+ const DrawerBackdrop = withClassName(BaseDrawer.Backdrop, cls.overlay);
19
+
20
+ const StyledPopup = withClassName(BaseDrawer.Popup, cls.popup);
21
+
22
+ export interface DrawerContentProps extends DrawerPopupProps {
23
+ disableDefaultClose?: boolean;
24
+ ref?: Ref<HTMLDivElement>;
25
+ innerClassName?: string;
26
+ }
27
+
28
+ const DrawerContent = function DrawerContent({
29
+ ref,
30
+ children,
31
+ className,
32
+ disableDefaultClose,
33
+ innerClassName,
34
+ ...props
35
+ }: DrawerContentProps) {
36
+ const particles = useParticles();
37
+ const wasOpenRef = useRef(false);
38
+
39
+ const openRef = useCallback(
40
+ (element: HTMLDivElement | null) => {
41
+ if (!wasOpenRef.current && element?.hasAttribute('data-open')) {
42
+ wasOpenRef.current = true;
43
+
44
+ setTimeout(() => {
45
+ particles?.addParticles(
46
+ particles.elementExplosion({
47
+ count: 20,
48
+ margin: 40,
49
+ borders: ['top'],
50
+ color: [
51
+ {
52
+ space: 'rgb',
53
+ values: [0, 0, 0],
54
+ opacity: 0.02,
55
+ },
56
+ {
57
+ space: 'rgb',
58
+ values: [0, 0, 0],
59
+ opacity: 0,
60
+ },
61
+ ],
62
+ element,
63
+ startRadius: 15,
64
+ endRadius: 0,
65
+ lifespan: 1000,
66
+ force: 0.5,
67
+ drag: 0.01,
68
+ forceFuzz: 0.5,
69
+ angleFuzz: 0.1,
70
+ }),
71
+ );
72
+ }, 180);
73
+ } else if (!element?.hasAttribute('data-open')) {
74
+ wasOpenRef.current = false;
75
+ }
76
+ },
77
+ [particles],
78
+ );
79
+
80
+ const finalRef = useMergedRef(ref, openRef);
81
+
82
+ return (
83
+ <BaseDrawer.VirtualKeyboardProvider>
84
+ <BaseDrawer.Portal>
85
+ <DrawerBackdrop data-role="backdrop" />
86
+ <BaseDrawer.Viewport className={cls.viewport}>
87
+ <StyledPopup ref={finalRef} {...props} className={className}>
88
+ {!disableDefaultClose && <DrawerDefaultClose />}
89
+ <DrawerSwipeHandle />
90
+ <div className={clsx(cls.contentScrollArea, innerClassName)}>
91
+ {children}
92
+ </div>
93
+ </StyledPopup>
94
+ </BaseDrawer.Viewport>
95
+ </BaseDrawer.Portal>
96
+ </BaseDrawer.VirtualKeyboardProvider>
97
+ );
98
+ };
99
+
100
+ function DrawerSwipeHandle({
101
+ ref,
102
+ className,
103
+ ...props
104
+ }: ComponentPropsWithoutRef<'div'> & {
105
+ ref?: React.Ref<HTMLDivElement>;
106
+ }) {
107
+ return (
108
+ <div ref={ref} {...props} className={clsx(cls.swipeHandle, className)}>
109
+ <div className={cls.swipeHandleBar} />
110
+ </div>
111
+ );
112
+ }
113
+
114
+ function DrawerDefaultClose({
115
+ ref,
116
+ className,
117
+ ...props
118
+ }: DrawerCloseProps & {
119
+ ref?: React.Ref<HTMLButtonElement>;
120
+ }) {
121
+ return (
122
+ <DrawerClose
123
+ className={clsx(cls.close, className)}
124
+ aria-label="Close drawer"
125
+ ref={ref}
126
+ {...props}
127
+ render={<Button emphasis="ghost" size="small" />}
128
+ >
129
+ <Icon name="x" />
130
+ </DrawerClose>
131
+ );
132
+ }
133
+
134
+ const DrawerTitle = withClassName(BaseDrawer.Title, cls.title);
135
+ const DrawerDescription = withClassName(
136
+ BaseDrawer.Description,
137
+ cls.description,
138
+ );
139
+
140
+ // TODO: support swipeDirection
141
+ export interface DrawerProps extends Omit<DrawerRootProps, 'swipeDirection'> {}
142
+ const defaultSnapPoints = [0.75, 1];
143
+ const DrawerRoot = ({
144
+ children,
145
+ snapPoints = defaultSnapPoints,
146
+ ...props
147
+ }: DrawerProps) => {
148
+ return (
149
+ <BaseDrawer.Root swipeDirection="down" snapPoints={snapPoints} {...props}>
150
+ {children}
151
+ </BaseDrawer.Root>
152
+ );
153
+ };
154
+
155
+ const DrawerTrigger = ({ render, ...props }: DrawerTriggerProps) => (
156
+ <BaseDrawer.Trigger render={render || <Button />} {...props} />
157
+ );
158
+
159
+ function DrawerClose({
160
+ children,
161
+ render,
162
+ ...props
163
+ }: DrawerCloseProps & {
164
+ ref?: React.Ref<HTMLButtonElement>;
165
+ }) {
166
+ return (
167
+ <BaseDrawer.Close
168
+ render={render ?? <Button emphasis="default" />}
169
+ {...props}
170
+ >
171
+ {children ?? 'Close'}
172
+ </BaseDrawer.Close>
173
+ );
174
+ }
175
+
176
+ const DrawerActions = withClassName(Box, cls.actions);
177
+
178
+ export const Drawer = Object.assign(DrawerRoot, {
179
+ Trigger: DrawerTrigger,
180
+ Content: DrawerContent,
181
+ Title: DrawerTitle,
182
+ Description: DrawerDescription,
183
+ Close: DrawerClose,
184
+ Actions: DrawerActions,
185
+ Unstyled: BaseDrawer as DrawerParts,
186
+ createHandle: BaseDrawer.createHandle,
187
+ });
188
+
189
+ export type DrawerParts = typeof BaseDrawer;
@@ -0,0 +1 @@
1
+ export * from './Drawer.js';
@@ -1,5 +1,6 @@
1
- import { Box, BoxProps, Heading, P } from '@a-type/ui';
2
1
  import { useLocalStorage } from '../../hooks.js';
2
+ import { Box, type BoxProps } from '../box/Box.js';
3
+ import { Heading, P } from '../typography/typography.js';
3
4
 
4
5
  export function GlobalErrorFallback({ children, ...rest }: BoxProps) {
5
6
  const hadRecentError = useHadRecentError();
@@ -14,6 +14,7 @@ export * from './combobox/Combobox.js';
14
14
  export * from './contextMenu/contextMenu.js';
15
15
  export * from './datePicker/index.js';
16
16
  export * from './dialog/index.js';
17
+ export * from './drawer/index.js';
17
18
  export * from './divider/index.js';
18
19
  export * from './dropdownMenu/index.js';
19
20
  export * from './editableText/EditableText.js';
@@ -4,6 +4,7 @@
4
4
  min-width: 0;
5
5
  display: flex;
6
6
  flex-direction: column;
7
+ touch-action: auto;
7
8
  }
8
9
 
9
10
  .viewport {
@@ -0,0 +1,24 @@
1
+ import { createContext, useContext } from 'react';
2
+
3
+ /**
4
+ * Creates a context and HOC which swaps between two components
5
+ * based on a contextual value
6
+ */
7
+ export function createWithContextVariant() {
8
+ const Context = createContext(false);
9
+ function withContextVariant<P extends object>(
10
+ TrueComponent: React.ComponentType<P>,
11
+ FalseComponent: React.ComponentType<P>,
12
+ ) {
13
+ return function WithContextVariant(props: P) {
14
+ const contextValue = useContext(Context);
15
+ const Component = contextValue ? TrueComponent : FalseComponent;
16
+ return <Component {...props} />;
17
+ };
18
+ }
19
+
20
+ return {
21
+ Context,
22
+ withContextVariant,
23
+ };
24
+ }