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/dist/picker-shell.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import type { Locale } from "date-fns";
|
|
3
3
|
import { type DateLike, type DateMatcher } from "./internal/date-like.js";
|
|
4
|
+
import { LazyCalendar } from "./lazy-calendar.js";
|
|
4
5
|
import { PopoverContent } from "./popover.js";
|
|
5
6
|
/**
|
|
6
|
-
* Not a public entry. The trigger, popover, and Clear
|
|
7
|
-
* `DatePickerButton` and `DateTimePickerButton` share.
|
|
7
|
+
* Not a public entry. The trigger, popover, calendar wiring, and Clear row
|
|
8
|
+
* that `DatePickerButton` and `DateTimePickerButton` share.
|
|
8
9
|
*/
|
|
9
10
|
/**
|
|
10
11
|
* Options for the picker's calendar. The picker owns the selection, focus,
|
|
@@ -80,13 +81,7 @@ export type PickerTriggerProps = {
|
|
|
80
81
|
/** Day Picker options passed through to the calendar. */
|
|
81
82
|
calendarProps?: PickerCalendarProps;
|
|
82
83
|
};
|
|
83
|
-
|
|
84
|
-
* `calendarProps` with `minDate` / `maxDate` applied: days outside them join
|
|
85
|
-
* any `disabled` matchers, and they bound paging unless `startMonth` /
|
|
86
|
-
* `endMonth` are set.
|
|
87
|
-
*/
|
|
88
|
-
export declare function limitCalendarProps(calendarProps: PickerCalendarProps | undefined, minDate: DateLike, maxDate: DateLike): PickerCalendarProps | undefined;
|
|
89
|
-
type PickerShellProps = Omit<PickerTriggerProps, "placeholder" | "defaultMonth" | "calendarProps" | "minDate" | "maxDate"> & {
|
|
84
|
+
type PickerShellProps = Omit<PickerTriggerProps, "placeholder"> & {
|
|
90
85
|
/** Muted trigger text while nothing is picked. */
|
|
91
86
|
empty: boolean;
|
|
92
87
|
/** Trigger text: the formatted value or the placeholder. */
|
|
@@ -97,17 +92,25 @@ type PickerShellProps = Omit<PickerTriggerProps, "placeholder" | "defaultMonth"
|
|
|
97
92
|
open: boolean;
|
|
98
93
|
/** Called when the popover opens or closes. */
|
|
99
94
|
onOpenChange: (open: boolean) => void;
|
|
100
|
-
/**
|
|
101
|
-
|
|
102
|
-
|
|
95
|
+
/**
|
|
96
|
+
* The picker's own calendar props: its mode, selection and `onSelect`.
|
|
97
|
+
* The shell adds `calendarProps`, the date limits, `defaultMonth`, and
|
|
98
|
+
* focus on the selected day.
|
|
99
|
+
*/
|
|
100
|
+
calendar: React.ComponentProps<typeof LazyCalendar>;
|
|
101
|
+
/**
|
|
102
|
+
* Shows a Clear row under the panel. Called when Clear is pressed; the
|
|
103
|
+
* popover then closes.
|
|
104
|
+
*/
|
|
105
|
+
onClear?: () => void;
|
|
106
|
+
/** Disable Clear, e.g. while nothing is picked. */
|
|
107
|
+
clearDisabled?: boolean;
|
|
108
|
+
/** Extra panel rows between the calendar and Clear, e.g. time selects. */
|
|
109
|
+
children?: React.ReactNode;
|
|
103
110
|
};
|
|
104
|
-
/**
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
disabled: boolean;
|
|
110
|
-
/** Called when Clear is pressed. */
|
|
111
|
-
onClear: () => void;
|
|
112
|
-
}): React.JSX.Element;
|
|
111
|
+
/**
|
|
112
|
+
* Form-control trigger plus the viewport-clamped popover holding the
|
|
113
|
+
* calendar, any extra rows, and an optional Clear row.
|
|
114
|
+
*/
|
|
115
|
+
export declare function PickerShell({ id, disabled, className, contentClassName, "aria-invalid": ariaInvalid, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, side, defaultMonth, minDate, maxDate, calendarProps, empty, label, dialogLabel, open, onOpenChange, calendar, onClear, clearDisabled, children, }: PickerShellProps): React.JSX.Element;
|
|
113
116
|
export {};
|
package/dist/picker-shell.js
CHANGED
|
@@ -5,7 +5,7 @@ import { CalendarIcon } from "lucide-react";
|
|
|
5
5
|
import { Button } from "./button.js";
|
|
6
6
|
import { toDate, } from "./internal/date-like.js";
|
|
7
7
|
import { PICKER_COLLISION_PADDING, pickerContentClassName, pickerTriggerClassName, pickerValueId, } from "./internal/form-control.js";
|
|
8
|
-
import { preloadCalendar } from "./lazy-calendar.js";
|
|
8
|
+
import { LazyCalendar, preloadCalendar } from "./lazy-calendar.js";
|
|
9
9
|
import { Popover, PopoverContent, PopoverTrigger } from "./popover.js";
|
|
10
10
|
import { cn } from "./utils.js";
|
|
11
11
|
/**
|
|
@@ -13,7 +13,7 @@ import { cn } from "./utils.js";
|
|
|
13
13
|
* any `disabled` matchers, and they bound paging unless `startMonth` /
|
|
14
14
|
* `endMonth` are set.
|
|
15
15
|
*/
|
|
16
|
-
|
|
16
|
+
function limitCalendarProps(calendarProps, minDate, maxDate) {
|
|
17
17
|
const min = toDate(minDate);
|
|
18
18
|
const max = toDate(maxDate);
|
|
19
19
|
if (!min && !max)
|
|
@@ -35,15 +35,17 @@ export function limitCalendarProps(calendarProps, minDate, maxDate) {
|
|
|
35
35
|
endMonth: calendarProps?.endMonth ?? max,
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Form-control trigger plus the viewport-clamped popover holding the
|
|
40
|
+
* calendar, any extra rows, and an optional Clear row.
|
|
41
|
+
*/
|
|
42
|
+
export function PickerShell({ id, disabled, className, contentClassName, "aria-invalid": ariaInvalid, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, side, defaultMonth, minDate, maxDate, calendarProps, empty, label, dialogLabel, open, onOpenChange, calendar, onClear, clearDisabled = false, children, }) {
|
|
43
|
+
return (_jsxs(Popover, { open: open, onOpenChange: onOpenChange, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { id: id, type: "button", variant: "outline", disabled: disabled, "aria-invalid": ariaInvalid, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "data-empty": empty, className: cn(pickerTriggerClassName, className), onPointerEnter: preloadCalendar, onFocus: preloadCalendar, children: [_jsx(CalendarIcon, { "data-icon": "inline-start" }), _jsx("span", { "data-slot": "date-picker-value", className: "min-w-0 text-left whitespace-normal", id: id ? pickerValueId(id) : undefined, children: label })] }) }), _jsxs(PopoverContent, { align: "start", side: side, collisionPadding: PICKER_COLLISION_PADDING, "aria-label": dialogLabel,
|
|
41
44
|
// The calendar's `autoFocus` puts focus on the selected day (or
|
|
42
45
|
// today) once it loads, as the APG date picker dialog does; Radix
|
|
43
46
|
// focusing the popover first would leave it on the container.
|
|
44
|
-
onOpenAutoFocus: (event) => event.preventDefault(),
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
return (_jsx("div", { "data-slot": "date-picker-footer", className: "flex justify-end border-t p-2", children: _jsx(Button, { type: "button", variant: "ghost", size: "sm", disabled: disabled, onClick: onClear, children: "Clear" }) }));
|
|
47
|
+
onOpenAutoFocus: (event) => event.preventDefault(), className: cn("w-auto min-w-0", pickerContentClassName, contentClassName), children: [_jsx(LazyCalendar, { ...limitCalendarProps(calendarProps, minDate, maxDate), ...calendar, autoFocus: true, defaultMonth: defaultMonth }), children, onClear ? (_jsx("div", { "data-slot": "date-picker-footer", className: "flex justify-end border-t p-2", children: _jsx(Button, { type: "button", variant: "ghost", size: "sm", disabled: clearDisabled, onClick: () => {
|
|
48
|
+
onClear();
|
|
49
|
+
onOpenChange(false);
|
|
50
|
+
}, children: "Clear" }) })) : null] })] }));
|
|
49
51
|
}
|
package/dist/tabs.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
2
|
import { type VariantProps } from "class-variance-authority";
|
|
3
3
|
import { Tabs as TabsPrimitive } from "radix-ui";
|
|
4
|
+
import type { AsChildProps } from "./internal/as-child.js";
|
|
4
5
|
import { tabsListVariants, tabsTriggerVariants } from "./internal/tabs-styles.js";
|
|
5
6
|
/** Props `TabsList` and `TabsNav` share for the scroller around their row. */
|
|
6
7
|
type TabsScrollerClassNameProps = {
|
|
@@ -16,18 +17,28 @@ declare function Tabs({ className, orientation, ...props }: React.ComponentProps
|
|
|
16
17
|
declare function TabsList({ className, scrollerClassName, variant, ...props }: React.ComponentProps<typeof TabsPrimitive.List> & Omit<VariantProps<typeof tabsListVariants>, "orientation"> & TabsScrollerClassNameProps): React.JSX.Element;
|
|
17
18
|
declare function TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Trigger>): React.JSX.Element;
|
|
18
19
|
declare function TabsContent({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Content>): React.JSX.Element;
|
|
20
|
+
/**
|
|
21
|
+
* Tab-styled link for `TabsNav`. Pass `active` from the route: the active
|
|
22
|
+
* link gets the trigger's selected chrome (`data-state="active"`) and
|
|
23
|
+
* `aria-current="page"`. `asChild` wraps a router `Link` with these styles,
|
|
24
|
+
* like `NavLink`; Next apps can import `next/tabs-nav-link` instead.
|
|
25
|
+
*/
|
|
26
|
+
declare function TabsNavLink({ asChild, active, className, ...props }: React.ComponentProps<"a"> & AsChildProps & {
|
|
27
|
+
/** This link is the current view: `aria-current="page"` and the active chrome. */
|
|
28
|
+
active: boolean;
|
|
29
|
+
}): React.JSX.Element;
|
|
19
30
|
/**
|
|
20
31
|
* Tab-styled links for a view switcher that changes the URL. Use this, not
|
|
21
32
|
* `Tabs` + `TabsTrigger asChild`, when there are no `TabsContent` panels:
|
|
22
|
-
* tabs would point assistive tech at panels that don't exist.
|
|
23
|
-
* with `
|
|
24
|
-
*
|
|
25
|
-
* `TabsNavLink` does this for you).
|
|
33
|
+
* tabs would point assistive tech at panels that don't exist. Each link is
|
|
34
|
+
* a `TabsNavLink` with `active` from the app (`asChild` for a router link,
|
|
35
|
+
* or the Next adapter `next/tabs-nav-link`).
|
|
26
36
|
*
|
|
27
37
|
* Overflow is handled here: the row scrolls horizontally only, with padding
|
|
28
38
|
* for active borders and focus rings counted in the scroller height (so the
|
|
29
|
-
* list cannot overflow on y), an edge fade while it overflows,
|
|
30
|
-
* active link scrolled into view on mount
|
|
39
|
+
* list cannot overflow on y), an edge fade while it overflows, the
|
|
40
|
+
* active link scrolled into view on mount, and a mouse drag that scrolls
|
|
41
|
+
* the row (a plain click still follows the link). Do not wrap it in
|
|
31
42
|
* `overflow-x-auto`. `scrollerClassName` styles that scroller and
|
|
32
43
|
* `listClassName` the row of links inside it.
|
|
33
44
|
*/
|
|
@@ -37,4 +48,4 @@ declare function TabsNav({ className, scrollerClassName, listClassName, children
|
|
|
37
48
|
/** Classes for the row of links inside the scroller. */
|
|
38
49
|
listClassName?: string;
|
|
39
50
|
}): React.JSX.Element;
|
|
40
|
-
export { Tabs, TabsList, TabsTrigger, TabsContent, TabsNav, tabsListVariants, tabsTriggerVariants, };
|
|
51
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent, TabsNav, TabsNavLink, tabsListVariants, tabsTriggerVariants, };
|
package/dist/tabs.js
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import {} from "class-variance-authority";
|
|
5
|
-
import { Tabs as TabsPrimitive } from "radix-ui";
|
|
5
|
+
import { Slot, Tabs as TabsPrimitive } from "radix-ui";
|
|
6
6
|
import { focusRing } from "./internal/styles.js";
|
|
7
7
|
import { tabsListVariants, tabsScrollerVariants, tabsTriggerVariants, } from "./internal/tabs-styles.js";
|
|
8
|
+
import { useDragScroll } from "./internal/use-drag-scroll.js";
|
|
8
9
|
import { useHorizontalOverflow } from "./internal/use-horizontal-overflow.js";
|
|
9
10
|
import { cn } from "./utils.js";
|
|
10
11
|
/**
|
|
@@ -22,12 +23,14 @@ function Tabs({ className, orientation = "horizontal", ...props }) {
|
|
|
22
23
|
* active tab's border and focus ring grow a vertical scrollbar. Pad for
|
|
23
24
|
* those strokes *inside* the box (`h-13` = `h-11` list + `p-1`), clip the
|
|
24
25
|
* y axis as a backstop, and fade the edges while the row overflows —
|
|
25
|
-
* same cue as `Table`.
|
|
26
|
+
* same cue as `Table`. While it overflows, a mouse can also drag the row
|
|
27
|
+
* sideways (`useDragScroll`); touch, wheel and keyboard scroll as before.
|
|
26
28
|
*/
|
|
27
29
|
function TabsScroller({ variant = "default", orientation = "horizontal", className, children, }) {
|
|
28
30
|
const ref = React.useRef(null);
|
|
29
31
|
const revealed = React.useRef(null);
|
|
30
32
|
const overflowing = useHorizontalOverflow(ref);
|
|
33
|
+
useDragScroll(ref, overflowing);
|
|
31
34
|
// Runs after every render, not just mount: TabsNav usually sits in a
|
|
32
35
|
// persistent layout, so a route change updates the active link in place.
|
|
33
36
|
React.useLayoutEffect(() => {
|
|
@@ -63,22 +66,32 @@ function TabsTrigger({ className, ...props }) {
|
|
|
63
66
|
function TabsContent({ className, ...props }) {
|
|
64
67
|
return (_jsx(TabsPrimitive.Content, { "data-slot": "tabs-content", className: cn("flex-1 text-sm", focusRing, className), ...props }));
|
|
65
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Tab-styled link for `TabsNav`. Pass `active` from the route: the active
|
|
71
|
+
* link gets the trigger's selected chrome (`data-state="active"`) and
|
|
72
|
+
* `aria-current="page"`. `asChild` wraps a router `Link` with these styles,
|
|
73
|
+
* like `NavLink`; Next apps can import `next/tabs-nav-link` instead.
|
|
74
|
+
*/
|
|
75
|
+
function TabsNavLink({ asChild, active, className, ...props }) {
|
|
76
|
+
const Comp = asChild ? Slot.Root : "a";
|
|
77
|
+
return (_jsx(Comp, { "data-slot": "tabs-nav-link", "data-state": active ? "active" : "inactive", "aria-current": active ? "page" : undefined, className: cn(tabsTriggerVariants(), className), ...props }));
|
|
78
|
+
}
|
|
66
79
|
/**
|
|
67
80
|
* Tab-styled links for a view switcher that changes the URL. Use this, not
|
|
68
81
|
* `Tabs` + `TabsTrigger asChild`, when there are no `TabsContent` panels:
|
|
69
|
-
* tabs would point assistive tech at panels that don't exist.
|
|
70
|
-
* with `
|
|
71
|
-
*
|
|
72
|
-
* `TabsNavLink` does this for you).
|
|
82
|
+
* tabs would point assistive tech at panels that don't exist. Each link is
|
|
83
|
+
* a `TabsNavLink` with `active` from the app (`asChild` for a router link,
|
|
84
|
+
* or the Next adapter `next/tabs-nav-link`).
|
|
73
85
|
*
|
|
74
86
|
* Overflow is handled here: the row scrolls horizontally only, with padding
|
|
75
87
|
* for active borders and focus rings counted in the scroller height (so the
|
|
76
|
-
* list cannot overflow on y), an edge fade while it overflows,
|
|
77
|
-
* active link scrolled into view on mount
|
|
88
|
+
* list cannot overflow on y), an edge fade while it overflows, the
|
|
89
|
+
* active link scrolled into view on mount, and a mouse drag that scrolls
|
|
90
|
+
* the row (a plain click still follows the link). Do not wrap it in
|
|
78
91
|
* `overflow-x-auto`. `scrollerClassName` styles that scroller and
|
|
79
92
|
* `listClassName` the row of links inside it.
|
|
80
93
|
*/
|
|
81
94
|
function TabsNav({ className, scrollerClassName, listClassName, children, ...props }) {
|
|
82
95
|
return (_jsx("nav", { "data-slot": "tabs-nav", "data-orientation": "horizontal", className: cn("group/tabs min-w-0 max-w-full", className), ...props, children: _jsx(TabsScroller, { className: scrollerClassName, children: _jsx("div", { "data-slot": "tabs-list", "data-variant": "default", className: cn(tabsListVariants({ orientation: "horizontal" }), "bg-transparent", listClassName), children: children }) }) }));
|
|
83
96
|
}
|
|
84
|
-
export { Tabs, TabsList, TabsTrigger, TabsContent, TabsNav, tabsListVariants, tabsTriggerVariants, };
|
|
97
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent, TabsNav, TabsNavLink, tabsListVariants, tabsTriggerVariants, };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "atlas-react-kit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Shared React components and Tailwind theme for Atlas",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -252,6 +252,10 @@
|
|
|
252
252
|
"types": "./dist/form/field-error.d.ts",
|
|
253
253
|
"default": "./dist/form/field-error.js"
|
|
254
254
|
},
|
|
255
|
+
"./form/focus-first-invalid": {
|
|
256
|
+
"types": "./dist/form/focus-first-invalid.d.ts",
|
|
257
|
+
"default": "./dist/form/focus-first-invalid.js"
|
|
258
|
+
},
|
|
255
259
|
"./form/markdown-field": {
|
|
256
260
|
"types": "./dist/form/markdown-field.d.ts",
|
|
257
261
|
"default": "./dist/form/markdown-field.js"
|
|
@@ -295,11 +299,11 @@
|
|
|
295
299
|
"docs:check": "node scripts/check-doc-links.mjs",
|
|
296
300
|
"lint": "oxlint --type-aware --deny-warnings",
|
|
297
301
|
"knip": "knip",
|
|
298
|
-
"sast": "
|
|
302
|
+
"sast": "semgrep scan --config .semgrep.yml --metrics=off --disable-version-check --error --quiet src",
|
|
299
303
|
"secrets": "secretlint \"**/*\"",
|
|
300
304
|
"size": "size-limit",
|
|
301
305
|
"gate:static": "npm run lint && npm run knip && npm run sast && npm run secrets",
|
|
302
|
-
"gate": "npm run check && npm run gate:static && npm run size && vitest run",
|
|
306
|
+
"gate": "npm run check && npm run gate:static && npm run size && vitest run --coverage",
|
|
303
307
|
"test": "npm run build && vitest run",
|
|
304
308
|
"test:coverage": "npm run build && vitest run --coverage",
|
|
305
309
|
"prepack": "npm run build",
|
|
@@ -42,10 +42,9 @@ root (or a `dark` class on an ancestor). Toasts need one `<Toaster />` from
|
|
|
42
42
|
`atlas-react-kit/toast`; show them with `toast()` from the same module, using
|
|
43
43
|
only the options it declares. Never import a toast library such as `sonner`
|
|
44
44
|
directly: the kit owns the toast API, and a second copy never reaches its
|
|
45
|
-
toaster.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
around unstyled components.
|
|
45
|
+
toaster. Some entry points need optional peers; install the extras from the
|
|
46
|
+
"Optional peer dependencies" table in `COMPONENTS.md` when you import them.
|
|
47
|
+
Fix missing setup rather than working around unstyled components.
|
|
49
48
|
|
|
50
49
|
## 3. Pick the kit component for the job
|
|
51
50
|
|
|
@@ -53,42 +52,49 @@ Import each component from its subpath, e.g.
|
|
|
53
52
|
`import { Button } from "atlas-react-kit/button"`. There is no root barrel, and
|
|
54
53
|
`dist/` or `internal/` paths are not public API.
|
|
55
54
|
|
|
56
|
-
| Need
|
|
57
|
-
|
|
|
58
|
-
| Short fixed list (a handful of options)
|
|
59
|
-
| Long or searchable list (timezones, currencies)
|
|
60
|
-
| Always-visible searchable list (places)
|
|
61
|
-
| One date, or a from/to range
|
|
62
|
-
| Date and time (not `datetime-local`)
|
|
63
|
-
| Labelled field with error text
|
|
64
|
-
|
|
|
65
|
-
|
|
|
66
|
-
|
|
|
67
|
-
|
|
|
68
|
-
|
|
|
69
|
-
|
|
|
70
|
-
|
|
|
71
|
-
|
|
|
72
|
-
|
|
|
73
|
-
|
|
|
74
|
-
|
|
|
75
|
-
|
|
|
76
|
-
|
|
|
77
|
-
|
|
|
78
|
-
|
|
|
79
|
-
|
|
|
80
|
-
|
|
|
81
|
-
|
|
|
82
|
-
|
|
|
83
|
-
|
|
|
84
|
-
|
|
|
85
|
-
|
|
|
86
|
-
|
|
|
87
|
-
|
|
|
88
|
-
|
|
|
89
|
-
|
|
|
90
|
-
|
|
|
91
|
-
|
|
|
55
|
+
| Need | Use |
|
|
56
|
+
| ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
57
|
+
| Short fixed list (a handful of options) | `Select` family from `select` |
|
|
58
|
+
| Long or searchable list (timezones, currencies) | `Combobox` from `combobox` with `{ value, label }[]` options; set `creatable` to invent a value the catalog lacks |
|
|
59
|
+
| Always-visible searchable list (places) | `SearchPicker` from `search-picker` |
|
|
60
|
+
| One date, or a from/to range | `DatePickerButton` from `date-picker` (`mode="range"` for ranges) |
|
|
61
|
+
| Date and time (not `datetime-local`) | `DateTimePickerButton` from `date-time-picker` |
|
|
62
|
+
| Labelled field with error text | `FormTextField`, `FormSelectField`, `FormDatePickerField`, `FormDateTimePickerField`, `FormMarkdownField` from `form/*` |
|
|
63
|
+
| Checkbox, checkbox group, toggle group, combobox in a form | `FormCheckboxField`, `FormCheckboxGroupField`, `FormToggleGroupField`, `FormComboboxField` from `form/*` |
|
|
64
|
+
| Custom control the kit has no form field for | `FormControlField` from `form/control-field` |
|
|
65
|
+
| Form-level error (save failed) | `FormFeedbackField` from `form/feedback-field` |
|
|
66
|
+
| Content panels on one page | `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent` from `tabs` |
|
|
67
|
+
| View switch that changes the URL | `TabsNav` with `TabsNavLink` from `tabs` (`asChild` for a router link); Next: `next/tabs-nav-link` |
|
|
68
|
+
| App shell nav (Calendar, Tasks, Settings) | `NavLink` from `nav-link` (`asChild` for a router link); Next: `next/nav-link`; `active={isNavActive(pathname, href)}` |
|
|
69
|
+
| Focus the first invalid field when a submit fails | `focusFirstInvalid(formId)` from `form/focus-first-invalid`; give fields `id={field.name}` |
|
|
70
|
+
| Skip-to-content link | `SkipLink` from `skip-link` |
|
|
71
|
+
| Page title row with actions | `PageHeader` from `page-header` |
|
|
72
|
+
| Previous / today / next period | `PeriodStepper` from `period-stepper`; `next/period-stepper` for URL periods |
|
|
73
|
+
| Cards in columns that drag | `KanbanBoard` from `kanban`; `KanbanSkeleton` while loading |
|
|
74
|
+
| SVG chart axes, grid, tooltip, legend | `chart` helpers (`chartFrame`, `ChartYAxisGrid`, `ChartTooltip`, `ChartLegend`) |
|
|
75
|
+
| Status chip (paid, pending, failed) | `Badge variant="soft"` with `tone="success"`, `"warning"`, `"destructive"`, or `"neutral"` |
|
|
76
|
+
| Data table | `Table` family from `table`; `SortableTableHead`; `TableBodySkeleton` while loading |
|
|
77
|
+
| Nothing to show yet | `EmptyPanel` from `empty-panel` |
|
|
78
|
+
| Grouped content | `Card` family from `card`; `<Card flush>` for an edge-to-edge table or list |
|
|
79
|
+
| Modal | `Dialog` family from `dialog` |
|
|
80
|
+
| Label for an icon button, or a short hint | `Tooltip` from `tooltip`; keep `aria-label` on icon buttons |
|
|
81
|
+
| Longer help from a glyph | `InfoTip` / `InlineTip` from `info-tip` (not `Tooltip`) |
|
|
82
|
+
| Color scale key | `ColorLegend` from `color-legend` |
|
|
83
|
+
| GitHub-flavoured Markdown | `Markdown` from `markdown`; write/preview with `FormMarkdownField` |
|
|
84
|
+
| Summary figure in a strip | `StatTile` from `stat-tile` (`statTileGrid`, `sectionLabel`) |
|
|
85
|
+
| Value against a scale, optional ticks | `Meter` from `meter` (not `Progress`) |
|
|
86
|
+
| Number on a continuous scale | `Slider` from `slider` |
|
|
87
|
+
| Row actions or an overflow menu of commands | `DropdownMenu` family from `dropdown-menu` |
|
|
88
|
+
| Setting that applies immediately | `Switch` from `switch`; `Checkbox` for choices submitted with a form |
|
|
89
|
+
| One choice from a few visible options | `RadioGroup` and `RadioGroupItem` from `radio-group` |
|
|
90
|
+
| Inline message about a page or section | `Alert` from `alert` (`success`, `warning`, `destructive` variants) |
|
|
91
|
+
| Person or workspace image | `Avatar`, `AvatarImage`, `AvatarFallback` from `avatar` |
|
|
92
|
+
| Sections that expand in place | `Accordion` family from `accordion` |
|
|
93
|
+
| Pages of a long list | `Pagination` family from `pagination` |
|
|
94
|
+
| Where the page sits in a hierarchy | `Breadcrumb` family from `breadcrumb` |
|
|
95
|
+
| Work in progress | `Progress` from `progress` when the percent is known, else `Spinner` from `spinner` |
|
|
96
|
+
| Crash recovery around a section | `ErrorBoundary` from `error-boundary` |
|
|
97
|
+
| Conditional classes | `cn` from `utils` |
|
|
92
98
|
|
|
93
99
|
If nothing fits, build the piece in the app from kit primitives and semantic
|
|
94
100
|
tokens. Do not copy kit source into the app; a missing shared component belongs
|
package/theme.css
CHANGED
|
@@ -90,6 +90,7 @@
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
:root {
|
|
93
|
+
color-scheme: light;
|
|
93
94
|
--background: oklch(1 0 0);
|
|
94
95
|
--foreground: oklch(0.145 0 0);
|
|
95
96
|
--card: oklch(1 0 0);
|
|
@@ -142,6 +143,7 @@
|
|
|
142
143
|
}
|
|
143
144
|
|
|
144
145
|
.dark {
|
|
146
|
+
color-scheme: dark;
|
|
145
147
|
--background: oklch(0.145 0 0);
|
|
146
148
|
--foreground: oklch(0.985 0 0);
|
|
147
149
|
--card: oklch(0.205 0 0);
|