@chassis-ui/react 0.1.0 → 0.1.1

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 +12 -6
  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
@@ -26,6 +26,7 @@ import { mergeIsDateUnavailable } from '../../utils/mergeIsDateUnavailable'
26
26
  import { suppressFocusRing } from '../../utils/suppressFocusRingGlobally'
27
27
  import './Calendar.scss'
28
28
  import './RangeCalendar.scss'
29
+ import { CalendarLabels, CalendarLabelsProvider } from './labels'
29
30
 
30
31
  export interface RangeCalendarProps extends Omit<
31
32
  HTMLAttributes<HTMLDivElement>,
@@ -67,6 +68,14 @@ export interface RangeCalendarProps extends Omit<
67
68
  * shown but cannot be selected.
68
69
  */
69
70
  isDateUnavailable?: (date: DateValue) => boolean
71
+ /**
72
+ * Overrides for the handful of strings this component renders itself rather than getting from
73
+ * the active locale (the year view's paging arrows, its live-region announcements, and the
74
+ * month/year header buttons). Everything else — month and weekday names, the calendar system —
75
+ * follows `I18nProvider`'s locale via react-aria and needs no override. Merged over the English
76
+ * defaults, so passing one key leaves the rest alone.
77
+ */
78
+ labels?: Partial<CalendarLabels>
70
79
  /**
71
80
  * The maximum allowed date that a user may select.
72
81
  */
@@ -97,7 +106,7 @@ export interface RangeCalendarProps extends Omit<
97
106
  value?: RangeValue<DateValue> | null
