@chassis-ui/react 0.1.0 → 0.1.2

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 (106) hide show
  1. package/README.md +11 -0
  2. package/dist/index.d.ts +682 -237
  3. package/dist/index.js +2 -2
  4. package/dist/style.css +1 -1
  5. package/package.json +32 -25
  6. package/src/components/accordion/Accordion.tsx +2 -2
  7. package/src/components/accordion/AccordionItem.tsx +6 -6
  8. package/src/components/autocomplete/Autocomplete.tsx +4 -4
  9. package/src/components/avatar/Avatar.tsx +5 -2
  10. package/src/components/breadcrumb/Breadcrumb.tsx +11 -2
  11. package/src/components/button/Button.tsx +7 -4
  12. package/src/components/button-group/ButtonGroup.tsx +2 -2
  13. package/src/components/calendar/Calendar.tsx +56 -42
  14. package/src/components/calendar/CalendarMonthGrid.tsx +3 -1
  15. package/src/components/calendar/CalendarMonthYearPicker.tsx +4 -2
  16. package/src/components/calendar/CalendarYearGrid.tsx +5 -3
  17. package/src/components/calendar/RangeCalendar.scss +8 -8
  18. package/src/components/calendar/RangeCalendar.tsx +56 -44
  19. package/src/components/calendar/index.ts +1 -0
  20. package/src/components/calendar/labels.tsx +71 -0
  21. package/src/components/card/Card.tsx +4 -4
  22. package/src/components/card/CardGroup.tsx +1 -1
  23. package/src/components/card/CardImage.tsx +1 -1
  24. package/src/components/carousel/Carousel.tsx +20 -9
  25. package/src/components/carousel/CarouselControlButton.tsx +1 -1
  26. package/src/components/carousel/CarouselPlayPause.tsx +1 -1
  27. package/src/components/carousel/carouselEngine.ts +3 -3
  28. package/src/components/checkbox/Checkbox.tsx +12 -11
  29. package/src/components/checkbox/CheckboxGroup.tsx +1 -1
  30. package/src/components/chip/Chip.tsx +5 -4
  31. package/src/components/chip-input/ChipInput.tsx +12 -4
  32. package/src/components/chip-input/ChipList.tsx +2 -2
  33. package/src/components/close-button/CloseButton.tsx +2 -2
  34. package/src/components/color-input/ColorInput.tsx +2 -2
  35. package/src/components/combobox/Combobox.tsx +2 -2
  36. package/src/components/datagrid/DataGrid.scss +203 -0
  37. package/src/components/datagrid/DataGrid.tsx +259 -0
  38. package/src/components/datagrid/DataGridBody.tsx +162 -0
  39. package/src/components/datagrid/DataGridCell.tsx +47 -0
  40. package/src/components/datagrid/DataGridColumn.tsx +109 -0
  41. package/src/components/datagrid/DataGridHeader.tsx +47 -0
  42. package/src/components/datagrid/DataGridPinBehavior.tsx +225 -0
  43. package/src/components/datagrid/DataGridRow.tsx +48 -0
  44. package/src/components/datagrid/DataGridSelectionCell.tsx +74 -0
  45. package/src/components/datagrid/DataGridTableLayout.ts +30 -0
  46. package/src/components/datagrid/index.ts +14 -0
  47. package/src/components/datagrid/useColumnGeometry.ts +161 -0
  48. package/src/components/datepicker/ClearButton.tsx +4 -1
  49. package/src/components/datepicker/DatePicker.scss +2 -2
  50. package/src/components/datepicker/DatePicker.tsx +179 -147
  51. package/src/components/datepicker/DateRangePicker.tsx +107 -89
  52. package/src/components/datepicker/renderDatePickerShell.tsx +1 -1
  53. package/src/components/drawer/Drawer.tsx +3 -1
  54. package/src/components/file-input/FileInput.tsx +2 -2
  55. package/src/components/floating-input/FloatingInput.tsx +8 -8
  56. package/src/components/form/renderFormCheck.tsx +3 -3
  57. package/src/components/form-field/FormField.tsx +8 -8
  58. package/src/components/form-field/renderFormField.tsx +14 -3
  59. package/src/components/grid/Col.tsx +1 -1
  60. package/src/components/grid/GridItem.tsx +1 -1
  61. package/src/components/grid/Row.tsx +1 -1
  62. package/src/components/input-group/InputGroup.tsx +2 -2
  63. package/src/components/list/List.tsx +5 -5
  64. package/src/components/menu/Menu.tsx +10 -0
  65. package/src/components/menu/MenuSubmenu.tsx +3 -3
  66. package/src/components/menu/MenuSubmenuBack.tsx +1 -1
  67. package/src/components/menu/MenuToggle.tsx +2 -2
  68. package/src/components/modal/Modal.tsx +5 -3
  69. package/src/components/navbar/Navbar.tsx +1 -1
  70. package/src/components/navbar/NavbarBrand.tsx +5 -2
  71. package/src/components/notification/NotificationStack.tsx +1 -1
  72. package/src/components/otp-input/OtpBox.tsx +1 -1
  73. package/src/components/otp-input/OtpInput.tsx +12 -11
  74. package/src/components/pagination/Pagination.tsx +2 -2
  75. package/src/components/pagination/PaginationItem.tsx +23 -4
  76. package/src/components/popover/Popover.tsx +40 -14
  77. package/src/components/progress/Progress.tsx +1 -1
  78. package/src/components/progress/ProgressBar.tsx +1 -1
  79. package/src/components/radio/Radio.tsx +2 -2
  80. package/src/components/radio/RadioGroup.tsx +1 -1
  81. package/src/components/select/Select.tsx +8 -8
  82. package/src/components/skeleton/Skeleton.tsx +1 -1
  83. package/src/components/spinner/Spinner.tsx +2 -2
  84. package/src/components/stepper/Stepper.tsx +5 -5
  85. package/src/components/switch/Switch.tsx +3 -3
  86. package/src/components/table/Table.css +2 -2
  87. package/src/components/table/Table.tsx +30 -7
  88. package/src/components/text-input/TextInput.tsx +2 -2
  89. package/src/components/textarea/Textarea.tsx +2 -2
  90. package/src/components/toast/ToastBody.tsx +1 -1
  91. package/src/components/toast/ToastHeader.tsx +2 -2
  92. package/src/components/toast/Toaster.tsx +1 -1
  93. package/src/components/tooltip/Tooltip.tsx +23 -7
  94. package/src/hooks/useAutoDismiss.ts +4 -1
  95. package/src/hooks/useDialogElement.ts +10 -0
  96. package/src/hooks/useFloatingOverlay.ts +12 -2
  97. package/src/hooks/usePagination.ts +6 -2
  98. package/src/index.ts +226 -0
  99. package/src/types.ts +26 -26
  100. package/src/utils/breakpoints.ts +1 -1
  101. package/src/utils/devWarning.ts +58 -0
  102. package/src/utils/direction.ts +14 -0
  103. package/src/utils/focusRedirect.ts +10 -16
  104. package/src/utils/polymorphic.ts +18 -1
  105. package/src/utils/suppressFocusRingGlobally.ts +22 -7
  106. package/src/utils/triggerElement.ts +25 -0
