atlas-react-kit 1.2.0 → 1.2.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/COMPONENTS.md +40 -40
- package/DESIGN_RULES.md +14 -40
- package/README.md +7 -6
- package/dist/alert.js +7 -6
- package/dist/calendar.js +5 -4
- package/dist/card.js +2 -1
- package/dist/combobox.d.ts +4 -3
- package/dist/combobox.js +8 -17
- package/dist/date-time-picker.js +2 -1
- package/dist/dialog.js +2 -1
- package/dist/dropdown-menu.js +7 -5
- package/dist/field.d.ts +6 -3
- package/dist/field.js +3 -2
- package/dist/form/checkbox-field.d.ts +3 -5
- package/dist/form/checkbox-field.js +13 -19
- package/dist/form/combobox-field.d.ts +2 -2
- package/dist/form/combobox-field.js +5 -6
- package/dist/form/control-field.d.ts +5 -5
- package/dist/form/control-field.js +6 -9
- package/dist/form/date-picker-field.d.ts +1 -1
- package/dist/form/date-picker-field.js +3 -7
- package/dist/form/date-time-picker-field.d.ts +1 -1
- package/dist/form/date-time-picker-field.js +3 -7
- package/dist/form/field-error.d.ts +13 -4
- package/dist/form/field-shell.d.ts +63 -47
- package/dist/form/field-shell.js +67 -35
- package/dist/form/focus-first-invalid.d.ts +7 -4
- package/dist/form/focus-first-invalid.js +10 -6
- package/dist/form/markdown-field.d.ts +1 -1
- package/dist/form/markdown-field.js +8 -4
- package/dist/form/select-field.d.ts +2 -8
- package/dist/form/select-field.js +5 -7
- package/dist/form/text-field.d.ts +2 -8
- package/dist/form/text-field.js +9 -6
- package/dist/form/toggle-group-field.d.ts +2 -2
- package/dist/form/toggle-group-field.js +5 -6
- package/dist/info-tip.d.ts +7 -4
- package/dist/info-tip.js +12 -4
- package/dist/internal/active-link.d.ts +7 -0
- package/dist/internal/active-link.js +9 -0
- package/dist/internal/calendar-size.d.ts +5 -0
- package/dist/internal/calendar-size.js +5 -0
- package/dist/internal/form-control.d.ts +7 -1
- package/dist/internal/form-control.js +8 -1
- package/dist/internal/kanban-layout.js +2 -1
- package/dist/internal/listbox-navigation.d.ts +8 -8
- package/dist/internal/listbox-navigation.js +23 -23
- package/dist/internal/listbox-option.d.ts +16 -3
- package/dist/internal/listbox-option.js +22 -5
- package/dist/internal/overlay-styles.d.ts +15 -5
- package/dist/internal/overlay-styles.js +15 -5
- package/dist/internal/search.d.ts +4 -0
- package/dist/internal/search.js +6 -0
- package/dist/internal/styles.d.ts +21 -0
- package/dist/internal/styles.js +21 -0
- package/dist/kanban.js +3 -2
- package/dist/label.js +2 -1
- package/dist/lazy-calendar.js +3 -2
- package/dist/nav-link.js +2 -1
- package/dist/picker-shell.d.ts +3 -1
- package/dist/picker-shell.js +3 -3
- package/dist/popover.js +6 -5
- package/dist/search-picker.js +8 -16
- package/dist/select.js +5 -5
- package/dist/skip-link.js +2 -1
- package/dist/tabs.js +3 -2
- package/dist/toast.js +11 -10
- package/dist/toggle-group.d.ts +1 -1
- package/dist/toggle-group.js +36 -2
- package/dist/tooltip.js +2 -2
- package/package.json +1 -1
- package/skills/atlas-react-kit/SKILL.md +46 -42
package/COMPONENTS.md
CHANGED
|
@@ -35,22 +35,22 @@ theme from `next-themes`, so toast consumers install that too.
|
|
|
35
35
|
| `alert` | `Alert`, `AlertTitle`, `AlertDescription`, `alertVariants`; `variant` `default`, `success`, `warning`, `destructive`; tone on title and icon, body stays muted; destructive is `role="alert"` |
|
|
36
36
|
| `avatar` | `Avatar`, `AvatarImage`, `AvatarFallback`; `size` `sm`, `default`, `lg`; round by design |
|
|
37
37
|
| `breadcrumb` | `Breadcrumb`, `BreadcrumbList`, `BreadcrumbItem`, `BreadcrumbLink` (`asChild` for router links), `BreadcrumbPage` (`aria-current`), `BreadcrumbSeparator`, `BreadcrumbEllipsis` |
|
|
38
|
-
| `button` | `Button`, `buttonVariants`, `quietButtonInteraction`; `variant`, `size`, `asChild
|
|
39
|
-
| `badge` | `Badge`, `badgeVariants`; `variant`, `tone`, `asChild`
|
|
38
|
+
| `button` | `Button`, `buttonVariants`, `quietButtonInteraction`; `variant`, `size`, `asChild` |
|
|
39
|
+
| `badge` | `Badge`, `badgeVariants`; `variant`, `tone` (`neutral` default, `success`, `warning`, `destructive`; only `variant="soft"` reads it), `asChild` |
|
|
40
40
|
| `calendar` | `Calendar`, `CalendarDayButton`; React Day Picker props and `buttonVariant` |
|
|
41
41
|
| `card` | `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardAction`, `CardContent`, `CardFooter`; `Card` supports `flush`; `CardTitle as="h2"` (up to `h4`) makes it a section heading; root uses `min-h-min shrink-0` so flex columns do not collapse it, and `min-w-0` so nested tables scroll; `CardContent className="px-0"` overrides padding without `!` |
|
|
42
42
|
| `chart` | `chartFrame(height, { width?, padding? })` (viewBox and plot box: `left`, `right`, `top`, `bottom`, `plotWidth`, `plotHeight`, `viewBox`), `niceCeil`, `niceTicks`, `scaleLinear`, `linePath`, `roundedTopBarPath`, `compactNumber`, `indexAtPlotX`, `stepIndex`, `useChartScrub({ frame, steps, min, max })` (pointer and arrow-key crosshair; Escape clears), `ChartYAxisGrid` (`frame`, `yMax`, optional `tickCount`, `format`), `ChartTooltip` (`xPct`, `yPct`; `role="status"`; flips left past 55%), `ChartTooltipRow`, `ChartLegend`, `ChartLegendKey` (`swatchClassName`, `dashed`). No chart library; draw the series in your own `<svg>`. |
|
|
43
43
|
| `checkbox` | `Checkbox` |
|
|
44
44
|
| `color-legend` | `ColorLegend`; `title`, `items` as `{ color, range?, label }[]`, optional `caption`, `children`, `aria-label`, `className` (overlay chrome). No attribution. |
|
|
45
45
|
| `combobox` | `Combobox`; `options` as `{ value, label }[]`, `value`, `onValueChange`; optional placeholder, search, empty text, `creatable`, `normalizeCreateValue` (query → value or `null`), `createLabel`; trigger matches Input/SelectTrigger chrome; keyboard highlight scrolls into view; create is a listbox option selected with Enter |
|
|
46
|
-
| `date-picker` | `DatePickerButton`, `toDate`, `formatLocalDate`, and the kit's own `DateRange`, `DateMatcher`, `DateLike`, and `DatePickerCalendarProps` types (no Day Picker types); `mode` `"single"` (default) or `"range"`; single `value` is `Date` or `yyyy-MM-dd` (`clearable` adds a Clear row that emits `undefined`); range `value` is `{ from, to }`; trigger matches Input chrome; optional `numberOfMonths` (range default 1 below `md`, 2 at `md+`), `minDate` / `maxDate` (days outside are disabled), `side`, `aria-labelledby`, and `aria-describedby`
|
|
47
|
-
| `date-time-picker` | `DateTimePickerButton`, `resolveTimeStep`, `DEFAULT_TIME_STEP`; controlled `value` is `Date` or `null`; `onValueChange` emits `Date
|
|
46
|
+
| `date-picker` | `DatePickerButton`, `toDate`, `formatLocalDate`, and the kit's own `DateRange`, `DateMatcher`, `DateLike`, and `DatePickerCalendarProps` types (no Day Picker types); `mode` `"single"` (default) or `"range"`; single `value` is `Date` or `yyyy-MM-dd` (`clearable` adds a Clear row that emits `undefined`); range `value` is `{ from, to }`; trigger matches Input chrome; optional `numberOfMonths` (range default 1 below `md`, 2 at `md+`), `minDate` / `maxDate` (days outside are disabled), `side`, `aria-labelledby`, and `aria-describedby`; `onChange` is a deprecated alias of `onValueChange` |
|
|
47
|
+
| `date-time-picker` | `DateTimePickerButton`, `resolveTimeStep`, `DEFAULT_TIME_STEP`; controlled `value` is `Date` or `null`; `onValueChange` emits `Date` or `null`; `onChange` is a deprecated alias of `onValueChange` |
|
|
48
48
|
| `dialog` | `Dialog`, trigger, portal, overlay, content, close, header, footer, title, description; `DialogContent` supports `showCloseButton` |
|
|
49
49
|
| `dropdown-menu` | `DropdownMenu` family (trigger, content, item, checkbox/radio items, label, separator, shortcut, sub menus); item `variant="destructive"` and `inset`; for commands, not values |
|
|
50
|
-
| `empty-panel` | `EmptyPanel`; required `children`; optional `size` (`sm`, `default`, `lg`), `muted`, `className`
|
|
50
|
+
| `empty-panel` | `EmptyPanel`; required `children`; optional `size` (`sm`, `default`, `lg`; `md` is a deprecated alias of `default`), `muted`, `className` |
|
|
51
51
|
| `error-boundary` | `ErrorBoundary`, `ErrorFallback`; optional `message`; retry resets, reload refreshes the page |
|
|
52
52
|
| `field` | Field, label, legend, description, group, set, title, content, separator, error; `Field` supports `orientation` |
|
|
53
|
-
| `info-tip` | `InfoTip`, `InlineTip`; required `label` (aria-label); `title`, `description`, `children`, `layout` `box` \| `inline`, `side`, `align`, `badge`, `icon`, `className`. Popover, 20px glyph, 44px hit. Hover opens only on a fine pointer. Not a Tooltip.
|
|
53
|
+
| `info-tip` | `InfoTip`, `InlineTip`; required `label` (aria-label); `title`, `description`, `children`, `layout` `box` \| `inline`, `side`, `align`, `badge`, `icon`, `className`. Popover, 20px glyph (1em inline, on the text's cap height), 44px hit. After a label the glyph sits a quarter em from the text in both layouts, so leave the gap off the parent. Hover opens only on a fine pointer. Not a Tooltip. |
|
|
54
54
|
| `kanban` | `KanbanBoard<T>`, `KanbanSkeleton`, `KanbanColumn` and `KanbanDropTarget` types; `columns` (`{ id, label, color?, badge?, emptyLabel? }`), `items` (`{ id, position }`), `getColumnId`, `onMove(item, toColumnId, { beforePosition, afterPosition })` for every column change or reorder, `renderCard`; optional `onCardClick` (body becomes a button), `onComplete` / `onDelete` (Finish / Remove drop zones while dragging), `isLoading` (skeleton cards on a first load, dimmed board on a refetch), `emptyLabel`, `completeLabel`, `deleteLabel`. Layout comes from `items`, so an optimistic cache update is the only state. Keep `renderCard` and `onCardClick` stable (`useCallback`) and only the changed card re-renders. Cards drag by the grip (`Drag to move`); dnd-kit edge-scrolls while dragging, and off-screen columns get fades and scroll buttons. `KanbanSkeleton` takes `columnCount` (default 4). |
|
|
55
55
|
| `input`, `textarea`, `label` | `Input`, `Textarea`, `Label`; `Textarea` starts at `min-h-16` (override with `className`); `Label` `required` shows a visible marker (set `required` on the control too) |
|
|
56
56
|
| `markdown` | `Markdown`, `MarkdownContent`; `content`, optional `className`. GFM via `react-markdown` + `remark-gfm` in this subpath only. Sanitized (no raw HTML; http/https/mailto/#/`/` URLs). Token styles, no typography plugin. |
|
|
@@ -67,7 +67,7 @@ theme from `next-themes`, so toast consumers install that too.
|
|
|
67
67
|
| `separator`, `skeleton` | `Separator`, `Skeleton` |
|
|
68
68
|
| `skip-link` | `SkipLink`; `href` (default `#main-content`), children (default "Skip to content"). Visually hidden until focused. |
|
|
69
69
|
| `slider` | `Slider`; `value` number, `onValueChange`, optional `onValueCommit`, `min`, `max`, `step`, `disabled`, `id`, `aria-labelledby`, `className`. Radix Slider; 44px thumb hit; square track; primary fill. Name it with `aria-labelledby`. |
|
|
70
|
-
| `sortable-table-head` | `SortableTableHead`; `sorted
|
|
70
|
+
| `sortable-table-head` | `SortableTableHead`, type `SortDirection`; `sorted` (default `false`), `direction` `asc` or `desc` (default `asc`), `onClick`; sets `aria-sort` and wraps the header text in a button. Column keys and comparators stay in the app. |
|
|
71
71
|
| `spinner` | `Spinner`; `role="status"` with `aria-label` (default `Loading`) |
|
|
72
72
|
| `stat-tile` | `StatTile`, `statTileGrid`, `sectionLabel`; `label`, `value`, optional `sub`, `children`, `className`. |
|
|
73
73
|
| `switch` | `Switch`; settings that apply immediately; square track and thumb |
|
|
@@ -76,24 +76,24 @@ theme from `next-themes`, so toast consumers install that too.
|
|
|
76
76
|
| `tabs` | `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent`, `TabsNav`, `TabsNavLink`, `tabsListVariants`, `tabsTriggerVariants`; list `variant` is `default` or `line`; `TabsNavLink` is the link inside `TabsNav` (required `active`, `asChild` for a router link); `TabsNav` / `TabsList` scroll horizontally only (`overflow-y` hidden, `p-1` counted in the scroller height, edge fade, active item into view, mouse drag scrolls the row) — do not wrap them in `overflow-x-auto` |
|
|
77
77
|
| `toast` | `Toaster`, `toast`; mount one `Toaster` (`position`, `closeButton` (default on), `expand`, `visibleToasts`, `duration`), then `toast(message, options)`. The stack expands on hover and while keyboard focus is inside it; collapsed toasts behind the front one are hidden, `.success`, `.info`, `.warning`, `.error`, `.loading`, `.dismiss(id?)`. Options: `id` (replaces that toast), `description`, `duration`, `action` / `cancel` as `{ label, onClick }`. Kit-owned API: no toast library is exposed |
|
|
78
78
|
| `toggle` | `Toggle`, `toggleVariants`; `variant`, `size`. `selectedToggleChrome` is deprecated: `toggleVariants()` already includes it. Pressed state is the selected wash, text, and border via `data-state=on`, `aria-pressed`, and `aria-checked` |
|
|
79
|
-
| `toggle-group` | `ToggleGroup`, `ToggleGroupItem`; `variant`, `size`. Items share `toggleVariants`, so single (radio) and multiple selections match a pressed `Toggle
|
|
79
|
+
| `toggle-group` | `ToggleGroup`, `ToggleGroupItem`; `variant`, `size`. Items share `toggleVariants`, so single (radio) and multiple selections match a pressed `Toggle`. In a single group the arrow keys select as they move focus, like radios; Tab and clicks do not. A multiple group only moves focus |
|
|
80
80
|
| `theme-provider` | `ThemeProvider`, `useTheme`; optional `storageKey` (default `atlas-theme`), `enableShortcut` for the `d` toggle |
|
|
81
81
|
| `tooltip` | `Tooltip`, `TooltipTrigger`, `TooltipContent`, `TooltipProvider`; opens on hover and focus; short labels only; content keeps a `max-w-xs` reading width (override with `className`) |
|
|
82
|
-
| `form/checkbox-field` | `FormCheckboxField` (`checked`, `onCheckedChange(boolean)`, `label`, optional `description`, `controlPosition` `start` \| `end`, `meta`, `required`, `disabled`, `onBlur`) and `FormCheckboxGroupField<T>` (`label` as legend, `options` `{ value, label }[]`, `value: T[]`, `onValueChange` in option order, `className` lays out the options).
|
|
83
|
-
| `form/combobox-field` | `FormComboboxField`; every `Combobox` prop plus `label`, `description`, `meta`, `required`; pass the field's `onBlur`.
|
|
84
|
-
| `form/control-field` | `FormControlField`, type `FormControlProps`; `label`, optional `
|
|
85
|
-
| `form/markdown-field` | `FormMarkdownField`; `field` is `TextFieldApi`; `label`,
|
|
82
|
+
| `form/checkbox-field` | `FormCheckboxField` (`checked`, `onCheckedChange(boolean)`, `label`, optional `description`, `controlPosition` `start` \| `end`, `meta`, `fieldName`, `required`, `disabled`, `onBlur`) and `FormCheckboxGroupField<T>` (`label` as legend, `options` `{ value, label }[]`, `value: T[]`, `onValueChange` in option order, `className` lays out the options). |
|
|
83
|
+
| `form/combobox-field` | `FormComboboxField`; every `Combobox` prop (but `id`) plus `label`, `description`, `meta`, `fieldName`, `required`; pass the field's `onBlur`. |
|
|
84
|
+
| `form/control-field` | `FormControlField`, type `FormControlProps`; `label`, optional `fieldName`, `description`, `required`, `meta`, `submitted`, `showError`; `children` renders the control from `{ id, data-field-name, aria-invalid, aria-describedby, aria-required }` |
|
|
85
|
+
| `form/markdown-field` | `FormMarkdownField`; `field` is `TextFieldApi`; `label`, `placeholder`, `rows`, `showError`, `required`. Write / preview; preview uses `Markdown`. |
|
|
86
86
|
| `form/text-field` | `FormTextField`, type `TextFieldApi`; controlled field, label, multiline, description, input constraints; `required` also marks the label |
|
|
87
|
-
| `form/select-field` | `FormSelectField`; `value`, `onValueChange`, `label`, optional `
|
|
88
|
-
| `form/date-picker-field` | `FormDatePickerField`; `value`, `onValueChange`, `label`, optional `
|
|
89
|
-
| `form/date-time-picker-field` | `FormDateTimePickerField`; `value` is `Date` or `null`; `onValueChange` emits `Date` or `null`; optional `
|
|
87
|
+
| `form/select-field` | `FormSelectField`; `value`, `onValueChange`, `label`, optional `fieldName` (for `focusFirstInvalid`), `meta`, placeholder, and `required`; select item children |
|
|
88
|
+
| `form/date-picker-field` | `FormDatePickerField`; `value`, `onValueChange`, `label`, optional `fieldName`, `meta`, placeholder, `required`, `clearable` (a Clear row that emits `undefined`), `minDate` / `maxDate`, and `className` (forwarded to the trigger) |
|
|
89
|
+
| `form/date-time-picker-field` | `FormDateTimePickerField`; `value` is `Date` or `null`; `onValueChange` emits `Date` or `null`; optional `fieldName`, `meta`, placeholder, `timeStep`, `required`, `minDate` / `maxDate`, and `className` (forwarded to the trigger) |
|
|
90
90
|
| `form/field-error` | `FieldError` (optional `id`), `getFieldErrorMessage`; types `FieldErrorMessage`, `FieldMetaState`, `ErrorTimingProps` |
|
|
91
|
-
| `form/focus-first-invalid` | `focusFirstInvalid(formId)`: an `onSubmitInvalid` handler that focuses the first control in the form whose field
|
|
91
|
+
| `form/focus-first-invalid` | `focusFirstInvalid(formId)`: an `onSubmitInvalid` handler that focuses the first control in the form whose field has errors, matched by the control's `data-field-name` (`field.name` for the text and Markdown fields, the `fieldName` prop on the others) and never outside that form; type `InvalidSubmitEvent` |
|
|
92
92
|
| `form/feedback-field` | `FormFeedbackField`; nullable `message` |
|
|
93
|
-
| `form/toggle-group-field` | `FormToggleGroupField<T>`; one choice from `options` `{ value, label }[]` as a joined outline toggle group named by its label; `value` (`""` for none), `onValueChange(T)` (pressing the active item keeps it), optional `description`, `meta`, `size` (`sm` default).
|
|
94
|
-
| `next/nav-link` | `NavLink`; Next Link props and required `active` boolean; the `nav-link` pill with `asChild` around a Next `Link
|
|
93
|
+
| `form/toggle-group-field` | `FormToggleGroupField<T>`; one choice from `options` `{ value, label }[]` as a joined outline toggle group named by its label; `value` (`""` for none), `onValueChange(T)` (pressing the active item keeps it), optional `description`, `meta`, `fieldName`, `size` (`sm` default). |
|
|
94
|
+
| `next/nav-link` | `NavLink`; Next Link props and required `active` boolean; the `nav-link` pill with `asChild` around a Next `Link` |
|
|
95
95
|
| `next/period-stepper` | `PeriodStepper` with prefetched Next links: `previousHref`, `nextHref`, optional `todayHref`. |
|
|
96
|
-
| `next/tabs-nav-link` | `TabsNavLink`; Next Link props and required `active` boolean; the `tabs` link with `asChild` around a Next `Link
|
|
96
|
+
| `next/tabs-nav-link` | `TabsNavLink`; Next Link props and required `active` boolean; the `tabs` link with `asChild` around a Next `Link` |
|
|
97
97
|
| `utils` | `cn`, `onInputChange` |
|
|
98
98
|
| `theme.css` | Tailwind 4 theme; installation in [README](./README.md) |
|
|
99
99
|
|
|
@@ -121,7 +121,7 @@ each part carries a `data-slot` for parent selectors such as
|
|
|
121
121
|
| `DialogContent` | `overlayClassName`, `closeButtonClassName` | `dialog-close-button` (the built-in X; your own `DialogClose` keeps `dialog-close`) |
|
|
122
122
|
| `SelectTrigger` | a `className` height replaces `size` | `select-icon`, `select-viewport`, `select-item-indicator` |
|
|
123
123
|
| `TooltipContent` | `arrowClassName` | `tooltip-arrow` |
|
|
124
|
-
| `Combobox` | `contentClassName` (panel, default trigger width), `listClassName` (default `max-h-60`); button props go to trigger
|
|
124
|
+
| `Combobox` | `contentClassName` (panel, default at least the trigger width, wider for a long option), `listClassName` (default `max-h-60`); button props go to trigger | `combobox-search`, `combobox-list`, `combobox-option` |
|
|
125
125
|
| `SearchPicker` | `listClassName` (default `max-h-60`); div props go to the root | `search-picker-option`, `search-picker-empty` |
|
|
126
126
|
| `InfoTip` | `contentClassName` (default `w-72`); button props go to the trigger | |
|
|
127
127
|
| `KanbanBoard` | `scrollerClassName`, `columnClassName` (default `flex-1 basis-72 min-w-72` and a drop-area min height; columns grow to fill the board and keep an 18rem minimum so a long row still scrolls), `cardClassName` (cards and the drag overlay); div props go to the root | `kanban-board`, `kanban-scroller`, `kanban-column`, `kanban-column-header`, `kanban-column-empty`, `kanban-card`, `kanban-card-handle`, `kanban-card-body`, `kanban-drag-overlay`, `kanban-action-rail`, `kanban-action-zone`, `kanban-scroll-button`, `kanban-overflow-fade`, `kanban-skeleton` |
|
|
@@ -168,12 +168,16 @@ for a Write / Preview GFM body. `FormSelectField`, `FormDatePickerField`, and
|
|
|
168
168
|
`FormDateTimePickerField` accept the same error meta shape.
|
|
169
169
|
`FormDatePickerField` takes a `Date` or `yyyy-MM-dd` string and calls
|
|
170
170
|
`onValueChange` with `Date | undefined`. `FormDateTimePickerField` takes a
|
|
171
|
-
`Date` or `null` and calls `onValueChange` with `Date | null`.
|
|
172
|
-
`
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
171
|
+
`Date` or `null` and calls `onValueChange` with `Date | null`. Every
|
|
172
|
+
`form/*` field generates its own control id, so two forms on a page can
|
|
173
|
+
share a field name (login and sign-up `email`) and each label, hint, and
|
|
174
|
+
error still points at its own control; there is no `id` prop. To make a
|
|
175
|
+
field addressable by name, for example by `focusFirstInvalid` from
|
|
176
|
+
`form/focus-first-invalid`, which focuses the first field with errors when
|
|
177
|
+
a submit fails, pass `fieldName={field.name}` to every field that is not
|
|
178
|
+
bound to a `field` (the text and Markdown fields read `field.name`
|
|
179
|
+
themselves); the name is put on the control as `data-field-name`.
|
|
180
|
+
`FieldError` accepts either `message` or pre-resolved `text`, and an `id` to point the
|
|
177
181
|
control's `aria-describedby` at. Every `form/*` field does that for you: the
|
|
178
182
|
error text (and `FormTextField`'s `description`) is the control's accessible
|
|
179
183
|
description. The date fields label their trigger with `aria-labelledby` (the
|
|
@@ -191,9 +195,11 @@ submit. Meta without `isTouched` shows errors as soon as they exist.
|
|
|
191
195
|
`FormSelectField` and both date fields included, takes `submitted` and
|
|
192
196
|
`showError`.
|
|
193
197
|
|
|
194
|
-
`FormControlField` gives a control the kit does not ship
|
|
195
|
-
required marker, hint, error, ids, and error timing.
|
|
196
|
-
|
|
198
|
+
`FormControlField` gives a control the kit does not ship (a colour picker,
|
|
199
|
+
say) the same label, required marker, hint, error, ids, and error timing.
|
|
200
|
+
Use it rather than `Field` and `FieldLabel`, whose `gap-3` spacing does not
|
|
201
|
+
match the form fields. `children` is a function that receives the props to
|
|
202
|
+
spread on the control:
|
|
197
203
|
|
|
198
204
|
```tsx
|
|
199
205
|
<div className="grid grid-cols-2 items-start gap-3">
|
|
@@ -211,8 +217,10 @@ function that receives the props to spread on the control:
|
|
|
211
217
|
</div>
|
|
212
218
|
```
|
|
213
219
|
|
|
214
|
-
|
|
215
|
-
|
|
220
|
+
Each `form/*` field stacks label, hint, control, and error with `gap-1.5`.
|
|
221
|
+
The error sits below the control and takes no space until it shows, so use
|
|
222
|
+
`items-start` on a row of fields, as above, to keep the controls on one top
|
|
223
|
+
edge; the default `stretch` is fine for a single column.
|
|
216
224
|
|
|
217
225
|
Set `required` on a `form/*` field to mark its label with a visible `*`.
|
|
218
226
|
The marker is hidden from screen readers, which announce the control's own
|
|
@@ -246,9 +254,6 @@ the trigger text's id (a `<label for>` alone names the button by the label
|
|
|
246
254
|
only).
|
|
247
255
|
`FormDatePickerField` forwards `className` to that trigger.
|
|
248
256
|
|
|
249
|
-
Both pickers took `onChange` before 1.0; it still works as a deprecated
|
|
250
|
-
alias until 2.0.
|
|
251
|
-
|
|
252
257
|
`DateTimePickerButton` is the date-and-time control. `value` is a `Date` or
|
|
253
258
|
`null`; `onValueChange` receives `Date | null`. The trigger matches the same
|
|
254
259
|
form-control chrome. The popover is one month, a labelled Hour and Minute
|
|
@@ -261,11 +266,6 @@ named dialog ("Choose date and time") that focuses the selected day or
|
|
|
261
266
|
today, and the trigger takes `aria-labelledby` and `aria-describedby`. `FormDateTimePickerField` forwards `className`
|
|
262
267
|
and `timeStep` to that control.
|
|
263
268
|
|
|
264
|
-
Outline and ghost `Button` hover uses `border-selected-foreground`,
|
|
265
|
-
`text-selected-foreground`, and `bg-selected`; keyboard focus adds the shared
|
|
266
|
-
solid `ring-ring`. They do not use a muted grey fill, including when the
|
|
267
|
-
control sits on a selected row (for example "Clear selection").
|
|
268
|
-
|
|
269
269
|
```tsx
|
|
270
270
|
import { DateTimePickerButton } from "atlas-react-kit/date-time-picker"
|
|
271
271
|
|
|
@@ -370,7 +370,7 @@ export function AppNav({ pathname }: { pathname: string }) {
|
|
|
370
370
|
```
|
|
371
371
|
|
|
372
372
|
In-page view switches that still belong in the URL use `TabsNav`, not
|
|
373
|
-
`NavLink
|
|
373
|
+
`NavLink`; `Tabs` is for `TabsContent` panels that stay on one page.
|
|
374
374
|
|
|
375
375
|
## URL navigation in Next.js
|
|
376
376
|
|
package/DESIGN_RULES.md
CHANGED
|
@@ -17,7 +17,8 @@ Selected or active navigation, tabs, toggles, chips, rows, and labels use
|
|
|
17
17
|
`bg-selected text-selected-foreground` plus a `border-selected-foreground`
|
|
18
18
|
border or bar: the wash alone is too faint to show state (1.2:1 against the
|
|
19
19
|
page). Outline and ghost buttons hover with `border-selected-foreground`,
|
|
20
|
-
`text-selected-foreground`, and the selected wash, not a muted grey fill
|
|
20
|
+
`text-selected-foreground`, and the selected wash, not a muted grey fill,
|
|
21
|
+
even on a selected row (a "Clear selection" button, for example).
|
|
21
22
|
Table row hover can stay `hover:bg-muted`. The shipped controls apply this
|
|
22
23
|
distinction.
|
|
23
24
|
|
|
@@ -26,8 +27,8 @@ it is the primary purple in light mode and a lighter purple in dark mode.
|
|
|
26
27
|
`text-primary` as text fails contrast on the dark background (about 3.6:1).
|
|
27
28
|
|
|
28
29
|
Use `success`, `warning`, and `destructive` for good, caution, and bad states.
|
|
29
|
-
For tinted status chips, use `Badge variant="soft"` with `tone
|
|
30
|
-
|
|
30
|
+
For tinted status chips, use `Badge variant="soft"` with a `tone` (listed in
|
|
31
|
+
its COMPONENTS.md row); each tone meets AA as text on the page, a card, and
|
|
31
32
|
its own soft tint in both themes. Status tones have no paired foreground token;
|
|
32
33
|
avoid placing text on solid status fills. Application-specific identity colors
|
|
33
34
|
belong to the application.
|
|
@@ -59,8 +60,8 @@ in COMPONENTS.md.
|
|
|
59
60
|
- `variant="default"` is the plain look; other variants name what they mean
|
|
60
61
|
(`outline`, `ghost`, `destructive`).
|
|
61
62
|
|
|
62
|
-
Deprecated
|
|
63
|
-
|
|
63
|
+
Deprecated props and exports say so, with the replacement, in their JSDoc
|
|
64
|
+
and their row in [COMPONENTS.md](./COMPONENTS.md).
|
|
64
65
|
|
|
65
66
|
## Accessibility
|
|
66
67
|
|
|
@@ -118,14 +119,11 @@ still read normal words.
|
|
|
118
119
|
Primitives have square corners. Reserve `rounded-full` for dots, avatars,
|
|
119
120
|
other circles, and the toast close badge. Card and dialog titles already use
|
|
120
121
|
uppercase, tracked `font-heading` styling. Toasts are overlay popovers with
|
|
121
|
-
the same square corners and Alert left
|
|
122
|
-
|
|
123
|
-
`gap-x-2.5` so the status icon and copy read as one unit. The close control is
|
|
124
|
-
a circular badge centred on the top-right corner (half outside the border),
|
|
122
|
+
the same square corners and Alert's left padding. The close control is a
|
|
123
|
+
circular badge centred on the top-right corner (half outside the border),
|
|
125
124
|
not a control in the text row. A plain `toast()` has no icon — status icons
|
|
126
125
|
belong to `success`, `info`, `warning`, `error`, and `loading`. Tone colors
|
|
127
|
-
the icon, title, and border, not the body.
|
|
128
|
-
or the viewport minus gutters on small screens.
|
|
126
|
+
the icon, title, and border, not the body.
|
|
129
127
|
|
|
130
128
|
Use `EmptyPanel` for empty states, `TableBodySkeleton` for loading table bodies,
|
|
131
129
|
and `<Card flush>` for an edge-to-edge list or table. `Table` scrolls wide
|
|
@@ -163,32 +161,8 @@ or its `data-slot`.
|
|
|
163
161
|
`text-base md:text-sm`, `border-input`, and `shadow-xs`. Do not restyle those
|
|
164
162
|
triggers as outline buttons in application code.
|
|
165
163
|
|
|
166
|
-
`form/*` fields
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
picker) in `FormControlField` instead of `Field` and `FieldLabel`, whose
|
|
172
|
-
`gap-3` spacing does not match. A field shows its error once the person has
|
|
173
|
-
touched it (`meta.isTouched`), never on a field they have not reached yet.
|
|
174
|
-
`submitted` is for form libraries that do not mark every field touched on
|
|
175
|
-
submit; TanStack Form does, so its users pass `meta` only.
|
|
176
|
-
|
|
177
|
-
Use `Tabs` with actual `TabsContent` panels. For URL view switches, use a labelled
|
|
178
|
-
`TabsNav` of `TabsNavLink`s with `active` from the app. App-shell destinations
|
|
179
|
-
(Calendar, Tasks, Settings) use `NavLink` from `nav-link`: a pill with `active`
|
|
180
|
-
from the app, not tabs. Both links take `asChild` for a router link, or come
|
|
181
|
-
as Next adapters (`next/tabs-nav-link`, `next/nav-link`); `isNavActive`
|
|
182
|
-
compares the pathname with a link's `href`. `TabsNav` and `TabsList` scroll overflowing
|
|
183
|
-
labels inside themselves, like `Table`. Do not wrap them in another
|
|
184
|
-
horizontal scroller: that pairing adds a stray vertical scrollbar.
|
|
185
|
-
|
|
186
|
-
Use the `form/*` fields (one row each in COMPONENTS.md) for portable form
|
|
187
|
-
controls, and `FieldError` for field messages. `Combobox` (popover)
|
|
188
|
-
and `SearchPicker` (always-visible list), `DatePickerButton` (including
|
|
189
|
-
`mode="range"`), `DateTimePickerButton`, `Slider`, `Meter` (scale with ticks;
|
|
190
|
-
`Progress` is a plain 0–100 job bar), `InfoTip` (longer than `Tooltip`),
|
|
191
|
-
`Markdown`, `ColorLegend`, `StatTile`, `ThemeProvider`, `ErrorBoundary`, and
|
|
192
|
-
`SortableTableHead` are first-class kit pieces. Domain data and application
|
|
193
|
-
shells stay with the application: currency or timezone option lists,
|
|
194
|
-
query-library error views, and WYSIWYG editors.
|
|
164
|
+
Build forms from the `form/*` fields; [Forms](./COMPONENTS.md#forms) in
|
|
165
|
+
COMPONENTS.md says how they lay out and when they show errors. Pick other
|
|
166
|
+
components by their rows there. Domain data and application shells stay
|
|
167
|
+
with the application: currency or timezone option lists, query-library
|
|
168
|
+
error views, and WYSIWYG editors.
|
package/README.md
CHANGED
|
@@ -4,11 +4,11 @@ Shared React components and a Tailwind theme with square corners, semantic
|
|
|
4
4
|
colors, and distinct selected states. From 1.0 the API is stable: breaking
|
|
5
5
|
changes to stable components only ship in a major release (see
|
|
6
6
|
[CONTRIBUTING.md](./CONTRIBUTING.md#versions-and-releases)).
|
|
7
|
-
React
|
|
8
|
-
|
|
7
|
+
The React, Tailwind, and Node versions it supports are `peerDependencies`
|
|
8
|
+
and `engines` in `package.json`.
|
|
9
9
|
|
|
10
10
|
```sh
|
|
11
|
-
npm install --save-exact atlas-react-kit
|
|
11
|
+
npm install --save-exact atlas-react-kit
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
Import components by subpath:
|
|
@@ -42,9 +42,10 @@ this example assumes it is one directory below the project root.
|
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
The theme includes animations and shared light/dark tokens. Wrap the tree with
|
|
45
|
-
`ThemeProvider` from `atlas-react-kit/theme-provider` (install
|
|
46
|
-
|
|
47
|
-
dark colors. Supply your
|
|
45
|
+
`ThemeProvider` from `atlas-react-kit/theme-provider` (install `next-themes`
|
|
46
|
+
from the [install table](./COMPONENTS.md#optional-peer-dependencies) first),
|
|
47
|
+
or set the `dark` class on an ancestor to activate dark colors. Supply your
|
|
48
|
+
own font configuration;
|
|
48
49
|
`font-heading` uses the configured sans font. Application-specific tokens stay
|
|
49
50
|
with the application.
|
|
50
51
|
|
package/dist/alert.js
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { cva } from "class-variance-authority";
|
|
4
|
+
import { descriptionText, statusTone } from "./internal/styles.js";
|
|
4
5
|
import { cn } from "./utils.js";
|
|
5
6
|
/**
|
|
6
7
|
* Status tone sits on the title and icon. The description stays
|
|
7
8
|
* `text-muted-foreground` on the card face, so body text keeps its contrast
|
|
8
9
|
* (the destructive tint is too faint for text in light mode).
|
|
9
10
|
*/
|
|
10
|
-
const alertVariants = cva("relative grid grid-cols-[0_1fr] items-start gap-y-1 rounded-none border bg-card px-4 py-3 text-sm text-card-foreground has-[>svg]:grid-cols-[--spacing(4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5", {
|
|
11
|
+
const alertVariants = cva("relative grid grid-cols-[0_1fr] items-start gap-y-1 rounded-none border bg-card px-4 py-3 text-sm text-card-foreground has-[>svg]:grid-cols-[--spacing(4)_1fr] has-[>svg]:gap-x-3 *:data-[slot=alert-title]:text-(--status-tone) [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-(--status-tone)", {
|
|
11
12
|
variants: {
|
|
12
13
|
variant: {
|
|
13
|
-
default:
|
|
14
|
-
success:
|
|
15
|
-
warning:
|
|
16
|
-
destructive:
|
|
14
|
+
default: statusTone.neutral,
|
|
15
|
+
success: statusTone.success,
|
|
16
|
+
warning: statusTone.warning,
|
|
17
|
+
destructive: statusTone.destructive,
|
|
17
18
|
},
|
|
18
19
|
},
|
|
19
20
|
defaultVariants: {
|
|
@@ -32,6 +33,6 @@ function AlertTitle({ className, ...props }) {
|
|
|
32
33
|
return (_jsx("div", { "data-slot": "alert-title", className: cn("col-start-2 min-h-4 text-xs font-semibold tracking-wider uppercase", className), ...props }));
|
|
33
34
|
}
|
|
34
35
|
function AlertDescription({ className, ...props }) {
|
|
35
|
-
return (_jsx("div", { "data-slot": "alert-description", className: cn("col-start-2 grid justify-items-start gap-1
|
|
36
|
+
return (_jsx("div", { "data-slot": "alert-description", className: cn("col-start-2 grid justify-items-start gap-1", descriptionText, "[&_p]:leading-relaxed", className), ...props }));
|
|
36
37
|
}
|
|
37
38
|
export { Alert, AlertDescription, AlertTitle, alertVariants };
|
package/dist/calendar.js
CHANGED
|
@@ -4,7 +4,8 @@ import * as React from "react";
|
|
|
4
4
|
import { DayPicker, getDefaultClassNames, useDayPicker, } from "react-day-picker";
|
|
5
5
|
import { cn } from "./utils.js";
|
|
6
6
|
import { Button, buttonVariants } from "./button.js";
|
|
7
|
-
import {
|
|
7
|
+
import { calendarSize } from "./internal/calendar-size.js";
|
|
8
|
+
import { ariaDisabledState, focusRingWithin } from "./internal/styles.js";
|
|
8
9
|
import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon, } from "lucide-react";
|
|
9
10
|
/** Previous / next month buttons share one look. */
|
|
10
11
|
const navButtonClassName = cn("size-(--cell-size) p-0 select-none", ariaDisabledState);
|
|
@@ -38,7 +39,7 @@ const calendarComponents = {
|
|
|
38
39
|
function Calendar({ className, classNames, showOutsideDays = true, captionLayout = "label", buttonVariant = "ghost", locale, formatters, labels, components, ...props }) {
|
|
39
40
|
const defaultClassNames = getDefaultClassNames();
|
|
40
41
|
const navButton = cn(buttonVariants({ variant: buttonVariant }), navButtonClassName);
|
|
41
|
-
return (_jsx(DayPicker, { showOutsideDays: showOutsideDays, className: cn("group/calendar bg-background p-
|
|
42
|
+
return (_jsx(DayPicker, { showOutsideDays: showOutsideDays, className: cn("group/calendar bg-background p-(--calendar-padding) [--cell-radius:0] in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent", calendarSize, String.raw `rtl:**:[.rdp-button\_next>svg]:rotate-180`, String.raw `rtl:**:[.rdp-button\_previous>svg]:rotate-180`, className), captionLayout: captionLayout, locale: locale, formatters: {
|
|
42
43
|
formatMonthDropdown: (date) => date.toLocaleString(locale?.code, { month: "short" }),
|
|
43
44
|
...formatters,
|
|
44
45
|
}, labels: {
|
|
@@ -70,7 +71,7 @@ function Calendar({ className, classNames, showOutsideDays = true, captionLayout
|
|
|
70
71
|
dropdowns: cn("flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium", defaultClassNames.dropdowns),
|
|
71
72
|
// The native select is invisible over the caption, so its wrapper
|
|
72
73
|
// shows keyboard focus with the kit's solid ring.
|
|
73
|
-
dropdown_root: cn("relative rounded-(--cell-radius)
|
|
74
|
+
dropdown_root: cn("relative rounded-(--cell-radius)", focusRingWithin, defaultClassNames.dropdown_root),
|
|
74
75
|
dropdown: cn("absolute inset-0 bg-popover opacity-0", defaultClassNames.dropdown),
|
|
75
76
|
caption_label: cn("font-medium select-none", captionLayout === "label"
|
|
76
77
|
? "text-sm"
|
|
@@ -107,6 +108,6 @@ function CalendarDayButton({ className, day, modifiers, locale, ...props }) {
|
|
|
107
108
|
return (_jsx(Button, { ref: ref, variant: "ghost", size: "icon", "data-day": day.date.toLocaleDateString(locale?.code), "data-selected-single": modifiers.selected &&
|
|
108
109
|
!modifiers.range_start &&
|
|
109
110
|
!modifiers.range_end &&
|
|
110
|
-
!modifiers.range_middle, "data-range-start": modifiers.range_start, "data-range-end": modifiers.range_end, "data-range-middle": modifiers.range_middle, className: cn("relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 leading-none font-normal
|
|
111
|
+
!modifiers.range_middle, "data-range-start": modifiers.range_start, "data-range-end": modifiers.range_end, "data-range-middle": modifiers.range_middle, className: cn("relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 leading-none font-normal data-[range-end=true]:rounded-(--cell-radius) data-[range-end=true]:rounded-r-(--cell-radius) data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:rounded-none data-[range-middle=true]:bg-selected data-[range-middle=true]:text-selected-foreground data-[range-start=true]:rounded-(--cell-radius) data-[range-start=true]:rounded-l-(--cell-radius) data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground dark:hover:text-foreground [&>span]:text-xs [&>span]:opacity-70", defaultClassNames.day, className), ...props }));
|
|
111
112
|
}
|
|
112
113
|
export { Calendar, CalendarDayButton };
|
package/dist/card.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
|
+
import { descriptionText } from "./internal/styles.js";
|
|
3
4
|
import { cn } from "./utils.js";
|
|
4
5
|
/**
|
|
5
6
|
* Size and flush padding live in `--card-p` (set here, read from `theme.css`
|
|
@@ -32,7 +33,7 @@ function CardTitle({ as: Component = "div", className, ...props }) {
|
|
|
32
33
|
return (_jsx(Component, { "data-slot": "card-title", className: cn("font-heading text-base font-semibold tracking-wider uppercase", className), ...props }));
|
|
33
34
|
}
|
|
34
35
|
function CardDescription({ className, ...props }) {
|
|
35
|
-
return (_jsx("div", { "data-slot": "card-description", className: cn(
|
|
36
|
+
return (_jsx("div", { "data-slot": "card-description", className: cn(descriptionText, className), ...props }));
|
|
36
37
|
}
|
|
37
38
|
function CardAction({ className, ...props }) {
|
|
38
39
|
return (_jsx("div", { "data-slot": "card-action", className: cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className), ...props }));
|
package/dist/combobox.d.ts
CHANGED
|
@@ -21,11 +21,12 @@ type ComboboxProps = {
|
|
|
21
21
|
disabled?: boolean;
|
|
22
22
|
/** Goes on the trigger, so a `<Label htmlFor>` names it. Generated when omitted. */
|
|
23
23
|
id?: string;
|
|
24
|
-
/** Classes for the trigger, e.g. its width; the list
|
|
24
|
+
/** Classes for the trigger, e.g. its width; the list is at least as wide. */
|
|
25
25
|
className?: string;
|
|
26
26
|
/**
|
|
27
|
-
* Classes for the popover panel, e.g. a width
|
|
28
|
-
*
|
|
27
|
+
* Classes for the popover panel, e.g. a fixed width. By default the panel
|
|
28
|
+
* is at least as wide as the trigger and grows to fit the longest option,
|
|
29
|
+
* up to the space the viewport leaves.
|
|
29
30
|
*/
|
|
30
31
|
contentClassName?: string;
|
|
31
32
|
/** Marks the trigger invalid, drawing the error border. */
|
package/dist/combobox.js
CHANGED
|
@@ -5,10 +5,10 @@ import { ChevronDownIcon } from "lucide-react";
|
|
|
5
5
|
import { Button } from "./button.js";
|
|
6
6
|
import { Input } from "./input.js";
|
|
7
7
|
import { formControlTriggerClassName } from "./internal/form-control.js";
|
|
8
|
-
import { handleListboxKey
|
|
9
|
-
import { ListboxEmptyStatus, PickerOption } from "./internal/listbox-option.js";
|
|
8
|
+
import { handleListboxKey } from "./internal/listbox-navigation.js";
|
|
9
|
+
import { Listbox, ListboxEmptyStatus, listboxInputAria, PickerOption, } from "./internal/listbox-option.js";
|
|
10
10
|
import { createSearchIndex, filterSearchIndex, foldSearchText, normalizeSearchQuery, } from "./internal/search.js";
|
|
11
|
-
import {
|
|
11
|
+
import { emptyValueText } from "./internal/styles.js";
|
|
12
12
|
import { Popover, PopoverContent, PopoverTrigger } from "./popover.js";
|
|
13
13
|
import { cn, onInputChange } from "./utils.js";
|
|
14
14
|
const CREATE_OPTION_KEY = "__atlas-combobox-create";
|
|
@@ -50,7 +50,7 @@ function resolveCreateValue(query, existingValues, normalize) {
|
|
|
50
50
|
* Opt-in `creatable` appends a create action when the query is not already
|
|
51
51
|
* an option; choosing it calls `onValueChange` like a normal selection.
|
|
52
52
|
*/
|
|
53
|
-
export function Combobox({ options, value, onValueChange, placeholder = "Select", searchPlaceholder = "Search", emptyText
|
|
53
|
+
export function Combobox({ options, value, onValueChange, placeholder = "Select", searchPlaceholder = "Search", emptyText, disabled, id, className, listClassName, contentClassName, "aria-invalid": ariaInvalid, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, creatable = false, normalizeCreateValue = defaultNormalizeCreateValue, createLabel = defaultCreateLabel, ...props }) {
|
|
54
54
|
const [open, setOpen] = React.useState(false);
|
|
55
55
|
const [query, setQuery] = React.useState("");
|
|
56
56
|
const [highlight, setHighlight] = React.useState(0);
|
|
@@ -74,12 +74,7 @@ export function Combobox({ options, value, onValueChange, placeholder = "Select"
|
|
|
74
74
|
label,
|
|
75
75
|
])
|
|
76
76
|
: []), [creatable, searchIndex]);
|
|
77
|
-
const filtered = React.useMemo(() =>
|
|
78
|
-
const needle = normalizeSearchQuery(query);
|
|
79
|
-
if (!needle)
|
|
80
|
-
return resolvedOptions;
|
|
81
|
-
return filterSearchIndex(searchIndex, needle);
|
|
82
|
-
}, [resolvedOptions, searchIndex, query]);
|
|
77
|
+
const filtered = React.useMemo(() => filterSearchIndex(searchIndex, normalizeSearchQuery(query)), [searchIndex, query]);
|
|
83
78
|
const createOption = React.useMemo(() => {
|
|
84
79
|
const createValue = creatable
|
|
85
80
|
? resolveCreateValue(query, existingValues, normalizeCreateValue)
|
|
@@ -125,17 +120,13 @@ export function Combobox({ options, value, onValueChange, placeholder = "Select"
|
|
|
125
120
|
};
|
|
126
121
|
const onSearchKeyDown = (event) => {
|
|
127
122
|
handleListboxKey(event, {
|
|
123
|
+
items: list,
|
|
128
124
|
highlight,
|
|
129
|
-
length: list.length,
|
|
130
125
|
setHighlight,
|
|
131
|
-
|
|
132
|
-
const option = list[index];
|
|
133
|
-
if (option)
|
|
134
|
-
selectOption(option);
|
|
135
|
-
},
|
|
126
|
+
onPick: selectOption,
|
|
136
127
|
});
|
|
137
128
|
};
|
|
138
|
-
return (_jsxs(Popover, { open: open, onOpenChange: onOpenChange, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { ...props, id: triggerId, type: "button", variant: "outline", role: "combobox", "aria-expanded": open, "aria-controls": listId, "aria-invalid": ariaInvalid, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, disabled: disabled, onKeyDown: onTriggerKeyDown, className: cn(formControlTriggerClassName, "justify-between",
|
|
129
|
+
return (_jsxs(Popover, { open: open, onOpenChange: onOpenChange, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { ...props, id: triggerId, type: "button", variant: "outline", role: "combobox", "aria-expanded": open, "aria-controls": listId, "aria-invalid": ariaInvalid, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "data-empty": !selected, disabled: disabled, onKeyDown: onTriggerKeyDown, className: cn(formControlTriggerClassName, "justify-between", emptyValueText, className), children: [_jsx("span", { className: "truncate", children: selected?.label ?? placeholder }), _jsx(ChevronDownIcon, { "data-icon": "inline-end", className: "text-muted-foreground" })] }) }), _jsxs(PopoverContent, { align: "start", className: cn("w-max min-w-(--radix-popover-trigger-width) gap-0 rounded-none p-0", contentClassName), children: [_jsx("div", { "data-slot": "combobox-search", className: "border-b p-2", children: _jsx(Input, { value: query, onChange: onQueryChange, onKeyDown: onSearchKeyDown, placeholder: searchPlaceholder, "aria-label": searchPlaceholder, "aria-controls": listId, ...listboxInputAria(listId, activeOption ? highlight : undefined) }) }), _jsx(Listbox, { id: listId, empty: empty, "data-slot": "combobox-list", "aria-label": ariaLabelledBy ? undefined : ariaLabel, "aria-labelledby": ariaLabelledBy ?? (ariaLabel ? undefined : triggerId), className: cn("p-1", listClassName), children: list.map((option, index) => {
|
|
139
130
|
const isCreate = createOption != null && option === createOption;
|
|
140
131
|
const isSelected = option.value === value && !isCreate;
|
|
141
132
|
return (_jsx(PickerOption, { "data-slot": "combobox-option", listId: listId, index: index, active: index === highlight, selected: isSelected, showCheck: true, item: option, getLabel: getOptionLabel, onHighlight: setHighlight, onSelect: selectOption }, isCreate ? CREATE_OPTION_KEY : option.value));
|