paris 0.14.2 → 0.15.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # paris
2
2
 
3
+ ## 0.15.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 5072392: Menu: refactored from dot notation to named exports, so new MenuButton, MenuItems, and MenuItem replaces Menu.Button, etc
8
+
9
+ ### Patch Changes
10
+
11
+ - 5072392: Checkbox: new `switch` kind and `hideLabel` prop
12
+ - 5072392: Select: multiselect listbox stays open for selecting multiple options
13
+ - 5072392: Combobox: options dropdown opens initially on focus, can disable with `hideOptionsInitially` prop
14
+ - 5072392: General: upgraded headlessui package to v2
15
+ - 5072392: Menu, Select, Combobox: dropdowns now render as modal, meaning page scroll is locked
16
+
3
17
  ## 0.14.2
4
18
 
5
19
  ### Patch Changes
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "paris",
3
3
  "author": "Sanil Chawla <sanil@slingshot.fm> (https://sanil.co)",
4
4
  "description": "Paris is Slingshot's React design system. It's a collection of reusable components, design tokens, and guidelines that help us build consistent, accessible, and performant user interfaces.",
5
- "version": "0.14.2",
5
+ "version": "0.15.0",
6
6
  "homepage": "https://paris.slingshot.fm",
7
7
  "license": "MIT",
8
8
  "repository": {
@@ -64,7 +64,7 @@
64
64
  "@fortawesome/free-regular-svg-icons": "^6.4.2",
65
65
  "@fortawesome/free-solid-svg-icons": "^6.4.2",
66
66
  "@fortawesome/react-fontawesome": "^0.2.0",
67
- "@headlessui/react": "^1.7.14",
67
+ "@headlessui/react": "^2.2.4",
68
68
  "@radix-ui/react-checkbox": "^1.0.4",
69
69
  "@radix-ui/react-tooltip": "^1.1.8",
70
70
  "clsx": "^1.2.1",
@@ -114,3 +114,47 @@
114
114
  color: var(--pte-new-colors-contentDisabled);
115
115
  }
116
116
  }
117
+
118
+ .switchContainer {
119
+ width: 24px;
120
+ height: 14px;
121
+ border-radius: 100px;
122
+
123
+ display: flex;
124
+ align-items: center;
125
+ justify-content: center;
126
+
127
+ background-color: var(--pte-new-colors-contentTertiary);
128
+
129
+ &[data-checked], &[data-state="indeterminate"] {
130
+ background-color: var(--pte-new-colors-contentPrimary);
131
+ }
132
+
133
+ &[data-disabled=true] {
134
+ background-color: var(--pte-new-colors-contentInverseSecondary);
135
+
136
+ &[data-checked], &[data-state="indeterminate"] {
137
+ background-color: var(--pte-new-colors-contentInverseTertiary);
138
+ }
139
+ }
140
+
141
+ &:hover {
142
+ background-color: var(--pte-new-colors-contentSecondary);
143
+ }
144
+ }
145
+
146
+ .knob {
147
+ width: 10px;
148
+ height: 10px;
149
+ display: inline-block;
150
+ border-radius: 100%;
151
+
152
+ background-color: var(--pte-new-colors-contentInversePrimary);
153
+
154
+ transform: translateX(-5px);
155
+ transition: transform var(--pte-animations-interaction);
156
+ }
157
+
158
+ .knobChecked {
159
+ transform: translateX(5px);
160
+ }
@@ -41,3 +41,36 @@ export const Surface: Story = {
41
41
  });
42
42
  },
43
43
  };
44
+
45
+ export const Switch: Story = {
46
+ args: {
47
+ children: 'ACH Bank Transfer',
48
+ kind: 'switch',
49
+ },
50
+ render: (args) => {
51
+ // eslint-disable-next-line react-hooks/rules-of-hooks
52
+ const [checked, setChecked] = useState(false);
53
+ return createElement(Checkbox, {
54
+ ...args,
55
+ checked,
56
+ onChange: (e) => setChecked(!!e),
57
+ });
58
+ },
59
+ };
60
+
61
+ export const HideLabel: Story = {
62
+ args: {
63
+ children: 'ACH Bank Transfer',
64
+ kind: 'switch',
65
+ hideLabel: true,
66
+ },
67
+ render: (args) => {
68
+ // eslint-disable-next-line react-hooks/rules-of-hooks
69
+ const [checked, setChecked] = useState(false);
70
+ return createElement(Checkbox, {
71
+ ...args,
72
+ checked,
73
+ onChange: (e) => setChecked(!!e),
74
+ });
75
+ },
76
+ };
@@ -1,18 +1,24 @@
1
1
  import type { FC, ReactNode } from 'react';
2
- import { useRef, useId } from 'react';
2
+ import { useId } from 'react';
3
3
  import * as RadixCheckbox from '@radix-ui/react-checkbox';
4
4
  import clsx from 'clsx';
5
+ import { Switch } from '@headlessui/react';
5
6
  import styles from './Checkbox.module.scss';
6
- import { pvar } from '../theme';
7
- import { TextWhenString } from '../utility';
7
+ import { TextWhenString, VisuallyHidden } from '../utility';
8
8
  import { Check, Icon } from '../icon';
9
9
 
10
10
  export type CheckboxProps = {
11
- /** The visual style of the Checkbox. `default` is a standard checkbox with a label next to it, `surface` is a clickable card that displays a check when selected. */
12
- kind?: 'default' | 'surface'
11
+ /** The visual style of the Checkbox. `default` is a standard checkbox with a label next to it, `surface` is a clickable card that displays a check when selected, `switch` is a switch toggle. */
12
+ kind?: 'default' | 'surface' | 'switch'
13
13
  checked?: boolean;
14
14
  onChange?: (checked: boolean | 'indeterminate') => void;
15
15
  disabled?: boolean;
16
+ /**
17
+ * Whether to hide the label text of the Checkbox. Does not apply to `kind="surface"` because there would be nothing visible.
18
+ *
19
+ * @default false
20
+ */
21
+ hideLabel?: boolean;
16
22
  /** The contents of the Checkbox. */
17
23
  children?: ReactNode | ReactNode[];
18
24
  } & Omit<React.ComponentPropsWithoutRef<'label'>, 'onChange' | 'children'>;