@@ -13,6 +13,7 @@ import { DateField } from './DateField'
13
13
  import { renderDatePickerShell } from './renderDatePickerShell'
14
14
  import './DatePicker.scss'
15
15
  import './DateRangePicker.scss'
16
+ import { CalendarLabels, CalendarLabelsProvider } from '../calendar/labels'
16
17
 
17
18
  export interface DateRangePickerProps extends Omit<
18
19
  HTMLAttributes<HTMLDivElement>,
@@ -72,6 +73,14 @@ export interface DateRangePickerProps extends Omit<
72
73
  * shown but cannot be selected.
73
74
  */
74
75
  isDateUnavailable?: (date: DateValue) => boolean
76
+ /**
77
+ * Overrides for the strings this component and its calendar render themselves rather than
78
+ * getting from the active locale — the clear adornment and the calendar's own year-view
79
+ * arrows/announcements. Date segment order, month and weekday names all follow `I18nProvider`'s
80
+ * locale via react-aria and need no override. Merged over the English defaults, so passing one
81
+ * key leaves the rest alone.
82
+ */
83
+ labels?: Partial<CalendarLabels>
75
84
  /**
76
85
  * Whether the calendar popover is open (controlled).
77
86
  */
@@ -110,9 +119,9 @@ export interface DateRangePickerProps extends Omit<
110
119
  */
111
120
  presets?: DateRangePreset[]
