@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.
- 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 +32 -25
- 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 +30 -7
- 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
|
@@ -14,6 +14,7 @@ import { useControllableState, useForkedRef, useFormField } from '../../hooks'
|
|
|
14
14
|
import { validationClassName } from '../../utils/validationClassName'
|
|
15
15
|
import { renderFormField } from '../form-field/renderFormField'
|
|
16
16
|
import { OtpBox } from './OtpBox'
|
|
17
|
+
import { isRTL } from '../../utils/direction'
|
|
17
18
|
|
|
18
19
|
export interface OtpInputProps extends Omit<
|
|
19
20
|
HTMLAttributes<HTMLDivElement>,
|
|
@@ -101,9 +102,9 @@ export interface OtpInputProps extends Omit<
|
|
|
101
102
|
*/
|
|
102
103
|
separator?: ReactNode
|
|
103
104
|
/**
|
|
104
|
-
* Size the boxes
|
|
105
|
+
* Size the boxes sm or lg.
|
|
105
106
|
*/
|
|
106
|
-
size?: '
|
|
107
|
+
size?: 'sm' | 'lg'
|
|
107
108
|
/**
|
|
108
109
|
* Set component validation state to valid.
|
|
109
110
|
*/
|
|
@@ -218,17 +219,17 @@ export const OtpInput = forwardRef<HTMLDivElement, OtpInputProps>(
|
|
|
218
219
|
commit(nextBoxes)
|
|
219
220
|
break
|
|
220
221
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
break
|
|
227
|
-
}
|
|
222
|
+
// Arrow keys move by *visual* direction, so they mirror under RTL: the box to the left of
|
|
223
|
+
// the focused one is the next box, not the previous. `Carousel` and `MenuSubmenu` already
|
|
224
|
+
// mirror their own arrow handling this way; this used to hardcode the LTR mapping and sent
|
|
225
|
+
// an RTL user backwards through the field.
|
|
226
|
+
case 'ArrowLeft':
|
|
228
227
|
case 'ArrowRight': {
|
|
229
|
-
|
|
228
|
+
const movesForward = (event.key === 'ArrowRight') !== isRTL(event.currentTarget)
|
|
229
|
+
const nextIndex = movesForward ? index + 1 : index - 1
|
|
230
|
+
if (nextIndex >= 0 && nextIndex < total) {
|
|
230
231
|
event.preventDefault()
|
|
231
|
-
focusBox(
|
|
232
|
+
focusBox(nextIndex)
|
|
232
233
|
}
|
|
233
234
|
break
|
|
234
235
|
}
|
|
@@ -67,9 +67,9 @@ export interface PaginationProps extends HTMLAttributes<HTMLElement> {
|
|
|
67
67
|
*/
|
|
68
68
|
showPrevNext?: boolean
|
|
69
69
|
/**
|
|
70
|
-
* Size the component
|
|
70
|
+
* Size the component sm or lg.
|
|
71
71
|
*/
|
|
72
|
-
size?: '
|
|
72
|
+
size?: 'sm' | 'lg'
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
function getPageRange(activePage: number, pages: number, maxVisible: number): (number | '...')[] {
|
|
@@ -13,7 +13,8 @@ import { useButtonSemantics } 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 PaginationItemOwnProps<C extends ElementType> = {
|
|
@@ -41,7 +42,9 @@ export type PaginationItemProps<C extends ElementType = 'button'> = PolymorphicC
|
|
|
41
42
|
>
|
|
42
43
|
|
|
43
44
|
type PaginationItemComponent = (<C extends ElementType = 'button'>(
|
|
44
|
-
props: PaginationItemProps<C> & {
|
|
45
|
+
props: PaginationItemProps<C> & {
|
|
46
|
+
ref?: PolymorphicRefWithFallback<C, HTMLButtonElement | HTMLAnchorElement | HTMLSpanElement>
|
|
47
|
+
}
|
|
45
48
|
) => ReactElement | null) & { displayName?: string }
|
|
46
49
|
|
|
47
50
|
function PaginationItemRender<C extends ElementType = 'button'>(
|
|
@@ -66,7 +69,15 @@ function PaginationItemRender<C extends ElementType = 'button'>(
|
|
|
66
69
|
className
|
|
67
70
|
)
|
|
68
71
|
|
|
69
|
-
|
|
72
|
+
// The active page keeps whatever element it would render anyway (`<button>`, or `<a>` when
|
|
73
|
+
// given `href`) rather than collapsing to a `<span>`. Swapping the tag on activation made React
|
|
74
|
+
// unmount the focused control and mount a different element in its place, so a keyboard user who
|
|
75
|
+
// activated a page was dropped to `<body>` and had to tab in from the top of the document again
|
|
76
|
+
// (WCAG 2.4.3). The `<span>` wasn't even inert: `Pagination`'s smart mode passes an `onClick`,
|
|
77
|
+
// so `useButtonSemantics` gave it `role="button"` and `tabIndex={0}` — a synthetic button,
|
|
78
|
+
// strictly worse than the real one it replaced. Styling is unaffected either way: chassis-css
|
|
79
|
+
// matches `.pagination-link { &.active, .active > & }`, and `active` still lands on the `<li>`.
|
|
80
|
+
const Component = (component ?? (href ? 'a' : 'button')) as ElementType
|
|
70
81
|
|
|
71
82
|
// `<a>` has no real `disabled` attribute, so a disabled anchor pagination item still fires
|
|
72
83
|
// click (and still navigates) unless it's blocked here, same guard `Button` applies.
|
|
@@ -86,8 +97,13 @@ function PaginationItemRender<C extends ElementType = 'button'>(
|
|
|
86
97
|
onClick: onClick as MouseEventHandler<HTMLElement> | undefined
|
|
87
98
|
})
|
|
88
99
|
|
|
100
|
+
// `aria-current="page"` goes on the control itself, not the wrapping `<li>` — that's where the
|
|
101
|
+
// WAI-ARIA pagination pattern puts it, and it's what a screen reader conveys when focus lands on
|
|
102
|
+
// the control. On the `<li>` it was attached to a presentational list item nothing focuses.
|
|
103
|
+
const currentProps = active ? { 'aria-current': 'page' as const } : {}
|
|
104
|
+
|
|
89
105
|
return (
|
|
90
|
-
<li className={_className}
|
|
106
|
+
<li className={_className}>
|
|
91
107
|
{Component === 'button' ? (
|
|
92
108
|
<button
|
|
93
109
|
{...(rest as Record<string, unknown>)}
|
|
@@ -95,6 +111,7 @@ function PaginationItemRender<C extends ElementType = 'button'>(
|
|
|
95
111
|
type="button"
|
|
96
112
|
disabled={disabled}
|
|
97
113
|
onClick={handleClick}
|
|
114
|
+
{...currentProps}
|
|
98
115
|
ref={ref as Ref<HTMLButtonElement>}
|
|
99
116
|
>
|
|
100
117
|
{children}
|
|
@@ -106,6 +123,7 @@ function PaginationItemRender<C extends ElementType = 'button'>(
|
|
|
106
123
|
href={href}
|
|
107
124
|
onClick={handleClick}
|
|
108
125
|
{...(disabled && { 'aria-disabled': true, tabIndex: -1 })}
|
|
126
|
+
{...currentProps}
|
|
109
127
|
ref={ref as Ref<HTMLAnchorElement>}
|
|
110
128
|
>
|
|
111
129
|
{children}
|
|
@@ -118,6 +136,7 @@ function PaginationItemRender<C extends ElementType = 'button'>(
|
|
|
118
136
|
unknown
|
|
119
137
|
>)}
|
|
120
138
|
{...(!needsButtonSemantics && { onClick: handleClick })}
|
|
139
|
+
{...currentProps}
|
|
121
140
|
ref={needsButtonSemantics ? forkedRef : ref}
|
|
122
141
|
>
|
|
123
142
|
{children}
|
|
@@ -4,6 +4,7 @@ import React, {
|
|
|
4
4
|
ReactElement,
|
|
5
5
|
ReactNode,
|
|
6
6
|
RefObject,
|
|
7
|
+
useCallback,
|
|
7
8
|
useEffect,
|
|
8
9
|
useRef
|
|
9
10
|
} from 'react'
|
|
@@ -13,13 +14,19 @@ import { mergeProps, useDialog, useOverlayPosition, useOverlayTrigger } from 're
|
|
|
13
14
|
import { useOverlayTriggerState } from 'react-stately'
|
|
14
15
|
import { Transition } from 'react-transition-group'
|
|
15
16
|
|
|
16
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
getOverlayArrowStyle,
|
|
19
|
+
getOverlayTransitionClass,
|
|
20
|
+
useFloatingOverlay,
|
|
21
|
+
useForkedRef
|
|
22
|
+
} from '../../hooks'
|
|
17
23
|
import { Placement, resolveDataPlacement, toAriaPlacement } from '../../utils/overlayPlacement'
|
|
24
|
+
import { asTriggerElement, getTriggerRef } from '../../utils/triggerElement'
|
|
18
25
|
|
|
19
26
|
interface PopoverPanelProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title' | 'content'> {
|
|
20
27
|
arrowProps: HTMLAttributes<HTMLDivElement>
|
|
21
28
|
content: ReactNode | string
|
|
22
|
-
overlayRef: RefObject<HTMLDivElement>
|
|
29
|
+
overlayRef: RefObject<HTMLDivElement | null>
|
|
23
30
|
overlayTriggerProps: HTMLAttributes<HTMLDivElement>
|
|
24
31
|
title?: ReactNode | string
|
|
25
32
|
}
|
|
@@ -103,6 +110,16 @@ export const Popover: FC<PopoverProps> = ({
|
|
|
103
110
|
const triggerRef = useRef<HTMLElement | null>(null)
|
|
104
111
|
const floatingRef = useRef<HTMLDivElement>(null)
|
|
105
112
|
|
|
113
|
+
// `cloneElement`'s config replaces the child's own `ref` outright rather than merging with it,
|
|
114
|
+
// so capturing the trigger node has to be forked with whatever ref the caller already put on
|
|
115
|
+
// that child — otherwise `<Popover><Button ref={mine} /></Popover>` silently never populates
|
|
116
|
+
// `mine`.
|
|
117
|
+
const setTriggerRef = useCallback((node: HTMLElement | null) => {
|
|
118
|
+
triggerRef.current = node
|
|
119
|
+
}, [])
|
|
120
|
+
const triggerElement = asTriggerElement(children)
|
|
121
|
+
const forkedTriggerRef = useForkedRef<HTMLElement>(setTriggerRef, getTriggerRef(triggerElement))
|
|
122
|
+
|
|
106
123
|
const state = useOverlayTriggerState({ defaultOpen: !!visible })
|
|
107
124
|
const {
|
|
108
125
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -189,15 +206,12 @@ export const Popover: FC<PopoverProps> = ({
|
|
|
189
206
|
|
|
190
207
|
return (
|
|
191
208
|
<>
|
|
192
|
-
{
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
...triggerProps,
|
|
197
|
-
|
|
198
|
-
children.props.onClick?.(event)
|
|
199
|
-
state.toggle()
|
|
200
|
-
}
|
|
209
|
+
{/* `mergeProps` chains the child's own `onClick` ahead of this one, so this no longer
|
|
210
|
+
calls `children.props.onClick` itself the way it did when it spread `triggerProps`
|
|
211
|
+
raw — doing both would fire the caller's handler twice per click. */}
|
|
212
|
+
{React.cloneElement(triggerElement, {
|
|
213
|
+
...mergeProps(triggerElement.props, triggerProps, { onClick: () => state.toggle() }),
|
|
214
|
+
ref: forkedTriggerRef
|
|
201
215
|
})}
|
|
202
216
|
{typeof window !== 'undefined' &&
|
|
203
217
|
createPortal(
|
|
@@ -207,9 +221,21 @@ export const Popover: FC<PopoverProps> = ({
|
|
|
207
221
|
nodeRef={floatingRef}
|
|
208
222
|
onExited={() => {
|
|
209
223
|
const trigger = triggerRef.current
|
|
210
|
-
if (trigger
|
|
211
|
-
|
|
212
|
-
|
|
224
|
+
if (!trigger || !document.contains(trigger)) return
|
|
225
|
+
// Only reclaim focus when the popover still owns it, or when nothing does.
|
|
226
|
+
// `useDialog` moves focus into the panel on open, so the panel still holds it here
|
|
227
|
+
// for an Escape/inside close (`onExited` runs before `unmountOnExit` detaches the
|
|
228
|
+
// node, so this reads the pre-removal state); an outside click on plain page
|
|
229
|
+
// content instead leaves focus loose on `<body>`. Both are worth restoring. Any
|
|
230
|
+
// other active element means the user deliberately put focus there — dismissing by
|
|
231
|
+
// clicking another control, or the app closing the popover via `visible` while
|
|
232
|
+
// they were typing somewhere else — and this used to steal it straight back off
|
|
233
|
+
// them. Mirrors react-aria's own `shouldRestoreFocus` condition.
|
|
234
|
+
const active = document.activeElement
|
|
235
|
+
const focusIsLoose = !active || active === document.body
|
|
236
|
+
const focusIsInPanel = !!active && !!floatingRef.current?.contains(active)
|
|
237
|
+
if (!focusIsLoose && !focusIsInPanel) return
|
|
238
|
+
trigger.focus()
|
|
213
239
|
}}
|
|
214
240
|
timeout={{
|
|
215
241
|
enter: 0,
|
|
@@ -132,7 +132,7 @@ function ProgressRender<C extends ElementType = 'div'>(
|
|
|
132
132
|
if (!hasCaption) return progressElement
|
|
133
133
|
|
|
134
134
|
return (
|
|
135
|
-
<div className="d-flex flex-column gap-
|
|
135
|
+
<div className="d-flex flex-column gap-xs">
|
|
136
136
|
<div
|
|
137
137
|
className={classNames('d-flex', label ? 'justify-content-between' : 'justify-content-end')}
|
|
138
138
|
>
|
|
@@ -34,9 +34,9 @@ export interface RadioProps extends Omit<
|
|
|
34
34
|
*/
|
|
35
35
|
label?: ReactNode
|
|
36
36
|
/**
|
|
37
|
-
* Size the component
|
|
37
|
+
* Size the component sm or lg.
|
|
38
38
|
*/
|
|
39
|
-
size?: '
|
|
39
|
+
size?: 'sm' | 'lg'
|
|
40
40
|
/**
|
|
41
41
|
* The value of the radio button, used to identify it within its `<RadioGroup>`.
|
|
42
42
|
*/
|
|
@@ -127,7 +127,7 @@ export const RadioGroup = forwardRef<HTMLFieldSetElement, RadioGroupProps>(
|
|
|
127
127
|
{label}
|
|
128
128
|
</legend>
|
|
129
129
|
)}
|
|
130
|
-
{orientation === 'horizontal' ? <Flex gap="
|
|
130
|
+
{orientation === 'horizontal' ? <Flex gap="md">{items}</Flex> : items}
|
|
131
131
|
{description && <FormHelp {...descriptionProps}>{description}</FormHelp>}
|
|
132
132
|
{invalid && errorMessage && (
|
|
133
133
|
<FormFeedback invalid {...errorMessageProps}>
|
|
@@ -11,6 +11,7 @@ import classNames from 'classnames'
|
|
|
11
11
|
import { useForkedRef, useFormField } from '../../hooks'
|
|
12
12
|
import { validationClassName } from '../../utils/validationClassName'
|
|
13
13
|
import { renderFormField } from '../form-field/renderFormField'
|
|
14
|
+
import { devWarning } from '../../utils/devWarning'
|
|
14
15
|
|
|
15
16
|
export interface SelectOptionDef {
|
|
16
17
|
/**
|
|
@@ -98,9 +99,9 @@ export interface SelectProps extends Omit<InputHTMLAttributes<HTMLSelectElement>
|
|
|
98
99
|
*/
|
|
99
100
|
placeholder?: string
|
|
100
101
|
/**
|
|
101
|
-
* Size the component
|
|
102
|
+
* Size the component sm or lg.
|
|
102
103
|
*/
|
|
103
|
-
size?: '
|
|
104
|
+
size?: 'sm' | 'lg'
|
|
104
105
|
/**
|
|
105
106
|
* Set component validation state to valid.
|
|
106
107
|
*/
|
|
@@ -151,12 +152,11 @@ export const Select = forwardRef<HTMLSelectElement, SelectProps>(
|
|
|
151
152
|
.map((option) => String(option.value ?? ''))
|
|
152
153
|
: []
|
|
153
154
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
155
|
+
devWarning(
|
|
156
|
+
!multiple && selectedValues.length > 1,
|
|
157
|
+
'Select: more than one option has `selected: true` but `multiple` is not set — only ' +
|
|
158
|
+
'the last one will be selected, matching native <select> behavior.'
|
|
159
|
+
)
|
|
160
160
|
|
|
161
161
|
// React warns against setting `selected` directly on <option>, recommending `defaultValue`/
|
|
162
162
|
// `value` on <select> instead — so `options[].selected` is resolved into a `defaultValue`
|
|
@@ -44,7 +44,7 @@ type SkeletonOwnProps<C extends ElementType> = {
|
|
|
44
44
|
/**
|
|
45
45
|
* Overrides `span` at a breakpoint and up.
|
|
46
46
|
*
|
|
47
|
-
* @type { Partial<Record<'
|
|
47
|
+
* @type { Partial<Record<'sm' | 'md' | 'lg' | 'xl' | '2xl', 'auto' | number | string | boolean>> }
|
|
48
48
|
*/
|
|
49
49
|
responsive?: Partial<Record<Breakpoint, Span>>
|
|
50
50
|
}
|
|
@@ -22,9 +22,9 @@ type SpinnerOwnProps<C extends ElementType> = {
|
|
|
22
22
|
*/
|
|
23
23
|
component?: C
|
|
24
24
|
/**
|
|
25
|
-
* Size the component
|
|
25
|
+
* Size the component sm.
|
|
26
26
|
*/
|
|
27
|
-
size?: '
|
|
27
|
+
size?: 'sm'
|
|
28
28
|
/**
|
|
29
29
|
* Set the button variant to an outlined button or a ghost button.
|
|
30
30
|
*/
|
|
@@ -65,11 +65,11 @@ type StepperOwnProps<C extends ElementType> = {
|
|
|
65
65
|
*/
|
|
66
66
|
layout?:
|
|
67
67
|
| 'horizontal'
|
|
68
|
-
| '
|
|
69
|
-
| '
|
|
70
|
-
| '
|
|
71
|
-
| '
|
|
72
|
-
| '
|
|
68
|
+
| 'sm:horizontal'
|
|
69
|
+
| 'md:horizontal'
|
|
70
|
+
| 'lg:horizontal'
|
|
71
|
+
| 'xl:horizontal'
|
|
72
|
+
| '2xl:horizontal'
|
|
73
73
|
/**
|
|
74
74
|
* Wraps the stepper in a horizontally scrollable container so steps keep their natural width
|
|
75
75
|
* instead of shrinking to fit.
|
|
@@ -46,9 +46,9 @@ export interface SwitchProps extends Omit<
|
|
|
46
46
|
*/
|
|
47
47
|
onChange?: (isSelected: boolean) => void
|
|
48
48
|
/**
|
|
49
|
-
* Size the component
|
|
49
|
+
* Size the component sm or lg.
|
|
50
50
|
*/
|
|
51
|
-
size?: '
|
|
51
|
+
size?: 'sm' | 'lg'
|
|
52
52
|
/**
|
|
53
53
|
* Specifies the type of component.
|
|
54
54
|
*/
|
|
@@ -91,7 +91,7 @@ interface RenderSwitchInputOptions {
|
|
|
91
91
|
inputProps: InputHTMLAttributes<HTMLInputElement>
|
|
92
92
|
invalid?: boolean
|
|
93
93
|
label?: ReactNode
|
|
94
|
-
size?: '
|
|
94
|
+
size?: 'sm' | 'lg'
|
|
95
95
|
valid?: boolean
|
|
96
96
|
}
|
|
97
97
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
.table-sort-icon {
|
|
11
11
|
display: inline-block;
|
|
12
|
-
margin-inline-start: var(--cx-space-
|
|
12
|
+
margin-inline-start: var(--cx-space-2xs);
|
|
13
13
|
font-size: .75em;
|
|
14
14
|
opacity: .6;
|
|
15
15
|
}
|
|
@@ -34,6 +34,6 @@
|
|
|
34
34
|
.table [role="gridcell"]:focus-visible,
|
|
35
35
|
.table [role="columnheader"]:focus-visible,
|
|
36
36
|
.table [role="rowheader"]:focus-visible {
|
|
37
|
-
outline: var(--cx-border-width-
|
|
37
|
+
outline: var(--cx-border-width-xl) solid var(--cx-primary);
|
|
38
38
|
outline-offset: -2px;
|
|
39
39
|
}
|
|
@@ -103,7 +103,7 @@ export interface TableProps<T extends object> {
|
|
|
103
103
|
/**
|
|
104
104
|
* Make any table responsive across all viewports or pick a maximum breakpoint.
|
|
105
105
|
*/
|
|
106
|
-
responsive?: boolean | '
|
|
106
|
+
responsive?: boolean | 'sm' | 'md' | 'lg' | 'xl' | '2xl'
|
|
107
107
|
/**
|
|
108
108
|
* The currently selected row keys (controlled).
|
|
109
109
|
*/
|
|
@@ -115,7 +115,7 @@ export interface TableProps<T extends object> {
|
|
|
115
115
|
/**
|
|
116
116
|
* Make table more compact by cutting all cell padding.
|
|
117
117
|
*/
|
|
118
|
-
|
|
118
|
+
sm?: boolean
|
|
119
119
|
/**
|
|
120
120
|
* The current sort column and direction.
|
|
121
121
|
*/
|
|
@@ -127,7 +127,7 @@ export interface TableProps<T extends object> {
|
|
|
127
127
|
* separately, since stacking needs the same `.table-responsive` container-query ancestor.
|
|
128
128
|
* Labels come from each `TableColumn`'s text (or its `textValue`, for non-text headers).
|
|
129
129
|
*/
|
|
130
|
-
stacked?: boolean | '
|
|
130
|
+
stacked?: boolean | 'sm' | 'md' | 'lg' | 'xl' | '2xl'
|
|
131
131
|
/**
|
|
132
132
|
* Add zebra-striping to table rows.
|
|
133
133
|
*/
|
|
@@ -152,7 +152,7 @@ const TableInner = <T extends object>(
|
|
|
152
152
|
responsive,
|
|
153
153
|
selectedKeys,
|
|
154
154
|
selectionMode = 'none',
|
|
155
|
-
|
|
155
|
+
sm,
|
|
156
156
|
sortDescriptor,
|
|
157
157
|
stacked,
|
|
158
158
|
striped,
|
|
@@ -187,7 +187,7 @@ const TableInner = <T extends object>(
|
|
|
187
187
|
bordered,
|
|
188
188
|
borderless,
|
|
189
189
|
hoverable: hover,
|
|
190
|
-
|
|
190
|
+
sm,
|
|
191
191
|
stacked: stacked === true,
|
|
192
192
|
striped
|
|
193
193
|
},
|
|
@@ -296,6 +296,12 @@ const TableColumnHeader = <T extends object>({ column, state }: TableColumnHeade
|
|
|
296
296
|
{...columnHeaderProps}
|
|
297
297
|
className={column.props?.className || undefined}
|
|
298
298
|
colSpan={column.colSpan ?? undefined}
|
|
299
|
+
// react-aria's `useTableColumnHeader` supplies `role="columnheader"` for the ARIA grid
|
|
300
|
+
// pattern but no `scope`, so the native table semantics were left ambiguous — a `<th>` with
|
|
301
|
+
// no `scope` fails WCAG H63, and assistive technology reading the table natively (rather
|
|
302
|
+
// than through the grid roles) has to guess whether the header applies to its column or its
|
|
303
|
+
// row. These are always column headers; the row-header case doesn't exist in this API.
|
|
304
|
+
scope="col"
|
|
299
305
|
ref={ref}
|
|
300
306
|
>
|
|
301
307
|
{column.rendered}
|
|
@@ -319,7 +325,22 @@ const TableSelectAllCell = <T extends object>({ column, state }: TableSelectAllC
|
|
|
319
325
|
const { checkboxProps } = useTableSelectAllCheckbox(state)
|
|
320
326
|
|
|
321
327
|
return (
|
|
322
|
-
<th
|
|
328
|
+
<th
|
|
329
|
+
{...columnHeaderProps}
|
|
330
|
+
className="table-selection-cell"
|
|
331
|
+
scope="col"
|
|
332
|
+
// react-stately's internal "select all" column key is a module-scoped
|
|
333
|
+
// `'row-header-column-' + Math.random()` constant (@react-stately/table's TableCollection),
|
|
334
|
+
// computed once per JS environment rather than per render. Under SSR this makes `id`/
|
|
335
|
+
// `data-key` here differ between the server module instance and the browser's, on every
|
|
336
|
+
// request — a real react-stately bug, not something this component can influence (the key
|
|
337
|
+
// never reaches a public prop) — see README.md's Table/DataGrid known-issue section for the
|
|
338
|
+
// upstream write-up. The mismatch is confined to this internal bookkeeping attribute (no
|
|
339
|
+
// visible content, nothing else references this id), so suppressing the warning is safe
|
|
340
|
+
// rather than papering over a real functional break.
|
|
341
|
+
suppressHydrationWarning
|
|
342
|
+
ref={ref}
|
|
343
|
+
>
|
|
323
344
|
<TableCheckbox checkboxProps={checkboxProps} />
|
|
324
345
|
</th>
|
|
325
346
|
)
|
|
@@ -375,7 +396,9 @@ const TableSelectionCell = <T extends object>({ cell, state }: TableCellProps<T>
|
|
|
375
396
|
const { checkboxProps } = useTableSelectionCheckbox({ key: cell.parentKey! }, state)
|
|
376
397
|
|
|
377
398
|
return (
|
|
378
|
-
|
|
399
|
+
// See `TableSelectAllCell`'s comment: this cell's `id`/`data-key` derive from the same
|
|
400
|
+
// module-scoped random column key and mismatch between server and client under SSR.
|
|
401
|
+
<td {...gridCellProps} className="table-selection-cell" suppressHydrationWarning ref={ref}>
|
|
379
402
|
<TableCheckbox checkboxProps={checkboxProps} />
|
|
380
403
|
</td>
|
|
381
404
|
)
|
|
@@ -63,9 +63,9 @@ export interface TextInputProps extends Omit<
|
|
|
63
63
|
*/
|
|
64
64
|
readOnly?: boolean
|
|
65
65
|
/**
|
|
66
|
-
* Size the component
|
|
66
|
+
* Size the component sm or lg.
|
|
67
67
|
*/
|
|
68
|
-
size?: '
|
|
68
|
+
size?: 'sm' | 'lg'
|
|
69
69
|
/**
|
|
70
70
|
* Specifies the type of component. For `color` or `file` inputs, use the dedicated `ColorInput` or `FileInput` components instead.
|
|
71
71
|
*/
|
|
@@ -55,9 +55,9 @@ export interface TextareaProps extends Omit<
|
|
|
55
55
|
*/
|
|
56
56
|
rows?: number
|
|
57
57
|
/**
|
|
58
|
-
* Size the component
|
|
58
|
+
* Size the component sm or lg.
|
|
59
59
|
*/
|
|
60
|
-
size?: '
|
|
60
|
+
size?: 'sm' | 'lg'
|
|
61
61
|
/**
|
|
62
62
|
* Set component validation state to valid.
|
|
63
63
|
*/
|
|
@@ -47,7 +47,7 @@ function ToastBodyRender<C extends ElementType = 'div'>(
|
|
|
47
47
|
const Component = component ?? 'div'
|
|
48
48
|
const _className = classNames(
|
|
49
49
|
'toast-body',
|
|
50
|
-
{ 'd-flex align-items-start justify-content-between gap-
|
|
50
|
+
{ 'd-flex align-items-start justify-content-between gap-sm': closeButton },
|
|
51
51
|
className
|
|
52
52
|
)
|
|
53
53
|
return (
|
|
@@ -81,11 +81,11 @@ function ToastHeaderRender<C extends ElementType = 'div'>(
|
|
|
81
81
|
<Component className={_className} {...rest} ref={ref}>
|
|
82
82
|
{icon != null &&
|
|
83
83
|
(typeof icon === 'string' ? (
|
|
84
|
-
<span aria-hidden="true" className="me-
|
|
84
|
+
<span aria-hidden="true" className="me-sm">
|
|
85
85
|
<ToastIcon name={icon} />
|
|
86
86
|
</span>
|
|
87
87
|
) : (
|
|
88
|
-
<span className="me-
|
|
88
|
+
<span className="me-sm">{icon}</span>
|
|
89
89
|
))}
|
|
90
90
|
{children != null && (
|
|
91
91
|
<strong id={titleId} className="me-auto">
|
|
@@ -43,7 +43,7 @@ export const Toaster = forwardRef<HTMLDivElement, ToasterProps>(
|
|
|
43
43
|
const { forkedRef, regionProps, state } = useToastRegionQueue(toastQueue, ref)
|
|
44
44
|
|
|
45
45
|
const _className = classNames(
|
|
46
|
-
'toaster toast-container p-
|
|
46
|
+
'toaster toast-container p-md',
|
|
47
47
|
{
|
|
48
48
|
'position-fixed': placement,
|
|
49
49
|
'position-static': !placement,
|
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
import React, { FC, ReactElement, ReactNode, useRef } from 'react'
|
|
1
|
+
import React, { FC, ReactElement, ReactNode, useCallback, useRef } from 'react'
|
|
2
2
|
import { createPortal } from 'react-dom'
|
|
3
3
|
import classNames from 'classnames'
|
|
4
4
|
import { mergeProps, useOverlayPosition, useTooltip, useTooltipTrigger } from 'react-aria'
|
|
5
5
|
import { useTooltipTriggerState } from 'react-stately'
|
|
6
6
|
import { Transition } from 'react-transition-group'
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
getOverlayArrowStyle,
|
|
10
|
+
getOverlayTransitionClass,
|
|
11
|
+
useFloatingOverlay,
|
|
12
|
+
useForkedRef
|
|
13
|
+
} from '../../hooks'
|
|
9
14
|
import { Placement, resolveDataPlacement, toAriaPlacement } from '../../utils/overlayPlacement'
|
|
15
|
+
import { asTriggerElement, getTriggerRef } from '../../utils/triggerElement'
|
|
10
16
|
|
|
11
17
|
export type { Placement }
|
|
12
18
|
|
|
@@ -59,6 +65,18 @@ export const Tooltip: FC<TooltipProps> = ({
|
|
|
59
65
|
const triggerRef = useRef<HTMLElement | null>(null)
|
|
60
66
|
const floatingRef = useRef<HTMLDivElement>(null)
|
|
61
67
|
|
|
68
|
+
// `cloneElement`'s config replaces the child's own `ref` outright rather than merging with it,
|
|
69
|
+
// so capturing the trigger node has to be forked with whatever ref the caller already put on
|
|
70
|
+
// that child — otherwise `<Tooltip><Button ref={mine} /></Tooltip>` silently never populates
|
|
71
|
+
// `mine`. Same reasoning for merging (rather than overwriting) the child's own props below:
|
|
72
|
+
// `triggerProps` carries the focus/hover handlers this component needs, and spreading them raw
|
|
73
|
+
// would drop a handler the caller put on their own trigger.
|
|
74
|
+
const setTriggerRef = useCallback((node: HTMLElement | null) => {
|
|
75
|
+
triggerRef.current = node
|
|
76
|
+
}, [])
|
|
77
|
+
const triggerElement = asTriggerElement(children)
|
|
78
|
+
const forkedTriggerRef = useForkedRef<HTMLElement>(setTriggerRef, getTriggerRef(triggerElement))
|
|
79
|
+
|
|
62
80
|
// react-stately defaults to a 1500ms warmup delay (and 500ms cooldown) before a first tooltip
|
|
63
81
|
// shows, spectrum-style — chassis-css's own JS plugin defaults to instant (`delay: 0`), so
|
|
64
82
|
// match that here rather than leaving new adopters to wonder why the first hover lags.
|
|
@@ -107,11 +125,9 @@ export const Tooltip: FC<TooltipProps> = ({
|
|
|
107
125
|
|
|
108
126
|
return (
|
|
109
127
|
<>
|
|
110
|
-
{React.cloneElement(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
},
|
|
114
|
-
...triggerProps
|
|
128
|
+
{React.cloneElement(triggerElement, {
|
|
129
|
+
...mergeProps(triggerElement.props, triggerProps),
|
|
130
|
+
ref: forkedTriggerRef
|
|
115
131
|
})}
|
|
116
132
|
{typeof window !== 'undefined' &&
|
|
117
133
|
createPortal(
|
|
@@ -36,7 +36,10 @@ export function useAutoDismiss({
|
|
|
36
36
|
visible,
|
|
37
37
|
onHide
|
|
38
38
|
}: UseAutoDismissOptions): UseAutoDismissResult {
|
|
39
|
-
|
|
39
|
+
// `useRef<number>()` (no argument) isn't valid under @types/react 19 — the overload requires
|
|
40
|
+
// an initial value, and the ref has to admit `undefined` for `clearAutoDismissTimeout` to reset
|
|
41
|
+
// it back to "no timer scheduled".
|
|
42
|
+
const timeout = useRef<number | undefined>(undefined)
|
|
40
43
|
const hasMouseInteraction = useRef(false)
|
|
41
44
|
const hasKeyboardInteraction = useRef(false)
|
|
42
45
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ForwardedRef,
|
|
3
3
|
MouseEvent as ReactMouseEvent,
|
|
4
|
+
MouseEventHandler,
|
|
4
5
|
SyntheticEvent,
|
|
5
6
|
useEffect,
|
|
6
7
|
useRef,
|
|
@@ -38,6 +39,13 @@ export interface UseDialogElementOptions {
|
|
|
38
39
|
* show effect, used to auto-close every other open drawer first.
|
|
39
40
|
*/
|
|
40
41
|
onBeforeShow?: (dialog: HTMLDialogElement) => void
|
|
42
|
+
/**
|
|
43
|
+
* The caller's own `onClick`, chained ahead of the backdrop-click handling below rather than
|
|
44
|
+
* left in the props spread — a `<dialog>` needs `onClick` for its own backdrop detection, so
|
|
45
|
+
* spreading the caller's alongside it silently dropped one of the two. Fires for every click
|
|
46
|
+
* on the dialog, not just backdrop ones.
|
|
47
|
+
*/
|
|
48
|
+
onClick?: MouseEventHandler<HTMLDialogElement>
|
|
41
49
|
onClose?: () => void
|
|
42
50
|
onClosePrevented?: () => void
|
|
43
51
|
onHidden?: () => void
|
|
@@ -62,6 +70,7 @@ export const useDialogElement = ({
|
|
|
62
70
|
isModal,
|
|
63
71
|
keyboard = true,
|
|
64
72
|
onBeforeShow,
|
|
73
|
+
onClick,
|
|
65
74
|
onClose,
|
|
66
75
|
onClosePrevented,
|
|
67
76
|
onHidden,
|
|
@@ -204,6 +213,7 @@ export const useDialogElement = ({
|
|
|
204
213
|
}
|
|
205
214
|
|
|
206
215
|
const handleBackdropClick = (event: ReactMouseEvent<HTMLDialogElement>) => {
|
|
216
|
+
onClick?.(event)
|
|
207
217
|
if (event.target !== dialogRef.current || !openedAsModalRef.current) return
|
|
208
218
|
if (backdrop === 'static') {
|
|
209
219
|
triggerStaticBounce()
|