atlas-react-kit 0.13.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/COMPONENTS.md +144 -120
  2. package/DESIGN_RULES.md +53 -32
  3. package/README.md +12 -53
  4. package/dist/accordion.d.ts +1 -0
  5. package/dist/avatar.d.ts +1 -0
  6. package/dist/badge.d.ts +2 -3
  7. package/dist/breadcrumb.d.ts +2 -3
  8. package/dist/button.d.ts +2 -3
  9. package/dist/calendar.d.ts +2 -0
  10. package/dist/card.d.ts +3 -0
  11. package/dist/chart.d.ts +152 -0
  12. package/dist/chart.js +177 -0
  13. package/dist/color-legend.d.ts +8 -0
  14. package/dist/combobox.d.ts +13 -4
  15. package/dist/combobox.js +1 -8
  16. package/dist/date-picker.d.ts +26 -3
  17. package/dist/date-picker.js +37 -31
  18. package/dist/date-time-picker.d.ts +5 -2
  19. package/dist/date-time-picker.js +25 -38
  20. package/dist/dialog.d.ts +1 -0
  21. package/dist/dialog.js +4 -2
  22. package/dist/dropdown-menu.d.ts +9 -8
  23. package/dist/empty-panel.d.ts +8 -2
  24. package/dist/empty-panel.js +2 -2
  25. package/dist/error-boundary.d.ts +6 -1
  26. package/dist/field.d.ts +3 -0
  27. package/dist/field.js +1 -1
  28. package/dist/form/checkbox-field.d.ts +45 -0
  29. package/dist/form/checkbox-field.js +39 -0
  30. package/dist/form/combobox-field.d.ts +13 -0
  31. package/dist/form/combobox-field.js +14 -0
  32. package/dist/form/control-field.d.ts +6 -7
  33. package/dist/form/date-picker-field.d.ts +12 -21
  34. package/dist/form/date-picker-field.js +5 -4
  35. package/dist/form/date-time-picker-field.d.ts +7 -22
  36. package/dist/form/date-time-picker-field.js +4 -3
  37. package/dist/form/feedback-field.d.ts +2 -0
  38. package/dist/form/field-error.d.ts +35 -3
  39. package/dist/form/field-shell.d.ts +35 -6
  40. package/dist/form/field-shell.js +1 -1
  41. package/dist/form/focus-first-invalid.d.ts +28 -0
  42. package/dist/form/focus-first-invalid.js +29 -0
  43. package/dist/form/markdown-field.d.ts +4 -16
  44. package/dist/form/select-field.d.ts +13 -7
  45. package/dist/form/select-field.js +3 -2
  46. package/dist/form/text-field.d.ts +29 -10
  47. package/dist/form/text-field.js +4 -0
  48. package/dist/form/toggle-group-field.d.ts +30 -0
  49. package/dist/form/toggle-group-field.js +19 -0
  50. package/dist/info-tip.d.ts +10 -1
  51. package/dist/internal/as-child.d.ts +8 -0
  52. package/dist/internal/as-child.js +1 -0
  53. package/dist/internal/form-control.d.ts +3 -2
  54. package/dist/internal/form-control.js +3 -2
  55. package/dist/internal/kanban-layout.d.ts +55 -0
  56. package/dist/internal/kanban-layout.js +80 -0
  57. package/dist/internal/kanban-plugins.d.ts +17 -0
  58. package/dist/internal/kanban-plugins.js +17 -0
  59. package/dist/internal/listbox-navigation.d.ts +0 -11
  60. package/dist/internal/listbox-navigation.js +2 -2
  61. package/dist/internal/listbox-option.d.ts +0 -15
  62. package/dist/internal/listbox-option.js +1 -1
  63. package/dist/internal/use-drag-scroll.d.ts +17 -0
  64. package/dist/internal/use-drag-scroll.js +98 -0
  65. package/dist/internal/use-media-query.d.ts +4 -2
  66. package/dist/internal/use-media-query.js +11 -15
  67. package/dist/kanban.d.ts +84 -0
  68. package/dist/kanban.js +197 -0
  69. package/dist/markdown.d.ts +2 -0
  70. package/dist/meter.d.ts +7 -0
  71. package/dist/nav-link.d.ts +15 -6
  72. package/dist/nav-link.js +14 -4
  73. package/dist/next/nav-link.d.ts +6 -3
  74. package/dist/next/nav-link.js +8 -6
  75. package/dist/next/period-stepper.d.ts +11 -0
  76. package/dist/next/period-stepper.js +16 -0
  77. package/dist/next/tabs-nav-link.d.ts +6 -0
  78. package/dist/next/tabs-nav-link.js +8 -3
  79. package/dist/page-header.d.ts +19 -0
  80. package/dist/page-header.js +11 -0
  81. package/dist/pagination.d.ts +2 -0
  82. package/dist/period-stepper.d.ts +32 -0
  83. package/dist/period-stepper.js +27 -0
  84. package/dist/picker-shell.d.ts +33 -18
  85. package/dist/picker-shell.js +12 -10
  86. package/dist/progress.d.ts +1 -0
  87. package/dist/search-picker.d.ts +12 -0
  88. package/dist/select.d.ts +1 -0
  89. package/dist/skip-link.d.ts +7 -0
  90. package/dist/skip-link.js +10 -0
  91. package/dist/slider.d.ts +11 -0
  92. package/dist/sortable-table-head.d.ts +2 -0
  93. package/dist/stat-tile.d.ts +5 -0
  94. package/dist/table-body-skeleton.d.ts +2 -0
  95. package/dist/table.d.ts +1 -0
  96. package/dist/tabs.d.ts +27 -12
  97. package/dist/tabs.js +23 -10
  98. package/dist/toast.d.ts +7 -1
  99. package/dist/toggle-group.d.ts +9 -2
  100. package/dist/toggle-group.js +2 -1
  101. package/package.json +72 -6
  102. package/skills/atlas-react-kit/SKILL.md +46 -40
  103. package/theme.css +13 -0