112
121
  /**
113
- * Size the component small or large.
122
+ * Size the component sm or lg.
114
123
  */
115
- size?: 'small' | 'large'
124
+ size?: 'sm' | 'lg'
116
125
  /**
117
126
  * ISO 8601 dates (`YYYY-MM-DD`) to mark unselectable, as a convenience alternative to
118
127
  * `isDateUnavailable` for data-driven cases (e.g. booked dates fetched from an API). Composed
@@ -162,6 +171,7 @@ export const DateRangePicker = forwardRef<HTMLDivElement, DateRangePickerProps>(
162
171
  isDateUnavailable,
163
172
  isOpen,
164
173
  label,
174
+ labels,
165
175
  maxValue,
166
176
  minValue,
167
177
  name,
@@ -248,95 +258,103 @@ export const DateRangePicker = forwardRef<HTMLDivElement, DateRangePickerProps>(
248
258
 
249
259
  const { dialogProps: domDialogProps } = useDialog(dialogProps, calendarRef)
250
260
 
251
- return renderFormField({
252
- children: (
253
- <>
254
- {renderDatePickerShell({
255
- calendar: (
256
- <RangeCalendar
257
- {...domDialogProps}
258
- autoFocus
259
- disabled={disabled}
260
- firstDayOfWeek={firstDayOfWeek}
261
- isDateUnavailable={combinedIsDateUnavailable}
262
- maxValue={maxValue}
263
- minValue={minValue}
264
- onChange={calendarProps.onChange}
265
- presets={presets}
266
- ref={calendarRef}
267
- value={calendarProps.value}
268
- visibleMonths={visibleMonths}
269
- />
270
- ),
271
- className,
272
- // `state.value` is always a `{ start, end }` object, never `null` itself — even with
273
- // nothing picked yet — so the endpoints are what actually indicate a selection to
274
- // clear (matching the hidden-input `value`s below, which check the same way).
275
- clearButton: (state.value?.start || state.value?.end) && !disabled && (
276
- <ClearButton
277
- onPress={() => {
278
- state.setValue(null)
279
- // See `DatePicker`'s identical `ClearButton` usage for why this is needed —
280
- // this button unmounts itself once cleared, so focus needs somewhere to land.
281
- toggleButtonRef.current?.focus()
282
- }}
283
- />
284
- ),
285
- disabled,
286
- field: (
287
- <>
288
- <DateField fieldProps={startFieldProps} />
289
- <span aria-hidden="true" className="daterangepicker-separator">
290
- –
291
- </span>
292
- <DateField fieldProps={endFieldProps} />
293
- </>
294
- ),
295
- fieldClassName: 'd-flex w-100',
296
- groupProps: {
297
- ...mergeProps(groupProps, rest),
298
- 'aria-describedby': describedBy,
299
- 'aria-labelledby': labelledBy
300
- },
301
- groupRef: forkedGroupRef,
302
- invalid,
303
- isOpen: state.isOpen,
304
- overlayDismissProps,
305
- overlayRef,
306
- overlayStyle,
307
- placementAttr,
308
- size,
309
- toggleButton: (
310
- <CalendarToggleButton buttonProps={buttonProps} ref={toggleButtonRef} state={state} />
311
- ),
312
- valid
313
- })}
314
- {name && (
261
+ return (
262
+ <CalendarLabelsProvider labels={labels}>
263
+ {renderFormField({
264
+ children: (
315
265
  <>
316
- <input
317
- disabled={disabled}
318
- name={`${name}Start`}
319
- type="hidden"
320
- value={state.value?.start ? state.value.start.toString() : ''}
321
- />
322
- <input
323
- disabled={disabled}
324
- name={`${name}End`}
325
- type="hidden"
326
- value={state.value?.end ? state.value.end.toString() : ''}
327
- />
266
+ {renderDatePickerShell({
267
+ calendar: (
268
+ <RangeCalendar
269
+ {...domDialogProps}
270
+ autoFocus
271
+ disabled={disabled}
272
+ firstDayOfWeek={firstDayOfWeek}
273
+ isDateUnavailable={combinedIsDateUnavailable}
274
+ maxValue={maxValue}
275
+ minValue={minValue}
276
+ onChange={calendarProps.onChange}
277
+ presets={presets}
278
+ ref={calendarRef}
279
+ value={calendarProps.value}
280
+ visibleMonths={visibleMonths}
281
+ />
282
+ ),
283
+ className,
284
+ // `state.value` is always a `{ start, end }` object, never `null` itself — even with
285
+ // nothing picked yet — so the endpoints are what actually indicate a selection to
286
+ // clear (matching the hidden-input `value`s below, which check the same way).
287
+ clearButton: (state.value?.start || state.value?.end) && !disabled && (
288
+ <ClearButton
289
+ onPress={() => {
290
+ state.setValue(null)
291
+ // See `DatePicker`'s identical `ClearButton` usage for why this is needed —
292
+ // this button unmounts itself once cleared, so focus needs somewhere to land.
293
+ toggleButtonRef.current?.focus()
294
+ }}
295
+ />
296
+ ),
297
+ disabled,
298
+ field: (
299
+ <>
300
+ <DateField fieldProps={startFieldProps} />
301
+ <span aria-hidden="true" className="daterangepicker-separator">
302
+ –
303
+ </span>
304
+ <DateField fieldProps={endFieldProps} />
305
+ </>
306
+ ),
307
+ fieldClassName: 'd-flex w-100',
308
+ groupProps: {
309
+ ...mergeProps(groupProps, rest),
310
+ 'aria-describedby': describedBy,
311
+ 'aria-labelledby': labelledBy
312
+ },
313
+ groupRef: forkedGroupRef,
314
+ invalid,
315
+ isOpen: state.isOpen,
316
+ overlayDismissProps,
317
+ overlayRef,
318
+ overlayStyle,
319
+ placementAttr,
320
+ size,
321
+ toggleButton: (
322
+ <CalendarToggleButton
323
+ buttonProps={buttonProps}
324
+ ref={toggleButtonRef}
325
+ state={state}
326
+ />
327
+ ),
328
+ valid
329
+ })}
330
+ {name && (
331
+ <>
332
+ <input
333
+ disabled={disabled}
334
+ name={`${name}Start`}
335
+ type="hidden"
336
+ value={state.value?.start ? state.value.start.toString() : ''}
337
+ />
338
+ <input
339
+ disabled={disabled}
340
+ name={`${name}End`}
341
+ type="hidden"
342
+ value={state.value?.end ? state.value.end.toString() : ''}
343
+ />
344
+ </>
345
+ )}
328
346
  </>
329
- )}
330
- </>
331
- ),
332
- help,
333
- ids: { feedback: feedbackId, help: helpId, label: labelId },
334
- invalid,
335
- invalidFeedback,
336
- label,
337
- valid,
338
- validFeedback
339
- })
347
+ ),
348
+ help,
349
+ ids: { feedback: feedbackId, help: helpId, label: labelId },
350
+ invalid,
351
+ invalidFeedback,
352
+ label,
353
+ valid,
354
+ validFeedback
355
+ })}
356
+ </CalendarLabelsProvider>
357
+ )
340
358
  }
341
359
  )
342
360
 
@@ -40,7 +40,7 @@ export interface RenderDatePickerShellOptions {
40
40
  overlayRef: Ref<HTMLDivElement>
41
41
  overlayStyle?: CSSProperties
42
42
  placementAttr?: string
43
- size?: 'small' | 'large'
43
+ size?: 'sm' | 'lg'
44
44
  toggleButton: ReactNode
45
45
  valid?: boolean
46
46
  }
@@ -69,7 +69,7 @@ export interface DrawerProps extends Omit<
69
69
  /**
70
70
  * Renders as a drawer only below this breakpoint — inline as a flex container above it.
71
71
  */
