atlas-react-kit 1.2.0 → 1.2.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/COMPONENTS.md +19 -23
- package/DESIGN_RULES.md +14 -40
- package/README.md +7 -6
- package/dist/alert.js +6 -5
- package/dist/calendar.js +5 -4
- package/dist/combobox.js +6 -11
- package/dist/date-time-picker.js +2 -1
- package/dist/dropdown-menu.js +2 -2
- package/dist/field.d.ts +6 -3
- package/dist/field.js +3 -2
- package/dist/form/checkbox-field.d.ts +1 -1
- package/dist/form/checkbox-field.js +8 -11
- package/dist/form/combobox-field.js +2 -2
- package/dist/form/control-field.js +2 -7
- package/dist/form/date-picker-field.js +5 -2
- package/dist/form/date-time-picker-field.js +5 -2
- package/dist/form/field-error.d.ts +3 -3
- package/dist/form/field-shell.d.ts +36 -31
- package/dist/form/field-shell.js +41 -34
- package/dist/form/markdown-field.js +2 -2
- package/dist/form/select-field.js +2 -2
- package/dist/form/text-field.js +2 -4
- package/dist/form/toggle-group-field.js +2 -2
- package/dist/info-tip.d.ts +5 -4
- package/dist/info-tip.js +9 -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 +1 -1
- package/dist/internal/form-control.js +2 -1
- package/dist/internal/listbox-option.d.ts +16 -3
- package/dist/internal/listbox-option.js +22 -5
- package/dist/internal/overlay-styles.d.ts +10 -5
- package/dist/internal/overlay-styles.js +10 -5
- package/dist/internal/search.d.ts +4 -0
- package/dist/internal/search.js +6 -0
- package/dist/internal/styles.d.ts +16 -0
- package/dist/internal/styles.js +16 -0
- package/dist/kanban.js +2 -1
- package/dist/label.js +2 -1
- package/dist/lazy-calendar.js +3 -2
- package/dist/nav-link.js +2 -1
- package/dist/popover.js +4 -3
- package/dist/search-picker.js +5 -9
- package/dist/select.js +4 -4
- package/dist/skip-link.js +2 -1
- package/dist/tabs.js +3 -2
- package/dist/toast.js +11 -10
- 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. 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 |
|
|
@@ -91,9 +91,9 @@ theme from `next-themes`, so toast consumers install that too.
|
|
|
91
91
|
| `form/focus-first-invalid` | `focusFirstInvalid(formId)`: an `onSubmitInvalid` handler that focuses the first control in the form whose field (matched by control id, so pass `id={field.name}`) has errors; type `InvalidSubmitEvent` |
|
|
92
92
|
| `form/feedback-field` | `FormFeedbackField`; nullable `message` |
|
|
93
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
|
|
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
|
|
|
@@ -191,9 +191,11 @@ submit. Meta without `isTouched` shows errors as soon as they exist.
|
|
|
191
191
|
`FormSelectField` and both date fields included, takes `submitted` and
|
|
192
192
|
`showError`.
|
|
193
193
|
|
|
194
|
-
`FormControlField` gives a control the kit does not ship
|
|
195
|
-
required marker, hint, error, ids, and error timing.
|
|
196
|
-
|
|
194
|
+
`FormControlField` gives a control the kit does not ship (a colour picker,
|
|
195
|
+
say) the same label, required marker, hint, error, ids, and error timing.
|
|
196
|
+
Use it rather than `Field` and `FieldLabel`, whose `gap-3` spacing does not
|
|
197
|
+
match the form fields. `children` is a function that receives the props to
|
|
198
|
+
spread on the control:
|
|
197
199
|
|
|
198
200
|
```tsx
|
|
199
201
|
<div className="grid grid-cols-2 items-start gap-3">
|
|
@@ -211,8 +213,10 @@ function that receives the props to spread on the control:
|
|
|
211
213
|
</div>
|
|
212
214
|
```
|
|
213
215
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
+
Each `form/*` field stacks label, hint, control, and error with `gap-1.5`.
|
|
217
|
+
The error sits below the control and takes no space until it shows, so use
|
|
218
|
+
`items-start` on a row of fields, as above, to keep the controls on one top
|
|
219
|
+
edge; the default `stretch` is fine for a single column.
|
|
216
220
|
|
|
217
221
|
Set `required` on a `form/*` field to mark its label with a visible `*`.
|
|
218
222
|
The marker is hidden from screen readers, which announce the control's own
|
|
@@ -246,9 +250,6 @@ the trigger text's id (a `<label for>` alone names the button by the label
|
|
|
246
250
|
only).
|
|
247
251
|
`FormDatePickerField` forwards `className` to that trigger.
|
|
248
252
|
|
|
249
|
-
Both pickers took `onChange` before 1.0; it still works as a deprecated
|
|
250
|
-
alias until 2.0.
|
|
251
|
-
|
|
252
253
|
`DateTimePickerButton` is the date-and-time control. `value` is a `Date` or
|
|
253
254
|
`null`; `onValueChange` receives `Date | null`. The trigger matches the same
|
|
254
255
|
form-control chrome. The popover is one month, a labelled Hour and Minute
|
|
@@ -261,11 +262,6 @@ named dialog ("Choose date and time") that focuses the selected day or
|
|
|
261
262
|
today, and the trigger takes `aria-labelledby` and `aria-describedby`. `FormDateTimePickerField` forwards `className`
|
|
262
263
|
and `timeStep` to that control.
|
|
263
264
|
|
|
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
265
|
```tsx
|
|
270
266
|
import { DateTimePickerButton } from "atlas-react-kit/date-time-picker"
|
|
271
267
|
|
|
@@ -370,7 +366,7 @@ export function AppNav({ pathname }: { pathname: string }) {
|
|
|
370
366
|
```
|
|
371
367
|
|
|
372
368
|
In-page view switches that still belong in the URL use `TabsNav`, not
|
|
373
|
-
`NavLink
|
|
369
|
+
`NavLink`; `Tabs` is for `TabsContent` panels that stay on one page.
|
|
374
370
|
|
|
375
371
|
## URL navigation in Next.js
|
|
376
372
|
|
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 { 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: {
|
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/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)
|
|
@@ -135,7 +130,7 @@ export function Combobox({ options, value, onValueChange, placeholder = "Select"
|
|
|
135
130
|
},
|
|
136
131
|
});
|
|
137
132
|
};
|
|
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",
|
|
133
|
+
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-[var(--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
134
|
const isCreate = createOption != null && option === createOption;
|
|
140
135
|
const isSelected = option.value === value && !isCreate;
|
|
141
136
|
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));
|
package/dist/date-time-picker.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { format } from "date-fns";
|
|
5
|
+
import { labelText } from "./internal/styles.js";
|
|
5
6
|
import { PickerShell } from "./picker-shell.js";
|
|
6
7
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "./select.js";
|
|
7
8
|
/** Default clock increment, in minutes. Must divide 60. */
|
|
@@ -71,7 +72,7 @@ export function DateTimePickerButton({ value, onValueChange, onChange, timeStep,
|
|
|
71
72
|
return;
|
|
72
73
|
emit?.(combineDateAndTime(next, date?.getHours() ?? 0, date?.getMinutes() ?? 0));
|
|
73
74
|
},
|
|
74
|
-
}, onClear: () => emit?.(null), clearDisabled: empty, children: _jsxs("div", { role: "group", "aria-label": "Time", "data-slot": "date-time-picker-time", className: "flex min-w-0 flex-col gap-2 border-t p-3", children: [_jsx("p", { className:
|
|
75
|
+
}, onClear: () => emit?.(null), clearDisabled: empty, children: _jsxs("div", { role: "group", "aria-label": "Time", "data-slot": "date-time-picker-time", className: "flex min-w-0 flex-col gap-2 border-t p-3", children: [_jsx("p", { className: labelText, children: "Time" }), _jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsxs(Select, { value: date ? padTimePart(date.getHours()) : undefined, onValueChange: (nextHour) => {
|
|
75
76
|
if (!date)
|
|
76
77
|
return;
|
|
77
78
|
emit?.(combineDateAndTime(date, Number(nextHour), date.getMinutes()));
|
package/dist/dropdown-menu.js
CHANGED
|
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
|
|
5
5
|
import { CheckIcon, ChevronRightIcon } from "lucide-react";
|
|
6
|
-
import { menuLabel, optionRow, overlayFadeZoom, overlaySideSlide, overlaySurface, } from "./internal/overlay-styles.js";
|
|
6
|
+
import { menuLabel, optionRow, OVERLAY_COLLISION_PADDING, OVERLAY_SIDE_OFFSET, overlayFadeZoom, overlaySideSlide, overlaySurface, } from "./internal/overlay-styles.js";
|
|
7
7
|
import { dataDisabledState, inlineIcon, optionState, } from "./internal/styles.js";
|
|
8
8
|
import { cn } from "./utils.js";
|
|
9
9
|
/** Menu row: the shared option row with menu padding and the inset slot. */
|
|
@@ -19,7 +19,7 @@ function DropdownMenuPortal({ ...props }) {
|
|
|
19
19
|
function DropdownMenuTrigger({ ...props }) {
|
|
20
20
|
return (_jsx(DropdownMenuPrimitive.Trigger, { "data-slot": "dropdown-menu-trigger", ...props }));
|
|
21
21
|
}
|
|
22
|
-
function DropdownMenuContent({ className, align = "start", sideOffset =
|
|
22
|
+
function DropdownMenuContent({ className, align = "start", sideOffset = OVERLAY_SIDE_OFFSET, collisionPadding = OVERLAY_COLLISION_PADDING, ...props }) {
|
|
23
23
|
return (_jsx(DropdownMenuPrimitive.Portal, { children: _jsx(DropdownMenuPrimitive.Content, { "data-slot": "dropdown-menu-content", align: align, sideOffset: sideOffset, collisionPadding: collisionPadding, className: cn(menuContentClassName, "max-h-(--radix-dropdown-menu-content-available-height) origin-(--radix-dropdown-menu-content-transform-origin)", className), ...props }) }));
|
|
24
24
|
}
|
|
25
25
|
function DropdownMenuGroup({ ...props }) {
|
package/dist/field.d.ts
CHANGED
|
@@ -31,10 +31,13 @@ declare function FieldSeparator({ children, className, ...props }: React.Compone
|
|
|
31
31
|
/** Optional text shown in a gap in the middle of the line, e.g. "Or". */
|
|
32
32
|
children?: React.ReactNode;
|
|
33
33
|
}): import("react").JSX.Element;
|
|
34
|
+
/** One error as form libraries report it; `FieldError` shows its `message`. */
|
|
35
|
+
type FieldErrorItem = {
|
|
36
|
+
message?: string;
|
|
37
|
+
} | undefined;
|
|
34
38
|
declare function FieldError({ className, children, errors, ...props }: React.ComponentProps<"div"> & {
|
|
35
39
|
/** Errors to list, deduplicated by message; `children` replaces them. */
|
|
36
|
-
errors?:
|
|
37
|
-
message?: string;
|
|
38
|
-
} | undefined>;
|
|
40
|
+
errors?: FieldErrorItem[];
|
|
39
41
|
}): import("react").JSX.Element | null;
|
|
40
42
|
export { Field, FieldLabel, FieldDescription, FieldError, FieldGroup, FieldLegend, FieldSeparator, FieldSet, FieldContent, FieldTitle, };
|
|
43
|
+
export type { FieldErrorItem };
|
package/dist/field.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useMemo } from "react";
|
|
4
4
|
import { cva } from "class-variance-authority";
|
|
5
|
+
import { labelText } from "./internal/styles.js";
|
|
5
6
|
import { cn } from "./utils.js";
|
|
6
7
|
import { Label } from "./label.js";
|
|
7
8
|
import { Separator } from "./separator.js";
|
|
@@ -9,7 +10,7 @@ function FieldSet({ className, ...props }) {
|
|
|
9
10
|
return (_jsx("fieldset", { "data-slot": "field-set", className: cn("flex flex-col gap-6 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3", className), ...props }));
|
|
10
11
|
}
|
|
11
12
|
function FieldLegend({ className, variant = "legend", ...props }) {
|
|
12
|
-
return (_jsx("legend", { "data-slot": "field-legend", "data-variant": variant, className: cn("mb-3
|
|
13
|
+
return (_jsx("legend", { "data-slot": "field-legend", "data-variant": variant, className: cn("mb-3", labelText, className), ...props }));
|
|
13
14
|
}
|
|
14
15
|
/**
|
|
15
16
|
* Stack of fields and the `@container/field-group` that responsive fields
|
|
@@ -49,7 +50,7 @@ function FieldLabel({ className, ...props }) {
|
|
|
49
50
|
return (_jsx(Label, { "data-slot": "field-label", className: cn("group/field-label peer/field-label flex w-fit gap-2 leading-relaxed group-data-[disabled=true]/field:opacity-50 has-data-checked:border-primary/30 has-data-checked:bg-selected has-[>[data-slot=field]]:rounded-none has-[>[data-slot=field]]:border *:data-[slot=field]:p-4 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-selected", "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col", className), ...props }));
|
|
50
51
|
}
|
|
51
52
|
function FieldTitle({ className, ...props }) {
|
|
52
|
-
return (_jsx("div", { "data-slot": "field-label", className: cn("flex w-fit items-center gap-2
|
|
53
|
+
return (_jsx("div", { "data-slot": "field-label", className: cn(labelText, "flex w-fit items-center gap-2 group-data-[disabled=true]/field:opacity-50 in-data-[slot=field-label]:font-semibold", className), ...props }));
|
|
53
54
|
}
|
|
54
55
|
function FieldDescription({ className, ...props }) {
|
|
55
56
|
return (_jsx("p", { "data-slot": "field-description", className: cn("text-left text-sm leading-normal font-normal tracking-normal text-muted-foreground normal-case group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5", "last:mt-0 nth-last-2:-mt-1", "[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-selected-foreground", className), ...props }));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { FormFieldBaseProps, FormFieldDescriptionProps, FormFieldMetaProps } from "./field-error.js";
|
|
2
2
|
type CheckboxFieldProps = FormFieldBaseProps & FormFieldMetaProps & FormFieldDescriptionProps & {
|
|
3
3
|
/** Whether the box is checked. */
|
|
4
4
|
checked: boolean;
|
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useId } from "react";
|
|
4
4
|
import { Checkbox } from "../checkbox.js";
|
|
5
|
-
import { Field, FieldContent,
|
|
5
|
+
import { Field, FieldContent, FieldLabel, FieldLegend } from "../field.js";
|
|
6
6
|
import { cn } from "../utils.js";
|
|
7
|
-
import {
|
|
8
|
-
import { describedBy, descriptionIdFor, errorIdFor, invalidWhen, metaErrorText, } from "./field-shell.js";
|
|
7
|
+
import { fieldAria, FormFieldDescription, FormFieldFrame, metaErrorText, } from "./field-shell.js";
|
|
9
8
|
/**
|
|
10
9
|
* One boolean as a labelled checkbox, with an optional hint and the same
|
|
11
10
|
* error text and timing as the other `form/*` fields.
|
|
@@ -14,16 +13,14 @@ export function FormCheckboxField({ checked, onCheckedChange, onBlur, label, des
|
|
|
14
13
|
const generatedId = useId();
|
|
15
14
|
const id = idProp ?? generatedId;
|
|
16
15
|
const errorText = metaErrorText(meta, { submitted, showError });
|
|
17
|
-
const
|
|
18
|
-
const checkbox = (_jsx(Checkbox, { id: id, checked: checked, onCheckedChange: (next) => onCheckedChange(next === true), onBlur: onBlur, disabled: disabled, required: required, "aria-required": required || undefined, "aria-invalid": invalidWhen(errorText), "aria-describedby": describedBy(id, errorText, descriptionId), className: className }));
|
|
16
|
+
const checkbox = (_jsx(Checkbox, { id: id, checked: checked, onCheckedChange: (next) => onCheckedChange(next === true), onBlur: onBlur, disabled: disabled, required: required, ...fieldAria(id, errorText, { description, required }), className: className }));
|
|
19
17
|
const labelNode = (_jsx(FieldLabel, { htmlFor: id, required: required, children: label }));
|
|
20
|
-
return (
|
|
18
|
+
return (_jsx(FormFieldFrame, { id: id, errorText: errorText, className: fieldClassName, children: _jsxs(Field, { orientation: "horizontal", children: [controlPosition === "start" ? checkbox : null, description ? (_jsxs(FieldContent, { children: [labelNode, _jsx(FormFieldDescription, { id: id, children: description })] })) : (labelNode), controlPosition === "end" ? checkbox : null] }) }));
|
|
21
19
|
}
|
|
22
20
|
/** Several values from a short list, as a labelled set of checkboxes. */
|
|
23
21
|
export function FormCheckboxGroupField({ label, description, options, value, onValueChange, meta, submitted, showError, disabled, className, fieldClassName, }) {
|
|
24
22
|
const id = useId();
|
|
25
23
|
const errorText = metaErrorText(meta, { submitted, showError });
|
|
26
|
-
const descriptionId = description ? descriptionIdFor(id) : undefined;
|
|
27
24
|
function toggle(option, on) {
|
|
28
25
|
const next = new Set(value);
|
|
29
26
|
if (on)
|
|
@@ -32,8 +29,8 @@ export function FormCheckboxGroupField({ label, description, options, value, onV
|
|
|
32
29
|
next.delete(option);
|
|
33
30
|
onValueChange(options.map((o) => o.value).filter((v) => next.has(v)));
|
|
34
31
|
}
|
|
35
|
-
return (
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
return (_jsx(FormFieldFrame, { asChild: true, id: id, errorText: errorText, className: fieldClassName, children: _jsxs("fieldset", { ...fieldAria(id, errorText, { description }), children: [_jsx(FieldLegend, { variant: "label", className: "mb-0", children: label }), description ? (_jsx(FormFieldDescription, { id: id, children: description })) : null, _jsx("div", { "data-slot": "form-checkbox-group-options", className: cn("flex flex-wrap gap-x-6 gap-y-3 pt-1", className), children: options.map((option) => {
|
|
33
|
+
const optionId = `${id}-${option.value}`;
|
|
34
|
+
return (_jsxs(Field, { orientation: "horizontal", children: [_jsx(Checkbox, { id: optionId, checked: value.includes(option.value), onCheckedChange: (next) => toggle(option.value, next === true), disabled: disabled }), _jsx(FieldLabel, { htmlFor: optionId, children: option.label })] }, option.value));
|
|
35
|
+
}) })] }) }));
|
|
39
36
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useId } from "react";
|
|
4
4
|
import { Combobox } from "../combobox.js";
|
|
5
|
-
import {
|
|
5
|
+
import { fieldAria, FormFieldShell, metaErrorText } from "./field-shell.js";
|
|
6
6
|
/**
|
|
7
7
|
* `Combobox` with a label, optional hint, and the `form/*` error text and
|
|
8
8
|
* timing. Pass the field's blur handler as `onBlur` so touch state updates.
|
|
@@ -10,5 +10,5 @@ import { describedBy, descriptionIdFor, FormFieldShell, invalidWhen, metaErrorTe
|
|
|
10
10
|
export function FormComboboxField({ label, description, meta, submitted, showError, required, fieldClassName, ...props }) {
|
|
11
11
|
const id = useId();
|
|
12
12
|
const errorText = metaErrorText(meta, { submitted, showError });
|
|
13
|
-
return (_jsx(FormFieldShell, { id: id, label: label, description: description, errorText: errorText, required: required, className: fieldClassName, children: _jsx(Combobox, { ...props, id: id,
|
|
13
|
+
return (_jsx(FormFieldShell, { id: id, label: label, description: description, errorText: errorText, required: required, className: fieldClassName, children: _jsx(Combobox, { ...props, id: id, ...fieldAria(id, errorText, { description, required }) }) }));
|
|
14
14
|
}
|