package/COMPONENTS.md CHANGED
@@ -20,83 +20,94 @@ Core entries need only the kit. These feature entries need an extra install
20
20
  (same version ranges as the kit's optional peers). `Toaster` also reads the
21
21
  theme from `next-themes`, so toast consumers install that too.
22
22
 
23
- | Entry points | Extra install |
24
- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------ |
25
- | `calendar`, `date-picker`, `date-time-picker`, `form/date-picker-field`, `form/date-time-picker-field` | `npm i react-day-picker@^10.0.1 date-fns@^4.4.0` |
26
- | `markdown`, `form/markdown-field` | `npm i react-markdown@^10.1.0 remark-gfm@^4.0.1` |
27
- | `theme-provider` | `npm i next-themes@^0.4.6` |
28
- | `toast` | `npm i sonner@^2.0.7 next-themes@^0.4.6` |
29
- | `next/nav-link`, `next/tabs-nav-link` | `npm i next@^16.0.0` |
30
-
31
- | Subpath | Main exports and options |
32
- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
33
- | `accordion` | `Accordion`, `AccordionItem`, `AccordionTrigger`, `AccordionContent`; Radix `type` `single` (with `collapsible`) or `multiple` |
34
- | `alert` | `Alert`, `AlertTitle`, `AlertDescription`, `alertVariants`; `variant` `default`, `success`, `warning`, `destructive`; tone on title and icon, body stays muted; destructive is `role="alert"` |
35
- | `avatar` | `Avatar`, `AvatarImage`, `AvatarFallback`; `size` `sm`, `default`, `lg`; round by design |
36
- | `breadcrumb` | `Breadcrumb`, `BreadcrumbList`, `BreadcrumbItem`, `BreadcrumbLink` (`asChild` for router links), `BreadcrumbPage` (`aria-current`), `BreadcrumbSeparator`, `BreadcrumbEllipsis` |
37
- | `button` | `Button`, `buttonVariants`, `quietButtonInteraction`; `variant`, `size`, `asChild`; outline/ghost hover is brand purple + selected wash, not muted grey |
38
- | `badge` | `Badge`, `badgeVariants`; `variant`, `tone`, `asChild` |
39
- | `calendar` | `Calendar`, `CalendarDayButton`; React Day Picker props and `buttonVariant` |
40
- | `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 `!` |
41
- | `checkbox` | `Checkbox` |
42
- | `color-legend` | `ColorLegend`; `title`, `items` as `{ color, range?, label }[]`, optional `caption`, `children`, `aria-label`, `className` (overlay chrome). No attribution. |
43
- | `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 |
44
- | `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`; 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` |
45
- | `date-time-picker` | `DateTimePickerButton`, `resolveTimeStep`, `DEFAULT_TIME_STEP`; controlled `value` is `Date` or `null`; `onChange` emits `Date \ |
46
- | `dialog` | `Dialog`, trigger, portal, overlay, content, close, header, footer, title, description; `DialogContent` supports `showCloseButton` |
47
- | `dropdown-menu` | `DropdownMenu` family (trigger, content, item, checkbox/radio items, label, separator, shortcut, sub menus); item `variant="destructive"` and `inset`; for commands, not values |
48
- | `empty-panel` | `EmptyPanel`; required `children`; optional `size`, `muted`, `className` |
49
- | `error-boundary` | `ErrorBoundary`, `ErrorFallback`; optional `message`; retry resets, reload refreshes the page |
50
- | `field` | Field, label, legend, description, group, set, title, content, separator, error; `Field` supports `orientation` |
51
- | `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. |
52
- | `input`, `textarea`, `label` | `Input`, `Textarea`, `Label`; `Label` `required` shows a visible marker (set `required` on the control too) |
53
- | `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. |
54
- | `meter` | `Meter`; `value`, `max`, optional `min` (default 0), `fillClassName`, `markers` as `{ position, className? }[]`, required `aria-label`, `className`. `role="meter"`. Not `Progress`. |
55
- | `nav-link` | `NavLink`, `navLinkVariants`; required `active` boolean, `asChild` for router links. Pill chrome: inactive `text-muted-foreground` + `hover:bg-accent`, active selected wash. No router. |
56
- | `pagination` | `Pagination`, `PaginationContent`, `PaginationItem`, `PaginationLink` (`isActive` sets `aria-current`, the selected wash, and a selected border; `disabled` drops the `href` for a page that does not exist, such as Previous on page 1), `PaginationPrevious`, `PaginationNext` (custom text becomes the link name), `PaginationEllipsis` |
57
- | `popover` | `Popover`, trigger, anchor, content, header, title, description |
58
- | `progress` | `Progress`; Radix props, `value` 0–100; label it with `aria-label` or `aria-labelledby`. For a value against a scale with ticks, use `Meter`. |
59
- | `radio-group` | `RadioGroup`, `RadioGroupItem`; short visible lists; arrow keys move the selection; round dots |
60
- | `search-picker` | `SearchPicker<T>`; always-visible combobox + listbox; `items`, `getValue`, `getLabel`, optional `filter` (default casefold + accent-fold includes), `value`, `onSelect`, `label`, `placeholder`, `emptyText`, `listLabel`, `disabled`, `className`. Arrows, Home/End, PageUp/PageDown, Enter, Escape. |
61
- | `select` | Select, trigger, value, content, group, label, item, separator, scroll buttons; trigger `size`; trigger text is `text-base md:text-sm` like Input |
62
- | `separator`, `skeleton` | `Separator`, `Skeleton` |
63
- | `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`. |
64
- | `sortable-table-head` | `SortableTableHead`; `sorted`, `direction` (`asc` \ |
65
- | `spinner` | `Spinner`; `role="status"` with `aria-label` (default `Loading`) |
66
- | `stat-tile` | `StatTile`, `statTileGrid`, `sectionLabel`; `label`, `value`, optional `sub`, `children`, `className`. |
67
- | `switch` | `Switch`; settings that apply immediately; square track and thumb |
68
- | `table` | Table, header, body, footer, row, head, cell, caption; root scroller is `min-w-0 overflow-x-auto` with a fade cue; cells are `px-2` below `sm` |
69
- | `table-body-skeleton` | `TableBodySkeleton`; `columns`, optional `rows` (default 5) |
70
- | `tabs` | `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent`, `TabsNav`, `tabsListVariants`, `tabsTriggerVariants`; list `variant` is `default` or `line`; style `TabsNav` links with `tabsTriggerVariants()`; `TabsNav` / `TabsList` scroll horizontally only (`overflow-y` hidden, `p-1` counted in the scroller height, edge fade, active item into view) — do not wrap them in `overflow-x-auto` |
71
- | `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 |
72
- | `toggle` | `Toggle`, `toggleVariants`, `selectedToggleChrome`; `variant`, `size`. Pressed state is the selected wash, text, and border via `data-state=on`, `aria-pressed`, and `aria-checked` |
73
- | `toggle-group` | `ToggleGroup`, `ToggleGroupItem`; `variant`, `size`. Items share `toggleVariants`, so single (radio) and multiple selections match a pressed `Toggle` |
74
- | `theme-provider` | `ThemeProvider`, `useTheme`; optional `storageKey` (default `atlas-theme`), `enableShortcut` for the `d` toggle |
75
- | `tooltip` | `Tooltip`, `TooltipTrigger`, `TooltipContent`, `TooltipProvider`; opens on hover and focus; short labels only |
76
- | `form/control-field` | `FormControlField`, type `FormControlProps`; `label`, optional `id`, `description`, `required`, `meta`, `submitted`, `showError`; `children` renders the control from `{ id, aria-invalid, aria-describedby, aria-required }` |
77
- | `form/markdown-field` | `FormMarkdownField`; `field` is `TextFieldApi`; `label`, optional `id`, `placeholder`, `rows`, `showError`, `required`. Write / preview; preview uses `Markdown`. |
78
- | `form/text-field` | `FormTextField`, type `TextFieldApi`; controlled field, label, multiline, description, input constraints; `required` also marks the label |
79
- | `form/select-field` | `FormSelectField`; `value`, `onValueChange`, `label`, optional `meta`, placeholder, and `required`; select item children |
80
- | `form/date-picker-field` | `FormDatePickerField`; `value`, `onValueChange`, `label`, optional `meta`, placeholder, `required`, `minDate` / `maxDate`, and `className` (forwarded to the trigger) |
81
- | `form/date-time-picker-field` | `FormDateTimePickerField`; `value` is `Date` or `null`; `onValueChange` emits `Date` or `null`; optional `meta`, placeholder, `timeStep`, `required`, `minDate` / `maxDate`, and `className` (forwarded to the trigger) |
82
- | `form/field-error` | `FieldError` (optional `id`), `getFieldErrorMessage`; types `FieldErrorMessage`, `FieldMetaState`, `ErrorTimingProps` |
83
- | `form/feedback-field` | `FormFeedbackField`; nullable `message` |
84
- | `next/nav-link` | `NavLink`; Next Link props and required `active` boolean; same pill as `nav-link`; requires Next.js 16 |
85
- | `next/tabs-nav-link` | `TabsNavLink`; Next Link props and required `active` boolean; requires Next.js 16 |
86
- | `utils` | `cn`, `onInputChange` |
87
- | `theme.css` | Tailwind 4 theme; installation in [README](./README.md) |
23
+ | Entry points | Extra install |
24
+ | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------- |
25
+ | `calendar`, `date-picker`, `date-time-picker`, `form/date-picker-field`, `form/date-time-picker-field` | `npm i react-day-picker@^10.0.1 date-fns@^4.4.0` |
26
+ | `markdown`, `form/markdown-field` | `npm i react-markdown@^10.1.0 remark-gfm@^4.0.1` |
27
+ | `kanban` | `npm i @dnd-kit/react@^0.4.0 @dnd-kit/dom@^0.4.0` |
28
+ | `theme-provider` | `npm i next-themes@^0.4.6` |
29
+ | `toast` | `npm i sonner@^2.0.7 next-themes@^0.4.6` |
30
+ | `next/nav-link`, `next/period-stepper`, `next/tabs-nav-link` | `npm i next@^16.0.0` |
31
+
32
+ | Subpath | Main exports and options |
33
+ | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
34
+ | `accordion` | `Accordion`, `AccordionItem`, `AccordionTrigger`, `AccordionContent`; Radix `type` `single` (with `collapsible`) or `multiple` |
35
+ | `alert` | `Alert`, `AlertTitle`, `AlertDescription`, `alertVariants`; `variant` `default`, `success`, `warning`, `destructive`; tone on title and icon, body stays muted; destructive is `role="alert"` |
36
+ | `avatar` | `Avatar`, `AvatarImage`, `AvatarFallback`; `size` `sm`, `default`, `lg`; round by design |
37
+ | `breadcrumb` | `Breadcrumb`, `BreadcrumbList`, `BreadcrumbItem`, `BreadcrumbLink` (`asChild` for router links), `BreadcrumbPage` (`aria-current`), `BreadcrumbSeparator`, `BreadcrumbEllipsis` |
38
+ | `button` | `Button`, `buttonVariants`, `quietButtonInteraction`; `variant`, `size`, `asChild`; outline/ghost hover is brand purple + selected wash, not muted grey |
39
+ | `badge` | `Badge`, `badgeVariants`; `variant`, `tone`, `asChild` |
40
+ | `calendar` | `Calendar`, `CalendarDayButton`; React Day Picker props and `buttonVariant` |
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
+ | `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
+ | `checkbox` | `Checkbox` |
44
+ | `color-legend` | `ColorLegend`; `title`, `items` as `{ color, range?, label }[]`, optional `caption`, `children`, `aria-label`, `className` (overlay chrome). No attribution. |
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 \ |
48
+ | `dialog` | `Dialog`, trigger, portal, overlay, content, close, header, footer, title, description; `DialogContent` supports `showCloseButton` |
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` |
51
+ | `error-boundary` | `ErrorBoundary`, `ErrorFallback`; optional `message`; retry resets, reload refreshes the page |
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. |
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
+ | `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
+ | `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. |
57
+ | `meter` | `Meter`; `value`, `max`, optional `min` (default 0), `fillClassName`, `markers` as `{ position, className? }[]`, required `aria-label`, `className`. `role="meter"`. Not `Progress`. |
58
+ | `nav-link` | `NavLink`, `navLinkVariants`, `isNavActive(pathname, href)` (true on the path or a page under it); required `active` boolean, `asChild` for router links. Pill chrome: inactive `text-muted-foreground` + `hover:bg-accent`, active selected wash. No router. |
59
+ | `page-header` | `PageHeader`; `title`, optional `description`, `actions`, `children` (under the description), `titleAs` (`h1` default), `titleClassName`. Stacks below `sm`. |
60
+ | `pagination` | `Pagination`, `PaginationContent`, `PaginationItem`, `PaginationLink` (`isActive` sets `aria-current`, the selected wash, and a selected border; `disabled` drops the `href` for a page that does not exist, such as Previous on page 1), `PaginationPrevious`, `PaginationNext` (custom text becomes the link name), `PaginationEllipsis` |
61
+ | `period-stepper` | `PeriodStepper`; `label` (announced with `aria-live`), `stepLabel` ("month" names "Previous month"), `onPrevious`, `onNext`, optional `onToday` (no Today button without it), `labelClassName`. `PeriodStepperFrame` takes a `control(step, content)` render for another router's links. `next/period-stepper` takes `previousHref`, `todayHref`, `nextHref` instead. |
62
+ | `popover` | `Popover`, trigger, anchor, content, header, title, description |
63
+ | `progress` | `Progress`; Radix props, `value` 0–100; label it with `aria-label` or `aria-labelledby`. For a value against a scale with ticks, use `Meter`. |
64
+ | `radio-group` | `RadioGroup`, `RadioGroupItem`; short visible lists; arrow keys move the selection; round dots |
65
+ | `search-picker` | `SearchPicker<T>`; always-visible combobox + listbox; `items`, `getValue`, `getLabel`, optional `filter` (default casefold + accent-fold includes), `value`, `onSelect`, `label`, `placeholder`, `emptyText`, `listLabel`, `disabled`, `className`. Arrows, Home/End, PageUp/PageDown, Enter, Escape. |
66
+ | `select` | Select, trigger, value, content, group, label, item, separator, scroll buttons; trigger `size`; trigger text is `text-base md:text-sm` like Input |
67
+ | `separator`, `skeleton` | `Separator`, `Skeleton` |
68
+ | `skip-link` | `SkipLink`; `href` (default `#main-content`), children (default "Skip to content"). Visually hidden until focused. |
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`, `direction` (`asc` \ |
71
+ | `spinner` | `Spinner`; `role="status"` with `aria-label` (default `Loading`) |
72
+ | `stat-tile` | `StatTile`, `statTileGrid`, `sectionLabel`; `label`, `value`, optional `sub`, `children`, `className`. |
73
+ | `switch` | `Switch`; settings that apply immediately; square track and thumb |
74
+ | `table` | Table, header, body, footer, row, head, cell, caption; root scroller is `min-w-0 overflow-x-auto` with a fade cue; cells are `px-2` below `sm` |
75
+ | `table-body-skeleton` | `TableBodySkeleton`; `columns`, optional `rows` (default 5) |
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
+ | `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
+ | `toggle` | `Toggle`, `toggleVariants`, `selectedToggleChrome`; `variant`, `size`. 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` |
80
+ | `theme-provider` | `ThemeProvider`, `useTheme`; optional `storageKey` (default `atlas-theme`), `enableShortcut` for the `d` toggle |
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 `id`, `description`, `required`, `meta`, `submitted`, `showError`; `children` renders the control from `{ id, aria-invalid, aria-describedby, aria-required }` |
85
+ | `form/markdown-field` | `FormMarkdownField`; `field` is `TextFieldApi`; `label`, optional `id`, `placeholder`, `rows`, `showError`, `required`. Write / preview; preview uses `Markdown`. |
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 `id` (default generated; pass the field name for `focusFirstInvalid`), `meta`, placeholder, and `required`; select item children |
88
+ | `form/date-picker-field` | `FormDatePickerField`; `value`, `onValueChange`, `label`, optional `id` (default generated), `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 `id` (default generated), `meta`, placeholder, `timeStep`, `required`, `minDate` / `maxDate`, and `className` (forwarded to the trigger) |
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 (matched by control id, so pass `id={field.name}`) has errors; type `InvalidSubmitEvent` |
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`; requires Next.js 16 |
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`; requires Next.js 16 |
97
+ | `utils` | `cn`, `onInputChange` |
98
+ | `theme.css` | Tailwind 4 theme; installation in [README](./README.md) |
88
99
 
89
100
  ## Status
90
101
 
91
102
  Components are **stable** unless listed here. Stable components change
92
103
  through the deprecation policy in
93
- [CONTRIBUTING.md](https://github.com/Kerubi-5/atlas-design-system/blob/main/CONTRIBUTING.md):
94
- a deprecation notice first, removal in a later minor release.
104
+ [CONTRIBUTING.md](https://github.com/Kerubi-5/atlas-design-system/blob/main/CONTRIBUTING.md#deprecations).
95
105
 
96
106
  **Beta** components may still change shape in a minor release, before and
97
107
  after 1.0; they are exempt from semver, and the changelog calls out every
98
- change: `accordion`, `avatar`, `breadcrumb`, `dropdown-menu`, `nav-link`,
99
- `pagination`, `radio-group`, `spinner`, `switch`.
108
+ change: `accordion`, `avatar`, `breadcrumb`, `chart`, `dropdown-menu`,
109
+ `kanban`, `nav-link`, `page-header`, `pagination`, `period-stepper`,
110
+ `radio-group`, `skip-link`, `spinner`, `switch`.
100
111
 
101
112
  ## Styling parts
102
113
 
@@ -105,27 +116,31 @@ through to it. Where a part sits inside a component, a prop names it, and
105
116
  each part carries a `data-slot` for parent selectors such as
106
117
  `*:data-[slot=stat-tile-value]:text-2xl`.
107
118
 
108
- | Component | Part props | Slots |
109
- | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
110
- | `DialogContent` | `overlayClassName`, `closeButtonClassName` | `dialog-close-button` (the built-in X; your own `DialogClose` keeps `dialog-close`) |
111
- | `SelectTrigger` | a `className` height replaces `size` | `select-icon`, `select-viewport`, `select-item-indicator` |
112
- | `TooltipContent` | `arrowClassName` | `tooltip-arrow` |
113
- | `Combobox` | `contentClassName` (panel, default trigger width), `listClassName`; button props go to trigger | `combobox-search`, `combobox-list`, `combobox-option` |
114
- | `SearchPicker` | `listClassName`; div props go to the root | `search-picker-option`, `search-picker-empty` |
115
- | `InfoTip` | `contentClassName`; button props go to the trigger | |
116
- | `Label`, `FieldLabel` | `requiredClassName` | `label-required` |
117
- | `DatePickerButton`, `DateTimePickerButton` | `contentClassName`, `calendarProps` (`disabled` / `hidden` days, `startMonth`, `endMonth`, `weekStartsOn`, date-fns `locale`, `showOutsideDays`, `fixedWeeks`, `showWeekNumber`, `captionLayout`, `today`, `className`); `minDate` / `maxDate` add to its disabled days | `date-picker-value`, `date-picker-footer`, `date-time-picker-time` |
118
- | `form/*` fields | `fieldClassName` (wrapper); `className` styles the control (on `FormControlField`, the wrapper); `FormSelectField` `contentClassName`; `FormMarkdownField` `previewClassName` | `form-field`, `form-field-header`, `form-field-description`, `form-field-error`, `form-feedback`, `form-markdown-preview` |
119
- | `Table` | `containerClassName` (the scroll box, e.g. `max-h-96 overflow-y-auto`); `className` styles the `<table>` | |
120
- | `TabsList`, `TabsNav` | `scrollerClassName`; `TabsNav` `listClassName`; a list `className` height replaces `h-11` | `tabs-scroller` |
121
- | `SortableTableHead` | `buttonClassName` | `sortable-table-head-button` |
122
- | `AccordionTrigger`, `AccordionContent` | `headerClassName`; `AccordionContent` `className` styles the inner box | `accordion-header`, `accordion-content-inner` |
123
- | `Progress` | `indicatorClassName` | `progress-indicator` |
124
- | `Meter` | `fillClassName` | `meter-fill`, `meter-marker` |
125
- | `StatTile` | | `stat-tile-label`, `stat-tile-value`, `stat-tile-sub` |
126
- | `ColorLegend` | | `color-legend-title`, `-list`, `-item`, `-swatch`, `-range`, `-label`, `-caption` |
127
- | `RadioGroupItem` | | `radio-group-dot` |
128
- | `EmptyPanel`, `ErrorFallback`, `TableBodySkeleton` | `className` | `empty-panel`, `error-boundary` |
119
+ | Component | Part props | Slots |
120
+ | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
121
+ | `DialogContent` | `overlayClassName`, `closeButtonClassName` | `dialog-close-button` (the built-in X; your own `DialogClose` keeps `dialog-close`) |
122
+ | `SelectTrigger` | a `className` height replaces `size` | `select-icon`, `select-viewport`, `select-item-indicator` |
123
+ | `TooltipContent` | `arrowClassName` | `tooltip-arrow` |
124
+ | `Combobox` | `contentClassName` (panel, default trigger width), `listClassName` (default `max-h-60`); button props go to trigger | `combobox-search`, `combobox-list`, `combobox-option` |
125
+ | `SearchPicker` | `listClassName` (default `max-h-60`); div props go to the root | `search-picker-option`, `search-picker-empty` |
126
+ | `InfoTip` | `contentClassName` (default `w-72`); button props go to the trigger | |
127
+ | `KanbanBoard` | `scrollerClassName`, `columnClassName` (default `w-72 min-w-72` and a drop-area min height), `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` |
128
+ | `chart` pieces | `ChartTooltip` and `ChartLegend` take div props; `ChartLegendKey` `className` | `chart-y-axis`, `chart-tooltip`, `chart-tooltip-row`, `chart-legend`, `chart-legend-key` |
129
+ | `PageHeader`, `PeriodStepper`, `SkipLink` | `PageHeader` `titleClassName`; `PeriodStepper` `labelClassName` | `page-header`, `page-header-heading`, `page-header-title`, `page-header-description`, `page-header-actions`, `period-stepper`, `period-stepper-label`, `skip-link` |
130
+ | `FormCheckboxGroupField` | `className` (the options box), `fieldClassName` (the fieldset) | `form-checkbox-group-options` |
131
+ | `Label`, `FieldLabel` | `requiredClassName` | `label-required` |
132
+ | `DatePickerButton`, `DateTimePickerButton` | `contentClassName`, `calendarProps` (`disabled` / `hidden` days, `startMonth`, `endMonth`, `weekStartsOn`, date-fns `locale`, `showOutsideDays`, `fixedWeeks`, `showWeekNumber`, `captionLayout`, `today`, `className`); `minDate` / `maxDate` add to its disabled days | `date-picker-value`, `date-picker-footer`, `date-time-picker-time` |
133
+ | `form/*` fields | `fieldClassName` (wrapper); `className` styles the control (on `FormControlField`, the wrapper); `FormSelectField` `contentClassName`; `FormMarkdownField` `previewClassName` | `form-field`, `form-field-header`, `form-field-description`, `form-field-error`, `form-feedback`, `form-markdown-preview` |
134
+ | `Table` | `containerClassName` (the scroll box, e.g. `max-h-96 overflow-y-auto`); `className` styles the `<table>` | |
135
+ | `TabsList`, `TabsNav` | `scrollerClassName`; `TabsNav` `listClassName`; a list `className` height replaces `h-11`; `TabsNav` sizes to its links in a flex row, so add `w-full` to stretch it | `tabs-scroller` |
136
+ | `SortableTableHead` | `buttonClassName` | `sortable-table-head-button` |
137
+ | `AccordionTrigger`, `AccordionContent` | `headerClassName`; `AccordionContent` `className` styles the inner box | `accordion-header`, `accordion-content-inner` |
138
+ | `Progress` | `indicatorClassName` | `progress-indicator` |
139
+ | `Meter` | `fillClassName` | `meter-fill`, `meter-marker` |
140
+ | `StatTile` | | `stat-tile-label`, `stat-tile-value`, `stat-tile-sub` |
141
+ | `ColorLegend` | | `color-legend-title`, `-list`, `-item`, `-swatch`, `-range`, `-label`, `-caption` |
142
+ | `RadioGroupItem` | | `radio-group-dot` |
143
+ | `EmptyPanel`, `ErrorFallback`, `TableBodySkeleton` | `className` | `empty-panel`, `error-boundary` |
129
144
 
130
145
  ## Forms
131
146
 
@@ -153,7 +168,11 @@ for a Write / Preview GFM body. `FormSelectField`, `FormDatePickerField`, and
153
168
  `FormDateTimePickerField` accept the same error meta shape.
154
169
  `FormDatePickerField` takes a `Date` or `yyyy-MM-dd` string and calls
155
170
  `onValueChange` with `Date | undefined`. `FormDateTimePickerField` takes a
156
- `Date` or `null` and calls `onValueChange` with `Date | null`. `FieldError`
171
+ `Date` or `null` and calls `onValueChange` with `Date | null`. Pass
172
+ `id={field.name}` to the select and date fields (the text field defaults to
173
+ it) so a field is addressable by name, for example by `focusFirstInvalid`
174
+ from `form/focus-first-invalid`, which focuses the first field with errors
175
+ when a submit fails. `FieldError`
157
176
  accepts either `message` or pre-resolved `text`, and an `id` to point the
158
177
  control's `aria-describedby` at. Every `form/*` field does that for you: the
159
178
  error text (and `FormTextField`'s `description`) is the control's accessible
@@ -163,12 +182,14 @@ part of its name.
163
182
 
164
183
  Errors wait for the person. When `meta.isTouched` is `false`, a `form/*`
165
184
  field hides its error text and invalid styling until the field is touched or
166
- its `submitted` prop is set (TanStack Form:
167
- `form.state.submissionAttempts > 0`). Pass TanStack's `field.state.meta` as
168
- is and this works on its own; meta without `isTouched` shows errors as soon
169
- as they exist, as before. `showError={false}` always hides them. Every
170
- `form/*` field, `FormSelectField` and both date fields included, takes
171
- `submitted` and `showError`.
185
+ its `submitted` prop is set. Pass TanStack Form's `field.state.meta` as is
186
+ and this works on its own: TanStack marks every mounted field touched when
187
+ the form is submitted, so untouched fields show their errors then without
188
+ `submitted`. `submitted` is for form libraries that do not touch fields on
189
+ submit. Meta without `isTouched` shows errors as soon as they exist.
190
+ `showError={false}` always hides them. Every `form/*` field,
191
+ `FormSelectField` and both date fields included, takes `submitted` and
192
+ `showError`.
172
193
 
173
194
  `FormControlField` gives a control the kit does not ship the same label,
174
195
  required marker, hint, error, ids, and error timing. `children` is a
@@ -202,12 +223,17 @@ draws the same marker; set `required` on the control yourself.
202
223
 
203
224
  `DatePickerButton` is single-day by default. For a from/to range, set
204
225
  `mode="range"`; `value` is `{ from, to }` (each side `Date` or `yyyy-MM-dd`)
205
- and `onChange` receives `DateRange | undefined`. The trigger uses the same
206
- form-control chrome as `Input` and `SelectTrigger`. The range popover shows
207
- one month below `md` and two months at `md+` unless `numberOfMonths` is set,
226
+ and `onValueChange` receives `DateRange | undefined`. The trigger uses the same
227
+ form-control chrome as `Input` and `SelectTrigger`. Long picker values wrap
228
+ inside narrow triggers; ordinary one-line values stay 40px tall, and consumer
229
+ height classes still override that default. The range popover shows one
230
+ month below `md` and two months at `md+` unless `numberOfMonths` is set,
208
231
  clamps to the viewport with collision padding and scroll, stays `w-auto` so
209
232
  it does not collapse to the default popover width, and includes a Clear
210
- action. Pass `side` to prefer a placement; Radix still flips on collision.
233
+ action. In single mode, `clearable` adds the same Clear row (emits
234
+ `undefined`, closes the popover) and keeps the selected day on a second
235
+ click; without it, a second click on the selected day clears it. Pass
236
+ `side` to prefer a placement; Radix still flips on collision.
211
237
  `minDate` and `maxDate` (each `Date` or `yyyy-MM-dd`) disable the days
212
238
  outside them, on top of any `calendarProps.disabled`, and stop month paging
213
239
  there unless `calendarProps` sets `startMonth` / `endMonth`. They limit the
@@ -220,8 +246,11 @@ the trigger text's id (a `<label for>` alone names the button by the label
220
246
  only).
221
247
  `FormDatePickerField` forwards `className` to that trigger.
222
248
 
249
+ Both pickers took `onChange` before 1.0; it still works as a deprecated
250
+ alias until 2.0.
251
+
223
252
  `DateTimePickerButton` is the date-and-time control. `value` is a `Date` or
224
- `null`; `onChange` receives `Date | null`. The trigger matches the same
253
+ `null`; `onValueChange` receives `Date | null`. The trigger matches the same
225
254
  form-control chrome. The popover is one month, a labelled Hour and Minute
226
255
  pair (24-hour, stepped by `timeStep`, default 15), and Clear. Picking a day
227
256
  keeps the popover open so the time can be set; the time selects stay
@@ -242,15 +271,15 @@ import { DateTimePickerButton } from "atlas-react-kit/date-time-picker"
242
271
 
243
272
  export function EventStarts({
244
273
  value,
245
- onChange,
274
+ onValueChange,
246
275
  }: {
247
276
  value: Date | null
248
- onChange: (date: Date | null) => void
277
+ onValueChange: (date: Date | null) => void
249
278
  }) {
250
279
  return (
251
280
  <DateTimePickerButton
252
281
  value={value}
253
- onChange={onChange}
282
+ onValueChange={onValueChange}
254
283
  timeStep={15}
255
284
  placeholder="Pick a date and time"
256
285
  />
@@ -263,16 +292,16 @@ import { DatePickerButton, type DateRange } from "atlas-react-kit/date-picker"
263
292
 
264
293
  export function ExportRange({
265
294
  value,
266
- onChange,
295
+ onValueChange,
267
296
  }: {
268
297
  value: DateRange | undefined
269
- onChange: (range: DateRange | undefined) => void
298
+ onValueChange: (range: DateRange | undefined) => void
270
299
  }) {
271
300
  return (
272
301
  <DatePickerButton
273
302
  mode="range"
274
303
  value={value}
275
- onChange={onChange}
304
+ onValueChange={onValueChange}
276
305
  numberOfMonths={2}
277
306
  placeholder="Export range"
278
307
  />
@@ -363,26 +392,21 @@ export function ViewSwitcher({ view }: { view: string }) {
363
392
  }
364
393
  ```
365
394
 
366
- Outside Next, compose `TabsNav` with your own links: style each one with
367
- `tabsTriggerVariants()` and set `data-state="active"` plus `aria-current="page"`
368
- on the active link.
395
+ Outside Next, use `TabsNavLink` from `tabs`: a plain anchor with the same
396
+ chrome, `data-state`, and `aria-current="page"`, or `asChild` around your
397
+ router's link. `isNavActive` from `nav-link` works for the `active` check
398
+ here too.
369
399
 
370
400
  ```tsx
371
- import { TabsNav, tabsTriggerVariants } from "atlas-react-kit/tabs"
401
+ import { TabsNav, TabsNavLink } from "atlas-react-kit/tabs"
372
402
 
373
403
  export function ViewSwitcher({ view }: { view: string }) {
374
404
  return (
375
405
  <TabsNav aria-label="Items view">
376
406
  {["list", "board"].map((item) => (
377
- <a
378
- key={item}
379
- href={`?view=${item}`}
380
- data-state={view === item ? "active" : "inactive"}
381
- aria-current={view === item ? "page" : undefined}
382
- className={tabsTriggerVariants()}
383
- >
407
+ <TabsNavLink key={item} href={`?view=${item}`} active={view === item}>
384
408
  {item}
385
- </a>
409
+ </TabsNavLink>
386
410
  ))}
387
411
  </TabsNav>
388
412
  )