72
- responsive?: 'small' | 'medium' | 'large' | 'xlarge' | '2xlarge'
72
+ responsive?: 'sm' | 'md' | 'lg' | 'xl' | '2xl'
73
73
  /**
74
74
  * Allow the page behind the drawer to scroll while it's open.
75
75
  */
@@ -118,6 +118,7 @@ export const Drawer = forwardRef<HTMLDialogElement, DrawerProps>(
118
118
  fullscreen,
119
119
  instant,
120
120
  keyboard = true,
121
+ onClick,
121
122
  onClose,
122
123
  onClosePrevented,
123
124
  onHidden,
@@ -144,6 +145,7 @@ export const Drawer = forwardRef<HTMLDialogElement, DrawerProps>(
144
145
  if (entry.dialog !== dialog) entry.close()
145
146
  }
146
147
  },
148
+ onClick,
147
149
  onClose,
148
150
  onClosePrevented,
149
151
  onHidden,
@@ -39,9 +39,9 @@ export interface FileInputProps extends Omit<InputHTMLAttributes<HTMLInputElemen
39
39
  */
40
40
  onChange?: ChangeEventHandler<HTMLInputElement>
41
41
  /**
42
- * Size the component small or large.
42
+ * Size the component sm or lg.
43
43
  */
44
- size?: 'small' | 'large'
44
+ size?: 'sm' | 'lg'
45
45
  /**
46
46
  * Set component validation state to valid.
47
47
  */
