atlas-react-kit 1.0.0 → 1.2.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 +43 -39
- package/DESIGN_RULES.md +23 -32
- package/README.md +5 -47
- package/dist/accordion.js +2 -2
- package/dist/badge.js +5 -2
- package/dist/breadcrumb.js +2 -2
- package/dist/button.js +7 -4
- package/dist/combobox.js +4 -5
- 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/dropdown-menu.js +3 -3
- 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/info-tip.js +7 -11
- package/dist/internal/listbox-navigation.d.ts +0 -16
- package/dist/internal/listbox-navigation.js +2 -9
- package/dist/internal/listbox-option.d.ts +0 -15
- package/dist/internal/listbox-option.js +1 -1
- package/dist/internal/overlay-styles.d.ts +0 -2
- package/dist/internal/overlay-styles.js +0 -2
- package/dist/internal/search.d.ts +5 -1
- package/dist/internal/search.js +8 -5
- package/dist/internal/styles.d.ts +27 -0
- package/dist/internal/styles.js +27 -0
- package/dist/internal/tabs-styles.d.ts +8 -7
- package/dist/internal/tabs-styles.js +15 -13
- package/dist/internal/use-drag-scroll.d.ts +17 -0
- package/dist/internal/use-drag-scroll.js +98 -0
- package/dist/internal/use-media-query.d.ts +5 -0
- package/dist/internal/use-media-query.js +5 -0
- package/dist/kanban.d.ts +1 -1
- package/dist/kanban.js +4 -3
- 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/search-picker.d.ts +1 -1
- package/dist/search-picker.js +5 -6
- package/dist/select.js +5 -5
- package/dist/tabs.d.ts +21 -10
- package/dist/tabs.js +26 -13
- package/dist/toggle.d.ts +2 -0
- package/dist/toggle.js +8 -3
- package/package.json +7 -3
- package/skills/atlas-react-kit/SKILL.md +46 -40
- package/theme.css +2 -0
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/search-picker.d.ts
CHANGED
package/dist/search-picker.js
CHANGED
|
@@ -3,9 +3,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { Input } from "./input.js";
|
|
5
5
|
import { Label } from "./label.js";
|
|
6
|
-
import {
|
|
6
|
+
import { handleListboxKey, listboxOptionId, } from "./internal/listbox-navigation.js";
|
|
7
7
|
import { ListboxEmptyStatus, PickerOption } from "./internal/listbox-option.js";
|
|
8
|
-
import { createSearchIndex, filterSearchIndex,
|
|
8
|
+
import { createSearchIndex, filterSearchIndex, foldSearchText, normalizeSearchQuery, } from "./internal/search.js";
|
|
9
|
+
import { listboxScroll } from "./internal/styles.js";
|
|
9
10
|
import { cn } from "./utils.js";
|
|
10
11
|
export { foldSearchText };
|
|
11
12
|
/**
|
|
@@ -15,7 +16,7 @@ export function defaultSearchFilter(item, query, getLabel) {
|
|
|
15
16
|
const needle = normalizeSearchQuery(query);
|
|
16
17
|
if (!needle)
|
|
17
18
|
return true;
|
|
18
|
-
return
|
|
19
|
+
return foldSearchText(getLabel(item)).includes(needle);
|
|
19
20
|
}
|
|
20
21
|
/** Frame shared by the listbox and the empty state that replaces it. */
|
|
21
22
|
const listFrame = "border border-input bg-background";
|
|
@@ -84,8 +85,6 @@ function SearchPicker({ items, getValue, getLabel, filter = defaultSearchFilter,
|
|
|
84
85
|
}
|
|
85
86
|
event.currentTarget.blur();
|
|
86
87
|
};
|
|
87
|
-
return (_jsxs("div", { "data-slot": "search-picker", className: cn("flex flex-col gap-1.5", className), ...props, children: [_jsx(Label, { htmlFor: inputId, children: label }), _jsx(Input, { id: inputId, role: "combobox", "aria-expanded": true, "aria-controls": listId, "aria-autocomplete": "list", "aria-activedescendant": active ? listboxOptionId(listId, highlight) : undefined, value: query, disabled: disabled, placeholder: placeholder, onChange: (event) => setQuery(event.target.value), onKeyDown: onKeyDown }), _jsx("ul", { id: listId, role: "listbox", "aria-label": listLabel ?? label, className: empty
|
|
88
|
-
? "sr-only"
|
|
89
|
-
: cn("max-h-60 overflow-y-auto", listFrame, listClassName), children: rows.map(({ item, value: itemValue }, index) => (_jsx(PickerOption, { "data-slot": "search-picker-option", listId: listId, index: index, active: index === highlight, selected: itemValue === value, disabled: disabled, item: item, getLabel: getLabel, onHighlight: setHighlight, onSelect: selectItem }, itemValue))) }), _jsx(ListboxEmptyStatus, { "data-slot": "search-picker-empty", empty: empty, className: listFrame, children: emptyText })] }));
|
|
88
|
+
return (_jsxs("div", { "data-slot": "search-picker", className: cn("flex flex-col gap-1.5", className), ...props, children: [_jsx(Label, { htmlFor: inputId, children: label }), _jsx(Input, { id: inputId, role: "combobox", "aria-expanded": true, "aria-controls": listId, "aria-autocomplete": "list", "aria-activedescendant": active ? listboxOptionId(listId, highlight) : undefined, value: query, disabled: disabled, placeholder: placeholder, onChange: (event) => setQuery(event.target.value), onKeyDown: onKeyDown }), _jsx("ul", { id: listId, role: "listbox", "aria-label": listLabel ?? label, className: empty ? "sr-only" : cn(listboxScroll, listFrame, listClassName), children: rows.map(({ item, value: itemValue }, index) => (_jsx(PickerOption, { "data-slot": "search-picker-option", listId: listId, index: index, active: index === highlight, selected: itemValue === value, disabled: disabled, item: item, getLabel: getLabel, onHighlight: setHighlight, onSelect: selectItem }, itemValue))) }), _jsx(ListboxEmptyStatus, { "data-slot": "search-picker-empty", empty: empty, className: listFrame, children: emptyText })] }));
|
|
90
89
|
}
|
|
91
90
|
export { SearchPicker };
|
package/dist/select.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { Select as SelectPrimitive } from "radix-ui";
|
|
5
|
-
import { dataDisabledState, disabledState, fieldSurface, focusRing, invalidState, optionState, } from "./internal/styles.js";
|
|
6
|
-
import { menuLabel,
|
|
5
|
+
import { dataDisabledState, disabledState, fieldSurface, focusRing, inlineIcon, invalidState, optionState, } from "./internal/styles.js";
|
|
6
|
+
import { menuLabel, optionRow, overlayFadeZoom, overlaySideSlide, overlaySurface, } from "./internal/overlay-styles.js";
|
|
7
7
|
import { cn } from "./utils.js";
|
|
8
8
|
import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react";
|
|
9
9
|
/**
|
|
@@ -11,7 +11,7 @@ import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react";
|
|
|
11
11
|
* replaces them (a `data-[size=*]:` variant would outrank it).
|
|
12
12
|
*/
|
|
13
13
|
const triggerHeight = { default: "h-10", sm: "h-9" };
|
|
14
|
-
const scrollButtonClassName = "z-10 flex cursor-default items-center justify-center bg-popover py-1
|
|
14
|
+
const scrollButtonClassName = cn("z-10 flex cursor-default items-center justify-center bg-popover py-1", inlineIcon.default);
|
|
15
15
|
function Select({ ...props }) {
|
|
16
16
|
return _jsx(SelectPrimitive.Root, { "data-slot": "select", ...props });
|
|
17
17
|
}
|
|
@@ -22,7 +22,7 @@ function SelectValue({ ...props }) {
|
|
|
22
22
|
return _jsx(SelectPrimitive.Value, { "data-slot": "select-value", ...props });
|
|
23
23
|
}
|
|
24
24
|
function SelectTrigger({ className, size = "default", children, ...props }) {
|
|
25
|
-
return (_jsxs(SelectPrimitive.Trigger, { "data-slot": "select-trigger", "data-size": size, className: cn(fieldSurface, focusRing, invalidState, disabledState,
|
|
25
|
+
return (_jsxs(SelectPrimitive.Trigger, { "data-slot": "select-trigger", "data-size": size, className: cn(fieldSurface, focusRing, invalidState, disabledState, inlineIcon.default, triggerHeight[size], "flex items-center justify-between gap-1.5 whitespace-nowrap data-placeholder:text-muted-foreground *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5", className), ...props, children: [children, _jsx(SelectPrimitive.Icon, { "data-slot": "select-icon", asChild: true, children: _jsx(ChevronDownIcon, { className: "pointer-events-none size-3.5 text-muted-foreground" }) })] }));
|
|
26
26
|
}
|
|
27
27
|
function SelectContent({ className, children, position = "popper", align = "center", sideOffset = 4, collisionPadding = 8, ...props }) {
|
|
28
28
|
return (_jsx(SelectPrimitive.Portal, { children: _jsxs(SelectPrimitive.Content, { "data-slot": "select-content", "data-align-trigger": position === "item-aligned", className: cn(overlaySurface, overlaySideSlide, overlayFadeZoom, "relative z-50 max-h-(--radix-select-content-available-height) min-w-(--radix-select-trigger-width) origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto data-[align-trigger=true]:animate-none", position === "popper" &&
|
|
@@ -32,7 +32,7 @@ function SelectLabel({ className, ...props }) {
|
|
|
32
32
|
return (_jsx(SelectPrimitive.Label, { "data-slot": "select-label", className: cn(menuLabel, className), ...props }));
|
|
33
33
|
}
|
|
34
34
|
function SelectItem({ className, children, ...props }) {
|
|
35
|
-
return (_jsxs(SelectPrimitive.Item, { "data-slot": "select-item", className: cn(optionRow, optionState.focus, optionState.checked, dataDisabledState,
|
|
35
|
+
return (_jsxs(SelectPrimitive.Item, { "data-slot": "select-item", className: cn(optionRow, optionState.focus, optionState.checked, dataDisabledState, inlineIcon.default, "pr-8 not-data-[variant=destructive]:focus:**:text-accent-foreground *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2", className), ...props, children: [_jsx("span", { className: "pointer-events-none absolute right-2 flex size-4 items-center justify-center", children: _jsx(SelectPrimitive.ItemIndicator, { "data-slot": "select-item-indicator", children: _jsx(CheckIcon, { className: "pointer-events-none" }) }) }), _jsx(SelectPrimitive.ItemText, { children: children })] }));
|
|
36
36
|
}
|
|
37
37
|
function SelectSeparator({ className, ...props }) {
|
|
38
38
|
return (_jsx(SelectPrimitive.Separator, { "data-slot": "select-separator", className: cn("pointer-events-none -mx-1.5 my-1.5 h-px bg-border", className), ...props }));
|
package/dist/tabs.d.ts
CHANGED
|
@@ -1,33 +1,44 @@
|
|
|
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 = {
|
|
7
|
-
/** Classes for the scroller around the tab row (`h-
|
|
8
|
+
/** Classes for the scroller around the tab row (`h-9` with `p-1` by default). */
|
|
8
9
|
scrollerClassName?: string;
|
|
9
10
|
};
|
|
10
11
|
declare function Tabs({ className, orientation, ...props }: React.ComponentProps<typeof TabsPrimitive.Root>): React.JSX.Element;
|
|
11
12
|
/**
|
|
12
|
-
* The tab row inside its scroller. `className` styles the list (its `h-
|
|
13
|
+
* The tab row inside its scroller. `className` styles the list (its `h-7`
|
|
13
14
|
* height, fill, gap); `scrollerClassName` styles the scroller around it
|
|
14
|
-
* (`h-
|
|
15
|
+
* (`h-9` with `p-1` for the active border and focus ring).
|
|
15
16
|
*/
|
|
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,13 +2,14 @@
|
|
|
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
|
/**
|
|
11
|
-
* The root's orientation, so the list can pick plain size classes (`h-
|
|
12
|
+
* The root's orientation, so the list can pick plain size classes (`h-7`)
|
|
12
13
|
* that a consumer `className` replaces, instead of `group-data-*` variants
|
|
13
14
|
* that would beat it.
|
|
14
15
|
*/
|
|
@@ -20,14 +21,16 @@ function Tabs({ className, orientation = "horizontal", ...props }) {
|
|
|
20
21
|
* Horizontal scroller around a tab row. `overflow-x: auto` alone would
|
|
21
22
|
* compute `overflow-y` to `auto` as well (CSS overflow pairing), so the
|
|
22
23
|
* active tab's border and focus ring grow a vertical scrollbar. Pad for
|
|
23
|
-
* those strokes *inside* the box (`h-
|
|
24
|
+
* those strokes *inside* the box (`h-9` = `h-7` 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(() => {
|
|
@@ -47,9 +50,9 @@ function TabsScroller({ variant = "default", orientation = "horizontal", classNa
|
|
|
47
50
|
return (_jsx("div", { ref: ref, "data-slot": "tabs-scroller", "data-variant": variant, tabIndex: overflowing ? 0 : undefined, className: cn(focusRing, tabsScrollerVariants({ variant, orientation }), className), children: children }));
|
|
48
51
|
}
|
|
49
52
|
/**
|
|
50
|
-
* The tab row inside its scroller. `className` styles the list (its `h-
|
|
53
|
+
* The tab row inside its scroller. `className` styles the list (its `h-7`
|
|
51
54
|
* height, fill, gap); `scrollerClassName` styles the scroller around it
|
|
52
|
-
* (`h-
|
|
55
|
+
* (`h-9` with `p-1` for the active border and focus ring).
|
|
53
56
|
*/
|
|
54
57
|
function TabsList({ className, scrollerClassName, variant = "default", ...props }) {
|
|
55
58
|
const orientation = React.useContext(TabsOrientationContext);
|
|
@@ -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/dist/toggle.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ import { Toggle as TogglePrimitive } from "radix-ui";
|
|
|
5
5
|
* Selected chrome shared by Toggle and ToggleGroupItem (see
|
|
6
6
|
* `selectedState.toggle`): the wash, selected text, and a selected border
|
|
7
7
|
* for `data-state=on`, `aria-pressed`, and `aria-checked`.
|
|
8
|
+
*
|
|
9
|
+
* @deprecated `toggleVariants()` already includes this chrome. Removed in 2.0.
|
|
8
10
|
*/
|
|
9
11
|
declare const selectedToggleChrome: string;
|
|
10
12
|
declare const toggleVariants: (props?: ({
|
package/dist/toggle.js
CHANGED
|
@@ -3,21 +3,26 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { cva } from "class-variance-authority";
|
|
5
5
|
import { Toggle as TogglePrimitive } from "radix-ui";
|
|
6
|
-
import { controlSize, disabledState, focusRing, invalidState, selectedState, } from "./internal/styles.js";
|
|
6
|
+
import { controlSize, controlText, disabledState, focusRing, inlineIcon, invalidState, selectedState, } from "./internal/styles.js";
|
|
7
7
|
import { cn } from "./utils.js";
|
|
8
8
|
/**
|
|
9
9
|
* Selected chrome shared by Toggle and ToggleGroupItem (see
|
|
10
10
|
* `selectedState.toggle`): the wash, selected text, and a selected border
|
|
11
11
|
* for `data-state=on`, `aria-pressed`, and `aria-checked`.
|
|
12
|
+
*
|
|
13
|
+
* @deprecated `toggleVariants()` already includes this chrome. Removed in 2.0.
|
|
12
14
|
*/
|
|
13
15
|
const selectedToggleChrome = selectedState.toggle;
|
|
14
16
|
const toggleVariants = cva([
|
|
15
17
|
focusRing,
|
|
16
18
|
selectedState.hover,
|
|
17
|
-
|
|
19
|
+
selectedState.toggle,
|
|
18
20
|
invalidState,
|
|
19
21
|
disabledState,
|
|
20
|
-
"group/toggle inline-flex items-center justify-center gap-1.5 rounded-none
|
|
22
|
+
"group/toggle inline-flex items-center justify-center gap-1.5 rounded-none",
|
|
23
|
+
controlText,
|
|
24
|
+
"transition-colors focus-visible:text-selected-foreground",
|
|
25
|
+
inlineIcon.default,
|
|
21
26
|
], {
|
|
22
27
|
variants: {
|
|
23
28
|
variant: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "atlas-react-kit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.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);
|