atlas-react-kit 1.0.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.
- package/COMPONENTS.md +41 -37
- package/DESIGN_RULES.md +23 -32
- package/README.md +5 -47
- package/dist/date-picker.d.ts +13 -2
- package/dist/date-picker.js +35 -22
- package/dist/date-time-picker.d.ts +1 -1
- package/dist/date-time-picker.js +24 -38
- package/dist/form/date-picker-field.d.ts +6 -1
- package/dist/form/date-picker-field.js +3 -2
- package/dist/form/date-time-picker-field.d.ts +1 -1
- package/dist/form/date-time-picker-field.js +3 -2
- package/dist/form/field-error.d.ts +4 -3
- package/dist/form/field-shell.d.ts +6 -5
- package/dist/form/field-shell.js +1 -1
- package/dist/form/focus-first-invalid.d.ts +28 -0
- package/dist/form/focus-first-invalid.js +29 -0
- package/dist/form/select-field.d.ts +7 -1
- package/dist/form/select-field.js +3 -2
- package/dist/internal/listbox-navigation.d.ts +0 -11
- package/dist/internal/listbox-navigation.js +2 -2
- package/dist/internal/listbox-option.d.ts +0 -15
- package/dist/internal/listbox-option.js +1 -1
- package/dist/internal/use-drag-scroll.d.ts +17 -0
- package/dist/internal/use-drag-scroll.js +98 -0
- package/dist/nav-link.d.ts +12 -4
- package/dist/nav-link.js +14 -4
- package/dist/next/nav-link.d.ts +6 -3
- package/dist/next/nav-link.js +8 -6
- package/dist/next/tabs-nav-link.d.ts +6 -0
- package/dist/next/tabs-nav-link.js +8 -3
- package/dist/picker-shell.d.ts +24 -21
- package/dist/picker-shell.js +12 -10
- package/dist/tabs.d.ts +18 -7
- package/dist/tabs.js +22 -9
- package/package.json +7 -3
- package/skills/atlas-react-kit/SKILL.md +46 -40
- package/theme.css +2 -0
package/COMPONENTS.md
CHANGED
|
@@ -43,7 +43,7 @@ theme from `next-themes`, so toast consumers install that too.
|
|
|
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
|
|
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
47
|
| `date-time-picker` | `DateTimePickerButton`, `resolveTimeStep`, `DEFAULT_TIME_STEP`; controlled `value` is `Date` or `null`; `onValueChange` emits `Date \ |
|
|
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 |
|
|
@@ -52,10 +52,10 @@ theme from `next-themes`, so toast consumers install that too.
|
|
|
52
52
|
| `field` | Field, label, legend, description, group, set, title, content, separator, error; `Field` supports `orientation` |
|
|
53
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
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`; `Label` `required` shows a visible marker (set `required` on the control too)
|
|
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. |
|
|
57
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
|
|
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
59
|
| `page-header` | `PageHeader`; `title`, optional `description`, `actions`, `children` (under the description), `titleAs` (`h1` default), `titleClassName`. Stacks below `sm`. |
|
|
60
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
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. |
|
|
@@ -73,26 +73,27 @@ theme from `next-themes`, so toast consumers install that too.
|
|
|
73
73
|
| `switch` | `Switch`; settings that apply immediately; square track and thumb |
|
|
74
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
75
|
| `table-body-skeleton` | `TableBodySkeleton`; `columns`, optional `rows` (default 5) |
|
|
76
|
-
| `tabs` | `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent`, `TabsNav`, `tabsListVariants`, `tabsTriggerVariants`; list `variant` is `default` or `line`;
|
|
76
|
+
| `tabs` | `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent`, `TabsNav`, `TabsNavLink`, `tabsListVariants`, `tabsTriggerVariants`; list `variant` is `default` or `line`; `TabsNavLink` is the link inside `TabsNav` (required `active`, `asChild` for a router link); `TabsNav` / `TabsList` scroll horizontally only (`overflow-y` hidden, `p-1` counted in the scroller height, edge fade, active item into view, mouse drag scrolls the row) — do not wrap them in `overflow-x-auto` |
|
|
77
77
|
| `toast` | `Toaster`, `toast`; mount one `Toaster` (`position`, `closeButton` (default on), `expand`, `visibleToasts`, `duration`), then `toast(message, options)`. The stack expands on hover and while keyboard focus is inside it; collapsed toasts behind the front one are hidden, `.success`, `.info`, `.warning`, `.error`, `.loading`, `.dismiss(id?)`. Options: `id` (replaces that toast), `description`, `duration`, `action` / `cancel` as `{ label, onClick }`. Kit-owned API: no toast library is exposed |
|
|
78
78
|
| `toggle` | `Toggle`, `toggleVariants`, `selectedToggleChrome`; `variant`, `size`. Pressed state is the selected wash, text, and border via `data-state=on`, `aria-pressed`, and `aria-checked` |
|
|
79
79
|
| `toggle-group` | `ToggleGroup`, `ToggleGroupItem`; `variant`, `size`. Items share `toggleVariants`, so single (radio) and multiple selections match a pressed `Toggle` |
|
|
80
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
|
|
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
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
83
|
| `form/combobox-field` | `FormComboboxField`; every `Combobox` prop plus `label`, `description`, `meta`, `required`; pass the field's `onBlur`. |
|
|
84
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
85
|
| `form/markdown-field` | `FormMarkdownField`; `field` is `TextFieldApi`; `label`, optional `id`, `placeholder`, `rows`, `showError`, `required`. Write / preview; preview uses `Markdown`. |
|
|
86
86
|
| `form/text-field` | `FormTextField`, type `TextFieldApi`; controlled field, label, multiline, description, input constraints; `required` also marks the label |
|
|
87
|
-
| `form/select-field` | `FormSelectField`; `value`, `onValueChange`, `label`, optional `meta`, placeholder, and `required`; select item children
|
|
88
|
-
| `form/date-picker-field` | `FormDatePickerField`; `value`, `onValueChange`, `label`, optional `meta`, placeholder, `required`, `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 `meta`, placeholder, `timeStep`, `required`, `minDate` / `maxDate`, and `className` (forwarded to the trigger)
|
|
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
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` |
|
|
91
92
|
| `form/feedback-field` | `FormFeedbackField`; nullable `message` |
|
|
92
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). |
|
|
93
|
-
| `next/nav-link` | `NavLink`; Next Link props and required `active` boolean;
|
|
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 |
|
|
94
95
|
| `next/period-stepper` | `PeriodStepper` with prefetched Next links: `previousHref`, `nextHref`, optional `todayHref`. |
|
|
95
|
-
| `next/tabs-nav-link` | `TabsNavLink`; Next Link props and required `active` boolean; requires Next.js 16
|
|
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 |
|
|
96
97
|
| `utils` | `cn`, `onInputChange` |
|
|
97
98
|
| `theme.css` | Tailwind 4 theme; installation in [README](./README.md) |
|
|
98
99
|
|
|
@@ -100,8 +101,7 @@ theme from `next-themes`, so toast consumers install that too.
|
|
|
100
101
|
|
|
101
102
|
Components are **stable** unless listed here. Stable components change
|
|
102
103
|
through the deprecation policy in
|
|
103
|
-
[CONTRIBUTING.md](https://github.com/Kerubi-5/atlas-design-system/blob/main/CONTRIBUTING.md)
|
|
104
|
-
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).
|
|
105
105
|
|
|
106
106
|
**Beta** components may still change shape in a minor release, before and
|
|
107
107
|
after 1.0; they are exempt from semver, and the changelog calls out every
|
|
@@ -121,9 +121,9 @@ each part carries a `data-slot` for parent selectors such as
|
|
|
121
121
|
| `DialogContent` | `overlayClassName`, `closeButtonClassName` | `dialog-close-button` (the built-in X; your own `DialogClose` keeps `dialog-close`) |
|
|
122
122
|
| `SelectTrigger` | a `className` height replaces `size` | `select-icon`, `select-viewport`, `select-item-indicator` |
|
|
123
123
|
| `TooltipContent` | `arrowClassName` | `tooltip-arrow` |
|
|
124
|
-
| `Combobox` | `contentClassName` (panel, default trigger width), `listClassName
|
|
125
|
-
| `SearchPicker` | `listClassName
|
|
126
|
-
| `InfoTip` | `contentClassName
|
|
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
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
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
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` |
|
|
@@ -132,7 +132,7 @@ each part carries a `data-slot` for parent selectors such as
|
|
|
132
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
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
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`
|
|
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
136
|
| `SortableTableHead` | `buttonClassName` | `sortable-table-head-button` |
|
|
137
137
|
| `AccordionTrigger`, `AccordionContent` | `headerClassName`; `AccordionContent` `className` styles the inner box | `accordion-header`, `accordion-content-inner` |
|
|
138
138
|
| `Progress` | `indicatorClassName` | `progress-indicator` |
|
|
@@ -168,7 +168,11 @@ for a Write / Preview GFM body. `FormSelectField`, `FormDatePickerField`, and
|
|
|
168
168
|
`FormDateTimePickerField` accept the same error meta shape.
|
|
169
169
|
`FormDatePickerField` takes a `Date` or `yyyy-MM-dd` string and calls
|
|
170
170
|
`onValueChange` with `Date | undefined`. `FormDateTimePickerField` takes a
|
|
171
|
-
`Date` or `null` and calls `onValueChange` with `Date | null`.
|
|
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`
|
|
172
176
|
accepts either `message` or pre-resolved `text`, and an `id` to point the
|
|
173
177
|
control's `aria-describedby` at. Every `form/*` field does that for you: the
|
|
174
178
|
error text (and `FormTextField`'s `description`) is the control's accessible
|
|
@@ -178,12 +182,14 @@ part of its name.
|
|
|
178
182
|
|
|
179
183
|
Errors wait for the person. When `meta.isTouched` is `false`, a `form/*`
|
|
180
184
|
field hides its error text and invalid styling until the field is touched or
|
|
181
|
-
its `submitted` prop is set
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
`
|
|
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`.
|
|
187
193
|
|
|
188
194
|
`FormControlField` gives a control the kit does not ship the same label,
|
|
189
195
|
required marker, hint, error, ids, and error timing. `children` is a
|
|
@@ -224,7 +230,10 @@ height classes still override that default. The range popover shows one
|
|
|
224
230
|
month below `md` and two months at `md+` unless `numberOfMonths` is set,
|
|
225
231
|
clamps to the viewport with collision padding and scroll, stays `w-auto` so
|
|
226
232
|
it does not collapse to the default popover width, and includes a Clear
|
|
227
|
-
action.
|
|
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.
|
|
228
237
|
`minDate` and `maxDate` (each `Date` or `yyyy-MM-dd`) disable the days
|
|
229
238
|
outside them, on top of any `calendarProps.disabled`, and stop month paging
|
|
230
239
|
there unless `calendarProps` sets `startMonth` / `endMonth`. They limit the
|
|
@@ -237,7 +246,7 @@ the trigger text's id (a `<label for>` alone names the button by the label
|
|
|
237
246
|
only).
|
|
238
247
|
`FormDatePickerField` forwards `className` to that trigger.
|
|
239
248
|
|
|
240
|
-
Both pickers took `onChange` before 0
|
|
249
|
+
Both pickers took `onChange` before 1.0; it still works as a deprecated
|
|
241
250
|
alias until 2.0.
|
|
242
251
|
|
|
243
252
|
`DateTimePickerButton` is the date-and-time control. `value` is a `Date` or
|
|
@@ -383,26 +392,21 @@ export function ViewSwitcher({ view }: { view: string }) {
|
|
|
383
392
|
}
|
|
384
393
|
```
|
|
385
394
|
|
|
386
|
-
Outside Next,
|
|
387
|
-
|
|
388
|
-
|
|
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.
|
|
389
399
|
|
|
390
400
|
```tsx
|
|
391
|
-
import { TabsNav,
|
|
401
|
+
import { TabsNav, TabsNavLink } from "atlas-react-kit/tabs"
|
|
392
402
|
|
|
393
403
|
export function ViewSwitcher({ view }: { view: string }) {
|
|
394
404
|
return (
|
|
395
405
|
<TabsNav aria-label="Items view">
|
|
396
406
|
{["list", "board"].map((item) => (
|
|
397
|
-
<
|
|
398
|
-
key={item}
|
|
399
|
-
href={`?view=${item}`}
|
|
400
|
-
data-state={view === item ? "active" : "inactive"}
|
|
401
|
-
aria-current={view === item ? "page" : undefined}
|
|
402
|
-
className={tabsTriggerVariants()}
|
|
403
|
-
>
|
|
407
|
+
<TabsNavLink key={item} href={`?view=${item}`} active={view === item}>
|
|
404
408
|
{item}
|
|
405
|
-
</
|
|
409
|
+
</TabsNavLink>
|
|
406
410
|
))}
|
|
407
411
|
</TabsNav>
|
|
408
412
|
)
|
package/DESIGN_RULES.md
CHANGED
|
@@ -129,13 +129,10 @@ or the viewport minus gutters on small screens.
|
|
|
129
129
|
|
|
130
130
|
Use `EmptyPanel` for empty states, `TableBodySkeleton` for loading table bodies,
|
|
131
131
|
and `<Card flush>` for an edge-to-edge list or table. `Table` scrolls wide
|
|
132
|
-
columns inside itself
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
column, and `min-w-0` so nested tables scroll instead of widening the card.
|
|
137
|
-
`Dialog` already scrolls inside the viewport on small screens; avoid
|
|
138
|
-
additional height limits.
|
|
132
|
+
columns inside itself, `Card` neither collapses in a bounded flex column nor
|
|
133
|
+
widens around a nested table, and `Dialog` already scrolls inside the
|
|
134
|
+
viewport on small screens; avoid additional height limits. The classes each
|
|
135
|
+
one uses are in its COMPONENTS.md row.
|
|
139
136
|
|
|
140
137
|
Apps decide how wide and tall containers and overlays are. The kit sets
|
|
141
138
|
intrinsic control sizes (the button, input, tab and row heights, icons,
|
|
@@ -144,12 +141,10 @@ clamps overlays to the viewport, but no design widths or heights on
|
|
|
144
141
|
dialogs, popovers, menus, or lists. Set them with `className`: give each
|
|
145
142
|
`DialogContent` a width such as `sm:max-w-md`, and each `PopoverContent`
|
|
146
143
|
one such as `w-72`. Where the sized part is not the element `className`
|
|
147
|
-
reaches, a prop names it
|
|
148
|
-
|
|
149
|
-
`
|
|
150
|
-
|
|
151
|
-
`min-h-16` starting height, and toasts their 356px width; override the first
|
|
152
|
-
two with `className`.
|
|
144
|
+
reaches, a `*ClassName` prop names it, with its default where there is one
|
|
145
|
+
(COMPONENTS.md, "Styling parts"). The few parts that keep a reading width or
|
|
146
|
+
a starting height (`Tooltip`, `Textarea`) take `className` to override it;
|
|
147
|
+
toasts keep their width.
|
|
153
148
|
|
|
154
149
|
Parents do not force sizes on their children. A vertical `Field` stretches
|
|
155
150
|
its controls through the flex column, so a control's own width class (such
|
|
@@ -172,32 +167,28 @@ triggers as outline buttons in application code.
|
|
|
172
167
|
error sits below the control. It takes no space until it shows, so a row of
|
|
173
168
|
fields keeps its controls on one top edge only with `items-start`
|
|
174
169
|
(`grid grid-cols-2 items-start gap-3`); the default `stretch` is fine for a
|
|
175
|
-
single column. Wrap a control the kit
|
|
170
|
+
single column. Wrap a control the kit has no form field for (a colour
|
|
176
171
|
picker) in `FormControlField` instead of `Field` and `FieldLabel`, whose
|
|
177
|
-
`gap-3` spacing does not match. A field shows its error once
|
|
178
|
-
`meta.isTouched`
|
|
179
|
-
|
|
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.
|
|
180
176
|
|
|
181
177
|
Use `Tabs` with actual `TabsContent` panels. For URL view switches, use a labelled
|
|
182
|
-
`TabsNav`
|
|
183
|
-
Next adapter provides `TabsNavLink` with this behavior. App-shell destinations
|
|
178
|
+
`TabsNav` of `TabsNavLink`s with `active` from the app. App-shell destinations
|
|
184
179
|
(Calendar, Tasks, Settings) use `NavLink` from `nav-link`: a pill with `active`
|
|
185
|
-
from the app, not tabs.
|
|
186
|
-
Next
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
Use `FormTextField`, `FormSelectField`, `FormDatePickerField`,
|
|
194
|
-
`FormDateTimePickerField`, `FormMarkdownField`, and `FormFeedbackField` for
|
|
195
|
-
portable form controls, and `FieldError` for field messages. `Combobox` (popover)
|
|
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)
|
|
196
188
|
and `SearchPicker` (always-visible list), `DatePickerButton` (including
|
|
197
189
|
`mode="range"`), `DateTimePickerButton`, `Slider`, `Meter` (scale with ticks;
|
|
198
190
|
`Progress` is a plain 0–100 job bar), `InfoTip` (longer than `Tooltip`),
|
|
199
191
|
`Markdown`, `ColorLegend`, `StatTile`, `ThemeProvider`, `ErrorBoundary`, and
|
|
200
192
|
`SortableTableHead` are first-class kit pieces. Domain data and application
|
|
201
193
|
shells stay with the application: currency or timezone option lists,
|
|
202
|
-
query-library error views, WYSIWYG editors
|
|
203
|
-
widgets.
|
|
194
|
+
query-library error views, and WYSIWYG editors.
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ React 19, Tailwind 4, and Node >=20.19 are required. Next 16 is needed only
|
|
|
8
8
|
when using `next/nav-link`, `next/period-stepper`, or `next/tabs-nav-link`.
|
|
9
9
|
|
|
10
10
|
```sh
|
|
11
|
-
npm install --save-exact atlas-react-kit@1.
|
|
11
|
+
npm install --save-exact atlas-react-kit@1.1.0
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
Import components by subpath:
|
|
@@ -55,7 +55,8 @@ with the application.
|
|
|
55
55
|
- [Design rules](./DESIGN_RULES.md): tokens, accessibility, writing, and
|
|
56
56
|
composition. This guide ships in the npm package.
|
|
57
57
|
- [Contributing](./CONTRIBUTING.md): component changes, checks, and releases.
|
|
58
|
-
- [
|
|
58
|
+
- [Decisions behind 1.0 (record)](./ROADMAP-1.0.md): the closed roadmap that
|
|
59
|
+
led to 1.0.0.
|
|
59
60
|
- [Changelog](./CHANGELOG.md): published and upcoming changes.
|
|
60
61
|
|
|
61
62
|
## AI coding assistants
|
|
@@ -86,51 +87,8 @@ and package entry points for search and indexing tools.
|
|
|
86
87
|
|
|
87
88
|
## Development and releases
|
|
88
89
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
npm run check
|
|
92
|
-
npm test
|
|
93
|
-
npm pack
|
|
94
|
-
npm ci --prefix site
|
|
95
|
-
npm run build --prefix site
|
|
96
|
-
npm run preview --prefix site
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
`tsc` emits individual ESM files and TypeScript declarations. CSS is shipped
|
|
100
|
-
without compilation and processed by the consuming application's Tailwind build.
|
|
101
|
-
The public site is Storybook in `site/`. `npm run build --prefix site`
|
|
102
|
-
typechecks the stories, builds Storybook into `site/dist`, then checks the
|
|
103
|
-
static output. Vercel serves that directory; `/playground` and `/storybook`
|
|
104
|
-
redirect to `/`. The site and Storybook dependencies are not part of the npm
|
|
105
|
-
package. Optional feature libraries (markdown, dates, toasts, theme, Next, dnd-kit) are
|
|
106
|
-
peer dependencies and are required only when those entry points are imported.
|
|
107
|
-
The package does not bundle React.
|
|
108
|
-
|
|
109
|
-
Run `npm run dev --prefix site` (or `npm run storybook --prefix site`) for the
|
|
110
|
-
playground on port 6006. Existing examples live in `stories/` and are imported
|
|
111
|
-
by CSF story files in `site/stories/`, so component tests and previews share the
|
|
112
|
-
same examples. Add Storybook entries there when adding an example; the
|
|
113
|
-
static-output check reports any existing demo that has no Storybook entry.
|
|
114
|
-
Interactive stories use Storybook args so changing a control or interacting with
|
|
115
|
-
the component updates the same value. Configuration is in `site/.storybook/`.
|
|
116
|
-
|
|
117
|
-
The Storybook preview disables the kit's theme shortcut and uses the theme
|
|
118
|
-
toolbar instead. The accessibility panel checks the rendered story; it does not
|
|
119
|
-
replace keyboard or assistive-technology testing.
|
|
120
|
-
|
|
121
|
-
Bumping the version in `package.json` and merging to `main` (path-filtered
|
|
122
|
-
to package files) or running `workflow_dispatch` publishes through npm OIDC
|
|
123
|
-
trusted publishing after check, test, and pack gates. Versions already on npm
|
|
124
|
-
are skipped.
|
|
125
|
-
|
|
126
|
-
If trusted publishing is not configured, pack and publish a validated
|
|
127
|
-
artifact with the package owner's npm login:
|
|
128
|
-
|
|
129
|
-
```sh
|
|
130
|
-
mkdir -p release-artifacts
|
|
131
|
-
npm pack --pack-destination release-artifacts
|
|
132
|
-
npm publish ./release-artifacts/atlas-react-kit-1.0.0.tgz --access public
|
|
133
|
-
```
|
|
90
|
+
Setup, checks, the component checklist, and releases are in
|
|
91
|
+
[CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
134
92
|
|
|
135
93
|
Pin an exact package version in consuming applications. Roll back an upgrade by
|
|
136
94
|
restoring the previous version and lockfile. License: MIT; upstream notices are
|
package/dist/date-picker.d.ts
CHANGED
|
@@ -26,10 +26,20 @@ export type DatePickerSingleProps = DatePickerSharedProps & {
|
|
|
26
26
|
mode?: "single";
|
|
27
27
|
/** Controlled day, as a `Date` or `yyyy-MM-dd`. Omitted is empty. */
|
|
28
28
|
value?: DateLike;
|
|
29
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* Called with the picked day. With `clearable`, Clear calls it with
|
|
31
|
+
* `undefined`; without, a second click on the selected day does.
|
|
32
|
+
*/
|
|
30
33
|
onValueChange?: (date: Date | undefined) => void;
|
|
31
34
|
/** @deprecated Use `onValueChange`; `onChange` keeps working until 2.0. */
|
|
32
35
|
onChange?: (date: Date | undefined) => void;
|
|
36
|
+
/**
|
|
37
|
+
* Shows a Clear row under the calendar, which emits `undefined` and
|
|
38
|
+
* closes the popover. A click on the selected day then keeps it instead
|
|
39
|
+
* of clearing it, so Clear is the one way to empty the field. Default
|
|
40
|
+
* `false`.
|
|
41
|
+
*/
|
|
42
|
+
clearable?: boolean;
|
|
33
43
|
};
|
|
34
44
|
export type DatePickerRangeProps = DatePickerSharedProps & {
|
|
35
45
|
/** Pick a from/to range of days. */
|
|
@@ -46,7 +56,8 @@ export type DatePickerButtonProps = DatePickerSingleProps | DatePickerRangeProps
|
|
|
46
56
|
* Calendar popover trigger styled as a form control (same chrome as Input
|
|
47
57
|
* and SelectTrigger). `mode="single"` (default) accepts `Date` or
|
|
48
58
|
* `yyyy-MM-dd` and emits `Date | undefined`. `mode="range"` accepts
|
|
49
|
-
* `{ from, to }` and emits `DateRange | undefined`. Range mode
|
|
59
|
+
* `{ from, to }` and emits `DateRange | undefined`. Range mode always has a
|
|
60
|
+
* Clear row; single mode shows one with `clearable`. Range mode shows one
|
|
50
61
|
* month below `md` and two at `md+` unless `numberOfMonths` is set. The
|
|
51
62
|
* popover clamps to the viewport (`side` is overridable). `className` styles
|
|
52
63
|
* the trigger, `contentClassName` the popover, and `calendarProps` reaches
|
package/dist/date-picker.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { format, isSameDay } from "date-fns";
|
|
5
5
|
import { toDate, } from "./internal/date-like.js";
|
|
6
6
|
import { MD_MEDIA_QUERY, useMediaQuery } from "./internal/use-media-query.js";
|
|
7
|
-
import {
|
|
8
|
-
import { PickerClearFooter, PickerShell, limitCalendarProps, } from "./picker-shell.js";
|
|
7
|
+
import { PickerShell, } from "./picker-shell.js";
|
|
9
8
|
export { toDate };
|
|
10
9
|
/** Format a Date as a timezone-safe calendar date (`yyyy-MM-dd`). */
|
|
11
10
|
export function formatLocalDate(date) {
|
|
@@ -22,16 +21,27 @@ function formatRangeTriggerLabel(from, to, placeholder) {
|
|
|
22
21
|
return format(from, "PPP");
|
|
23
22
|
return `${format(from, "PPP")} – ${format(to, "PPP")}`;
|
|
24
23
|
}
|
|
25
|
-
function DatePickerSingleButton({ value, onValueChange, onChange, placeholder = "Pick a date", numberOfMonths, defaultMonth,
|
|
24
|
+
function DatePickerSingleButton({ value, onValueChange, onChange, placeholder = "Pick a date", numberOfMonths, defaultMonth, clearable = false, ...shell }) {
|
|
26
25
|
const [open, setOpen] = React.useState(false);
|
|
27
26
|
const emit = onValueChange ?? onChange;
|
|
28
27
|
const date = toDate(value);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
const onSelect = (next) => {
|
|
29
|
+
emit?.(next);
|
|
30
|
+
setOpen(false);
|
|
31
|
+
};
|
|
32
|
+
return (_jsx(PickerShell, { ...shell, empty: !date, label: date ? format(date, "PPP") : placeholder, dialogLabel: "Choose date", open: open, onOpenChange: setOpen, defaultMonth: defaultMonth ?? date, calendar: clearable
|
|
33
|
+
? // Day Picker's `required` keeps the day on a second click, so
|
|
34
|
+
// Clear is the one way out; its type needs the literal `true`.
|
|
35
|
+
{
|
|
36
|
+
mode: "single",
|
|
37
|
+
required: true,
|
|
38
|
+
selected: date,
|
|
39
|
+
numberOfMonths,
|
|
40
|
+
onSelect,
|
|
41
|
+
}
|
|
42
|
+
: { mode: "single", selected: date, numberOfMonths, onSelect }, onClear: clearable ? () => emit?.(undefined) : undefined, clearDisabled: !date }));
|
|
33
43
|
}
|
|
34
|
-
function DatePickerRangeButton({ value, onValueChange, onChange, placeholder = "Pick a date range", numberOfMonths, defaultMonth,
|
|
44
|
+
function DatePickerRangeButton({ value, onValueChange, onChange, placeholder = "Pick a date range", numberOfMonths, defaultMonth, ...shell }) {
|
|
35
45
|
const [open, setOpen] = React.useState(false);
|
|
36
46
|
const emit = onValueChange ?? onChange;
|
|
37
47
|
const mdUp = useMediaQuery(MD_MEDIA_QUERY);
|
|
@@ -39,25 +49,28 @@ function DatePickerRangeButton({ value, onValueChange, onChange, placeholder = "
|
|
|
39
49
|
const from = toDate(value?.from);
|
|
40
50
|
const to = toDate(value?.to);
|
|
41
51
|
const selected = from || to ? { from, to } : undefined;
|
|
42
|
-
return (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
return (_jsx(PickerShell, { ...shell, empty: !from, label: formatRangeTriggerLabel(from, to, placeholder), dialogLabel: "Choose dates", open: open, onOpenChange: setOpen, defaultMonth: defaultMonth ?? from ?? to, calendar: {
|
|
53
|
+
mode: "range",
|
|
54
|
+
// A click on a complete range (or on none) starts a new one, so a
|
|
55
|
+
// fresh range never needs Clear first. The start emits `to: undefined`.
|
|
56
|
+
resetOnSelect: true,
|
|
57
|
+
selected,
|
|
58
|
+
numberOfMonths: months,
|
|
59
|
+
onSelect: (range) => {
|
|
60
|
+
emit?.(range);
|
|
61
|
+
// Close once the ends differ; a start or a one-day range stays open.
|
|
62
|
+
if (range?.from && range.to && !isSameDay(range.from, range.to)) {
|
|
53
63
|
setOpen(false);
|
|
54
|
-
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
}, onClear: () => emit?.(undefined), clearDisabled: !from && !to }));
|
|
55
67
|
}
|
|
56
68
|
/**
|
|
57
69
|
* Calendar popover trigger styled as a form control (same chrome as Input
|
|
58
70
|
* and SelectTrigger). `mode="single"` (default) accepts `Date` or
|
|
59
71
|
* `yyyy-MM-dd` and emits `Date | undefined`. `mode="range"` accepts
|
|
60
|
-
* `{ from, to }` and emits `DateRange | undefined`. Range mode
|
|
72
|
+
* `{ from, to }` and emits `DateRange | undefined`. Range mode always has a
|
|
73
|
+
* Clear row; single mode shows one with `clearable`. Range mode shows one
|
|
61
74
|
* month below `md` and two at `md+` unless `numberOfMonths` is set. The
|
|
62
75
|
* popover clamps to the viewport (`side` is overridable). `className` styles
|
|
63
76
|
* the trigger, `contentClassName` the popover, and `calendarProps` reaches
|
|
@@ -28,4 +28,4 @@ export declare function resolveTimeStep(step: number | undefined): number;
|
|
|
28
28
|
* stays on-screen at a 390px width. `className` styles the trigger,
|
|
29
29
|
* `contentClassName` the popover, and `calendarProps` reaches Day Picker.
|
|
30
30
|
*/
|
|
31
|
-
export declare function DateTimePickerButton({ value, onValueChange, onChange, timeStep, placeholder, defaultMonth,
|
|
31
|
+
export declare function DateTimePickerButton({ value, onValueChange, onChange, timeStep, placeholder, defaultMonth, ...shell }: DateTimePickerButtonProps): React.JSX.Element;
|