atlas-react-kit 0.4.1 → 0.6.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 (77) hide show
  1. package/COMPONENTS.md +135 -41
  2. package/DESIGN_RULES.md +79 -20
  3. package/README.md +60 -11
  4. package/dist/accordion.d.ts +8 -0
  5. package/dist/accordion.js +20 -0
  6. package/dist/alert.d.ts +19 -0
  7. package/dist/alert.js +37 -0
  8. package/dist/avatar.d.ts +9 -0
  9. package/dist/avatar.js +16 -0
  10. package/dist/badge.d.ts +2 -2
  11. package/dist/badge.js +1 -1
  12. package/dist/breadcrumb.d.ts +16 -0
  13. package/dist/breadcrumb.js +33 -0
  14. package/dist/button.d.ts +6 -5
  15. package/dist/button.js +7 -6
  16. package/dist/calendar.d.ts +1 -1
  17. package/dist/calendar.js +20 -2
  18. package/dist/checkbox.js +1 -1
  19. package/dist/color-legend.d.ts +22 -0
  20. package/dist/color-legend.js +11 -0
  21. package/dist/combobox.d.ts +4 -1
  22. package/dist/combobox.js +11 -5
  23. package/dist/date-picker.js +14 -3
  24. package/dist/date-time-picker.d.ts +36 -0
  25. package/dist/date-time-picker.js +105 -0
  26. package/dist/dropdown-menu.d.ts +26 -0
  27. package/dist/dropdown-menu.js +56 -0
  28. package/dist/error-boundary.d.ts +1 -1
  29. package/dist/field.js +1 -1
  30. package/dist/form/date-time-picker-field.d.ts +16 -0
  31. package/dist/form/date-time-picker-field.js +12 -0
  32. package/dist/form/markdown-field.d.ts +17 -0
  33. package/dist/form/markdown-field.js +26 -0
  34. package/dist/form/text-field.d.ts +7 -1
  35. package/dist/form/text-field.js +3 -3
  36. package/dist/info-tip.d.ts +27 -0
  37. package/dist/info-tip.js +63 -0
  38. package/dist/input.js +1 -1
  39. package/dist/internal/form-control.d.ts +1 -1
  40. package/dist/internal/form-control.js +1 -1
  41. package/dist/internal/tabs-styles.js +3 -3
  42. package/dist/markdown.d.ts +18 -0
  43. package/dist/markdown.js +62 -0
  44. package/dist/meter.d.ts +22 -0
  45. package/dist/meter.js +17 -0
  46. package/dist/pagination.d.ts +18 -0
  47. package/dist/pagination.js +35 -0
  48. package/dist/progress.d.ts +8 -0
  49. package/dist/progress.js +13 -0
  50. package/dist/radio-group.d.ts +13 -0
  51. package/dist/radio-group.js +20 -0
  52. package/dist/search-picker.d.ts +30 -0
  53. package/dist/search-picker.js +100 -0
  54. package/dist/select.js +2 -2
  55. package/dist/slider.d.ts +20 -0
  56. package/dist/slider.js +20 -0
  57. package/dist/spinner.d.ts +8 -0
  58. package/dist/spinner.js +13 -0
  59. package/dist/stat-tile.d.ts +21 -0
  60. package/dist/stat-tile.js +17 -0
  61. package/dist/switch.d.ts +9 -0
  62. package/dist/switch.js +14 -0
  63. package/dist/table.d.ts +2 -0
  64. package/dist/table.js +26 -2
  65. package/dist/tabs.d.ts +6 -3
  66. package/dist/tabs.js +5 -2
  67. package/dist/textarea.js +1 -1
  68. package/dist/theme-provider.d.ts +2 -1
  69. package/dist/theme-provider.js +28 -4
  70. package/dist/toggle-group.js +2 -2
  71. package/dist/toggle.d.ts +11 -1
  72. package/dist/toggle.js +13 -3
  73. package/dist/tooltip.d.ts +15 -0
  74. package/dist/tooltip.js +26 -0
  75. package/package.json +92 -5
  76. package/skills/atlas-react-kit/SKILL.md +148 -0
  77. package/theme.css +28 -14
package/COMPONENTS.md CHANGED
@@ -5,39 +5,73 @@ underlying element or Radix props, including `className`, alongside their option
5
5
  Form helpers and `TableBodySkeleton` accept only their documented props. There is
6
6
  no root component barrel.
7
7
 