@@ -34,6 +40,7 @@ export const Checkbox: FC<CheckboxProps> = ({
34
40
  checked,
35
41
  onChange,
36
42
  disabled,
43
+ hideLabel = false,
37
44
  children,
38
45
  className,
39
46
  ...props
@@ -45,43 +52,68 @@ export const Checkbox: FC<CheckboxProps> = ({
45
52
  className={clsx(styles.container, disabled && styles.disabled, className, checked && styles.checked)}
46
53
  {...props}
47
54
  >
48
- <RadixCheckbox.Root
49
- id={inputID}
50
- className={clsx(styles.root, styles[kind])}
51
- checked={checked}
52
- onCheckedChange={onChange}
53
- data-disabled={disabled}
54
- >
55
- {kind === 'surface' && (
56
- <TextWhenString kind="paragraphXSmall">
57
- {children}
58
- </TextWhenString>
59
- )}
60
- <RadixCheckbox.Indicator className={styles.indicator}>
61
- {kind === 'default' && (
62
- <svg width={14} height={14} viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
63
- <path
64
- className={styles.checkSvg}
55
+ {(kind === 'default' || kind === 'surface') && (
56
+ <RadixCheckbox.Root
57
+ id={inputID}
58
+ className={clsx(styles.root, styles[kind])}
59
+ checked={checked}
60
+ onCheckedChange={onChange}
61
+ data-disabled={disabled}
62
+ aria-details={typeof children === 'string' ? children : undefined}
63
+ >
64
+ {kind === 'surface' && (
65
+ <TextWhenString kind="paragraphXSmall">
66
+ {children}
67
+ </TextWhenString>
68
+ )}
69
+ <RadixCheckbox.Indicator className={styles.indicator}>
70
+ {kind === 'default' && (
71
+ <svg width={14} height={14} viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
72
+ <path
73
+ className={styles.checkSvg}
74
+ data-disabled={disabled}
75
+ d="M0.333374 0.333252V13.6666H13.6667V0.333252H0.333374ZM6.00004 10.3999L2.26672 6.66658L3.66671 5.26658L5.93339 7.53325L10.2 3.26658L11.6001 4.66659L6.00004 10.3999Z"
76
+ />
77
+ </svg>
78
+ )}
79
+ {kind === 'surface' && (
80
+ <Icon
81
+ icon={Check}
82
+ size={12.8}
65
83
  data-disabled={disabled}
66
- d="M0.333374 0.333252V13.6666H13.6667V0.333252H0.333374ZM6.00004 10.3999L2.26672 6.66658L3.66671 5.26658L5.93339 7.53325L10.2 3.26658L11.6001 4.66659L6.00004 10.3999Z"
84
+ className={styles.checkIcon}
67
85
  />
68
- </svg>
69
- )}
70
- {kind === 'surface' && (
71
- <Icon
72
- icon={Check}
73
- size={12.8}
74
- data-disabled={disabled}
75
- className={styles.checkIcon}
86
+ )}
87
+ </RadixCheckbox.Indicator>
88
+ </RadixCheckbox.Root>
89
+ )}
90
+ {kind === 'switch' && (
91
+ <>
92
+ <Switch
93
+ checked={checked}
94
+ onChange={onChange}
95
+ className={styles.switchContainer}
96
+ data-disabled={disabled}
97
+ id={inputID}
98
+ aria-details={typeof children === 'string' ? children : undefined}
99
+ >
100
+ <span
101
+ aria-hidden="true"
102
+ className={clsx(styles.knob, checked && styles.knobChecked)}
76
103
  />
77
- )}
78
- </RadixCheckbox.Indicator>
79
- </RadixCheckbox.Root>
80
- {kind === 'default' && (
104
+ </Switch>
105
+ </>
106
+ )}
107
+ {(kind === 'default' || kind === 'switch') && !hideLabel && (
81
108
  <TextWhenString kind="paragraphXSmall">
82
109
  {children}
83
110
  </TextWhenString>
84
111
  )}
112
+ {hideLabel && (
113
+ <VisuallyHidden>
114
+ {children}
115
+ </VisuallyHidden>
116
+ )}
85
117
  </label>
86
118
  );
87
119
  };
@@ -59,6 +59,42 @@ const ComboboxArgs: ComboboxProps<{ name: string }> = {
59
59
  ],
60
60
  };
61
61
 
62
+ const ComboboxArgs2: ComboboxProps<{ name: string }> = {
63
+ label: 'Share',
64
+ description: 'Search for a friend to share this document with.',
65
+ placeholder: 'Search...',
66
+ options: [
67
+ {
68
+ id: '1',
69
+ node: 'Mia Dolan',
70
+ metadata: {
71
+ name: 'Mia Dolan',
72
+ },
73
+ },
74
+ {
75
+ id: '2',
76
+ node: 'SEB',
77
+ metadata: {
78
+ name: 'Sebastian Wilder',
79
+ },
80
+ },
81
+ {
82
+ id: '3',
83
+ node: 'Amy Brandt',
84
+ metadata: {
85
+ name: 'Amy Brandt',
86
+ },
87
+ },
88
+ {
89
+ id: '4',
90
+ node: 'Laura Wilder',
91
+ metadata: {
92
+ name: 'Laura Wilder',
93
+ },
94
+ },
95
+ ],
96
+ };
97
+
62
98
  export const Default: Story = {
63
99
  args: ComboboxArgs,
64
100
  render: (args) => {
@@ -104,3 +140,51 @@ export const AllowCustomValue: Story = {
104
140
  }));
105
141
  },
106
142
  };
143
+
144
+ export const HideOptionsInitially: Story = {
145
+ args: ComboboxArgs,
146
+ render: (args) => {
147
+ const [selected, setSelected] = useState<Option<{ name: string }> | null>(null);
148
+ const [inputValue, setInputValue] = useState<string>('');
149
+ return createElement('div', {
150
+ style: { minHeight: '200px' },
151
+ }, createElement(Combobox<{ name: string }>, {
152
+ ...args,
153
+ value: (selected?.id === null) ? {
154
+ id: null,
155
+ node: inputValue,
156
+ metadata: {
157
+ name: inputValue,
158
+ },
159
+ } : selected as Option<{ name: string }> | null,
160
+ options: (args.options as Option<{ name: string }>[]).filter((o) => (o.metadata?.name as string || '').toLowerCase().includes(inputValue.toLowerCase())),
161
+ onChange: (e) => setSelected(e),
162
+ onInputChange: (e) => setInputValue(e),
163
+ hideOptionsInitially: true,
164
+ }));
165
+ },
166
+ };
167
+
168
+ export const HideClearButton: Story = {
169
+ args: ComboboxArgs2,
170
+ render: (args) => {
171
+ const [selected, setSelected] = useState<Option<{ name: string }> | null>(null);
172
+ const [inputValue, setInputValue] = useState<string>('');
173
+ return createElement('div', {
174
+ style: { minHeight: '200px' },
175
+ }, createElement(Combobox<{ name: string }>, {
176
+ ...args,
177
+ value: (selected?.id === null) ? {
178
+ id: null,
179
+ node: inputValue,
180
+ metadata: {
181
+ name: inputValue,
182
+ },
183
+ } : selected as Option<{ name: string }> | null,
184
+ options: (args.options as Option<{ name: string }>[]).filter((o) => (o.metadata?.name as string || '').toLowerCase().includes(inputValue.toLowerCase())),
185
+ onChange: (e) => setSelected(e),
186
+ onInputChange: (e) => setInputValue(e),
187
+ hideClearButton: true,
188
+ }));
189
+ },
190
+ };
@@ -2,11 +2,11 @@
2
2
 
3
3
  import type { ComponentPropsWithoutRef, CSSProperties, ReactNode } from 'react';
4
4
  import {
5
- Fragment,
6
-
7
5
  useMemo, useId, useState,
8
6
  } from 'react';
9
- import { Combobox as HCombobox, Transition } from '@headlessui/react';
7
+ import {
8
+ Combobox as HCombobox, ComboboxInput, ComboboxOptions, ComboboxOption, Transition,
9
+ } from '@headlessui/react';
10
10
  import clsx from 'clsx';
11
11
  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
12
12
  import { faClose } from '@fortawesome/free-solid-svg-icons';
@@ -23,6 +23,7 @@ import { Field } from '../field';
23
23
  import type { ButtonProps } from '../button';
24
24
  import { Button } from '../button';
25
25
  import { TextWhenString } from '../utility';
26
+ import { Check, Icon } from '../icon';
26
27
 
27
28
  export type Option<T extends Record<string, any> = Record<string, any>> = {
28
29
  id: string,
@@ -97,6 +98,11 @@ export type ComboboxProps<T extends Record<string, any>> = {
97
98
  * @default false
98
99
  */
99
100
  hasOptionBorder?: boolean;
101
+ /**
102
+ * Whether the dropdown should open immediately when focused, vs only after starting to type.
103
+ * @default false
104
+ */
105
+ hideOptionsInitially?: boolean;
100
106
  /**
101
107
  * Prop overrides for other rendered elements. Overrides for the input itself should be passed directly to the component.
102
108
  */
@@ -153,6 +159,7 @@ export function Combobox<T extends Record<string, any> = Record<string, any>>({
153
159
  hideClearButton = false,
154
160
  maxHeight = 320,
155
161
  hasOptionBorder = false,
162
+ hideOptionsInitially = false,
156
163
  overrides,
157
164
  }: ComboboxProps<T>) {
158
165
  const inputID = useId();
@@ -184,6 +191,7 @@ export function Combobox<T extends Record<string, any> = Record<string, any>>({
184
191
  >
185
192
  <HCombobox
186
193
  as="div"
194
+ immediate={!hideOptionsInitially}
187
195
  value={selectedID}
188
196
  onChange={(id) => {
189
197
  if (onChange) {
@@ -224,7 +232,7 @@ export function Combobox<T extends Record<string, any> = Record<string, any>>({
224
232
  )}
225
233
  <div className={styles.content}>
226
234
  {(value?.node && typeof value.node !== 'string') ? value.node : (
227
- <HCombobox.Input
235
+ <ComboboxInput
228
236
  id={inputID}
229
237
  {...overrides?.input}
230
238
  placeholder={placeholder}
@@ -293,7 +301,8 @@ export function Combobox<T extends Record<string, any> = Record<string, any>>({
293
301
  leaveFrom={dropdownStyles.leaveFrom}
294
302
  leaveTo={dropdownStyles.leaveTo}
295
303
  >
296
- <HCombobox.Options
304
+ <ComboboxOptions
305
+ as="ul"
297
306
  {...overrides?.optionsContainer}
298
307
  className={clsx(
299
308
  overrides?.optionsContainer?.className,
@@ -305,7 +314,8 @@ export function Combobox<T extends Record<string, any> = Record<string, any>>({
305
314
  } as CSSProperties}
306
315
  >
307
316
  {(allowCustomValue && showCustomValueOption && !customValueToOption && query.length > 0) && (
308
- <HCombobox.Option
317
+ <ComboboxOption
318
+ as="li"
309
319
  value={query}
310
320
  data-selected={false}
311
321
  className={clsx(
@@ -317,18 +327,18 @@ export function Combobox<T extends Record<string, any> = Record<string, any>>({
317
327
  <Text as="span" kind="paragraphSmall">
318
328
  {customValueString.replace('%v', query)}
319
329
  </Text>
320
- </HCombobox.Option>
330
+ </ComboboxOption>
321
331
  )}
322
332
  {
323
333
  (
324
334
  optionsWithCustomValue || []
325
335
  )
326
336
  .map((option) => (
327
- <HCombobox.Option
337
+ <ComboboxOption
338
+ as="li"
328
339
  key={option.id}
329
340
  value={option.id}
330
341
  {...overrides?.option}
331
- data-selected={option.id === value}
332
342
  className={clsx(
333
343
  overrides?.option?.className,
334
344
  styles.option,
@@ -338,10 +348,10 @@ export function Combobox<T extends Record<string, any> = Record<string, any>>({
338
348
  <TextWhenString as="span" kind="paragraphSmall">
339
349
  {option.node}
340
350
  </TextWhenString>
341
- </HCombobox.Option>
351
+ </ComboboxOption>
342
352
  ))
343
353
  }
344
- </HCombobox.Options>
354
+ </ComboboxOptions>
345
355
  </Transition>
346
356
  </HCombobox>
347
357
  </Field>
@@ -17,7 +17,7 @@ $panelAnimationDelay: var(--pte-animations-duration-fast);
17
17
  transition: var(--pte-animations-duration-normal) var(--pte-animations-timing-easeInOut);
18
18
 
19
19
  &.overlayBlur {
20
- backdrop-filter: blur(0);
20
+ backdrop-filter: blur(2px);
21
21
  background-color: var(--pte-new-colors-overlayPageBackground);
22
22
  will-change: backdrop-filter, opacity;
23
23
  }
@@ -3,8 +3,10 @@
3
3
  import type {
4
4
  ComponentPropsWithoutRef, FC, MouseEventHandler, PropsWithChildren, ReactNode,
5
5
  } from 'react';
6
- import { Fragment, useEffect, useState } from 'react';
7
- import { Dialog as HDialog, Transition } from '@headlessui/react';
6
+ import { useEffect, useState } from 'react';
7
+ import {
8
+ Dialog as HDialog, DialogPanel, DialogTitle, Transition, TransitionChild,
9
+ } from '@headlessui/react';
8
10
  import clsx from 'clsx';
9
11
  import styles from './Dialog.module.scss';
10
12
  import { Text } from '../text';
@@ -166,7 +168,6 @@ export const Dialog: FC<PropsWithChildren<DialogProps>> = ({
166
168
  <Transition
167
169
  appear
168
170
  show={isOpen}
169
- as={Fragment}
170
171
  >
171
172
  <HDialog
172
173
  as="div"
@@ -176,8 +177,9 @@ export const Dialog: FC<PropsWithChildren<DialogProps>> = ({
176
177
  styles.root,
177
178
  overrides.root?.className,
178
179
  )}
180
+ role="dialog"
179
181
  >
180
- <HDialog.Overlay
182
+ <div
181
183
  {...overrides.overlayContainer}
182
184
  className={clsx(
183
185
  overlayStyle === 'blur' && styles.overlayBlurContainer,
@@ -185,8 +187,7 @@ export const Dialog: FC<PropsWithChildren<DialogProps>> = ({
185
187
  overrides.overlayContainer?.className,
186
188
  )}
187
189
  >
188
- <Transition.Child
189
- as={Fragment}
190
+ <TransitionChild
190
191
  enter={styles.enter}
191
192
  enterFrom={styles.enterFrom}
192
193
  enterTo={styles.enterTo}
@@ -203,8 +204,8 @@ export const Dialog: FC<PropsWithChildren<DialogProps>> = ({
203
204
  overrides.overlay?.className,
204
205
  )}
205
206
  />
206
- </Transition.Child>
207
- </HDialog.Overlay>
207
+ </TransitionChild>
208
+ </div>
208
209
 
209
210
  <div
210
211
  {...overrides.panelContainer}
@@ -213,8 +214,7 @@ export const Dialog: FC<PropsWithChildren<DialogProps>> = ({
213
214
  overrides.panelContainer?.className,
214
215
  )}
215
216
  >
216
- <Transition.Child
217
- as={Fragment}
217
+ <TransitionChild
218
218
  enter={styles.enter}
219
219
  enterFrom={styles.enterFrom}
220
220
  enterTo={styles.enterTo}
@@ -222,7 +222,7 @@ export const Dialog: FC<PropsWithChildren<DialogProps>> = ({
222
222
  leaveFrom={styles.leaveFrom}
223
223
  leaveTo={styles.leaveTo}
224
224
  >
225
- <HDialog.Panel
225
+ <DialogPanel
226
226
  {...overrides.panel}
227
227
  className={clsx(
228
228
  styles.panel,
@@ -248,7 +248,7 @@ export const Dialog: FC<PropsWithChildren<DialogProps>> = ({
248
248
  } : {}}
249
249
  >
250
250
  <VisuallyHidden when={hideTitle}>
251
- <HDialog.Title
251
+ <DialogTitle
252
252
  {...overrides.panelTitle}
253
253
  as="h1"
254
254
  className={clsx(
@@ -261,7 +261,7 @@ export const Dialog: FC<PropsWithChildren<DialogProps>> = ({
261
261
  >
262
262
  {title}
263
263
  </TextWhenString>
264
- </HDialog.Title>
264
+ </DialogTitle>
265
265
  </VisuallyHidden>
266
266
  <RemoveFromDOM when={hideCloseButton}>
267
267
  <div className={clsx(styles.closeButton)}>
@@ -285,8 +285,8 @@ export const Dialog: FC<PropsWithChildren<DialogProps>> = ({
285
285
  </div>
286
286
  </VisuallyHidden>
287
287
  {children}
288
- </HDialog.Panel>
289
- </Transition.Child>
288
+ </DialogPanel>
289
+ </TransitionChild>
290
290
  </div>
291
291
  </HDialog>
292
292
  </Transition>
@@ -27,7 +27,7 @@ $panelAnimationDelay: var(--pte-animations-duration-fast);
27
27
  inset: 0;
28
28
 
29
29
  &.overlayBlur {
30
- backdrop-filter: blur(0);
30
+ backdrop-filter: blur(2px);
31
31
  background-color: var(--pte-new-colors-overlayPageBackground);
32
32
  will-change: backdrop-filter, opacity;
33
33
  }
@@ -6,7 +6,7 @@ import { Drawer } from './Drawer';
6
6
  import { Button } from '../button';
7
7
  import { Callout } from '../callout';
8
8
  import {
9
- Menu,
9
+ Menu, MenuButton, MenuItems, MenuItem,
10
10
  } from '../menu';
11
11
  import { usePagination } from '../pagination';
12
12
  import { ChevronRight, Ellipsis } from '../icon';
@@ -41,7 +41,7 @@ export const Default: Story = {
41
41
  onClose={setIsOpen}
42
42
  additionalActions={(
43
43
  <Menu as="div">
44
- <Menu.Button>
44
+ <MenuButton>
45
45
  <Button
46
46
  kind="tertiary"
47
47
  shape="circle"
@@ -51,16 +51,16 @@ export const Default: Story = {
51
51
  >
52
52
  Action menu
53
53
  </Button>
54
- </Menu.Button>
55
- <Menu.Items position="right">
56
- <Menu.Item as="button">
54
+ </MenuButton>
55
+ <MenuItems position="right">
56
+ <MenuItem as="button">
57
57
  Dispute
58
- </Menu.Item>
59
- <Menu.Item as="button">
58
+ </MenuItem>
59
+ <MenuItem as="button">
60
60
  Transfer
61
61
  <ChevronRight size={20} />
62
- </Menu.Item>
63
- </Menu.Items>
62
+ </MenuItem>
63
+ </MenuItems>
64
64
  </Menu>
65
65
  )}
66
66
  >
@@ -101,7 +101,7 @@ export const Paginated: Story = {
101
101
  title={currentPageTitle}
102
102
  additionalActions={(
103
103
  <Menu as="div">
104
- <Menu.Button>
104
+ <MenuButton>
105
105
  <Button
106
106
  kind="tertiary"
107
107
  shape="circle"
@@ -111,16 +111,16 @@ export const Paginated: Story = {
111
111
  >
112
112
  Action menu
113
113
  </Button>
114
- </Menu.Button>
115
- <Menu.Items position="right">
116
- <Menu.Item as="button">
114
+ </MenuButton>
115
+ <MenuItems position="right">
116
+ <MenuItem as="button">
117
117
  Dispute
118
- </Menu.Item>
119
- <Menu.Item as="button">
118
+ </MenuItem>
119
+ <MenuItem as="button">
120
120
  Transfer
121
121
  <ChevronRight size={20} />
122
- </Menu.Item>
123
- </Menu.Items>
122
+ </MenuItem>
123
+ </MenuItems>
124
124
  </Menu>
125
125
  )}
126
126
  >
@@ -222,7 +222,7 @@ export const BottomPanel: Story = {
222
222
  onClose={setIsOpen}
223
223
  additionalActions={(
224
224
  <Menu as="div">
225
- <Menu.Button>
225
+ <MenuButton>
226
226
  <Button
227
227
  kind="tertiary"
228
228
  shape="circle"
@@ -232,16 +232,16 @@ export const BottomPanel: Story = {
232
232
  >
233
233
  Action menu
234
234
  </Button>
235
- </Menu.Button>
236
- <Menu.Items position="right">
237
- <Menu.Item as="button">
235
+ </MenuButton>
236
+ <MenuItems position="right">
237
+ <MenuItem as="button">
238
238
  Dispute
239
- </Menu.Item>
240
- <Menu.Item as="button">
239
+ </MenuItem>
240
+ <MenuItem as="button">
241
241
  Transfer
242
242
  <ChevronRight size={20} />
243
- </Menu.Item>
244
- </Menu.Items>
243
+ </MenuItem>
244
+ </MenuItems>
245
245
  </Menu>
246
246
  )}
247
247
  >
@@ -2,10 +2,11 @@
2
2
 
3
3
  import type { ReactNode, ComponentPropsWithoutRef } from 'react';
4
4
  import {
5
- useEffect,
6
- useRef, Fragment, useMemo, useState,
5
+ useMemo, useState,
7
6
  } from 'react';
8
- import { Dialog, Transition } from '@headlessui/react';
7
+ import {
8
+ Dialog, DialogPanel, DialogTitle, Transition, TransitionChild,
9
+ } from '@headlessui/react';
9
10
  import clsx from 'clsx';
10
11
  import type { CSSLength } from '@ssh/csstypes';
11
12
  import styles from './Drawer.module.scss';
@@ -205,7 +206,7 @@ export const Drawer = <T extends string[] | readonly string[] = string[]>({
205
206
  }, [children, pagination]);
206
207
 
207
208
  return (
208
- <Transition show={isOpen} as={Fragment}>
209
+ <Transition show={isOpen}>
209
210
  <Dialog
210
211
  as="div"
211
212
  className={clsx(
@@ -214,6 +215,7 @@ export const Drawer = <T extends string[] | readonly string[] = string[]>({
214
215
  )}
215
216
  onClose={onClose}
216
217
  {...overrides?.dialog}
218
+ role="dialog"
217
219
  >
218
220
  <div
219
221
  className={clsx(
@@ -222,8 +224,7 @@ export const Drawer = <T extends string[] | readonly string[] = string[]>({
222
224
  overrides?.overlay?.className,
223
225
  )}
224
226
  >
225
- <Transition.Child
226
- as={Fragment}
227
+ <TransitionChild
227
228
  enter={styles.enter}
228
229
  enterFrom={styles.enterFrom}
229
230
  enterTo={styles.enterTo}
@@ -231,14 +232,14 @@ export const Drawer = <T extends string[] | readonly string[] = string[]>({
231
232
  leaveFrom={styles.leaveFrom}
232
233
  leaveTo={styles.leaveTo}
233
234
  >
234
- <Dialog.Overlay
235
+ <div
235
236
  className={clsx(
236
237
  styles.overlay,
237
238
  overlayStyle === 'blur' && styles.overlayBlur,
238
239
  overlayStyle === 'grey' && styles.overlayGrey,
239
240
  )}
240
241
  />
241
- </Transition.Child>
242
+ </TransitionChild>
242
243
  </div>
243
244
 
244
245
  <div
@@ -254,8 +255,7 @@ export const Drawer = <T extends string[] | readonly string[] = string[]>({
254
255
  } : overrides?.panelContainer?.style}
255
256
  {...overrides?.panelContainer}
256
257
  >
257
- <Transition.Child
258
- as={Fragment}
258
+ <TransitionChild
259
259
  enter={styles.enter}
260
260
  enterFrom={styles.enterFrom}
261
261
  enterTo={styles.enterTo}
@@ -263,7 +263,7 @@ export const Drawer = <T extends string[] | readonly string[] = string[]>({
263
263
  leaveFrom={styles.leaveFrom}
264
264
  leaveTo={styles.leaveTo}
265
265
  >
266
- <Dialog.Panel
266
+ <DialogPanel
267
267
  className={clsx(
268
268
  styles.panel,
269
269
  styles[`from-${from}`],
@@ -316,11 +316,11 @@ export const Drawer = <T extends string[] | readonly string[] = string[]>({
316
316
  // Hide when requested, or when pagination is enabled (the title isn't relevant to any specific page).
317
317
  when={hideTitle}
318
318
  >
319
- <Dialog.Title as="h2" className={styles.titleTextContainer}>
319
+ <DialogTitle as="h2" className={styles.titleTextContainer}>
320
320
  <TextWhenString kind="paragraphSmall" weight="medium">
321
321
  {title}
322
322
  </TextWhenString>
323
- </Dialog.Title>
323
+ </DialogTitle>
324
324
  </VisuallyHidden>
325
325
  </div>
326
326
  <div className={clsx(styles.titleBarButtons, overrides?.titleBarButtons?.className)}>
@@ -389,8 +389,8 @@ export const Drawer = <T extends string[] | readonly string[] = string[]>({
389
389
  </>
390
390
  )}
391
391
  </div>
392
- </Dialog.Panel>
393
- </Transition.Child>
392
+ </DialogPanel>
393
+ </TransitionChild>
394
394
  </div>
395
395
  </Dialog>
396
396
  </Transition>
@@ -1,6 +1,6 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react';
2
2
  import {
3
- Menu,
3
+ Menu, MenuButton, MenuItems, MenuItem,
4
4
  } from './Menu';
5
5
  import { Button } from '../button';
6
6
  import { ChevronRight, Ellipsis } from '../icon';
@@ -21,7 +21,7 @@ export const Default: Story = {
21
21
  render: (args) => (
22
22
  <div style={{ height: '150px' }}>
23
23
  <Menu as="div">
24
- <Menu.Button>
24
+ <MenuButton>
25
25
  <Button
26
26
  kind="tertiary"
27
27
  shape="circle"
@@ -31,16 +31,16 @@ export const Default: Story = {
31
31
  >
32
32
  Action menu
33
33
  </Button>
34
- </Menu.Button>
35
- <Menu.Items>
36
- <Menu.Item as="button">
34
+ </MenuButton>
35
+ <MenuItems>
36
+ <MenuItem as="button">
37
37
  Dispute
38
- </Menu.Item>
39
- <Menu.Item as="button">
38
+ </MenuItem>
39
+ <MenuItem as="button">
40
40
  Transfer
41
41
  <ChevronRight size={20} />
42
- </Menu.Item>
43
- </Menu.Items>
42
+ </MenuItem>
43
+ </MenuItems>
44
44
  </Menu>
45
45
  </div>
46
46
  ),
@@ -1,42 +1,33 @@
1
1
  import type { FC } from 'react';
2
- import { Fragment, ReactNode } from 'react';
3
2
  import type {
4
3
  MenuProps, MenuItemsProps, MenuItemProps, MenuButtonProps,
5
4
  } from '@headlessui/react';
6
- import { Menu as HeadlessMenu, Transition } from '@headlessui/react';
5
+ import {
6
+ Menu as HeadlessMenu, MenuButton as HMenuButton, MenuItems as HMenuItems, MenuItem as HMenuItem, Transition,
7
+ } from '@headlessui/react';
7
8
  import clsx from 'clsx';
8
9
 
9
10
  import styles from './Menu.module.scss';
10
11
  import dropdownStyles from '../utility/Dropdown.module.scss';
11
12
 
12
- export { Menu as HeadlessMenu } from '@headlessui/react';
13
-
14
13
  /**
15
14
  * Wraps the `HeadlessMenu` component from `@headlessui/react` to provide a styled dropdown menu.
16
15
  *
17
- * This component serves as the root of the menu and should wrap `Menu.Button`, `Menu.Items`, and `Menu.Item` components.
16
+ * This component serves as the root of the menu and should wrap `MenuButton`, `MenuItems`, and `MenuItem` components.
18
17
  *
19
18
  * Usage:
20
19
  *
21
20
  * ```tsx
22
21
  * <Menu>
23
- * <Menu.Button>Options</Menu.Button>
24
- * <Menu.Items>
25
- * <Menu.Item>Item 1</Menu.Item>
26
- * <Menu.Item>Item 2</Menu.Item>
27
- * </Menu.Items>
22
+ * <MenuButton>Options</MenuButton>
23
+ * <MenuItems>
24
+ * <MenuItem>Item 1</MenuItem>
25
+ * <MenuItem>Item 2</MenuItem>
26
+ * </MenuItems>
28
27
  * </Menu>
29
28
  * ```
30
29
  */
31
- export const Menu: FC<MenuProps<React.ElementType>> & {
32
- Button: FC<MenuButtonProps<React.ElementType>>;
33
- Items: FC<MenuItemsProps<React.ElementType> & {
34
- position?: 'left' | 'right';
35
- }>;
36
- Item: FC<MenuItemProps<React.ElementType> & {
37
- isNew?: boolean;
38
- }>;
39
- } = ({ className, children, ...props }) => (
30
+ export const Menu: FC<MenuProps<React.ElementType>> = ({ className, children, ...props }) => (
40
31
  <HeadlessMenu as="div" className={clsx(styles.menu, className)} {...props}>
41
32
  {children}
42
33
  </HeadlessMenu>
@@ -47,10 +38,10 @@ export const Menu: FC<MenuProps<React.ElementType>> & {
47
38
  *
48
39
  * Should be used inside a `Menu` component to serve as the toggle for `MenuItems`.
49
40
  */
50
- Menu.Button = ({ className, children, ...props }) => (
51
- <HeadlessMenu.Button className={clsx(styles.menuButton, className)} {...props}>
41
+ export const MenuButton: FC<MenuButtonProps<React.ElementType>> = ({ className, children, ...props }) => (
42
+ <HMenuButton className={clsx(styles.menuButton, className)} {...props}>
52
43
  {children}
53
- </HeadlessMenu.Button>
44
+ </HMenuButton>
54
45
  );
55
46
 
56
47
  /**
@@ -60,7 +51,9 @@ Menu.Button = ({ className, children, ...props }) => (
60
51
  *
61
52
  * @param position - Controls the positioning of the menu items ('left' or 'right').
62
53
  */
63
- Menu.Items = ({
54
+ export const MenuItems: FC<MenuItemsProps<React.ElementType> & {
55
+ position?: 'left' | 'right';
56
+ }> = ({
64
57
  className, children, position = 'left', ...props
65
58
  }) => (
66
59
  <Transition
@@ -73,9 +66,9 @@ Menu.Items = ({
73
66
  leaveFrom={dropdownStyles.leaveFrom}
74
67
  leaveTo={dropdownStyles.leaveTo}
75
68
  >
76
- <HeadlessMenu.Items className={clsx(styles.menuItems, position === 'left' && styles.leftPosition, position === 'right' && styles.rightPosition, className)} {...props}>
69
+ <HMenuItems className={clsx(styles.menuItems, position === 'left' && styles.leftPosition, position === 'right' && styles.rightPosition, className)} {...props}>
77
70
  {children}
78
- </HeadlessMenu.Items>
71
+ </HMenuItems>
79
72
  </Transition>
80
73
  );
81
74
 
@@ -86,10 +79,12 @@ Menu.Items = ({
86
79
  *
87
80
  * @param isNew - Whether the menu items should be styled as new items.
88
81
  */
89
- Menu.Item = ({
82
+ export const MenuItem: FC<MenuItemProps<React.ElementType> & {
83
+ isNew?: boolean;
84
+ }> = ({
90
85
  className, children, isNew = false, ...props
91
86
  }) => (
92
- <HeadlessMenu.Item className={clsx(styles.menuItem, isNew && styles.newItem, className)} {...props}>
87
+ <HMenuItem className={clsx(styles.menuItem, isNew && styles.newItem, className)} {...props}>
93
88
  {children}
94
- </HeadlessMenu.Item>
89
+ </HMenuItem>
95
90
  );
@@ -66,15 +66,15 @@
66
66
 
67
67
  transition: var(--pte-animations-interaction);
68
68
 
69
- &[data-selected=true] {
69
+ &[data-selected] {
70
70
  background-color: var(--pte-new-colors-overlaySubtle);
71
71
  }
72
72
 
73
- &:hover, &[data-headlessui-state="active"] {
73
+ &:hover, &[data-active], &[data-focus] {
74
74
  background-color: var(--pte-new-colors-overlayMedium);
75
75
  }
76
76
 
77
- &[data-status="disabled"], &[data-headlessui-state~="disabled"] {
77
+ &[data-status="disabled"], &[data-disabled] {
78
78
  pointer-events: none;
79
79
  cursor: default;
80
80
 
@@ -110,19 +110,19 @@
110
110
  justify-content: flex-start;
111
111
  align-items: center;
112
112
 
113
- &[data-headlessui-state~="active"] {
113
+ &[data-focus] {
114
114
  .radioCircle {
115
115
  border-color: var(--pte-new-colors-contentPrimary);
116
116
  }
117
117
  }
118
118
 
119
- &[data-headlessui-state~="checked"] {
119
+ &[data-checked] {
120
120
  .radioCircle {
121
121
  border: 5px solid var(--pte-new-colors-contentPrimary);
122
122
  }
123
123
  }
124
124
 
125
- &[data-status="disabled"], &[data-headlessui-state~="disabled"] {
125
+ &[data-status="disabled"], &[data-disabled] {
126
126
  pointer-events: none;
127
127
  cursor: default;
128
128
 
@@ -171,21 +171,21 @@
171
171
  border-radius: 6px;
172
172
  width: 100%;
173
173
 
174
- &[data-headlessui-state~="active"] {
174
+ &[data-focus] {
175
175
  //.cardSurface {
176
176
  // background-color: var(--pte-new-colors-overlayWhiteSubtle);
177
177
  // border-color: var(--pte-new-colors-borderStrong);
178
178
  //}
179
179
  }
180
180
 
181
- &[data-headlessui-state~="checked"] {
181
+ &[data-checked] {
182
182
  .cardSurface {
183
183
  background-color: var(--pte-new-colors-overlayMedium);
184
184
  border-color: var(--pte-new-colors-borderUltrastrong);
185
185
  }
186
186
  }
187
187
 
188
- &[data-status="disabled"], &[data-headlessui-state~="disabled"] {
188
+ &[data-status="disabled"], &[data-disabled] {
189
189
  pointer-events: none;
190
190
  cursor: default;
191
191
 
@@ -262,11 +262,7 @@
262
262
  padding: 6px 20px;
263
263
  }
264
264
 
265
- &[data-headlessui-state~="active"] {
266
-
267
- }
268
-
269
- &[data-status="disabled"], &[data-headlessui-state~="disabled"] {
265
+ &[data-status="disabled"], &[data-disabled] {
270
266
  pointer-events: none;
271
267
  cursor: default;
272
268
 
@@ -284,7 +280,7 @@
284
280
  color: var(--pte-new-colors-contentPrimary);
285
281
  }
286
282
 
287
- &[data-headlessui-state~="checked"] {
283
+ &[data-checked] {
288
284
  color: var(--pte-new-colors-contentPrimary);
289
285
  }
290
286
  }
@@ -133,7 +133,7 @@ export const Multiple: Story = {
133
133
  },
134
134
  render,
135
135
  };
136
-
136
+
137
137
  export const Segmented: Story = {
138
138
  args: {
139
139
  label: 'Donation',
@@ -6,7 +6,9 @@ import type {
6
6
  CSSProperties, ComponentPropsWithoutRef, ForwardedRef, ReactNode,
7
7
  } from 'react';
8
8
  import { forwardRef, useId } from 'react';
9
- import { Listbox, RadioGroup, Transition } from '@headlessui/react';
9
+ import {
10
+ Listbox, ListboxButton, ListboxOptions, ListboxOption, RadioGroup, Radio, Transition,
11
+ } from '@headlessui/react';
10
12
  import clsx from 'clsx';
11
13
  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
12
14
  import { faChevronDown } from '@fortawesome/free-solid-svg-icons';
@@ -187,7 +189,7 @@ export const Select = forwardRef(function <T = Record<string, any>>({
187
189
  onChange={onChange}
188
190
  multiple={multiple}
189
191
  >
190
- <Listbox.Button
192
+ <ListboxButton
191
193
  id={inputID}
192
194
  {...overrides?.selectInput}
193
195
  aria-disabled={disabled}
@@ -230,7 +232,7 @@ export const Select = forwardRef(function <T = Record<string, any>>({
230
232
  ) : (
231
233
  <FontAwesomeIcon className={clsx(inputStyles.enhancer, styles.chevron)} data-status={disabled ? 'disabled' : (status || 'default')} width="10px" icon={faChevronDown} />
232
234
  )}
233
- </Listbox.Button>
235
+ </ListboxButton>
234
236
  <Transition
235
237
  as="div"
236
238
  className={dropdownStyles.transitionContainer}
@@ -241,7 +243,7 @@ export const Select = forwardRef(function <T = Record<string, any>>({
241
243
  leaveFrom={dropdownStyles.leaveFrom}
242
244
  leaveTo={dropdownStyles.leaveTo}
243
245
  >
244
- <Listbox.Options
246
+ <ListboxOptions
245
247
  className={clsx(
246
248
  overrides?.optionsContainer,
247
249
  styles.options,
@@ -251,10 +253,9 @@ export const Select = forwardRef(function <T = Record<string, any>>({
251
253
  } as CSSProperties}
252
254
  >
253
255
  {(options || []).map((option) => (
254
- <Listbox.Option
256
+ <ListboxOption
255
257
  key={option.id}
256
258
  value={option.id}
257
- data-selected={option.id === value || (value && value.includes(option.id))}
258
259
  className={clsx(
259
260
  overrides?.option,
260
261
  styles.option,
@@ -270,16 +271,16 @@ export const Select = forwardRef(function <T = Record<string, any>>({
270
271
  {(option.id === value || (value && value.includes(option.id))) && (
271
272
  <Icon icon={Check} size={12} />
272
273
  )}
273
- </Listbox.Option>
274
+ </ListboxOption>
274
275
  ))}
275
- </Listbox.Options>
276
+ </ListboxOptions>
276
277
  </Transition>
277
278
  </Listbox>
278
279
  )}
279
280
  {kind === 'radio' && (
280
281
  <RadioGroup ref={ref} as="div" className={styles.radioContainer} value={value} onChange={onChange}>
281
282
  {options.map((option) => (
282
- <RadioGroup.Option
283
+ <Radio
283
284
  as="div"
284
285
  className={clsx(
285
286
  styles.radioOption,
@@ -293,14 +294,14 @@ export const Select = forwardRef(function <T = Record<string, any>>({
293
294
  <TextWhenString kind="paragraphXSmall">
294
295
  {option.node}
295
296
  </TextWhenString>
296
- </RadioGroup.Option>
297
+ </Radio>
297
298
  ))}
298
299
  </RadioGroup>
299
300
  )}
300
301
  {kind === 'card' && (
301
302
  <RadioGroup ref={ref} as="div" className={styles.cardContainer} value={value} onChange={onChange}>
302
303
  {options.map((option) => (
303
- <RadioGroup.Option
304
+ <Radio
304
305
  as="div"
305
306
  className={clsx(
306
307
  styles.cardOption,
@@ -315,14 +316,14 @@ export const Select = forwardRef(function <T = Record<string, any>>({
315
316
  {option.node}
316
317
  </TextWhenString>
317
318
  </div>
318
- </RadioGroup.Option>
319
+ </Radio>
319
320
  ))}
320
321
  </RadioGroup>
321
322
  )}
322
323
  {kind === 'segmented' && (
323
324
  <RadioGroup ref={ref} as="div" className={styles.segmentedContainer} value={value || options[0].id} onChange={onChange}>
324
325
  {options.map((option) => (
325
- <RadioGroup.Option
326
+ <Radio
326
327
  as="div"
327
328
  className={clsx(
328
329
  styles.segmentedOption,
@@ -346,7 +347,7 @@ export const Select = forwardRef(function <T = Record<string, any>>({
346
347
  <TextWhenString kind="paragraphXSmall" weight="medium" className={styles.segmentedText}>
347
348
  {option.node}
348
349
  </TextWhenString>
349
- </RadioGroup.Option>
350
+ </Radio>
350
351
  ))}
351
352
  </RadioGroup>
352
353
  )}
@@ -5,7 +5,9 @@ import type {
5
5
  } from 'react';
6
6
  import { useId, useState } from 'react';
7
7
  import type { TabGroupProps, TabPanelProps, TabProps } from '@headlessui/react';
8
- import { Tab } from '@headlessui/react';
8
+ import {
9
+ Tab, TabGroup, TabList, TabPanels, TabPanel,
10
+ } from '@headlessui/react';
9
11
  import clsx from 'clsx';
10
12
  import type { CSSLength } from '@ssh/csstypes';
11
13
  import { motion } from 'framer-motion';
@@ -99,7 +101,7 @@ export const Tabs: FC<TabsProps> = ({
99
101
  const [selectedIndex, setSelectedIndex] = useState(defaultIndex);
100
102
 
101
103
  return (
102
- <Tab.Group
104
+ <TabGroup
103
105
  as="div"
104
106
  selectedIndex={index ?? selectedIndex}
105
107
  onChange={(i) => {
@@ -121,7 +123,7 @@ export const Tabs: FC<TabsProps> = ({
121
123
  <div className={styles.glassBlend} />
122
124
  </div>
123
125
  )}
124
- <Tab.List
126
+ <TabList
125
127
  {...overrides?.tabList}
126
128
  style={{
127
129
  '--tab-width': tabWidth,
@@ -160,19 +162,19 @@ export const Tabs: FC<TabsProps> = ({
160
162
  ))}
161
163
 
162
164
  {/* <div key={`${id}-tab-active-border`} className={styles.activeTabBorder} /> */}
163
- </Tab.List>
165
+ </TabList>
164
166
  <div
165
167
  {...overrides?.tabListBorder}
166
168
  className={clsx(styles.tabListBorder, styles[barStyle], overrides?.tabListBorder?.className)}
167
169
  />
168
170
  </div>
169
171
 
170
- <Tab.Panels
172
+ <TabPanels
171
173
  {...overrides?.panelContainer}
172
174
  className={clsx(styles.tabPanels, styles[backgroundStyle], overrides?.panelContainer?.className)}
173
175
  >
174
176
  {tabs.map(({ title, content }) => (
175
- <Tab.Panel
177
+ <TabPanel
176
178
  key={`${id}-tab-${title}-content`}
177
179
  {...overrides?.panel}
178
180
  className={clsx(
@@ -184,9 +186,9 @@ export const Tabs: FC<TabsProps> = ({
184
186
  )}
185
187
  >
186
188
  {content}
187
- </Tab.Panel>
189
+ </TabPanel>
188
190
  ))}
189
- </Tab.Panels>
190
- </Tab.Group>
191
+ </TabPanels>
192
+ </TabGroup>
191
193
  );
192
194
  };