98
107
  /**
99
108
  * Number of months to display side by side, sharing one selection. Wraps to multiple rows in
100
- * a narrow container (e.g. a popover on a small screen) rather than overflowing — the wrap is
109
+ * a narrow container (e.g. a popover on a sm screen) rather than overflowing — the wrap is
101
110
  * driven by the calendar's own width, not the viewport, so it adapts correctly regardless of
102
111
  * where the calendar is embedded.
103
112
  *
@@ -122,6 +131,7 @@ export const RangeCalendar = forwardRef<HTMLDivElement, RangeCalendarProps>(
122
131
  disabled,
123
132
  firstDayOfWeek = 'mon',
124
133
  isDateUnavailable,
134
+ labels,
125
135
  maxValue,
126
136
  minValue,
127
137
  onChange,
@@ -205,53 +215,55 @@ export const RangeCalendar = forwardRef<HTMLDivElement, RangeCalendarProps>(
205
215
  }
206
216
 
207
217
  return (
208
- <div
209
- {...mergeProps(calendarProps, rest)}
210
- className={classNames('datepicker', className)}
211
- data-cx-inline="true"
212
- ref={ref}
213
- >
214
- <div className="datepicker-grid">
215
- {resolvedPresets && (
216
- <DateRangePresets
217
- onSelect={handlePresetSelect}
218
- presets={resolvedPresets}
219
- value={state.value}
220
- />
221
- )}
222
- <div className="datepicker-body">
223
- {visibleMonths > 1 && !hasPickerView && (
224
- <div className="datepicker-controls">
225
- {prevButton}
226
- {nextButton}
227
- </div>
218
+ <CalendarLabelsProvider labels={labels}>
219
+ <div
220
+ {...mergeProps(calendarProps, rest)}
221
+ className={classNames('datepicker', className)}
222
+ data-cx-inline="true"
223
+ ref={ref}
224
+ >
225
+ <div className="datepicker-grid">
226
+ {resolvedPresets && (
227
+ <DateRangePresets
228
+ onSelect={handlePresetSelect}
229
+ presets={resolvedPresets}
230
+ value={state.value}
231
+ />
228
232
  )}
229
- <div className="datepicker-grid">
230
- {[...new Array(visibleMonths).keys()].map((monthIndex) => (
231
- <CalendarMonthBlock
232
- arrows={monthIndex === 0 ? singleMonthArrows : null}
233
- firstDayOfWeek={firstDayOfWeek}
234
- key={monthIndex}
235
- monthIndex={monthIndex}
236
- onViewChange={(view) =>
237
- setMonthViews((prev) => ({ ...prev, [monthIndex]: view }))
238
- }
239
- renderCell={(date, i, week) => (
240
- <CalendarCell
241
- date={date}
242
- isFirstInRow={i === 0}
243
- isLastInRow={i === week.length - 1}
244
- locale={locale}
245
- state={state}
246
- />
247
- )}
248
- state={state}
249
- />
250
- ))}
233
+ <div className="datepicker-body">
234
+ {visibleMonths > 1 && !hasPickerView && (
235
+ <div className="datepicker-controls">
236
+ {prevButton}
237
+ {nextButton}
238
+ </div>
239
+ )}
240
+ <div className="datepicker-grid">
241
+ {[...new Array(visibleMonths).keys()].map((monthIndex) => (
242
+ <CalendarMonthBlock
243
+ arrows={monthIndex === 0 ? singleMonthArrows : null}
244
+ firstDayOfWeek={firstDayOfWeek}
245
+ key={monthIndex}
246
+ monthIndex={monthIndex}
247
+ onViewChange={(view) =>
248
+ setMonthViews((prev) => ({ ...prev, [monthIndex]: view }))
249
+ }
250
+ renderCell={(date, i, week) => (
251
+ <CalendarCell
252
+ date={date}
253
+ isFirstInRow={i === 0}
254
+ isLastInRow={i === week.length - 1}
255
+ locale={locale}
256
+ state={state}
257
+ />
258
+ )}
259
+ state={state}
260
+ />
261
+ ))}
262
+ </div>
251
263
  </div>
252
264
  </div>
253
265
  </div>
254
- </div>
266
+ </CalendarLabelsProvider>
255
267
  )
256
268
  }
257
269
  )
@@ -2,4 +2,5 @@ export { Calendar } from './Calendar'
2
2
  export type { CalendarProps, CalendarSingleProps, CalendarMultipleProps } from './Calendar'
3
3
  export { RangeCalendar } from './RangeCalendar'
4
4
  export type { RangeCalendarProps } from './RangeCalendar'
5
+ export type { CalendarLabels } from './labels'
5
6
  export type { DateRangePreset } from '../../utils/dateRangePresets'
@@ -0,0 +1,71 @@
1
+ import React, { createContext, ReactNode, useContext, useMemo } from 'react'
2
+
3
+ /**
4
+ * The user-facing strings the calendar/datepicker family renders itself, rather than getting from
5
+ * the active locale.
6
+ *
7
+ * Everything else these components say — weekday names, month names, the calendar system, segment
8
+ * order — comes from `I18nProvider`'s locale via react-aria, which ships its own translations. The
9
+ * strings below have no equivalent in react-aria's dictionaries, so they were hardcoded English
10
+ * with no way to override them: under `<I18nProvider locale="ar-SA">` a user got Arabic month
11
+ * names interleaved with an English "Previous years". Pass `labels` to translate them, the same
12
+ * way `Pagination`'s `previousLabel`/`nextLabel` are translated (see the Internationalization
13
+ * docs page).
14
+ */
15
+ export interface CalendarLabels {
16
+ /** Accessible name of the year view's "page back" arrow. */
17
+ previousYears: string
18
+ /** Accessible name of the year view's "page forward" arrow. */
19
+ nextYears: string
20
+ /** Prefix of the year view's live-region announcement, rendered as `${selectYear}, 2020 – 2034`. */
21
+ selectYear: string
22
+ /** Prefix of the month view's live-region announcement, rendered as `${selectMonth}, 2024`. */
23
+ selectMonth: string
24
+ /** Prefix of the header month button's accessible name, rendered as `${month}: March`. */
25
+ month: string
26
+ /** Prefix of the header year button's accessible name, rendered as `${year}: 2024`. */
27
+ year: string
28
+ /** Accessible name of the trigger that opens the calendar overlay. */
29
+ calendar: string
30
+ /** Accessible name of the adornment that resets the current selection. */
31
+ clear: string
32
+ }
33
+
34
+ export const DEFAULT_CALENDAR_LABELS: CalendarLabels = {
35
+ previousYears: 'Previous years',
36
+ nextYears: 'Next years',
37
+ selectYear: 'Select year',
38
+ selectMonth: 'Select month',
39
+ month: 'Month',
40
+ year: 'Year',
41
+ calendar: 'Calendar',
42
+ clear: 'Clear'
43
+ }
44
+
45
+ const CalendarLabelsContext = createContext<CalendarLabels>(DEFAULT_CALENDAR_LABELS)
46
+
47
+ /**
48
+ * Reads the merged labels. Internal — every component in this family that renders one of these
49
+ * strings goes through here rather than taking its own prop, because most of them
50
+ * (`CalendarYearGrid`, `CalendarMonthGrid`, `CalendarMonthYearPicker`) sit three or four levels
51
+ * below the public component a consumer actually passes `labels` to.
52
+ */
53
+ export const useCalendarLabels = (): CalendarLabels => useContext(CalendarLabelsContext)
54
+
55
+ interface CalendarLabelsProviderProps {
56
+ children: ReactNode
57
+ labels?: Partial<CalendarLabels>
58
+ }
59
+
60
+ /**
61
+ * Merges `labels` over whatever is already in context, rather than replacing it. `DatePicker`
62
+ * renders a `Calendar` internally, so both provide — without merging, the inner `Calendar`
63
+ * (which usually has no `labels` of its own) would reset the outer `DatePicker`'s overrides back
64
+ * to English.
65
+ */
66
+ export const CalendarLabelsProvider = ({ children, labels }: CalendarLabelsProviderProps) => {
67
+ const parent = useContext(CalendarLabelsContext)
68
+ const value = useMemo(() => (labels ? { ...parent, ...labels } : parent), [parent, labels])
69
+
70
+ return <CalendarLabelsContext.Provider value={value}>{children}</CalendarLabelsContext.Provider>
71
+ }
@@ -59,8 +59,8 @@ type CardOwnProps<C extends ElementType> = {
59
59
  */
60
60
  imageOrientation?: 'top' | 'bottom'
61
61
  /**
62
- * Overrides `direction` at one or more breakpoints — e.g. `{ large: 'row' }` to lay the card
63
- * out horizontally from `large` up while stacking below it.
62
+ * Overrides `direction` at one or more breakpoints — e.g. `{ lg: 'row' }` to lay the card
63
+ * out horizontally from `lg` up while stacking below it.
64
64
  */
65
65
  responsive?: Partial<Record<Breakpoint, FlexDirection>>
66
66
  /**
@@ -127,8 +127,8 @@ function CardRender<C extends ElementType = 'div'>(
127
127
  solid: variant === 'solid',
128
128
  smooth: variant === 'smooth',
129
129
  outline: variant === 'outline',
130
- small: size === 'small',
131
- large: size === 'large'
130
+ sm: size === 'sm',
131
+ lg: size === 'lg'
132
132
  },
133
133
  buildResponsiveClassNames(flexDirectionClassNames, direction, responsive),
134
134
  className
@@ -28,7 +28,7 @@ type CardGroupComponent = (<C extends ElementType = 'div'>(
28
28
  ) => ReactElement | null) & { displayName?: string }
29
29
 
30
30
  // Arranges direct-child Cards as an equal-width row, joined edge-to-edge, once its container
31
- // reaches the small container breakpoint — below that, cards stack vertically. Requires a
31
+ // reaches the sm container breakpoint — below that, cards stack vertically. Requires a
32
32
  // `.contains-inline` ancestor (not applied by CardGroup itself, e.g. a wrapping
33
33
  // `<div className="contains-inline">`) to establish the container query context.
34
34
  function CardGroupRender<C extends ElementType = 'div'>(
@@ -28,7 +28,7 @@ type CardImageOwnProps<C extends ElementType> = {
28
28
  */
29
29
  orientation?: CardImageOrientation
30
30
  /**
31
- * Overrides `orientation` at one or more breakpoints — e.g. `{ large: 'start' }` to switch an
31
+ * Overrides `orientation` at one or more breakpoints — e.g. `{ lg: 'start' }` to switch an
32
32
  * image cap from `top` to `start` once the card lays out horizontally.
33
33
  */
34
34
  responsive?: Partial<Record<Breakpoint, CardImageOrientation>>
@@ -26,6 +26,7 @@ import {
26
26
  prefersReducedMotion,
27
27
  scrollDeltaFor
28
28
  } from './carouselEngine'
29
+ import { devWarning } from '../../utils/devWarning'
29
30
 
30
31
  export type { CarouselEnds }
31
32
  export type CarouselTransition = 'scroll' | 'fade'
@@ -131,6 +132,9 @@ export const Carousel = forwardRef<HTMLDivElement, CarouselProps>(
131
132
  itemsGap,
132
133
  itemsPeek,
133
134
  keyboard = true,
135
+ onKeyDown,
136
+ onMouseEnter,
137
+ onMouseLeave,
134
138
  onSlid,
135
139
  onSlide,
136
140
  pause = 'hover',
@@ -145,13 +149,12 @@ export const Carousel = forwardRef<HTMLDivElement, CarouselProps>(
145
149
  // "Controlled usage" section). Missing both means every click/swipe/autoplay advance updates
146
150
  // internal state that `useControllableState` immediately discards (controlled mode always
147
151
  // mirrors the prop), so the carousel silently freezes instead of erroring.
148
- if (activeIndexProp !== undefined && !onSlide && !onSlid) {
149
- console.warn(
150
- 'Carousel: `activeIndex` is set (controlled) but neither `onSlide` nor `onSlid` is — ' +
151
- 'nothing feeds the new index back to your state, so the carousel will visually freeze ' +
152
- 'on click/swipe/autoplay. Pass `onSlide` or `onSlid` and update `activeIndex` from it.'
153
- )
154
- }
152
+ devWarning(
153
+ activeIndexProp !== undefined && !onSlide && !onSlid,
154
+ 'Carousel: `activeIndex` is set (controlled) but neither `onSlide` nor `onSlid` is — ' +
155
+ 'nothing feeds the new index back to your state, so the carousel will visually freeze ' +
156
+ 'on click/swipe/autoplay. Pass `onSlide` or `onSlid` and update `activeIndex` from it.'
157
+ )
155
158
 
156
159
  const carouselRef = useRef<HTMLDivElement>(null)
157
160
  const forkedRef = useForkedRef(ref, carouselRef)
@@ -655,7 +658,13 @@ export const Carousel = forwardRef<HTMLDivElement, CarouselProps>(
655
658
  }
656
659
  }, [])
657
660
 
661
+ // Compose rather than let a caller's handler silently replace the carousel's own — same rule
662
+ // `CarouselControlButton`/`CarouselPlayPause` follow for `onClick`. These three sit on the
663
+ // root element, so before this they were simply overwritten by the `{...rest}` spread below:
664
+ // passing `onKeyDown` turned off arrow-key navigation and passing `onMouseEnter`/
665
+ // `onMouseLeave` turned off pause-on-hover, both silently.
658
666
  const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
667
+ onKeyDown?.(event)
659
668
  if (!keyboard) return
660
669
  const target = event.target as HTMLElement
661
670
  if (target.closest('input, textarea, select, [contenteditable="true"]')) return
@@ -667,11 +676,13 @@ export const Carousel = forwardRef<HTMLDivElement, CarouselProps>(
667
676
  else goNext()
668
677
  }
669
678
 
670
- const handleMouseEnter = () => {
679
+ const handleMouseEnter = (event: React.MouseEvent<HTMLDivElement>) => {
680
+ onMouseEnter?.(event)
671
681
  if (pause === 'hover') pauseCycle()
672
682
  }
673
683
 
674
- const handleMouseLeave = () => {
684
+ const handleMouseLeave = (event: React.MouseEvent<HTMLDivElement>) => {
685
+ onMouseLeave?.(event)
675
686
  if (pause === 'hover' && playing) startCycle()
676
687
  }
677
688
 
@@ -51,7 +51,7 @@ export const CarouselControlButton = forwardRef<HTMLButtonElement, CarouselContr
51
51
  return (
52
52
  <button
53
53
  type="button"
54
- className={classNames('button small icon-only', className)}
54
+ className={classNames('button sm icon-only', className)}
55
55
  disabled={isDisabled}
56
56
  onClick={handleClick}
57
57
  {...rest}
@@ -28,7 +28,7 @@ export const CarouselPlayPause = forwardRef<HTMLButtonElement, CarouselPlayPause
28
28
  ({ children, className, onClick, pauseLabel = 'Pause', playLabel = 'Play', ...rest }, ref) => {
29
29
  const { playing, togglePlayPause } = useCarouselContext()
30
30
  const _className = classNames(
31
- 'carousel-control-play-pause button small icon-only',
31
+ 'carousel-control-play-pause button sm icon-only',
32
32
  { paused: !playing },
33
33
  className
34
34
  )
@@ -1,15 +1,15 @@
1
1
  // Internal helpers shared by the carousel family. Not exported from the component's public
2
2
  // barrel (see CONVENTIONS.md's helper-module carve-out).
3
3
 
4
+ import { isRTL } from '../../utils/direction'
5
+
4
6
  export const ITEM_SELECTOR = '.carousel-item:not(.carousel-item-clone)'
5
7
 
6
8
  export function getCarouselItems(viewport: HTMLElement): HTMLElement[] {
7
9
  return Array.from(viewport.querySelectorAll<HTMLElement>(`:scope > ${ITEM_SELECTOR}`))
8
10
  }
9
11
 
10
- export function isRTL(element: Element): boolean {
11
- return getComputedStyle(element).direction === 'rtl'
12
- }
12
+ export { isRTL }
13
13
 
14
14
  export function prefersReducedMotion(): boolean {
15
15
  return (
@@ -21,6 +21,7 @@ import { validationClassName } from '../../utils/validationClassName'
21
21
 
22
22
  import { CheckboxGroupContext } from './context'
23
23
  import { ButtonObject, renderFormCheck } from '../form/renderFormCheck'
24
+ import { devError, devWarning } from '../../utils/devWarning'
24
25
 
25
26
  export type { ButtonObject } from '../form/renderFormCheck'
26
27
 
@@ -72,9 +73,9 @@ export interface CheckboxProps extends Omit<
72
73
  */
73
74
  onChange?: (isSelected: boolean) => void
74
75
  /**
75
- * Size the component small or large.
76
+ * Size the component sm or lg.
76
77
  */
77
- size?: 'small' | 'large'
78
+ size?: 'sm' | 'lg'
78
79
  /**
79
80
  * Set component validation state to valid.
80
81
  */
@@ -183,15 +184,15 @@ const CheckboxGroupItem = forwardRef<HTMLInputElement, CheckboxGroupItemProps>(
183
184
  // Once per mount, not on every render — these are dev-time misuse warnings, not something
184
185
  // that needs to re-fire for every keystroke a parent's re-render happens to cause.
185
186
  useEffect(() => {
186
- if (!rest.value) {
187
- console.error('Checkbox: a `value` prop is required when rendered inside a CheckboxGroup.')
188
- }
189
- if (_defaultSelected !== undefined || _isSelected !== undefined || _onChange !== undefined) {
190
- console.warn(
191
- 'Checkbox: `defaultSelected`, `isSelected`, and `onChange` are ignored inside a ' +
192
- "CheckboxGroup — selection is owned by the group's `value`/`defaultValue`/`onChange`."
193
- )
194
- }
187
+ devError(
188
+ !rest.value,
189
+ 'Checkbox: a `value` prop is required when rendered inside a CheckboxGroup.'
190
+ )
191
+ devWarning(
192
+ _defaultSelected !== undefined || _isSelected !== undefined || _onChange !== undefined,
193
+ 'Checkbox: `defaultSelected`, `isSelected`, and `onChange` are ignored inside a ' +
194
+ "CheckboxGroup — selection is owned by the group's `value`/`defaultValue`/`onChange`."
195
+ )
195
196
  // eslint-disable-next-line react-hooks/exhaustive-deps
196
197
  }, [])
197
198
 
@@ -130,7 +130,7 @@ export const CheckboxGroup = forwardRef<HTMLFieldSetElement, CheckboxGroupProps>
130
130
  {label}
131
131
  </legend>
132
132
  )}
133
- {orientation === 'horizontal' ? <Flex gap="medium">{items}</Flex> : items}
133
+ {orientation === 'horizontal' ? <Flex gap="md">{items}</Flex> : items}
134
134
  {description && <FormHelp {...descriptionProps}>{description}</FormHelp>}
135
135
  {invalid && errorMessage && (
136
136
  <FormFeedback invalid {...errorMessageProps}>
@@ -13,7 +13,8 @@ import { useButtonSemantics, useDisabledAnchorGuard } from '../../hooks'
13
13
  import {
14
14
  createPolymorphicComponent,
15
15
  PolymorphicComponentProps,
16
- PolymorphicRef
16
+ PolymorphicRef,
17
+ PolymorphicRefWithFallback
17
18
  } from '../../utils/polymorphic'
18
19
 
19
20
  type ChipOwnProps<C extends ElementType> = {
@@ -51,9 +52,9 @@ type ChipOwnProps<C extends ElementType> = {
51
52
  */
52
53
  pressed?: boolean
53
54
  /**
54
- * Size the component small or large.
55
+ * Size the component sm or lg.
55
56
  */
56
- size?: 'small' | 'large'
57
+ size?: 'sm' | 'lg'
57
58
  /**
58
59
  * Specifies the type of button. Only applies when `component` is `button`. Different browsers
59
60
  * may use different default types for the `<button>` element, so always specify it explicitly.
@@ -71,7 +72,7 @@ export type ChipProps<C extends ElementType = 'span'> = PolymorphicComponentProp
71
72
  >
72
73
 
73
74
  type ChipComponent = (<C extends ElementType = 'span'>(
74
- props: ChipProps<C> & { ref?: PolymorphicRef<C> }
75
+ props: ChipProps<C> & { ref?: PolymorphicRefWithFallback<C, HTMLSpanElement | HTMLAnchorElement> }
75
76
  ) => ReactElement | null) & { displayName?: string }
76
77
 
77
78
  function ChipRender<C extends ElementType = 'span'>(
@@ -15,6 +15,7 @@ import { useControllableState, useForkedRef, useFormField } from '../../hooks'
15
15
  import { validationClassName } from '../../utils/validationClassName'
16
16
  import { renderFormField } from '../form-field/renderFormField'
17
17
  import { ChipList, ChipItem } from './ChipList'
18
+ import { isRTL } from '../../utils/direction'
18
19
 
19
20
  // `allowDuplicates` means two tags can share a value, but react-stately's collection needs a
20
21
  // unique key per item — keying on the value alone collapses duplicates into the same node, so
@@ -106,9 +107,9 @@ export interface ChipInputProps extends Omit<
106
107
  */
107
108
  separator?: string | null
108
109
  /**
109
- * Size the component small or large.
110
+ * Size the component sm or lg.
110
111
  */
111
- size?: 'small' | 'large'
112
+ size?: 'sm' | 'lg'
112
113
  /**
113
114
  * Set component validation state to valid.
114
115
  */
@@ -237,9 +238,16 @@ export const ChipInput = forwardRef<HTMLDivElement, ChipInputProps>(
237
238
  }
238
239
  break
239
240
  }
240
- case 'ArrowLeft': {
241
+ // Stepping backwards out of the text field and into the chips is a *visual* movement, so
242
+ // the key that does it mirrors under RTL — ArrowLeft in an LTR field, ArrowRight in an RTL
243
+ // one, matching where the chips actually sit relative to the caret. The caret check is
244
+ // unchanged: `selectionStart === 0` is the start of the text in either direction.
245
+ case 'ArrowLeft':
246
+ case 'ArrowRight': {
241
247
  const input = inputRef.current
242
- if (input && input.selectionStart === 0 && input.selectionEnd === 0) {
248
+ if (!input) break
249
+ const movesBackward = (event.key === 'ArrowLeft') !== isRTL(input)
250
+ if (movesBackward && input.selectionStart === 0 && input.selectionEnd === 0) {
243
251
  event.preventDefault()
244
252
  focusLastChip(event.shiftKey)
245
253
  }
@@ -18,7 +18,7 @@ interface ChipListProps {
18
18
  disabled?: boolean
19
19
  groupRef: RefObject<HTMLDivElement | null>
20
20
  props: AriaTagGroupOptions<ChipItem>
21
- size?: 'small' | 'large'
21
+ size?: 'sm' | 'lg'
22
22
  state: ListState<ChipItem>
23
23
  }
24
24
 
@@ -52,7 +52,7 @@ interface ChipRowProps {
52
52
  chipVariant?: string
53
53
  disabled?: boolean
54
54
  item: Node<ChipItem>
55
- size?: 'small' | 'large'
55
+ size?: 'sm' | 'lg'
56
56
  state: ListState<ChipItem>
57
57
  }
58
58
 
@@ -55,9 +55,9 @@ type CloseButtonOwnProps<C extends ElementType> = {
55
55
  */
56
56
  onClick?: MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>
57
57
  /**
58
- * Size the component small or large.
58
+ * Size the component sm or lg.
59
59
  */
60
- size?: 'small' | 'large'
60
+ size?: 'sm' | 'lg'
61
61
  /**
62
62
  * Specifies the type of button. Always specify the type attribute for the `<button>` element.
63
63
  * Different browsers may use different default types for the `<button>` element.
@@ -39,9 +39,9 @@ export interface ColorInputProps extends Omit<InputHTMLAttributes<HTMLInputEleme
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
  */
@@ -108,9 +108,9 @@ export interface ComboboxProps extends Omit<
108
108
  */
109
109
  placeholder?: string
110
110
  /**
111
- * Size the component small or large.
111
+ * Size the component sm or lg.
112
112
  */
113
- size?: 'small' | 'large'
113
+ size?: 'sm' | 'lg'
114
114
  /**
115
115
  * Set component validation state to valid.
116
116
  */