@chassis-ui/react 0.0.0-20260831090525 → 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.
- package/README.md +11 -0
- package/dist/index.d.ts +682 -237
- package/dist/index.js +2 -2
- package/dist/style.css +1 -1
- package/package.json +53 -46
- package/src/components/accordion/Accordion.tsx +2 -2
- package/src/components/accordion/AccordionItem.tsx +6 -6
- package/src/components/autocomplete/Autocomplete.tsx +4 -4
- package/src/components/avatar/Avatar.tsx +5 -2
- package/src/components/breadcrumb/Breadcrumb.tsx +11 -2
- package/src/components/button/Button.tsx +7 -4
- package/src/components/button-group/ButtonGroup.tsx +2 -2
- package/src/components/calendar/Calendar.tsx +56 -42
- package/src/components/calendar/CalendarMonthGrid.tsx +3 -1
- package/src/components/calendar/CalendarMonthYearPicker.tsx +4 -2
- package/src/components/calendar/CalendarYearGrid.tsx +5 -3
- package/src/components/calendar/RangeCalendar.scss +8 -8
- package/src/components/calendar/RangeCalendar.tsx +56 -44
- package/src/components/calendar/index.ts +1 -0
- package/src/components/calendar/labels.tsx +71 -0
- package/src/components/card/Card.tsx +4 -4
- package/src/components/card/CardGroup.tsx +1 -1
- package/src/components/card/CardImage.tsx +1 -1
- package/src/components/carousel/Carousel.tsx +20 -9
- package/src/components/carousel/CarouselControlButton.tsx +1 -1
- package/src/components/carousel/CarouselPlayPause.tsx +1 -1
- package/src/components/carousel/carouselEngine.ts +3 -3
- package/src/components/checkbox/Checkbox.tsx +12 -11
- package/src/components/checkbox/CheckboxGroup.tsx +1 -1
- package/src/components/chip/Chip.tsx +5 -4
- package/src/components/chip-input/ChipInput.tsx +12 -4
- package/src/components/chip-input/ChipList.tsx +2 -2
- package/src/components/close-button/CloseButton.tsx +2 -2
- package/src/components/color-input/ColorInput.tsx +2 -2
- package/src/components/combobox/Combobox.tsx +2 -2
- package/src/components/datagrid/DataGrid.scss +203 -0
- package/src/components/datagrid/DataGrid.tsx +259 -0
- package/src/components/datagrid/DataGridBody.tsx +162 -0
- package/src/components/datagrid/DataGridCell.tsx +47 -0
- package/src/components/datagrid/DataGridColumn.tsx +109 -0
- package/src/components/datagrid/DataGridHeader.tsx +47 -0
- package/src/components/datagrid/DataGridPinBehavior.tsx +225 -0
- package/src/components/datagrid/DataGridRow.tsx +48 -0
- package/src/components/datagrid/DataGridSelectionCell.tsx +74 -0
- package/src/components/datagrid/DataGridTableLayout.ts +30 -0
- package/src/components/datagrid/index.ts +14 -0
- package/src/components/datagrid/useColumnGeometry.ts +161 -0
- package/src/components/datepicker/ClearButton.tsx +4 -1
- package/src/components/datepicker/DatePicker.scss +2 -2
- package/src/components/datepicker/DatePicker.tsx +179 -147
- package/src/components/datepicker/DateRangePicker.tsx +107 -89
- package/src/components/datepicker/renderDatePickerShell.tsx +1 -1
- package/src/components/drawer/Drawer.tsx +3 -1
- package/src/components/file-input/FileInput.tsx +2 -2
- package/src/components/floating-input/FloatingInput.tsx +8 -8
- package/src/components/form/renderFormCheck.tsx +3 -3
- package/src/components/form-field/FormField.tsx +8 -8
- package/src/components/form-field/renderFormField.tsx +14 -3
- package/src/components/grid/Col.tsx +1 -1
- package/src/components/grid/GridItem.tsx +1 -1
- package/src/components/grid/Row.tsx +1 -1
- package/src/components/input-group/InputGroup.tsx +2 -2
- package/src/components/list/List.tsx +5 -5
- package/src/components/menu/Menu.tsx +10 -0
- package/src/components/menu/MenuSubmenu.tsx +3 -3
- package/src/components/menu/MenuSubmenuBack.tsx +1 -1
- package/src/components/menu/MenuToggle.tsx +2 -2
- package/src/components/modal/Modal.tsx +5 -3
- package/src/components/navbar/Navbar.tsx +1 -1
- package/src/components/navbar/NavbarBrand.tsx +5 -2
- package/src/components/notification/NotificationStack.tsx +1 -1
- package/src/components/otp-input/OtpBox.tsx +1 -1
- package/src/components/otp-input/OtpInput.tsx +12 -11
- package/src/components/pagination/Pagination.tsx +2 -2
- package/src/components/pagination/PaginationItem.tsx +23 -4
- package/src/components/popover/Popover.tsx +40 -14
- package/src/components/progress/Progress.tsx +1 -1
- package/src/components/progress/ProgressBar.tsx +1 -1
- package/src/components/radio/Radio.tsx +2 -2
- package/src/components/radio/RadioGroup.tsx +1 -1
- package/src/components/select/Select.tsx +8 -8
- package/src/components/skeleton/Skeleton.tsx +1 -1
- package/src/components/spinner/Spinner.tsx +2 -2
- package/src/components/stepper/Stepper.tsx +5 -5
- package/src/components/switch/Switch.tsx +3 -3
- package/src/components/table/Table.css +2 -2
- package/src/components/table/Table.tsx +12 -6
- package/src/components/text-input/TextInput.tsx +2 -2
- package/src/components/textarea/Textarea.tsx +2 -2
- package/src/components/toast/ToastBody.tsx +1 -1
- package/src/components/toast/ToastHeader.tsx +2 -2
- package/src/components/toast/Toaster.tsx +1 -1
- package/src/components/tooltip/Tooltip.tsx +23 -7
- package/src/hooks/useAutoDismiss.ts +4 -1
- package/src/hooks/useDialogElement.ts +10 -0
- package/src/hooks/useFloatingOverlay.ts +12 -2
- package/src/hooks/usePagination.ts +6 -2
- package/src/index.ts +226 -0
- package/src/types.ts +26 -26
- package/src/utils/breakpoints.ts +1 -1
- package/src/utils/devWarning.ts +58 -0
- package/src/utils/direction.ts +14 -0
- package/src/utils/focusRedirect.ts +10 -16
- package/src/utils/polymorphic.ts +18 -1
- package/src/utils/suppressFocusRingGlobally.ts +22 -7
- package/src/utils/triggerElement.ts +25 -0
|
@@ -22,8 +22,12 @@ export interface UsePaginationOptions {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export interface UsePaginationResult<T extends HTMLElement = HTMLButtonElement> {
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
// `RefObject<T | null>`, not `RefObject<T>`: these come from `useRef<T>(null)`, so `.current`
|
|
26
|
+
// really is null before mount (and whenever the control isn't rendered — a hand-composed
|
|
27
|
+
// pagination may omit Prev/Next entirely). Declaring the non-null form let a consumer write
|
|
28
|
+
// `prevRef.current.focus()` with no type error and crash at runtime.
|
|
29
|
+
prevRef: RefObject<T | null>
|
|
30
|
+
nextRef: RefObject<T | null>
|
|
27
31
|
handlePrevClick: (event: MouseEvent<T>) => void
|
|
28
32
|
handleNextClick: (event: MouseEvent<T>) => void
|
|
29
33
|
}
|
package/src/index.ts
CHANGED
|
@@ -24,6 +24,16 @@ import { Breadcrumb, BreadcrumbItem } from './components/breadcrumb'
|
|
|
24
24
|
import { Button } from './components/button'
|
|
25
25
|
import { ButtonGroup, ButtonToolbar } from './components/button-group'
|
|
26
26
|
import { Calendar, RangeCalendar } from './components/calendar'
|
|
27
|
+
import {
|
|
28
|
+
DataGrid,
|
|
29
|
+
DataGridBody,
|
|
30
|
+
DataGridCell,
|
|
31
|
+
DataGridColumn,
|
|
32
|
+
DataGridHeader,
|
|
33
|
+
DataGridRow,
|
|
34
|
+
DataGridSelectAllCell,
|
|
35
|
+
DataGridSelectionCell
|
|
36
|
+
} from './components/datagrid'
|
|
27
37
|
import {
|
|
28
38
|
Card,
|
|
29
39
|
CardBody,
|
|
@@ -145,6 +155,14 @@ export {
|
|
|
145
155
|
ButtonToolbar,
|
|
146
156
|
Calendar,
|
|
147
157
|
RangeCalendar,
|
|
158
|
+
DataGrid,
|
|
159
|
+
DataGridBody,
|
|
160
|
+
DataGridCell,
|
|
161
|
+
DataGridColumn,
|
|
162
|
+
DataGridHeader,
|
|
163
|
+
DataGridRow,
|
|
164
|
+
DataGridSelectAllCell,
|
|
165
|
+
DataGridSelectionCell,
|
|
148
166
|
Card,
|
|
149
167
|
CardBody,
|
|
150
168
|
CardFooter,
|
|
@@ -274,3 +292,211 @@ export {
|
|
|
274
292
|
useToast,
|
|
275
293
|
Tooltip
|
|
276
294
|
}
|
|
295
|
+
|
|
296
|
+
// Type-only public surface. Kept as `export type ... from` re-exports (the same shape
|
|
297
|
+
// `src/hooks/index.ts` uses) rather than the import-then-name-in-the-export-block pattern the
|
|
298
|
+
// value exports above follow — 164 type names would otherwise need 164 more import lines for no
|
|
299
|
+
// added clarity. Every component folder's barrel already exports its own prop types; this block
|
|
300
|
+
// is what actually makes them reachable as `@chassis-ui/react` imports, so a consumer can write
|
|
301
|
+
// `interface MyButtonProps extends ButtonProps` instead of re-deriving it with
|
|
302
|
+
// `ComponentProps<typeof Button>`.
|
|
303
|
+
export type {
|
|
304
|
+
AccordionBodyProps,
|
|
305
|
+
AccordionHeaderProps,
|
|
306
|
+
AccordionItemDef,
|
|
307
|
+
AccordionItemProps,
|
|
308
|
+
AccordionProps
|
|
309
|
+
} from './components/accordion'
|
|
310
|
+
export type {
|
|
311
|
+
AutocompleteGroupProps,
|
|
312
|
+
AutocompleteItemProps,
|
|
313
|
+
AutocompleteProps
|
|
314
|
+
} from './components/autocomplete'
|
|
315
|
+
export type {
|
|
316
|
+
AvatarImageProps,
|
|
317
|
+
AvatarProps,
|
|
318
|
+
AvatarStackItemDef,
|
|
319
|
+
AvatarStackProps
|
|
320
|
+
} from './components/avatar'
|
|
321
|
+
export type { BadgeProps } from './components/badge'
|
|
322
|
+
export type {
|
|
323
|
+
BreadcrumbItemDef,
|
|
324
|
+
BreadcrumbItemProps,
|
|
325
|
+
BreadcrumbProps
|
|
326
|
+
} from './components/breadcrumb'
|
|
327
|
+
export type { ButtonProps } from './components/button'
|
|
328
|
+
export type { ButtonGroupProps, ButtonToolbarProps } from './components/button-group'
|
|
329
|
+
export type {
|
|
330
|
+
CalendarLabels,
|
|
331
|
+
CalendarMultipleProps,
|
|
332
|
+
CalendarProps,
|
|
333
|
+
CalendarSingleProps,
|
|
334
|
+
DateRangePreset,
|
|
335
|
+
RangeCalendarProps
|
|
336
|
+
} from './components/calendar'
|
|
337
|
+
export type {
|
|
338
|
+
CardBodyProps,
|
|
339
|
+
CardFooterProps,
|
|
340
|
+
CardGroupProps,
|
|
341
|
+
CardHeaderProps,
|
|
342
|
+
CardImageOverlayProps,
|
|
343
|
+
CardImageProps,
|
|
344
|
+
CardLinkProps,
|
|
345
|
+
CardProps,
|
|
346
|
+
CardSubtitleProps,
|
|
347
|
+
CardTextProps,
|
|
348
|
+
CardTitleProps
|
|
349
|
+
} from './components/card'
|
|
350
|
+
export type {
|
|
351
|
+
CarouselControlNextProps,
|
|
352
|
+
CarouselControlPrevProps,
|
|
353
|
+
CarouselEnds,
|
|
354
|
+
CarouselIndicatorsProps,
|
|
355
|
+
CarouselInnerProps,
|
|
356
|
+
CarouselItemProps,
|
|
357
|
+
CarouselOverlayProps,
|
|
358
|
+
CarouselPlayPauseProps,
|
|
359
|
+
CarouselProps,
|
|
360
|
+
CarouselSlideDetail,
|
|
361
|
+
CarouselTransition
|
|
362
|
+
} from './components/carousel'
|
|
363
|
+
export type { ButtonObject, CheckboxGroupProps, CheckboxProps } from './components/checkbox'
|
|
364
|
+
export type { ChipProps } from './components/chip'
|
|
365
|
+
export type { ChipInputProps } from './components/chip-input'
|
|
366
|
+
export type { CloseButtonProps } from './components/close-button'
|
|
367
|
+
export type { CollapseProps } from './components/collapse'
|
|
368
|
+
export type { ColorInputProps } from './components/color-input'
|
|
369
|
+
export type { ComboboxGroupProps, ComboboxItemProps, ComboboxProps } from './components/combobox'
|
|
370
|
+
export type {
|
|
371
|
+
DatePickerMultipleProps,
|
|
372
|
+
DatePickerProps,
|
|
373
|
+
DatePickerSingleProps,
|
|
374
|
+
DateRangePickerProps
|
|
375
|
+
} from './components/datepicker'
|
|
376
|
+
export type {
|
|
377
|
+
DrawerBodyProps,
|
|
378
|
+
DrawerFooterProps,
|
|
379
|
+
DrawerHeaderProps,
|
|
380
|
+
DrawerProps,
|
|
381
|
+
DrawerTitleProps
|
|
382
|
+
} from './components/drawer'
|
|
383
|
+
export type { FileInputProps } from './components/file-input'
|
|
384
|
+
export type { FlexProps } from './components/flex'
|
|
385
|
+
export type { FloatingInputProps } from './components/floating-input'
|
|
386
|
+
export type { FormFeedbackProps, FormHelpProps, FormLabelProps, FormProps } from './components/form'
|
|
387
|
+
export type { FormFieldProps } from './components/form-field'
|
|
388
|
+
export type {
|
|
389
|
+
ColProps,
|
|
390
|
+
ContainerProps,
|
|
391
|
+
GridItemLayout,
|
|
392
|
+
GridItemProps,
|
|
393
|
+
GridProps,
|
|
394
|
+
RowProps
|
|
395
|
+
} from './components/grid'
|
|
396
|
+
export type { IconProps } from './components/icon'
|
|
397
|
+
export type { InputAdornProps } from './components/input-adorn'
|
|
398
|
+
export type { InputGroupAddonProps, InputGroupProps } from './components/input-group'
|
|
399
|
+
export type { LinkProps } from './components/link'
|
|
400
|
+
export type { ListItemDef, ListItemProps, ListProps } from './components/list'
|
|
401
|
+
export type {
|
|
402
|
+
MenuAutoClose,
|
|
403
|
+
MenuDividerDef,
|
|
404
|
+
MenuDividerProps,
|
|
405
|
+
MenuFocusStrategy,
|
|
406
|
+
MenuHeaderDef,
|
|
407
|
+
MenuHeaderProps,
|
|
408
|
+
MenuItemDef,
|
|
409
|
+
MenuItemProps,
|
|
410
|
+
MenuItemsDef,
|
|
411
|
+
MenuListProps,
|
|
412
|
+
MenuProps,
|
|
413
|
+
MenuSubmenuBackProps,
|
|
414
|
+
MenuSubmenuProps,
|
|
415
|
+
MenuTextProps,
|
|
416
|
+
MenuToggleProps
|
|
417
|
+
} from './components/menu'
|
|
418
|
+
export type {
|
|
419
|
+
ModalBodyProps,
|
|
420
|
+
ModalFooterProps,
|
|
421
|
+
ModalHeaderProps,
|
|
422
|
+
ModalProps,
|
|
423
|
+
ModalTitleProps
|
|
424
|
+
} from './components/modal'
|
|
425
|
+
export type { NavItemDef, NavLinkProps, NavProps, NavTitleProps } from './components/nav'
|
|
426
|
+
export type {
|
|
427
|
+
NavbarBrandProps,
|
|
428
|
+
NavbarNavProps,
|
|
429
|
+
NavbarProps,
|
|
430
|
+
NavbarTextProps,
|
|
431
|
+
NavbarTogglerProps
|
|
432
|
+
} from './components/navbar'
|
|
433
|
+
export type {
|
|
434
|
+
NotificationContent,
|
|
435
|
+
NotificationIconProps,
|
|
436
|
+
NotificationProps,
|
|
437
|
+
NotificationStackProps,
|
|
438
|
+
NotificationTextProps,
|
|
439
|
+
NotificationTitleProps
|
|
440
|
+
} from './components/notification'
|
|
441
|
+
export type { OtpInputProps } from './components/otp-input'
|
|
442
|
+
export type { PaginationItemProps, PaginationProps } from './components/pagination'
|
|
443
|
+
export type { PasswordStrengthProps } from './components/password-strength'
|
|
444
|
+
export type { PlaceholderProps } from './components/placeholder'
|
|
445
|
+
export type { PopoverProps } from './components/popover'
|
|
446
|
+
export type { ProgressBarProps, ProgressProps } from './components/progress'
|
|
447
|
+
export type { RadioGroupProps, RadioProps } from './components/radio'
|
|
448
|
+
export type { RangeInputProps } from './components/range-input'
|
|
449
|
+
export type { SelectOptionDef, SelectProps } from './components/select'
|
|
450
|
+
export type { SkeletonLoaderProps, SkeletonProps } from './components/skeleton'
|
|
451
|
+
export type { SpinnerProps } from './components/spinner'
|
|
452
|
+
export type { StackProps } from './components/stack'
|
|
453
|
+
export type { StepperItemDef, StepperItemProps, StepperProps } from './components/stepper'
|
|
454
|
+
export type { SwitchProps } from './components/switch'
|
|
455
|
+
export type {
|
|
456
|
+
TableBodyProps,
|
|
457
|
+
TableCellProps,
|
|
458
|
+
TableColumnProps,
|
|
459
|
+
TableHeaderProps,
|
|
460
|
+
TableProps,
|
|
461
|
+
TableRowProps
|
|
462
|
+
} from './components/table'
|
|
463
|
+
export type { TabListProps, TabPanelProps, TabProps, TabsProps } from './components/tabs'
|
|
464
|
+
export type { TextInputProps } from './components/text-input'
|
|
465
|
+
export type { TextareaProps } from './components/textarea'
|
|
466
|
+
export type {
|
|
467
|
+
ToastBodyProps,
|
|
468
|
+
ToastContent,
|
|
469
|
+
ToastFooterProps,
|
|
470
|
+
ToastHeaderProps,
|
|
471
|
+
ToastIconProps,
|
|
472
|
+
ToastProps,
|
|
473
|
+
ToasterProps
|
|
474
|
+
} from './components/toast'
|
|
475
|
+
export type { TooltipProps } from './components/tooltip'
|
|
476
|
+
|
|
477
|
+
// Cross-cutting types that aren't owned by any one component folder: the scales and vocabularies
|
|
478
|
+
// the prop types above are built from. `types.ts`'s `FontFamily`/`FontWeight`/`FontSize`/
|
|
479
|
+
// `Placements`/`TextColor`/`Triggers` are deliberately not re-exported — nothing in this package
|
|
480
|
+
// references them, so publishing them would commit this package's public API to types it doesn't
|
|
481
|
+
// actually use.
|
|
482
|
+
export type {
|
|
483
|
+
Breakpoint,
|
|
484
|
+
ContextColor,
|
|
485
|
+
ContextStyle,
|
|
486
|
+
ExtendedSizing,
|
|
487
|
+
Shapes,
|
|
488
|
+
Sizing,
|
|
489
|
+
Spacing
|
|
490
|
+
} from './types'
|
|
491
|
+
// Backs the `placement` prop on `Menu`/`MenuSubmenu`/`Popover`/`Tooltip`. Lives in `utils/`
|
|
492
|
+
// rather than a component barrel because all four share it.
|
|
493
|
+
export type { Placement } from './utils/overlayPlacement'
|
|
494
|
+
// Result types for the five hooks exported as values above. Mirrors what `hooks/index.ts` itself
|
|
495
|
+
// exposes for them — the other hooks in that folder stay internal, so their types do too.
|
|
496
|
+
export type {
|
|
497
|
+
UseDrawerResult,
|
|
498
|
+
UseModalResult,
|
|
499
|
+
UseNotificationResult,
|
|
500
|
+
UsePaginationResult,
|
|
501
|
+
UseToastResult
|
|
502
|
+
} from './hooks'
|
package/src/types.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Breakpoints
|
|
3
3
|
*/
|
|
4
|
-
export type Breakpoint = '
|
|
4
|
+
export type Breakpoint = 'sm' | 'md' | 'lg' | 'xl' | '2xl'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Context colors
|
|
@@ -27,12 +27,12 @@ export type ContextStyle = 'basic' | 'solid' | 'outline' | 'smooth'
|
|
|
27
27
|
/**
|
|
28
28
|
* Component sizes
|
|
29
29
|
*/
|
|
30
|
-
export type Sizing = '
|
|
30
|
+
export type Sizing = 'sm' | 'md' | 'lg'
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Extended sizes
|
|
34
34
|
*/
|
|
35
|
-
export type ExtendedSizing = '
|
|
35
|
+
export type ExtendedSizing = '2xs' | 'xs' | Sizing | 'xl' | '2xl'
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* Spacing values. `SPACING` is the runtime source of truth — `Spacing` is derived from it so the
|
|
@@ -41,19 +41,19 @@ export type ExtendedSizing = '2xsmall' | 'xsmall' | Sizing | 'xlarge' | '2xlarge
|
|
|
41
41
|
*/
|
|
42
42
|
export const SPACING = [
|
|
43
43
|
'zero',
|
|
44
|
-
'
|
|
45
|
-
'
|
|
46
|
-
'
|
|
47
|
-
'
|
|
48
|
-
'
|
|
49
|
-
'
|
|
50
|
-
'
|
|
51
|
-
'
|
|
52
|
-
'
|
|
53
|
-
'
|
|
54
|
-
'
|
|
55
|
-
'
|
|
56
|
-
'
|
|
44
|
+
'4xs',
|
|
45
|
+
'3xs',
|
|
46
|
+
'2xs',
|
|
47
|
+
'xs',
|
|
48
|
+
'sm',
|
|
49
|
+
'md',
|
|
50
|
+
'lg',
|
|
51
|
+
'xl',
|
|
52
|
+
'2xl',
|
|
53
|
+
'3xl',
|
|
54
|
+
'4xl',
|
|
55
|
+
'5xl',
|
|
56
|
+
'6xl'
|
|
57
57
|
] as const
|
|
58
58
|
|
|
59
59
|
export type Spacing = (typeof SPACING)[number]
|
|
@@ -72,16 +72,16 @@ export type FontWeight = 'elegant' | 'normal' | 'strong' | 'mass'
|
|
|
72
72
|
* Font sizes
|
|
73
73
|
*/
|
|
74
74
|
export type FontSize =
|
|
75
|
-
| '
|
|
76
|
-
| '
|
|
77
|
-
| '
|
|
78
|
-
| '
|
|
79
|
-
| '
|
|
80
|
-
| '
|
|
81
|
-
| '
|
|
82
|
-
| '
|
|
83
|
-
| '
|
|
84
|
-
| '
|
|
75
|
+
| '2xs'
|
|
76
|
+
| 'xs'
|
|
77
|
+
| 'sm'
|
|
78
|
+
| 'md'
|
|
79
|
+
| 'lg'
|
|
80
|
+
| 'xl'
|
|
81
|
+
| '2xl'
|
|
82
|
+
| '3xl'
|
|
83
|
+
| '4xl'
|
|
84
|
+
| '5xl'
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
87
|
* Component placements
|
package/src/utils/breakpoints.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Breakpoint } from '../types'
|
|
|
2
2
|
|
|
3
3
|
// Mobile-first ascending order. Every responsive layout prop (`Flex`/`Stack`/`Row`/`Col`/
|
|
4
4
|
// `Skeleton`'s `responsive`) keys its overrides by these literal chassis-css breakpoint names.
|
|
5
|
-
export const BREAKPOINTS: Breakpoint[] = ['
|
|
5
|
+
export const BREAKPOINTS: Breakpoint[] = ['sm', 'md', 'lg', 'xl', '2xl']
|
|
6
6
|
|
|
7
7
|
// A column/width span: a track count, `'auto'` for a natural-width track, or `true` for the bare
|
|
8
8
|
// (no explicit width) case. Shared by `Col` and `Skeleton`, both of which map it to the same
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Development-only misuse warnings, in one place so every component reports misuse the same way
|
|
2
|
+
// and none of it reaches a consumer's production bundle.
|
|
3
|
+
//
|
|
4
|
+
// Two problems this fixes over calling `console.warn` directly, which is what every call site used
|
|
5
|
+
// to do:
|
|
6
|
+
//
|
|
7
|
+
// 1. **They fired in production.** Nothing in this package guarded on `NODE_ENV`, so every
|
|
8
|
+
// `console.warn` ran for end users of a consuming app. `process.env.NODE_ENV` is the idiom
|
|
9
|
+
// bundlers (Vite, webpack, Turbopack, Rollup) replace with a literal, so in a production build
|
|
10
|
+
// `IS_DEV` folds to `false` and no warning is ever emitted. Relying on that substitution adds
|
|
11
|
+
// no new constraint: `react-dom` — a peer dependency, so always present — already requires
|
|
12
|
+
// exactly the same replacement.
|
|
13
|
+
// 2. **They repeated.** Most call sites warn during render, so a warning re-fired on every
|
|
14
|
+
// re-render, and twice per render under StrictMode's double-invoke. De-duplicating on the
|
|
15
|
+
// message means a misuse is reported once and stays readable in the console.
|
|
16
|
+
//
|
|
17
|
+
// What this does *not* do is strip the message strings from the bundle. Measured with esbuild
|
|
18
|
+
// against the built `dist/index.js`: a `NODE_ENV=production` consumer bundle still contains them.
|
|
19
|
+
// `tsdown` minifies this package's own output first (`minify: true`), collapsing each function to
|
|
20
|
+
// a `IS_DEV && cond && ...` expression, and a consumer's minifier won't then inline that call to
|
|
21
|
+
// discover its arguments are droppable. Building unminified would let them drop, at the cost of
|
|
22
|
+
// shipping pretty-printed dist — not worth it for a few hundred gzipped bytes. The behavior that
|
|
23
|
+
// mattered (nothing logged in production, nothing repeated in development) is the part that's
|
|
24
|
+
// fixed; treat the strings as part of the bundle's fixed cost.
|
|
25
|
+
const IS_DEV = process.env.NODE_ENV !== 'production'
|
|
26
|
+
|
|
27
|
+
const warned = new Set<string>()
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Resets the de-duplication set. Test-only — the suite asserts on these warnings, and without a
|
|
31
|
+
* reset between tests the *second* test in a file to trigger the same message would see nothing.
|
|
32
|
+
* Called from a global `beforeEach` in `test/setup.ts`.
|
|
33
|
+
*/
|
|
34
|
+
export function resetDevWarnings(): void {
|
|
35
|
+
warned.clear()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Logs `message` once via `console.warn` when `condition` is true, in development builds only.
|
|
40
|
+
* Use for a recoverable misuse: the component still renders something sensible.
|
|
41
|
+
*/
|
|
42
|
+
export function devWarning(condition: boolean, message: string): void {
|
|
43
|
+
if (!IS_DEV) return
|
|
44
|
+
if (!condition || warned.has(message)) return
|
|
45
|
+
warned.add(message)
|
|
46
|
+
console.warn(message)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Logs `message` once via `console.error` when `condition` is true, in development builds only.
|
|
51
|
+
* Use for a misuse that leaves the component genuinely broken (e.g. a required prop missing).
|
|
52
|
+
*/
|
|
53
|
+
export function devError(condition: boolean, message: string): void {
|
|
54
|
+
if (!IS_DEV) return
|
|
55
|
+
if (!condition || warned.has(message)) return
|
|
56
|
+
warned.add(message)
|
|
57
|
+
console.error(message)
|
|
58
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether `element` renders right-to-left.
|
|
3
|
+
*
|
|
4
|
+
* Reads the *computed* direction rather than react-aria's `useLocale()`. The two are deliberately
|
|
5
|
+
* separate signals in this library (see the Internationalization docs page): `useLocale()` follows
|
|
6
|
+
* `I18nProvider`, and drives locale-dependent behavior like which calendar system to use;
|
|
7
|
+
* everything about visual layout — including which physical arrow key moves "forward" through a
|
|
8
|
+
* horizontally-laid-out control — follows the `dir` attribute, which is what `getComputedStyle`
|
|
9
|
+
* resolves here. A page can set one without the other, and for arrow-key navigation it's the
|
|
10
|
+
* rendered order the user sees that has to match.
|
|
11
|
+
*/
|
|
12
|
+
export function isRTL(element: Element): boolean {
|
|
13
|
+
return getComputedStyle(element).direction === 'rtl'
|
|
14
|
+
}
|
|
@@ -1,29 +1,23 @@
|
|
|
1
|
+
import { suppressFocusRing } from './suppressFocusRingGlobally'
|
|
2
|
+
|
|
1
3
|
// Safari shows a `:focus-visible` ring on an element focused via script even when the
|
|
2
4
|
// interaction that triggered the script was a real pointer click — unlike Chromium/Firefox,
|
|
3
5
|
// which correctly suppress it in that case. Force the ring off for a pointer-triggered redirect,
|
|
4
6
|
// then let normal focus-visible behavior resume the next time this element is genuinely
|
|
5
7
|
// (re)focused, e.g. via Tab.
|
|
8
|
+
//
|
|
9
|
+
// The suppress/restore cycle itself is `suppressFocusRing`'s, not reimplemented here: it owns the
|
|
10
|
+
// re-entrancy guard that keeps a second call before the element blurs from capturing the
|
|
11
|
+
// already-suppressed value as the one to restore (which left the ring off for good — reachable by
|
|
12
|
+
// clicking twice at the same disabled end of a `ends="stop"` carousel). `boxShadow` is suppressed
|
|
13
|
+
// alongside `outline` here, but not on the global pointerdown path, because a redirect can land
|
|
14
|
+
// on an element whose ring is painted that way.
|
|
6
15
|
export function focusRedirect(
|
|
7
16
|
el: HTMLElement | null,
|
|
8
17
|
viaPointer: boolean,
|
|
9
18
|
focusOptions?: FocusOptions
|
|
10
19
|
): void {
|
|
11
20
|
if (!el) return
|
|
12
|
-
if (
|
|
13
|
-
el.focus(focusOptions)
|
|
14
|
-
return
|
|
15
|
-
}
|
|
16
|
-
const prevOutline = el.style.outline
|
|
17
|
-
const prevBoxShadow = el.style.boxShadow
|
|
18
|
-
el.style.outline = 'none'
|
|
19
|
-
el.style.boxShadow = 'none'
|
|
21
|
+
if (viaPointer) suppressFocusRing(el, ['outline', 'boxShadow'])
|
|
20
22
|
el.focus(focusOptions)
|
|
21
|
-
el.addEventListener(
|
|
22
|
-
'blur',
|
|
23
|
-
() => {
|
|
24
|
-
el.style.outline = prevOutline
|
|
25
|
-
el.style.boxShadow = prevBoxShadow
|
|
26
|
-
},
|
|
27
|
-
{ once: true }
|
|
28
|
-
)
|
|
29
23
|
}
|
package/src/utils/polymorphic.ts
CHANGED
|
@@ -3,12 +3,29 @@ import {
|
|
|
3
3
|
ComponentPropsWithRef,
|
|
4
4
|
ElementType,
|
|
5
5
|
ForwardRefRenderFunction,
|
|
6
|
-
forwardRef
|
|
6
|
+
forwardRef,
|
|
7
|
+
Ref
|
|
7
8
|
} from 'react'
|
|
8
9
|
|
|
9
10
|
// Ref type inferred from a polymorphic component's currently-selected `component` element type.
|
|
10
11
|
export type PolymorphicRef<C extends ElementType> = ComponentPropsWithRef<C>['ref']
|
|
11
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Ref type for a polymorphic component that can also pick its element from a prop *other* than
|
|
15
|
+
* `component` — `Button`, `Chip`, `Avatar`, `NavbarBrand` and `PaginationItem` all render an
|
|
16
|
+
* `<a>` when given `href`, and `PaginationItem` a `<span>` when `active`, none of which moves `C`
|
|
17
|
+
* off its default. A plain `PolymorphicRef<C>` describes only the `component` branch, so a caller
|
|
18
|
+
* holding a ref for the element that actually gets rendered (`<Button href="/x" ref={anchorRef}>`)
|
|
19
|
+
* got a spurious type error while the runtime happily populated it.
|
|
20
|
+
*
|
|
21
|
+
* `Ref` over the *union* rather than a union of `Ref`s is what makes a
|
|
22
|
+
* `createRef<HTMLButtonElement | HTMLAnchorElement>()` assignable here: `RefObject` is invariant
|
|
23
|
+
* in its type argument, so `RefObject<A | B>` satisfies `Ref<A | B>` but neither `Ref<A>` nor
|
|
24
|
+
* `Ref<B>` on its own.
|
|
25
|
+
*/
|
|
26
|
+
export type PolymorphicRefWithFallback<C extends ElementType, Fallback extends HTMLElement> =
|
|
27
|
+
PolymorphicRef<C> | Ref<Fallback>
|
|
28
|
+
|
|
12
29
|
/**
|
|
13
30
|
* Props for a polymorphic component: `OwnProps` (which must declare `component?: C`) plus
|
|
14
31
|
* whatever props `C` itself accepts, minus any name already claimed by `OwnProps` so the two
|
|
@@ -20,10 +20,22 @@ const FOCUSABLE_SELECTOR = 'a[href], button, input, select, textarea, [tabindex]
|
|
|
20
20
|
|
|
21
21
|
const suppressed = new WeakSet<HTMLElement>()
|
|
22
22
|
|
|
23
|
+
// Which ring-painting style properties a given call suppresses. The global `pointerdown` path
|
|
24
|
+
// only ever needs `outline` (that's the whole of chassis-css's `:focus-visible` ring); a
|
|
25
|
+
// script-driven focus redirect also clears `boxShadow`, since an element it lands on may carry a
|
|
26
|
+
// ring painted that way — see `focusRedirect`.
|
|
27
|
+
export type SuppressibleStyle = 'outline' | 'boxShadow'
|
|
28
|
+
|
|
29
|
+
const DEFAULT_STYLES: readonly SuppressibleStyle[] = ['outline']
|
|
30
|
+
|
|
23
31
|
// Shared by the global `pointerdown` listener below and by call sites that move focus themselves
|
|
24
32
|
// via script (e.g. `RangeCalendar`'s hover-to-preview-a-range, which calls `element.focus()` from
|
|
25
|
-
// a `pointerenter` handler — outside the `pointerdown` this module otherwise listens for
|
|
26
|
-
|
|
33
|
+
// a `pointerenter` handler — outside the `pointerdown` this module otherwise listens for, and
|
|
34
|
+
// `focusRedirect`, which delegates here rather than reimplementing the save/restore cycle).
|
|
35
|
+
export function suppressFocusRing(
|
|
36
|
+
el: HTMLElement,
|
|
37
|
+
styles: readonly SuppressibleStyle[] = DEFAULT_STYLES
|
|
38
|
+
): void {
|
|
27
39
|
// chassis-css's `.form-input` placeholder deliberately grants its focus ring on `:focus-within`
|
|
28
40
|
// as well as `:focus-visible` (see `_form.scss`), unlike the `:focus-visible`-only policy this
|
|
29
41
|
// module otherwise mirrors for buttons/links/checks/etc. — a form field is meant to show its
|
|
@@ -33,17 +45,20 @@ export function suppressFocusRing(el: HTMLElement): void {
|
|
|
33
45
|
// an outline chassis-css wants visible the whole time the field is focused.
|
|
34
46
|
if (el.classList.contains('form-input')) return
|
|
35
47
|
// `suppressed` guards re-entrancy: a second call for the same element before it blurs (e.g. a
|
|
36
|
-
// rapid re-press,
|
|
37
|
-
//
|
|
48
|
+
// rapid re-press, the pointer re-entering a cell, or a repeated `focusRedirect` onto the same
|
|
49
|
+
// element) would otherwise stack a second blur listener whose "previous" value it captured
|
|
50
|
+
// *after* the first call already set the property to `none` — restoring `none` and leaving the
|
|
51
|
+
// ring permanently suppressed. One shared set across every caller, so a global-listener
|
|
52
|
+
// suppression and a `focusRedirect` one can't stack on each other either.
|
|
38
53
|
if (suppressed.has(el)) return
|
|
39
54
|
|
|
40
55
|
suppressed.add(el)
|
|
41
|
-
const
|
|
42
|
-
el.style
|
|
56
|
+
const restore = styles.map((style) => [style, el.style[style]] as const)
|
|
57
|
+
for (const [style] of restore) el.style[style] = 'none'
|
|
43
58
|
el.addEventListener(
|
|
44
59
|
'blur',
|
|
45
60
|
() => {
|
|
46
|
-
el.style
|
|
61
|
+
for (const [style, value] of restore) el.style[style] = value
|
|
47
62
|
suppressed.delete(el)
|
|
48
63
|
},
|
|
49
64
|
{ once: true }
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { HTMLAttributes, ReactElement, Ref, version as reactVersion } from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `Popover`/`Tooltip` both take their trigger as a single `ReactElement` child and re-render it
|
|
5
|
+
* with `cloneElement`. A bare `ReactElement` types its props as `unknown`, which makes
|
|
6
|
+
* `cloneElement`'s config `Partial<unknown> & Attributes` — no `ref`, no event handlers, nothing
|
|
7
|
+
* to merge against. This is the narrowed view both components read the child through.
|
|
8
|
+
*
|
|
9
|
+
* Kept internal: each component's own public `children: ReactElement` stays as-is, so a caller
|
|
10
|
+
* can still pass any element without having to satisfy this shape.
|
|
11
|
+
*/
|
|
12
|
+
export type TriggerElementProps = HTMLAttributes<HTMLElement> & { ref?: Ref<HTMLElement> }
|
|
13
|
+
|
|
14
|
+
export type TriggerElement = ReactElement<TriggerElementProps>
|
|
15
|
+
|
|
16
|
+
export const asTriggerElement = (element: ReactElement): TriggerElement => element as TriggerElement
|
|
17
|
+
|
|
18
|
+
// React 19 turned `ref` into an ordinary prop; before that it lived on the element itself and was
|
|
19
|
+
// stripped out of `props`. React 19's development build installs a warning getter on
|
|
20
|
+
// `element.ref`, so this reads only the location that's correct for the React actually running
|
|
21
|
+
// rather than probing both and tripping that warning on every trigger that has no ref at all.
|
|
22
|
+
const REF_LIVES_IN_PROPS = parseInt(reactVersion, 10) >= 19
|
|
23
|
+
|
|
24
|
+
export const getTriggerRef = (element: TriggerElement): Ref<HTMLElement> | undefined =>
|
|
25
|
+
REF_LIVES_IN_PROPS ? element.props.ref : (element as { ref?: Ref<HTMLElement> }).ref
|