8
- | Subpath | Main exports and options |
9
- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
10
- | `button` | `Button`, `buttonVariants`, `quietButtonInteraction`; `variant`, `size`, `asChild`; outline/ghost hover is brand purple + selected wash, not muted grey |
11
- | `badge` | `Badge`, `badgeVariants`; `variant`, `tone`, `asChild` |
12
- | `calendar` | `Calendar`, `CalendarDayButton`; React Day Picker props and `buttonVariant` |
13
- | `card` | `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardAction`, `CardContent`, `CardFooter`; `Card` supports `flush`; 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 `!` |
14
- | `checkbox` | `Checkbox` |
15
- | `combobox` | `Combobox`; `options` as `{ value, label }[]`, `value`, `onValueChange`; optional placeholder, search, empty text; trigger matches Input/SelectTrigger chrome; keyboard highlight scrolls into view |
16
- | `date-picker` | `DatePickerButton`, `toDate`, `formatLocalDate`, `DateRange`; `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+`) and `side` |
17
- | `dialog` | `Dialog`, trigger, portal, overlay, content, close, header, footer, title, description; `DialogContent` supports `showCloseButton` |
18
- | `empty-panel` | `EmptyPanel`; required `children`; optional `size`, `muted`, `className` |
19
- | `error-boundary` | `ErrorBoundary`, `ErrorFallback`; optional `message`; retry resets, reload refreshes the page |
20
- | `field` | Field, label, legend, description, group, set, title, content, separator, error; `Field` supports `orientation` |
21
- | `input`, `textarea`, `label` | `Input`, `Textarea`, `Label` |
22
- | `popover` | `Popover`, trigger, anchor, content, header, title, description |
23
- | `select` | Select, trigger, value, content, group, label, item, separator, scroll buttons; trigger `size`; trigger text is `text-base md:text-sm` like Input |
24
- | `separator`, `skeleton` | `Separator`, `Skeleton` |
25
- | `sortable-table-head` | `SortableTableHead`; `sorted`, `direction` (`asc` \| `desc`), `onClick`; sets `aria-sort` |
26
- | `sonner` | `Toaster`; Sonner props, with `next-themes` theme defaults |
27
- | `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` |
28
- | `table-body-skeleton` | `TableBodySkeleton`; `columns`, optional `rows` (default 5) |
29
- | `tabs` | `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent`, `TabsNav`, `tabsListVariants`; list `variant` is `default` or `line` |
30
- | `toggle` | `Toggle`, `toggleVariants`; `variant`, `size` |
31
- | `toggle-group` | `ToggleGroup`, `ToggleGroupItem`; `variant`, `size` |
32
- | `theme-provider` | `ThemeProvider`, `useTheme`; optional `storageKey` (default `atlas-theme`), `enableShortcut` for the `d` toggle |
33
- | `form/text-field` | `FormTextField`, type `TextFieldApi`; controlled field, label, multiline, description, input constraints |
34
- | `form/select-field` | `FormSelectField`; `value`, `onValueChange`, `label`, optional `meta` and placeholder; select item children |
35
- | `form/date-picker-field` | `FormDatePickerField`; `value`, `onValueChange`, `label`, optional `meta`, placeholder, and `className` (forwarded to the trigger) |
36
- | `form/field-error` | `FieldError`, `getFieldErrorMessage`; types `FieldErrorMessage`, `FieldMetaState` |
37
- | `form/feedback-field` | `FormFeedbackField`; nullable `message` |
38
- | `next/tabs-nav-link` | `TabsNavLink`; Next Link props and required `active` boolean; requires Next.js 16 |
39
- | `utils` | `cn`, `onInputChange` |
40
- | `theme.css` | Tailwind 4 theme; installation in [README](./README.md) |
8
+ | Subpath | Main exports and options |
9
+ | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
10
+ | `accordion` | `Accordion`, `AccordionItem`, `AccordionTrigger`, `AccordionContent`; Radix `type` `single` (with `collapsible`) or `multiple` |
11
+ | `alert` | `Alert`, `AlertTitle`, `AlertDescription`, `alertVariants`; `variant` `default`, `success`, `warning`, `destructive`; tone on title and icon, body stays muted; destructive is `role="alert"` |
12
+ | `avatar` | `Avatar`, `AvatarImage`, `AvatarFallback`; `size` `sm`, `default`, `lg`; round by design |
13
+ | `breadcrumb` | `Breadcrumb`, `BreadcrumbList`, `BreadcrumbItem`, `BreadcrumbLink` (`asChild` for router links), `BreadcrumbPage` (`aria-current`), `BreadcrumbSeparator`, `BreadcrumbEllipsis` |
14
+ | `button` | `Button`, `buttonVariants`, `quietButtonInteraction`; `variant`, `size`, `asChild`; outline/ghost hover is brand purple + selected wash, not muted grey |
15
+ | `badge` | `Badge`, `badgeVariants`; `variant`, `tone`, `asChild` |
16
+ | `calendar` | `Calendar`, `CalendarDayButton`; React Day Picker props and `buttonVariant` |
17
+ | `card` | `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardAction`, `CardContent`, `CardFooter`; `Card` supports `flush`; 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 `!` |
18
+ | `checkbox` | `Checkbox` |
19
+ | `color-legend` | `ColorLegend`; `title`, `items` as `{ color, range?, label }[]`, optional `caption`, `children`, `aria-label`, `className` (overlay chrome). No attribution. |
20
+ | `combobox` | `Combobox`; `options` as `{ value, label }[]`, `value`, `onValueChange`; optional placeholder, search, empty text; trigger matches Input/SelectTrigger chrome; keyboard highlight scrolls into view |
21
+ | `date-picker` | `DatePickerButton`, `toDate`, `formatLocalDate`, `DateRange`; `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+`) and `side` |
22
+ | `date-time-picker` | `DateTimePickerButton`, `resolveTimeStep`, `DEFAULT_TIME_STEP`; controlled `value` is `Date` or `null`; `onChange` emits `Date \ |
23
+ | `dialog` | `Dialog`, trigger, portal, overlay, content, close, header, footer, title, description; `DialogContent` supports `showCloseButton` |
24
+ | `dropdown-menu` | `DropdownMenu` family (trigger, content, item, checkbox/radio items, label, separator, shortcut, sub menus); item `variant="destructive"` and `inset`; for commands, not values |
25
+ | `empty-panel` | `EmptyPanel`; required `children`; optional `size`, `muted`, `className` |
26
+ | `error-boundary` | `ErrorBoundary`, `ErrorFallback`; optional `message`; retry resets, reload refreshes the page |
27
+ | `field` | Field, label, legend, description, group, set, title, content, separator, error; `Field` supports `orientation` |
28
+ | `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. |
29
+ | `input`, `textarea`, `label` | `Input`, `Textarea`, `Label` |
30
+ | `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. |
31
+ | `meter` | `Meter`; `value`, `max`, optional `min` (default 0), `fillClassName`, `markers` as `{ position, className? }[]`, required `aria-label`, `className`. `role="meter"`. Not `Progress`. |
32
+ | `pagination` | `Pagination`, `PaginationContent`, `PaginationItem`, `PaginationLink` (`isActive` sets `aria-current`, the selected wash, and a selected border), `PaginationPrevious`, `PaginationNext`, `PaginationEllipsis` |
33
+ | `popover` | `Popover`, trigger, anchor, content, header, title, description |
34
+ | `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`. |
35
+ | `radio-group` | `RadioGroup`, `RadioGroupItem`; short visible lists; arrow keys move the selection; round dots |
36
+ | `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. |
37
+ | `select` | Select, trigger, value, content, group, label, item, separator, scroll buttons; trigger `size`; trigger text is `text-base md:text-sm` like Input |
38
+ | `separator`, `skeleton` | `Separator`, `Skeleton` |
39
+ | `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`. |
40
+ | `sortable-table-head` | `SortableTableHead`; `sorted`, `direction` (`asc` \ |
41
+ | `sonner` | `Toaster`; Sonner props, with `next-themes` theme defaults |
42
+ | `spinner` | `Spinner`; `role="status"` with `aria-label` (default `Loading`) |
43
+ | `stat-tile` | `StatTile`, `statTileGrid`, `sectionLabel`; `label`, `value`, optional `sub`, `children`, `className`. |
44
+ | `switch` | `Switch`; settings that apply immediately; square track and thumb |
45
+ | `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` |
46
+ | `table-body-skeleton` | `TableBodySkeleton`; `columns`, optional `rows` (default 5) |
47
+ | `tabs` | `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent`, `TabsNav`, `tabsListVariants`, `tabsTriggerVariants`; list `variant` is `default` or `line`; style `TabsNav` links with `tabsTriggerVariants()` |
48
+ | `toggle` | `Toggle`, `toggleVariants`, `selectedToggleChrome`; `variant`, `size`. Pressed state is the selected wash, text, and border via `data-state=on`, `aria-pressed`, and `aria-checked` |
49
+ | `toggle-group` | `ToggleGroup`, `ToggleGroupItem`; `variant`, `size`. Items share `toggleVariants`, so single (radio) and multiple selections match a pressed `Toggle` |
50
+ | `theme-provider` | `ThemeProvider`, `useTheme`; optional `storageKey` (default `atlas-theme`), `enableShortcut` for the `d` toggle |
51
+ | `tooltip` | `Tooltip`, `TooltipTrigger`, `TooltipContent`, `TooltipProvider`; opens on hover and focus; short labels only |
52
+ | `form/markdown-field` | `FormMarkdownField`; `field` is `TextFieldApi`; `label`, optional `id`, `placeholder`, `rows`, `showError`. Write / preview; preview uses `Markdown`. |
53
+ | `form/text-field` | `FormTextField`, type `TextFieldApi`; controlled field, label, multiline, description, input constraints |
54
+ | `form/select-field` | `FormSelectField`; `value`, `onValueChange`, `label`, optional `meta` and placeholder; select item children |
55
+ | `form/date-picker-field` | `FormDatePickerField`; `value`, `onValueChange`, `label`, optional `meta`, placeholder, and `className` (forwarded to the trigger) |
56
+ | `form/date-time-picker-field` | `FormDateTimePickerField`; `value` is `Date` or `null`; `onValueChange` emits `Date \ |
57
+ | `form/field-error` | `FieldError`, `getFieldErrorMessage`; types `FieldErrorMessage`, `FieldMetaState` |
58
+ | `form/feedback-field` | `FormFeedbackField`; nullable `message` |
59
+ | `next/tabs-nav-link` | `TabsNavLink`; Next Link props and required `active` boolean; requires Next.js 16 |
60
+ | `utils` | `cn`, `onInputChange` |
61
+ | `theme.css` | Tailwind 4 theme; installation in [README](./README.md) |
62
+
63
+ ## Status
64
+
65
+ Components are **stable** unless listed here. Stable components change
66
+ through the deprecation policy in
67
+ [CONTRIBUTING.md](https://github.com/Kerubi-5/atlas-design-system/blob/main/CONTRIBUTING.md):
68
+ a deprecation notice first, removal in a later minor release.
69
+
70
+ **Beta** components may still change shape in a minor release; the
71
+ changelog calls out every change: `accordion`, `alert`, `avatar`,
72
+ `breadcrumb`, `color-legend`, `date-time-picker`, `dropdown-menu`, `info-tip`,
73
+ `markdown`, `meter`, `pagination`, `progress`, `radio-group`, `search-picker`,
74
+ `slider`, `spinner`, `stat-tile`, `switch`, `tooltip`.
41
75
 
42
76
  ## Forms
43
77
 
@@ -57,10 +91,13 @@ type TextFieldApi = {
57
91
 
58
92
  `FormTextField` accepts `field` and `label`; use `multiline` and `rows` for a
59
93
  textarea, `inputType` for a specific input, and `showError={false}` to suppress
60
- error text and invalid styling. `FormSelectField` and `FormDatePickerField`
61
- accept the same error meta shape. `FormDatePickerField` takes a `Date` or
62
- `yyyy-MM-dd` string and calls `onValueChange` with `Date | undefined`.
63
- `FieldError` accepts either `message` or pre-resolved `text`.
94
+ error text and invalid styling. `FormMarkdownField` uses the same `field` shape
95
+ for a Write / Preview GFM body. `FormSelectField`, `FormDatePickerField`, and
96
+ `FormDateTimePickerField` accept the same error meta shape.
97
+ `FormDatePickerField` takes a `Date` or `yyyy-MM-dd` string and calls
98
+ `onValueChange` with `Date | undefined`. `FormDateTimePickerField` takes a
99
+ `Date` or `null` and calls `onValueChange` with `Date | null`. `FieldError`
100
+ accepts either `message` or pre-resolved `text`.
64
101
 
65
102
  `DatePickerButton` is single-day by default. For a from/to range, set
66
103
  `mode="range"`; `value` is `{ from, to }` (each side `Date` or `yyyy-MM-dd`)
@@ -72,9 +109,41 @@ it does not collapse to the default popover width, and includes a Clear
72
109
  action. Pass `side` to prefer a placement; Radix still flips on collision.
73
110
  `FormDatePickerField` forwards `className` to that trigger.
74
111
 
75
- Outline and ghost `Button` hover and focus use `border-primary`,
76
- `text-primary`, and `bg-selected`. They do not use a muted grey fill, including
77
- when the control sits on a selected row (for example "Clear selection").
112
+ `DateTimePickerButton` is the date-and-time control. `value` is a `Date` or
113
+ `null`; `onChange` receives `Date | null`. The trigger matches the same
114
+ form-control chrome. The popover is one month, a labelled Hour and Minute
115
+ pair (24-hour, stepped by `timeStep`, default 15), and Clear. Picking a day
116
+ keeps the popover open so the time can be set; the time selects stay
117
+ disabled until a day is chosen. The panel is `w-auto`, viewport-clamped, and
118
+ capped at `min(100vw - 2rem, 24rem)` so it stays on-screen at 390px. Pass
119
+ `side` to prefer a placement. `FormDateTimePickerField` forwards `className`
120
+ and `timeStep` to that control.
121
+
122
+ Outline and ghost `Button` hover uses `border-selected-foreground`,
123
+ `text-selected-foreground`, and `bg-selected`; keyboard focus adds the shared
124
+ solid `ring-ring`. They do not use a muted grey fill, including when the
125
+ control sits on a selected row (for example "Clear selection").
126
+
127
+ ```tsx
128
+ import { DateTimePickerButton } from "atlas-react-kit/date-time-picker"
129
+
130
+ export function EventStarts({
131
+ value,
132
+ onChange,
133
+ }: {
134
+ value: Date | null
135
+ onChange: (date: Date | null) => void
136
+ }) {
137
+ return (
138
+ <DateTimePickerButton
139
+ value={value}
140
+ onChange={onChange}
141
+ timeStep={15}
142
+ placeholder="Pick a date and time"
143
+ />
144
+ )
145
+ }
146
+ ```
78
147
 
79
148
  ```tsx
80
149
  import { DatePickerButton, type DateRange } from "atlas-react-kit/date-picker"
@@ -136,7 +205,32 @@ export function ViewSwitcher({ view }: { view: string }) {
136
205
  }
137
206
  ```
138
207
 
139
- Outside Next, compose `TabsNav` with your own links and active-page semantics.
208
+ Outside Next, compose `TabsNav` with your own links: style each one with
209
+ `tabsTriggerVariants()` and set `data-state="active"` plus `aria-current="page"`
210
+ on the active link.
211
+
212
+ ```tsx
213
+ import { TabsNav, tabsTriggerVariants } from "atlas-react-kit/tabs"
214
+
215
+ export function ViewSwitcher({ view }: { view: string }) {
216
+ return (
217
+ <TabsNav aria-label="Items view">
218
+ {["list", "board"].map((item) => (
219
+ <a
220
+ key={item}
221
+ href={`?view=${item}`}
222
+ data-state={view === item ? "active" : "inactive"}
223
+ aria-current={view === item ? "page" : undefined}
224
+ className={tabsTriggerVariants()}
225
+ >
226
+ {item}
227
+ </a>
228
+ ))}
229
+ </TabsNav>
230
+ )
231
+ }
232
+ ```
233
+
140
234
  Hook-driven controls and form handlers declare their client boundary; presentational
141
235
  modules such as `button`, `badge`, `card`, and `skeleton` can be composed from
142
236
  React Server Components. Pass interactive callbacks from client components.
package/DESIGN_RULES.md CHANGED
@@ -11,17 +11,72 @@ Use semantic colors, radii, and fonts: `bg-primary`, `text-muted-foreground`,
11
11
  light/dark color pairs; tokens already switch with the theme.
12
12
 
13
13
  Selected or active navigation, tabs, toggles, chips, rows, and labels use
14
- `bg-selected text-selected-foreground`. Outline and ghost buttons hover and
15
- focus with `border-primary text-primary` and the selected wash, not a muted
16
- grey fill. Table row hover can stay `hover:bg-muted`. The shipped controls
17
- apply this distinction.
14
+ `bg-selected text-selected-foreground` plus a `border-selected-foreground`
15
+ border or bar: the wash alone is too faint to show state (1.2:1 against the
16
+ page). Outline and ghost buttons hover with `border-selected-foreground`,
17
+ `text-selected-foreground`, and the selected wash, not a muted grey fill.
18
+ Table row hover can stay `hover:bg-muted`. The shipped controls apply this
19
+ distinction.
20
+
21
+ For brand-colored text (links, hovered labels), use `text-selected-foreground`:
22
+ it is the primary purple in light mode and a lighter purple in dark mode.
23
+ `text-primary` as text fails contrast on the dark background (about 3.6:1).
18
24
 
19
25
  Use `success`, `warning`, and `destructive` for good, caution, and bad states.
20
- For tinted status chips, use `Badge variant="soft" tone="success"` or
21
- `tone="warning"`. The destructive tone has a known contrast limitation on its
22
- own light-mode tint; prefer plain `text-destructive` or a solid fill. Status
23
- tones have no paired foreground token; avoid placing text on solid status fills.
24
- Application-specific identity colors belong to the application.
26
+ For tinted status chips, use `Badge variant="soft"` with `tone="success"`,
27
+ `"warning"`, or `"destructive"`; each meets AA as text on the page, a card, and
28
+ its own soft tint in both themes. Status tones have no paired foreground token;
29
+ avoid placing text on solid status fills. Application-specific identity colors
30
+ belong to the application.
31
+
32
+ ## Accessibility
33
+
34
+ The target is WCAG 2.2 AA. CI runs axe-core's WCAG 2.2 A and AA rules against
35
+ every Storybook story in light and dark mode, and checks the theme's token
36
+ pairs for text and non-text contrast in both themes (`npm run test:a11y
37
+ --prefix site`). A violation fails the build. Automated checks do not replace
38
+ keyboard and screen-reader testing.
39
+
40
+ - Text meets 4.5:1 against its background. The token pairs are tuned for it,
41
+ including `text-muted-foreground` on `bg-muted` and status text on its soft
42
+ tint; raw colors and opacity tricks are where contrast breaks.
43
+ - Every control has an accessible name: a `Label` with `htmlFor`, or
44
+ `aria-label` on icon-only buttons and an unlabelled `Combobox`.
45
+ - Visible text is part of the accessible name, so speech users can say what
46
+ they see (the calendar labels days "September 27", not "27th").
47
+ - Non-text contrast is 3:1 against what the element sits on (WCAG 1.4.11):
48
+ field, checkbox, radio, and switch boundaries (`border-input`), checked
49
+ fills (`bg-primary`), selected borders (`border-selected-foreground`), and
50
+ the focus ring. `border-border` is for dividers and card edges only; never
51
+ make it the only boundary of a control.
52
+ - Focus is a solid `ring-ring` (brand purple), never a translucent ring such
53
+ as `ring-ring/30`, which fades below 3:1. Keyboard-highlighted menu and list
54
+ items show a 2px `ring` bar on their leading edge. Content that scrolls must
55
+ be reachable by keyboard; `Table` adds itself to the tab order while it
56
+ overflows.
57
+
58
+ ## Writing
59
+
60
+ Write labels and messages in sentence case; components that display uppercase
61
+ (buttons, card and dialog titles, badges) apply it with CSS, so screen readers
62
+ still read normal words.
63
+
64
+ - **Buttons** say what happens: a verb and, when needed, a noun ("Save",
65
+ "Export orders", "Delete order"). Avoid "OK", "Submit", and "Click here".
66
+ In a confirmation dialog, the confirm button repeats the action from the
67
+ title ("Delete order?" / "Delete order").
68
+ - **Errors** say what happened and what to do next, in plain words: "Could
69
+ not save. Check your connection and try again." Don't blame the user or
70
+ show only a code. Field errors name the fix: "Enter an email address like
71
+ name@company.com."
72
+ - **Empty states** say what will appear and how to add the first one: "No
73
+ orders yet. Orders you create show up here."
74
+ - **Toasts** confirm in past tense and stay short ("Settings saved"). Anything
75
+ the user must act on belongs in an `Alert` or dialog, not a toast.
76
+ - **Placeholders** show an example, never the label; every field keeps a
77
+ visible `Label`.
78
+ - Skip "please", exclamation marks, and filler ("Successfully saved" is
79
+ "Saved"). Use numerals, and `tabular-nums` where numbers line up in columns.
25
80
 
26
81
  ## Shape and composition
27
82
 
@@ -39,19 +94,23 @@ column, and `min-w-0` so nested tables scroll instead of widening the card.
39
94
  `Dialog` already scrolls inside the viewport on small screens; avoid
40
95
  additional height limits.
41
96
 
42
- `Input`, `SelectTrigger`, `DatePickerButton`, and `Combobox` share form-control
43
- chrome: 40px height, `px-3`, `text-base md:text-sm`, `border-input`, and
44
- `shadow-xs`. Do not restyle those triggers as outline buttons in application
45
- code.
97
+ `Input`, `SelectTrigger`, `DatePickerButton`, `DateTimePickerButton`, and
98
+ `Combobox` share form-control chrome: 40px height, `px-3`,
99
+ `text-base md:text-sm`, `border-input`, and `shadow-xs`. Do not restyle those
100
+ triggers as outline buttons in application code.
46
101
 
47
102
  Use `Tabs` with actual `TabsContent` panels. For URL view switches, use a labelled
48
103
  `TabsNav` with links and `aria-current="page"` on the active link. The optional
49
104
  Next adapter provides `TabsNavLink` with this behavior.
50
105
 
51
- Use `FormTextField`, `FormSelectField`, `FormDatePickerField`, and
52
- `FormFeedbackField` for portable form controls, and `FieldError` for field
53
- messages. `Combobox`, `DatePickerButton` (including `mode="range"`),
54
- `ThemeProvider`, `ErrorBoundary`, and `SortableTableHead` are first-class kit
55
- pieces. Domain data and application shells stay with the application: currency
56
- or timezone option lists, query-library error views, markdown editors, kanban
57
- boards, and dashboard widgets.
106
+ Use `FormTextField`, `FormSelectField`, `FormDatePickerField`,
107
+ `FormDateTimePickerField`, `FormMarkdownField`, and `FormFeedbackField` for
108
+ portable form controls, and `FieldError` for field messages. `Combobox` (popover)
109
+ and `SearchPicker` (always-visible list), `DatePickerButton` (including
110
+ `mode="range"`), `DateTimePickerButton`, `Slider`, `Meter` (scale with ticks;
111
+ `Progress` is a plain 0–100 job bar), `InfoTip` (longer than `Tooltip`),
112
+ `Markdown`, `ColorLegend`, `StatTile`, `ThemeProvider`, `ErrorBoundary`, and
113
+ `SortableTableHead` are first-class kit pieces. Domain data and application
114
+ shells stay with the application: currency or timezone option lists,
115
+ query-library error views, WYSIWYG editors, kanban boards, and dashboard
116
+ widgets.
package/README.md CHANGED
@@ -1,10 +1,13 @@
1
1
  # Atlas React Kit
2
2
 
3
3
  Shared React components and a Tailwind theme with square corners, semantic
4
- colors, and distinct selected states. React 19 and Tailwind 4 are required.
4
+ colors, and distinct selected states. 0.6.0 adds generic pieces apps were
5
+ keeping locally (`InfoTip`, `ColorLegend`, `SearchPicker`, `Markdown`,
6
+ `StatTile`, `Meter`, `Slider`) plus the ToggleGroup selected-state fix.
7
+ React 19 and Tailwind 4 are required.
5
8
 
6
9
  ```sh
7
- npm install --save-exact atlas-react-kit@0.4.1
10
+ npm install --save-exact atlas-react-kit@0.6.0
8
11
  ```
9
12
 
10
13
  Import components by subpath:
@@ -42,9 +45,33 @@ with the application.
42
45
 
43
46
  See [design rules](./DESIGN_RULES.md) for shared styling decisions and the
44
47
  [component guide](./COMPONENTS.md) for exports and composition examples. Both
45
- files ship in the npm package. Shared component changes belong in this repository;
48
+ files ship in the npm package.
49
+
50
+ ## AI coding assistants
51
+
52
+ The package also ships an agent skill,
53
+ [`skills/atlas-react-kit/SKILL.md`](./skills/atlas-react-kit/SKILL.md). It
54
+ tells an assistant to read the guides for the installed version, pick kit
55
+ components over hand-rolled markup, style with tokens, and check its work. Link
56
+ it into Claude Code from the app root, so it updates with the package:
57
+
58
+ ```sh
59
+ mkdir -p .claude/skills
60
+ ln -s ../../node_modules/atlas-react-kit/skills/atlas-react-kit .claude/skills/atlas-react-kit
61
+ ```
62
+
63
+ On Windows, or to commit a pinned copy, use
64
+ `cp -r node_modules/atlas-react-kit/skills/atlas-react-kit .claude/skills/`
65
+ instead. For Cursor, Codex, or other agents, point your `AGENTS.md` at
66
+ `node_modules/atlas-react-kit/skills/atlas-react-kit/SKILL.md`. Shared component changes belong in this repository;
46
67
  applications compose their feature UI around the package and upgrade deliberately.
47
68
 
69
+ Public docs and a live playground are at
70
+ [design.querobines.com](https://design.querobines.com). The site is Storybook:
71
+ tokens parsed from `theme.css`, these markdown guides rendered from source, and
72
+ editable component stories with light/dark themes, viewport previews, and
73
+ accessibility checks.
74
+
48
75
  ## Development and releases
49
76
 
50
77
  ```sh
@@ -52,17 +79,36 @@ npm ci
52
79
  npm run check
53
80
  npm test
54
81
  npm pack
82
+ npm ci --prefix site
83
+ npm run build --prefix site
84
+ npm run preview --prefix site
55
85
  ```
56
86
 
57
87
  `tsc` emits individual ESM files and TypeScript declarations. CSS is shipped
58
88
  without compilation and processed by the consuming application's Tailwind build.
59
- The optional Next.js 16 adapter requires Next only when imported. The package
60
- does not bundle React.
61
-
62
- 0.4.0 is already on npm. Bumping the version in `package.json` and
63
- merging to `main` (path-filtered to package files) or running
64
- `workflow_dispatch` publishes through npm OIDC trusted publishing after
65
- check, test, and pack gates. Versions already on npm are skipped.
89
+ The public site is Storybook in `site/`. `npm run build --prefix site`
90
+ typechecks the stories, builds Storybook into `site/dist`, then checks the
91
+ static output. Vercel serves that directory; `/playground` and `/storybook`
92
+ redirect to `/`. The site and Storybook dependencies are not part of the npm
93
+ package. The optional Next.js 16 adapter requires Next only when imported. The
94
+ package does not bundle React.
95
+
96
+ Run `npm run dev --prefix site` (or `npm run storybook --prefix site`) for the
97
+ playground on port 6006. Existing examples live in `stories/` and are imported
98
+ by CSF story files in `site/stories/`, so component tests and previews share the
99
+ same examples. Add Storybook entries there when adding an example; the
100
+ static-output check reports any existing demo that has no Storybook entry.
101
+ Interactive stories use Storybook args so changing a control or interacting with
102
+ the component updates the same value. Configuration is in `site/.storybook/`.
103
+
104
+ The Storybook preview disables the kit's theme shortcut and uses the theme
105
+ toolbar instead. The accessibility panel checks the rendered story; it does not
106
+ replace keyboard or assistive-technology testing.
107
+
108
+ Bumping the version in `package.json` and merging to `main` (path-filtered
109
+ to package files) or running `workflow_dispatch` publishes through npm OIDC
110
+ trusted publishing after check, test, and pack gates. Versions already on npm
111
+ are skipped.
66
112
 
67
113
  If trusted publishing is not configured, pack and publish a validated
68
114
  artifact with the package owner's npm login:
@@ -70,9 +116,12 @@ artifact with the package owner's npm login:
70
116
  ```sh
71
117
  mkdir -p release-artifacts
72
118
  npm pack --pack-destination release-artifacts
73
- npm publish ./release-artifacts/atlas-react-kit-0.4.1.tgz --access public
119
+ npm publish ./release-artifacts/atlas-react-kit-0.6.0.tgz --access public
74
120
  ```
75
121
 
122
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for adding components, the browser
123
+ checks, versioning, and deprecations.
124
+
76
125
  Pin an exact package version in consuming applications. Roll back an upgrade by
77
126
  restoring the previous version and lockfile. License: MIT; upstream notices are
78
127
  preserved in [NOTICE](./NOTICE).
@@ -0,0 +1,8 @@
1
+ import * as React from "react";
2
+ import { Accordion as AccordionPrimitive } from "radix-ui";
3
+ /** Stacked sections that expand in place (FAQs, settings groups). */
4
+ declare function Accordion({ ...props }: React.ComponentProps<typeof AccordionPrimitive.Root>): React.JSX.Element;
5
+ declare function AccordionItem({ className, ...props }: React.ComponentProps<typeof AccordionPrimitive.Item>): React.JSX.Element;
6
+ declare function AccordionTrigger({ className, children, ...props }: React.ComponentProps<typeof AccordionPrimitive.Trigger>): React.JSX.Element;
7
+ declare function AccordionContent({ className, children, ...props }: React.ComponentProps<typeof AccordionPrimitive.Content>): React.JSX.Element;
8
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
@@ -0,0 +1,20 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Accordion as AccordionPrimitive } from "radix-ui";
5
+ import { ChevronDownIcon } from "lucide-react";
6
+ import { cn } from "./utils.js";
7
+ /** Stacked sections that expand in place (FAQs, settings groups). */
8
+ function Accordion({ ...props }) {
9
+ return _jsx(AccordionPrimitive.Root, { "data-slot": "accordion", ...props });
10
+ }
11
+ function AccordionItem({ className, ...props }) {
12
+ return (_jsx(AccordionPrimitive.Item, { "data-slot": "accordion-item", className: cn("border-b last:border-b-0", className), ...props }));
13
+ }
14
+ function AccordionTrigger({ className, children, ...props }) {
15
+ return (_jsx(AccordionPrimitive.Header, { className: "flex", children: _jsxs(AccordionPrimitive.Trigger, { "data-slot": "accordion-trigger", className: cn("flex flex-1 items-start justify-between gap-4 rounded-none py-4 text-left text-sm font-medium transition-colors outline-none hover:text-selected-foreground focus-visible:text-selected-foreground focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180", className), ...props, children: [children, _jsx(ChevronDownIcon, { className: "pointer-events-none size-4 shrink-0 translate-y-0.5 text-muted-foreground transition-transform duration-200" })] }) }));
16
+ }
17
+ function AccordionContent({ className, children, ...props }) {
18
+ return (_jsx(AccordionPrimitive.Content, { "data-slot": "accordion-content", className: "overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down", ...props, children: _jsx("div", { className: cn("pt-0 pb-4", className), children: children }) }));
19
+ }
20
+ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
@@ -0,0 +1,19 @@
1
+ import * as React from "react";
2
+ import { type VariantProps } from "class-variance-authority";
3
+ /**
4
+ * Status tone sits on the title and icon. The description stays
5
+ * `text-muted-foreground` on the card face, so body text keeps its contrast
6
+ * (the destructive tint is too faint for text in light mode).
7
+ */
8
+ declare const alertVariants: (props?: ({
9
+ variant?: "default" | "success" | "warning" | "destructive" | null | undefined;
10
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
11
+ /**
12
+ * Inline message about the page or a section (save failed, trial ending).
13
+ * Destructive alerts use `role="alert"` so screen readers announce them;
14
+ * others are `role="status"`. Use a toast for transient confirmations.
15
+ */
16
+ declare function Alert({ className, variant, role, ...props }: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>): React.JSX.Element;
17
+ declare function AlertTitle({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
18
+ declare function AlertDescription({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
19
+ export { Alert, AlertDescription, AlertTitle, alertVariants };
package/dist/alert.js ADDED
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { cva } from "class-variance-authority";
4
+ import { cn } from "./utils.js";
5
+ /**
6
+ * Status tone sits on the title and icon. The description stays
7
+ * `text-muted-foreground` on the card face, so body text keeps its contrast
8
+ * (the destructive tint is too faint for text in light mode).
9
+ */
10
+ const alertVariants = cva("relative grid w-full 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
+ variants: {
12
+ variant: {
13
+ default: "[&>svg]:text-foreground",
14
+ success: "border-success/40 *:data-[slot=alert-title]:text-success [&>svg]:text-success",
15
+ warning: "border-warning/40 *:data-[slot=alert-title]:text-warning [&>svg]:text-warning",
16
+ destructive: "border-destructive/40 *:data-[slot=alert-title]:text-destructive [&>svg]:text-destructive",
17
+ },
18
+ },
19
+ defaultVariants: {
20
+ variant: "default",
21
+ },
22
+ });
23
+ /**
24
+ * Inline message about the page or a section (save failed, trial ending).
25
+ * Destructive alerts use `role="alert"` so screen readers announce them;
26
+ * others are `role="status"`. Use a toast for transient confirmations.
27
+ */
28
+ function Alert({ className, variant = "default", role, ...props }) {
29
+ return (_jsx("div", { "data-slot": "alert", "data-variant": variant, role: role ?? (variant === "destructive" ? "alert" : "status"), className: cn(alertVariants({ variant }), className), ...props }));
30
+ }
31
+ function AlertTitle({ className, ...props }) {
32
+ return (_jsx("div", { "data-slot": "alert-title", className: cn("col-start-2 min-h-4 text-xs font-semibold tracking-wider uppercase", className), ...props }));
33
+ }
34
+ function AlertDescription({ className, ...props }) {
35
+ return (_jsx("div", { "data-slot": "alert-description", className: cn("col-start-2 grid justify-items-start gap-1 text-sm leading-relaxed text-muted-foreground [&_p]:leading-relaxed", className), ...props }));
36
+ }
37
+ export { Alert, AlertDescription, AlertTitle, alertVariants };
@@ -0,0 +1,9 @@
1
+ import * as React from "react";
2
+ import { Avatar as AvatarPrimitive } from "radix-ui";
3
+ /** Person or workspace image with initials while loading or on error. Round by design. */
4
+ declare function Avatar({ className, size, ...props }: React.ComponentProps<typeof AvatarPrimitive.Root> & {
5
+ size?: "sm" | "default" | "lg";
6
+ }): React.JSX.Element;
7
+ declare function AvatarImage({ className, ...props }: React.ComponentProps<typeof AvatarPrimitive.Image>): React.JSX.Element;
8
+ declare function AvatarFallback({ className, ...props }: React.ComponentProps<typeof AvatarPrimitive.Fallback>): React.JSX.Element;
9
+ export { Avatar, AvatarFallback, AvatarImage };
package/dist/avatar.js ADDED
@@ -0,0 +1,16 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Avatar as AvatarPrimitive } from "radix-ui";
5
+ import { cn } from "./utils.js";
6
+ /** Person or workspace image with initials while loading or on error. Round by design. */
7
+ function Avatar({ className, size = "default", ...props }) {
8
+ return (_jsx(AvatarPrimitive.Root, { "data-slot": "avatar", "data-size": size, className: cn("relative flex shrink-0 overflow-hidden rounded-full select-none data-[size=default]:size-8 data-[size=lg]:size-10 data-[size=sm]:size-6", className), ...props }));
9
+ }
10
+ function AvatarImage({ className, ...props }) {
11
+ return (_jsx(AvatarPrimitive.Image, { "data-slot": "avatar-image", className: cn("aspect-square size-full object-cover", className), ...props }));
12
+ }
13
+ function AvatarFallback({ className, ...props }) {
14
+ return (_jsx(AvatarPrimitive.Fallback, { "data-slot": "avatar-fallback", className: cn("flex size-full items-center justify-center rounded-full bg-muted text-xs font-semibold text-muted-foreground uppercase", className), ...props }));
15
+ }
16
+ export { Avatar, AvatarFallback, AvatarImage };
package/dist/badge.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as React from "react";
2
2
  import { type VariantProps } from "class-variance-authority";
3
3
  declare const badgeVariants: (props?: ({
4
- variant?: "default" | "secondary" | "destructive" | "outline" | "ghost" | "link" | "soft" | null | undefined;
5
- tone?: "destructive" | "neutral" | "success" | "warning" | null | undefined;
4
+ variant?: "default" | "link" | "destructive" | "secondary" | "outline" | "ghost" | "soft" | null | undefined;
5
+ tone?: "success" | "warning" | "destructive" | "neutral" | null | undefined;
6
6
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
7
7
  declare function Badge({ className, variant, tone, asChild, ...props }: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & {
8
8
  asChild?: boolean;