@@ -3,6 +3,7 @@ import classNames from 'classnames'
3
3
 
4
4
  import { FormLabel } from '../form/FormLabel'
5
5
  import { FormFieldIds, renderFormField } from '../form-field/renderFormField'
6
+ import { devWarning } from '../../utils/devWarning'
6
7
 
7
8
  export interface FloatingInputProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
8
9
  /**
@@ -63,14 +64,13 @@ export const FloatingInput = forwardRef<HTMLDivElement, FloatingInputProps>(
63
64
  },
64
65
  ref
65
66
  ) => {
66
- if (!ids?.input && !ids?.label) {
67
- console.warn(
68
- 'FloatingInput: `ids.input`/`ids.label` are not set — the rendered label ' +
69
- "won't be associated with your control (no `htmlFor`, no `aria-labelledby` target). " +
70
- 'Pass `ids={{ input: yourControlId }}` (or `ids.label` for a group with no single ' +
71
- 'input) so the label has something to point at.'
72
- )
73
- }
67
+ devWarning(
68
+ !ids?.input && !ids?.label,
69
+ 'FloatingInput: `ids.input`/`ids.label` are not set — the rendered label ' +
70
+ "won't be associated with your control (no `htmlFor`, no `aria-labelledby` target). " +
71
+ 'Pass `ids={{ input: yourControlId }}` (or `ids.label` for a group with no single ' +
72
+ 'input) so the label has something to point at.'
73
+ )
74
74
 
75
75
  return renderFormField({
76
76
  children: (
@@ -16,9 +16,9 @@ export type ButtonObject = {
16
16
  */
17
17
  shape?: Shapes
18
18
  /**
19
- * Size the component small or large.
19
+ * Size the component sm or lg.
20
20
  */
21
- size?: 'small' | 'large'
21
+ size?: 'sm' | 'lg'
22
22
  /**
23
23
  * Set the button variant to an outlined button or a ghost button.
24
24
  */
@@ -32,7 +32,7 @@ export interface RenderFormCheckOptions {
32
32
  input: ReactNode
33
33
  invalid?: boolean
34
34
  label?: ReactNode
35
- size?: 'small' | 'large'
35
+ size?: 'sm' | 'lg'
36
36
  valid?: boolean
37
37
  }
38
38
 
@@ -1,6 +1,7 @@
1
1
  import { ReactNode } from 'react'
2
2
 
3
3
  import { FormFieldIds, renderFormField } from './renderFormField'
4
+ import { devWarning } from '../../utils/devWarning'
4
5
 
5
6
  export interface FormFieldProps {
6
7
  /**
@@ -59,14 +60,13 @@ export const FormField = ({
59
60
  valid,
60
61
  validFeedback
61
62
  }: FormFieldProps): ReactNode => {
62
- if (label && !ids?.input && !ids?.label) {
63
- console.warn(
64
- 'FormField: `label` is set but `ids.input`/`ids.label` are not — the rendered label ' +
65
- "won't be associated with your control (no `htmlFor`, no `aria-labelledby` target). " +
66
- 'Pass `ids={{ input: yourControlId }}` (or `ids.label` for a group with no single ' +
67
- 'input) so the label has something to point at.'
68
- )
69
- }
63
+ devWarning(
64
+ !!label && !ids?.input && !ids?.label,
65
+ 'FormField: `label` is set but `ids.input`/`ids.label` are not — the rendered label ' +
66
+ "won't be associated with your control (no `htmlFor`, no `aria-labelledby` target). " +
67
+ 'Pass `ids={{ input: yourControlId }}` (or `ids.label` for a group with no single ' +
68
+ 'input) so the label has something to point at.'
69
+ )
70
70
 
71
71
  return renderFormField({
72
72
  children,
@@ -43,10 +43,21 @@ export const renderFormField = ({
43
43
  valid,
44
44
  validFeedback
45
45
  }: RenderFormFieldOptions): ReactNode => {
46
- const showInvalidFeedback = invalid && invalidFeedback
47
- const showValidFeedback = valid && validFeedback
46
+ // At most one feedback node renders, and `invalid` wins: both carry the same `ids.feedback`,
47
+ // so rendering the pair (which `invalid` + `valid` set together used to do) put a duplicate id
48
+ // in the DOM and left every control's `aria-describedby` pointing at an ambiguous target —
49
+ // axe's `duplicate-id-aria`. `useFormField` only ever budgets one feedback id for the same
50
+ // reason. Contradictory state, but nothing in the types rules it out and ten components route
51
+ // through here.
52
+ const showInvalidFeedback = Boolean(invalid && invalidFeedback)
53
+ const showValidFeedback = Boolean(valid && validFeedback) && !showInvalidFeedback
48
54
 
49
- if (!label && !help && !showInvalidFeedback && !showValidFeedback) {
55
+ // `className` alone is enough to warrant the wrapper: it has nowhere else to land, and
56
+ // returning `children` bare (the drop-in-compatibility path every field-capable leaf relies on
57
+ // until a consumer opts into wrapping) silently dropped it. Only `FormField` — the standalone
58
+ // "wrap this yourself" component — passes one, so this is exactly the case where the caller
59
+ // asked for a `.form-field` element by hand.
60
+ if (!label && !help && !showInvalidFeedback && !showValidFeedback && !className) {
50
61
  return children
51
62
  }
52
63
 
@@ -31,7 +31,7 @@ export interface ColProps extends HTMLAttributes<HTMLDivElement>, ColLayout {
31
31
  /**
32
32
  * Overrides `span`/`offset`/`order` at a breakpoint and up.
33
33
  *
34
- * @type { Partial<Record<'small' | 'medium' | 'large' | 'xlarge' | '2xlarge', { span?: 'auto' | number | string | boolean, offset?: number | string, order?: 'first' | 'last' | number | string }>> }
34
+ * @type { Partial<Record<'sm' | 'md' | 'lg' | 'xl' | '2xl', { span?: 'auto' | number | string | boolean, offset?: number | string, order?: 'first' | 'last' | number | string }>> }
35
35
  */
36
36
  responsive?: Partial<Record<Breakpoint, ColLayout>>
37
37
  }
@@ -39,7 +39,7 @@ type GridItemOwnProps<C extends ElementType> = GridItemLayout & {
39
39
  /**
40
40
  * Overrides `span`/`start` at a breakpoint and up.
41
41
  *
42
- * @type { Partial<Record<'small' | 'medium' | 'large' | 'xlarge' | '2xlarge', { span?: number, start?: number }>> }
42
+ * @type { Partial<Record<'sm' | 'md' | 'lg' | 'xl' | '2xl', { span?: number, start?: number }>> }
43
43
  */
44
44
  responsive?: Partial<Record<Breakpoint, GridItemLayout>>
45
45
  /**
@@ -40,7 +40,7 @@ export interface RowProps extends HTMLAttributes<HTMLDivElement>, RowLayout {
40
40
  /**
41
41
  * Overrides `cols`/`gutter`/`gutterX`/`gutterY` at a breakpoint and up.
42
42
  *
43
- * @type { Partial<Record<'small' | 'medium' | 'large' | 'xlarge' | '2xlarge', { cols?: 'auto' | number | string, gutter?: Spacing | 0, gutterX?: Spacing | 0, gutterY?: Spacing | 0 }>> }
43
+ * @type { Partial<Record<'sm' | 'md' | 'lg' | 'xl' | '2xl', { cols?: 'auto' | number | string, gutter?: Spacing | 0, gutterX?: Spacing | 0, gutterY?: Spacing | 0 }>> }
44
44
  */
45
45
  responsive?: Partial<Record<Breakpoint, RowLayout>>
46
46
  }
@@ -17,9 +17,9 @@ type InputGroupOwnProps<C extends ElementType> = {
17
17
  */
18
18
  component?: C
19
19
  /**
20
- * Size the component small or large.
20
+ * Size the component sm or lg.
21
21
  */
22
- size?: 'small' | 'large'
22
+ size?: 'sm' | 'lg'
23
23
  }
24
24
 
25
25
  export type InputGroupProps<C extends ElementType = 'div'> = PolymorphicComponentProps<
@@ -72,11 +72,11 @@ type ListOwnProps<C extends ElementType> = {
72
72
  */
73
73
  layout?:
74
74
  | 'horizontal'
75
- | 'small:horizontal'
76
- | 'medium:horizontal'
77
- | 'large:horizontal'
78
- | 'xlarge:horizontal'
79
- | '2xlarge:horizontal'
75
+ | 'sm:horizontal'
76
+ | 'md:horizontal'
77
+ | 'lg:horizontal'
78
+ | 'xl:horizontal'
79
+ | '2xl:horizontal'
80
80
  /**
81
81
  * Number list items sequentially using CSS counters. Pair with `component="ol"` for semantic correctness.
82
82
  */
@@ -220,8 +220,18 @@ function MenuRender<C extends ElementType = typeof Fragment>(
220
220
  // reflecting the new `.show` class by the time this effect runs, since React commits the
221
221
  // render before passive effects execute. Falls back to firing synchronously when no
222
222
  // `MenuList` is mounted to measure (nothing to transition, so nothing to wait for).
223
+ //
224
+ // All four report *transitions*, so none fires for the initial commit — mounting closed is not
225
+ // a hide. Without this guard, mounting fired `onHide` (and scheduled `onHidden`) for every menu
226
+ // on the page before `onShow` had ever fired once, which `Menu.spec.tsx` used to have to
227
+ // `mockClear()` past.
228
+ const mountedRef = useRef(false)
229
+
223
230
  useEffect(() => {
224
231
  const overlay = overlayRef.current
232
+ const isInitialCommit = !mountedRef.current
233
+ mountedRef.current = true
234
+ if (isInitialCommit) return undefined
225
235
 
226
236
  if (state.isOpen) {
227
237
  onShow?.()
@@ -49,7 +49,7 @@ export interface MenuSubmenuProps extends Omit<HTMLAttributes<HTMLDivElement>, '
49
49
  */
50
50
  placement?: Placement
51
51
  /**
52
- * Switches to a view-replacement pattern below the `small` breakpoint. Pair with a
52
+ * Switches to a view-replacement pattern below the `sm` breakpoint. Pair with a
53
53
  * `MenuSubmenuBack` as the first item of the nested menu.
54
54
  */
55
55
  stacked?: boolean
@@ -268,10 +268,10 @@ export const MenuSubmenu = forwardRef<HTMLDivElement, MenuSubmenuProps>(
268
268
  </div>
269
269
  )
270
270
 
271
- // `stacked`'s small-breakpoint CSS (`.menu:has(.submenu-stacked.show) ...`) needs the
271
+ // `stacked`'s sm-breakpoint CSS (`.menu:has(.submenu-stacked.show) ...`) needs the
272
272
  // panel to be a real DOM descendant of `.menu`/`.submenu` to match — portaling would
273
273
  // break that ancestor relationship, so it renders inline instead. Trade-off: above the
274
- // `small` breakpoint a `stacked` submenu no longer escapes ancestor overflow/stacking-
274
+ // `sm` breakpoint a `stacked` submenu no longer escapes ancestor overflow/stacking-
275
275
  // context clipping the way a portaled one does; accepted since `stacked` targets mobile
276
276
  // nav/drawer usage, not floating dropdowns.
277
277
  return mounted && !stacked ? createPortal(panel, document.body) : panel
@@ -11,7 +11,7 @@ export interface MenuSubmenuBackProps extends ButtonHTMLAttributes<HTMLButtonEle
11
11
  }
12
12
 
13
13
  // First item of a stacked submenu's nested menu (see `MenuSubmenu`'s `stacked` prop). Closes
14
- // the submenu and returns focus to its trigger — visible only below the `small` breakpoint.
14
+ // the submenu and returns focus to its trigger — visible only below the `sm` breakpoint.
15
15
  export const MenuSubmenuBack = forwardRef<HTMLButtonElement, MenuSubmenuBackProps>(
16
16
  ({ children, className, onClick, type = 'button', ...rest }, ref) => {
17
17
  const actions = useContext(SubmenuActionsContext)
@@ -52,9 +52,9 @@ type MenuToggleOwnProps<C extends ElementType> = {
52
52
  */
53
53
  shape?: Shapes
54
54
  /**
55
- * Size the component small or large. Only applies to the default `Button` root.
55
+ * Size the component sm or lg. Only applies to the default `Button` root.
56
56
  */
57
- size?: 'small' | 'large'
57
+ size?: 'sm' | 'lg'
58
58
  /**
59
59
  * Sets the context style of the component. Only applies to the default `Button` root.
60
60
  */
@@ -24,7 +24,7 @@ export interface ModalProps extends Omit<
24
24
  * Set modal to cover the entire user viewport. A breakpoint value goes fullscreen only
25
25
  * below that breakpoint.
26
26
  */
27
- fullscreen?: boolean | 'small' | 'medium' | 'large' | 'xlarge' | '2xlarge'
27
+ fullscreen?: boolean | 'sm' | 'md' | 'lg' | 'xl' | '2xl'
28
28
  /**
29
29
  * Disable the open/close transition entirely.
30
30
  */
@@ -63,9 +63,9 @@ export interface ModalProps extends Omit<
63
63
  */
64
64
  scrollable?: boolean
65
65
  /**
66
- * Size the component small, large, or extra large.
66
+ * Size the component sm, lg, or extra lg.
67
67
  */
68
- size?: 'small' | 'large' | 'xlarge'
68
+ size?: 'sm' | 'lg' | 'xl'
69
69
  /**
70
70
  * Toggle the visibility of modal component.
71
71
  */
@@ -97,6 +97,7 @@ export const Modal = forwardRef<HTMLDialogElement, ModalProps>(
97
97
  instant,
98
98
  keyboard = true,
99
99
  modal = true,
100
+ onClick,
100
101
  onClose,
101
102
  onClosePrevented,
102
103
  onHidden,
@@ -115,6 +116,7 @@ export const Modal = forwardRef<HTMLDialogElement, ModalProps>(
115
116
  instant,
116
117
  isModal: modal,
117
118
  keyboard,
119
+ onClick,
118
120
  onClose,
119
121
  onClosePrevented,
120
122
  onHidden,
@@ -24,7 +24,7 @@ type NavbarOwnProps<C extends ElementType> = {
24
24
  /**
25
25
  * Defines optional container wrapping children elements.
26
26
  */
27
- container?: boolean | 'small' | 'medium' | 'large' | 'xlarge' | '2xlarge' | 'fluid'
27
+ container?: boolean | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'fluid'
28
28
  /**
29
29
  * Opts this navbar into the framework's dark or light theming, independent of the page's own
30
30
  * theme.
@@ -4,7 +4,8 @@ import classNames from 'classnames'
4
4
  import {
5
5
  createPolymorphicComponent,
6
6
  PolymorphicComponentProps,
7
- PolymorphicRef
7
+ PolymorphicRef,
8
+ PolymorphicRefWithFallback
8
9
  } from '../../utils/polymorphic'
9
10
 
10
11
  type NavbarBrandOwnProps<C extends ElementType> = {
@@ -30,7 +31,9 @@ export type NavbarBrandProps<C extends ElementType = 'span'> = PolymorphicCompon
30
31
  >
31
32
 
32
33
  type NavbarBrandComponent = (<C extends ElementType = 'span'>(
33
- props: NavbarBrandProps<C> & { ref?: PolymorphicRef<C> }
34
+ props: NavbarBrandProps<C> & {
35
+ ref?: PolymorphicRefWithFallback<C, HTMLSpanElement | HTMLAnchorElement>
36
+ }
34
37
  ) => ReactElement | null) & { displayName?: string }
35
38
 
36
39
  function NavbarBrandRender<C extends ElementType = 'span'>(
@@ -39,7 +39,7 @@ export const NotificationStack = forwardRef<HTMLDivElement, NotificationStackPro
39
39
  return (
40
40
  <Stack
41
41
  direction="vertical"
42
- gap="small"
42
+ gap="sm"
43
43
  className={className}
44
44
  {...regionProps}
45
45
  {...rest}
@@ -12,7 +12,7 @@ interface OtpBoxProps {
12
12
  onChangeValue: (value: string) => void
13
13
  onKeyDownBox: (event: KeyboardEvent<HTMLInputElement>) => void
14
14
  onPasteBox: (event: ClipboardEvent<HTMLInputElement>) => void
15
- size?: 'small' | 'large'
15
+ size?: 'sm' | 'lg'
16
16
  valid?: boolean
17
17
  value: string
18
18
  }