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/internal/search.js
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Case-fold and strip combining marks so "nino" matches "Niño". Shared by
|
|
3
|
+
* Combobox and SearchPicker (which re-exports it).
|
|
4
|
+
*/
|
|
5
|
+
export function foldSearchText(value) {
|
|
6
|
+
return value.normalize("NFD").replace(/\p{M}/gu, "").toLowerCase();
|
|
7
|
+
}
|
|
2
8
|
/** Fold the query once; item labels are folded when their catalog changes. */
|
|
3
9
|
export function normalizeSearchQuery(query) {
|
|
4
10
|
return foldSearchText(query.trim());
|
|
5
11
|
}
|
|
6
|
-
export function matchesSearchLabel(label, needle) {
|
|
7
|
-
return label.includes(needle);
|
|
8
|
-
}
|
|
9
12
|
export function createSearchIndex(items, getLabel) {
|
|
10
13
|
return items.map((item) => ({ item, label: foldSearchText(getLabel(item)) }));
|
|
11
14
|
}
|
|
12
15
|
export function filterSearchIndex(index, needle) {
|
|
13
16
|
return index
|
|
14
|
-
.filter(({ label }) =>
|
|
17
|
+
.filter(({ label }) => label.includes(needle))
|
|
15
18
|
.map(({ item }) => item);
|
|
16
19
|
}
|
|
@@ -39,6 +39,11 @@ export declare const selectedState: {
|
|
|
39
39
|
};
|
|
40
40
|
/** The "No results" row of a listbox. */
|
|
41
41
|
export declare const listboxEmpty = "px-3 py-4 text-center text-sm text-muted-foreground";
|
|
42
|
+
/**
|
|
43
|
+
* The scrolling frame of a listbox (Combobox, SearchPicker): about ten rows
|
|
44
|
+
* tall, then scrolls. A consumer `listClassName` replaces the height.
|
|
45
|
+
*/
|
|
46
|
+
export declare const listboxScroll = "max-h-60 overflow-y-auto";
|
|
42
47
|
/** Options in menus and listboxes. */
|
|
43
48
|
export declare const optionState: {
|
|
44
49
|
/** Highlight as focus moves: grey fill plus a 2px ring bar (the fill alone is 1.1:1). */
|
|
@@ -72,6 +77,28 @@ export declare const controlSize: {
|
|
|
72
77
|
sm: string;
|
|
73
78
|
lg: string;
|
|
74
79
|
};
|
|
80
|
+
/**
|
|
81
|
+
* Label text of a control (Button, Toggle, Badge): small, bold, wide-tracked
|
|
82
|
+
* uppercase on one line. Badge adds its own `text-[0.625rem]` after it;
|
|
83
|
+
* tailwind-merge then drops the `text-xs`.
|
|
84
|
+
*/
|
|
85
|
+
export declare const controlText = "text-xs font-semibold tracking-widest whitespace-nowrap uppercase";
|
|
86
|
+
/**
|
|
87
|
+
* Icons inside a control or row: no pointer events, no shrinking, 14px
|
|
88
|
+
* unless the icon sets its own `size-*`. `sm` lowers that default to 12px
|
|
89
|
+
* for the xs controls; it goes after `default`, and tailwind-merge keeps
|
|
90
|
+
* the later size.
|
|
91
|
+
*/
|
|
92
|
+
export declare const inlineIcon: {
|
|
93
|
+
default: string;
|
|
94
|
+
sm: string;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Brand-colored text on hover and keyboard focus for quiet links and
|
|
98
|
+
* triggers (BreadcrumbLink, AccordionTrigger, InfoTip). DESIGN_RULES.md,
|
|
99
|
+
* "Tokens": brand text is `text-selected-foreground`, never `text-primary`.
|
|
100
|
+
*/
|
|
101
|
+
export declare const quietTextHover = "hover:text-selected-foreground focus-visible:text-selected-foreground";
|
|
75
102
|
/**
|
|
76
103
|
* Uppercase muted label used on tiles, legends, and thin section chrome.
|
|
77
104
|
* Re-exported publicly from `atlas-react-kit/stat-tile`.
|
package/dist/internal/styles.js
CHANGED
|
@@ -39,6 +39,11 @@ export const selectedState = {
|
|
|
39
39
|
};
|
|
40
40
|
/** The "No results" row of a listbox. */
|
|
41
41
|
export const listboxEmpty = "px-3 py-4 text-center text-sm text-muted-foreground";
|
|
42
|
+
/**
|
|
43
|
+
* The scrolling frame of a listbox (Combobox, SearchPicker): about ten rows
|
|
44
|
+
* tall, then scrolls. A consumer `listClassName` replaces the height.
|
|
45
|
+
*/
|
|
46
|
+
export const listboxScroll = "max-h-60 overflow-y-auto";
|
|
42
47
|
/** Options in menus and listboxes. */
|
|
43
48
|
export const optionState = {
|
|
44
49
|
/** Highlight as focus moves: grey fill plus a 2px ring bar (the fill alone is 1.1:1). */
|
|
@@ -72,6 +77,28 @@ export const controlSize = {
|
|
|
72
77
|
sm: "h-9 px-4 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
|
|
73
78
|
lg: "h-11 px-8 has-data-[icon=inline-end]:pr-5 has-data-[icon=inline-start]:pl-5",
|
|
74
79
|
};
|
|
80
|
+
/**
|
|
81
|
+
* Label text of a control (Button, Toggle, Badge): small, bold, wide-tracked
|
|
82
|
+
* uppercase on one line. Badge adds its own `text-[0.625rem]` after it;
|
|
83
|
+
* tailwind-merge then drops the `text-xs`.
|
|
84
|
+
*/
|
|
85
|
+
export const controlText = "text-xs font-semibold tracking-widest whitespace-nowrap uppercase";
|
|
86
|
+
/**
|
|
87
|
+
* Icons inside a control or row: no pointer events, no shrinking, 14px
|
|
88
|
+
* unless the icon sets its own `size-*`. `sm` lowers that default to 12px
|
|
89
|
+
* for the xs controls; it goes after `default`, and tailwind-merge keeps
|
|
90
|
+
* the later size.
|
|
91
|
+
*/
|
|
92
|
+
export const inlineIcon = {
|
|
93
|
+
default: "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
|
|
94
|
+
sm: "[&_svg:not([class*='size-'])]:size-3",
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Brand-colored text on hover and keyboard focus for quiet links and
|
|
98
|
+
* triggers (BreadcrumbLink, AccordionTrigger, InfoTip). DESIGN_RULES.md,
|
|
99
|
+
* "Tokens": brand text is `text-selected-foreground`, never `text-primary`.
|
|
100
|
+
*/
|
|
101
|
+
export const quietTextHover = "hover:text-selected-foreground focus-visible:text-selected-foreground";
|
|
75
102
|
/**
|
|
76
103
|
* Uppercase muted label used on tiles, legends, and thin section chrome.
|
|
77
104
|
* Re-exported publicly from `atlas-react-kit/stat-tile`.
|
|
@@ -10,13 +10,14 @@ declare const tabsListVariants: (props?: ({
|
|
|
10
10
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
11
11
|
/**
|
|
12
12
|
* Horizontal scroller around a tab row. `p-1` leaves room for the active
|
|
13
|
-
* border and focus ring; the height is the `h-
|
|
14
|
-
* (`h-
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* `overflow-y` is not hidden. Line
|
|
18
|
-
* (`h-
|
|
19
|
-
* `w-full`. `theme.css` hides the
|
|
13
|
+
* border and focus ring; the height is the `h-7` list plus that padding
|
|
14
|
+
* (`h-9` = 28px + 8px) so the list fits the content box and the outer
|
|
15
|
+
* row matches `Button` `size="sm"`. Putting padding on an overflow box
|
|
16
|
+
* without this height makes `scrollHeight` taller than `clientHeight`
|
|
17
|
+
* and a vertical scrollbar appears if `overflow-y` is not hidden. Line
|
|
18
|
+
* variant adds `pb-2` for the underline (`h-10` = 28px + 4px + 8px). A
|
|
19
|
+
* block, so it fills its parent without `w-full`. `theme.css` hides the
|
|
20
|
+
* scrollbar and draws the edge fades.
|
|
20
21
|
*/
|
|
21
22
|
declare const tabsScrollerVariants: (props?: ({
|
|
22
23
|
variant?: "default" | "line" | null | undefined;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { cva } from "class-variance-authority";
|
|
2
|
-
import { disabledState, focusRing, selectedState } from "./styles.js";
|
|
2
|
+
import { disabledState, focusRing, inlineIcon, selectedState, } from "./styles.js";
|
|
3
3
|
/**
|
|
4
4
|
* Tab row. `TabsList` and `TabsNav` pass `orientation`, which picks plain
|
|
5
5
|
* size classes so a consumer `className` (`h-9`) replaces them. Called
|
|
@@ -13,8 +13,8 @@ const tabsListVariants = cva("group/tabs-list inline-flex w-max min-w-full items
|
|
|
13
13
|
line: "gap-1 bg-transparent",
|
|
14
14
|
},
|
|
15
15
|
orientation: {
|
|
16
|
-
auto: 'group-data-[orientation="horizontal"]/tabs:h-
|
|
17
|
-
horizontal: "h-
|
|
16
|
+
auto: 'group-data-[orientation="horizontal"]/tabs:h-7 group-data-[orientation="vertical"]/tabs:h-fit group-data-[orientation="vertical"]/tabs:flex-col',
|
|
17
|
+
horizontal: "h-7",
|
|
18
18
|
vertical: "h-fit flex-col",
|
|
19
19
|
},
|
|
20
20
|
},
|
|
@@ -25,13 +25,14 @@ const tabsListVariants = cva("group/tabs-list inline-flex w-max min-w-full items
|
|
|
25
25
|
});
|
|
26
26
|
/**
|
|
27
27
|
* Horizontal scroller around a tab row. `p-1` leaves room for the active
|
|
28
|
-
* border and focus ring; the height is the `h-
|
|
29
|
-
* (`h-
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* `overflow-y` is not hidden. Line
|
|
33
|
-
* (`h-
|
|
34
|
-
* `w-full`. `theme.css` hides the
|
|
28
|
+
* border and focus ring; the height is the `h-7` list plus that padding
|
|
29
|
+
* (`h-9` = 28px + 8px) so the list fits the content box and the outer
|
|
30
|
+
* row matches `Button` `size="sm"`. Putting padding on an overflow box
|
|
31
|
+
* without this height makes `scrollHeight` taller than `clientHeight`
|
|
32
|
+
* and a vertical scrollbar appears if `overflow-y` is not hidden. Line
|
|
33
|
+
* variant adds `pb-2` for the underline (`h-10` = 28px + 4px + 8px). A
|
|
34
|
+
* block, so it fills its parent without `w-full`. `theme.css` hides the
|
|
35
|
+
* scrollbar and draws the edge fades.
|
|
35
36
|
*/
|
|
36
37
|
const tabsScrollerVariants = cva("relative box-border min-w-0 max-w-full overflow-x-auto overflow-y-hidden overscroll-x-contain p-1", {
|
|
37
38
|
variants: {
|
|
@@ -45,8 +46,8 @@ const tabsScrollerVariants = cva("relative box-border min-w-0 max-w-full overflo
|
|
|
45
46
|
},
|
|
46
47
|
},
|
|
47
48
|
compoundVariants: [
|
|
48
|
-
{ variant: "default", orientation: "horizontal", className: "h-
|
|
49
|
-
{ variant: "line", orientation: "horizontal", className: "h-
|
|
49
|
+
{ variant: "default", orientation: "horizontal", className: "h-9" },
|
|
50
|
+
{ variant: "line", orientation: "horizontal", className: "h-10" },
|
|
50
51
|
],
|
|
51
52
|
defaultVariants: {
|
|
52
53
|
variant: "default",
|
|
@@ -59,7 +60,8 @@ const tabsTriggerVariants = cva([
|
|
|
59
60
|
// flex-none + px-4: every trigger and TabsNav link keeps the same
|
|
60
61
|
// horizontal padding. flex-1 used to grow short labels and make the
|
|
61
62
|
// longest (e.g. Apple Reminders) look tighter. No last-child padding.
|
|
62
|
-
'relative inline-flex h-full min-w-max flex-none items-center justify-center gap-2 border border-transparent px-4 py-1.5 text-xs font-semibold tracking-wider whitespace-nowrap text-foreground/60 uppercase transition-all group-data-[orientation="vertical"]/tabs:w-full group-data-[orientation="vertical"]/tabs:justify-start group-data-[orientation="vertical"]/tabs:px-4 group-data-[orientation="vertical"]/tabs:py-2 hover:text-foreground has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5 dark:text-muted-foreground dark:hover:text-foreground
|
|
63
|
+
'relative inline-flex h-full min-w-max flex-none items-center justify-center gap-2 border border-transparent px-4 py-1.5 text-xs font-semibold tracking-wider whitespace-nowrap text-foreground/60 uppercase transition-all group-data-[orientation="vertical"]/tabs:w-full group-data-[orientation="vertical"]/tabs:justify-start group-data-[orientation="vertical"]/tabs:px-4 group-data-[orientation="vertical"]/tabs:py-2 hover:text-foreground has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5 dark:text-muted-foreground dark:hover:text-foreground',
|
|
64
|
+
inlineIcon.default,
|
|
63
65
|
'group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state="active"]:border-transparent group-data-[variant=line]/tabs-list:data-[state="active"]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state="active"]:bg-transparent',
|
|
64
66
|
selectedState.active,
|
|
65
67
|
'after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-[orientation="horizontal"]/tabs:after:inset-x-0 group-data-[orientation="horizontal"]/tabs:after:bottom-[-5px] group-data-[orientation="horizontal"]/tabs:after:h-0.5 group-data-[orientation="vertical"]/tabs:after:inset-y-0 group-data-[orientation="vertical"]/tabs:after:-right-1 group-data-[orientation="vertical"]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state="active"]:after:opacity-100',
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/** Pointer travel, in px, before a press on the scroller counts as a drag. */
|
|
3
|
+
export declare const DRAG_THRESHOLD = 4;
|
|
4
|
+
/**
|
|
5
|
+
* Lets a mouse drag a horizontal scroller sideways, the way touch already
|
|
6
|
+
* does natively. Mouse only (`pointerType === "mouse"`, left button); touch
|
|
7
|
+
* and pen keep the browser's own scrolling. A press becomes a drag after
|
|
8
|
+
* `DRAG_THRESHOLD` px of travel, so a plain click on a tab or link still
|
|
9
|
+
* activates it; once a drag has moved, the click after pointerup is
|
|
10
|
+
* swallowed so the drag never also follows a link. (Radix `TabsTrigger`
|
|
11
|
+
* selects on mousedown, so a drag that starts on an inactive tab still
|
|
12
|
+
* selects that tab on press.)
|
|
13
|
+
*
|
|
14
|
+
* Listens only while `enabled` (the scroller overflows): a row that fits
|
|
15
|
+
* behaves exactly as before. Keyboard scrolling is untouched.
|
|
16
|
+
*/
|
|
17
|
+
export declare function useDragScroll(ref: React.RefObject<HTMLElement | null>, enabled: boolean): void;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
/** Pointer travel, in px, before a press on the scroller counts as a drag. */
|
|
4
|
+
export const DRAG_THRESHOLD = 4;
|
|
5
|
+
/**
|
|
6
|
+
* Lets a mouse drag a horizontal scroller sideways, the way touch already
|
|
7
|
+
* does natively. Mouse only (`pointerType === "mouse"`, left button); touch
|
|
8
|
+
* and pen keep the browser's own scrolling. A press becomes a drag after
|
|
9
|
+
* `DRAG_THRESHOLD` px of travel, so a plain click on a tab or link still
|
|
10
|
+
* activates it; once a drag has moved, the click after pointerup is
|
|
11
|
+
* swallowed so the drag never also follows a link. (Radix `TabsTrigger`
|
|
12
|
+
* selects on mousedown, so a drag that starts on an inactive tab still
|
|
13
|
+
* selects that tab on press.)
|
|
14
|
+
*
|
|
15
|
+
* Listens only while `enabled` (the scroller overflows): a row that fits
|
|
16
|
+
* behaves exactly as before. Keyboard scrolling is untouched.
|
|
17
|
+
*/
|
|
18
|
+
export function useDragScroll(ref, enabled) {
|
|
19
|
+
React.useEffect(() => {
|
|
20
|
+
const element = ref.current;
|
|
21
|
+
if (!element || !enabled)
|
|
22
|
+
return;
|
|
23
|
+
let pointerId = null;
|
|
24
|
+
let startX = 0;
|
|
25
|
+
let startScrollLeft = 0;
|
|
26
|
+
let dragged = false;
|
|
27
|
+
const onPointerDown = (event) => {
|
|
28
|
+
if (event.pointerType !== "mouse" || event.button !== 0)
|
|
29
|
+
return;
|
|
30
|
+
pointerId = event.pointerId;
|
|
31
|
+
startX = event.clientX;
|
|
32
|
+
startScrollLeft = element.scrollLeft;
|
|
33
|
+
dragged = false;
|
|
34
|
+
};
|
|
35
|
+
const onPointerMove = (event) => {
|
|
36
|
+
if (pointerId === null || event.pointerId !== pointerId)
|
|
37
|
+
return;
|
|
38
|
+
if ((event.buttons & 1) === 0) {
|
|
39
|
+
// The button came up outside the scroller before capture.
|
|
40
|
+
pointerId = null;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const delta = event.clientX - startX;
|
|
44
|
+
if (!dragged) {
|
|
45
|
+
if (Math.abs(delta) < DRAG_THRESHOLD)
|
|
46
|
+
return;
|
|
47
|
+
dragged = true;
|
|
48
|
+
// Keep the moves and the up when the pointer leaves the box. The
|
|
49
|
+
// browser releases capture on pointerup and pointercancel.
|
|
50
|
+
element.setPointerCapture(event.pointerId);
|
|
51
|
+
}
|
|
52
|
+
element.scrollLeft = startScrollLeft - delta;
|
|
53
|
+
};
|
|
54
|
+
const onPointerUp = (event) => {
|
|
55
|
+
if (event.pointerId !== pointerId)
|
|
56
|
+
return;
|
|
57
|
+
pointerId = null;
|
|
58
|
+
// `dragged` stays set so the click that follows is swallowed.
|
|
59
|
+
};
|
|
60
|
+
const onPointerCancel = (event) => {
|
|
61
|
+
if (event.pointerId !== pointerId)
|
|
62
|
+
return;
|
|
63
|
+
pointerId = null;
|
|
64
|
+
// No click follows a cancel, so the next real click must go through.
|
|
65
|
+
dragged = false;
|
|
66
|
+
};
|
|
67
|
+
// Capture phase, so it runs before the link's or trigger's own handler.
|
|
68
|
+
const onClick = (event) => {
|
|
69
|
+
if (!dragged)
|
|
70
|
+
return;
|
|
71
|
+
dragged = false;
|
|
72
|
+
event.preventDefault();
|
|
73
|
+
event.stopPropagation();
|
|
74
|
+
};
|
|
75
|
+
// Links are draggable by default: a native drag would cancel the pointer.
|
|
76
|
+
// A text selection started in the row would smear across the labels.
|
|
77
|
+
const onDragOrSelectStart = (event) => {
|
|
78
|
+
if (pointerId !== null)
|
|
79
|
+
event.preventDefault();
|
|
80
|
+
};
|
|
81
|
+
element.addEventListener("pointerdown", onPointerDown);
|
|
82
|
+
element.addEventListener("pointermove", onPointerMove);
|
|
83
|
+
element.addEventListener("pointerup", onPointerUp);
|
|
84
|
+
element.addEventListener("pointercancel", onPointerCancel);
|
|
85
|
+
element.addEventListener("click", onClick, true);
|
|
86
|
+
element.addEventListener("dragstart", onDragOrSelectStart);
|
|
87
|
+
element.addEventListener("selectstart", onDragOrSelectStart);
|
|
88
|
+
return () => {
|
|
89
|
+
element.removeEventListener("pointerdown", onPointerDown);
|
|
90
|
+
element.removeEventListener("pointermove", onPointerMove);
|
|
91
|
+
element.removeEventListener("pointerup", onPointerUp);
|
|
92
|
+
element.removeEventListener("pointercancel", onPointerCancel);
|
|
93
|
+
element.removeEventListener("click", onClick, true);
|
|
94
|
+
element.removeEventListener("dragstart", onDragOrSelectStart);
|
|
95
|
+
element.removeEventListener("selectstart", onDragOrSelectStart);
|
|
96
|
+
};
|
|
97
|
+
}, [ref, enabled]);
|
|
98
|
+
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
/** Tailwind `md` breakpoint. Used for range calendars that stack on small screens. */
|
|
2
2
|
export declare const MD_MEDIA_QUERY = "(min-width: 768px)";
|
|
3
|
+
/**
|
|
4
|
+
* A pointer that can hover (a mouse or trackpad). Touch and coarse pointers
|
|
5
|
+
* must use click; a hover-open popover would trap them after the first tap.
|
|
6
|
+
*/
|
|
7
|
+
export declare const HOVER_MEDIA_QUERY = "(hover: hover) and (pointer: fine)";
|
|
3
8
|
/**
|
|
4
9
|
* Subscribe to a CSS media query with `useSyncExternalStore`. The server
|
|
5
10
|
* render and hydration use `defaultMatches`, so the markup always agrees;
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
/** Tailwind `md` breakpoint. Used for range calendars that stack on small screens. */
|
|
4
4
|
export const MD_MEDIA_QUERY = "(min-width: 768px)";
|
|
5
|
+
/**
|
|
6
|
+
* A pointer that can hover (a mouse or trackpad). Touch and coarse pointers
|
|
7
|
+
* must use click; a hover-open popover would trap them after the first tap.
|
|
8
|
+
*/
|
|
9
|
+
export const HOVER_MEDIA_QUERY = "(hover: hover) and (pointer: fine)";
|
|
5
10
|
function hasMatchMedia() {
|
|
6
11
|
return (typeof window !== "undefined" && typeof window.matchMedia === "function");
|
|
7
12
|
}
|
package/dist/kanban.d.ts
CHANGED
|
@@ -59,7 +59,7 @@ export type KanbanBoardProps<T extends {
|
|
|
59
59
|
deleteLabel?: string;
|
|
60
60
|
/** Classes for the horizontal scroller that holds the columns. */
|
|
61
61
|
scrollerClassName?: string;
|
|
62
|
-
/** Classes for every column, e.g.
|
|
62
|
+
/** Classes for every column, e.g. to pin a width instead of growing. */
|
|
63
63
|
columnClassName?: string;
|
|
64
64
|
/** Classes for every card, including the one under the pointer while dragging. */
|
|
65
65
|
cardClassName?: string;
|
package/dist/kanban.js
CHANGED
|
@@ -13,7 +13,8 @@ import { cn } from "./utils.js";
|
|
|
13
13
|
const COLUMN_DROP_PRIORITY = 1;
|
|
14
14
|
/** Rail zones beat columns when both are under the pointer. */
|
|
15
15
|
const ACTION_ZONE_PRIORITY = 10;
|
|
16
|
-
|
|
16
|
+
/** Grow to fill the board; 18rem floor so a long row still scrolls. */
|
|
17
|
+
const columnSize = "flex-1 basis-72 min-w-72 min-h-[min(70vh,32rem)]";
|
|
17
18
|
/**
|
|
18
19
|
* Drag-and-drop board built on dnd-kit. The consumer owns `items` and maps
|
|
19
20
|
* each to a column; the board derives its layout from them, so an optimistic
|
|
@@ -123,7 +124,7 @@ function KanbanColumnView({ column, count, emptyLabel, loading, className, child
|
|
|
123
124
|
collisionPriority: COLUMN_DROP_PRIORITY,
|
|
124
125
|
});
|
|
125
126
|
const empty = count === 0;
|
|
126
|
-
return (_jsxs("div", { ref: ref, "data-slot": "kanban-column", "data-drop-target": isDropTarget || undefined, className: cn("flex
|
|
127
|
+
return (_jsxs("div", { ref: ref, "data-slot": "kanban-column", "data-drop-target": isDropTarget || undefined, className: cn("flex snap-start flex-col gap-3 p-3", "bg-muted/40 ring-1 ring-foreground/5", "data-drop-target:bg-muted/60 data-drop-target:ring-2 data-drop-target:ring-ring/60", columnSize, className), children: [_jsxs("header", { "data-slot": "kanban-column-header", className: "flex items-start justify-between gap-2", children: [_jsxs("span", { className: "flex items-center gap-2 text-xs font-semibold tracking-widest uppercase", children: [column.color ? (_jsx("span", { "aria-hidden": true, className: "size-2 shrink-0 rounded-full", style: { backgroundColor: column.color } })) : null, _jsx("span", { className: "leading-tight", children: column.label })] }), _jsxs("span", { className: "flex shrink-0 flex-col items-end gap-0.5 text-muted-foreground tabular-nums", children: [_jsx("span", { className: "text-xs", children: count }), column.badge ? (_jsx("span", { className: "text-[10px] font-medium", children: column.badge })) : null] })] }), _jsx("div", { className: "flex min-h-32 flex-1 flex-col gap-2 overflow-y-auto pr-1", children: loading ? (_jsxs(_Fragment, { children: [_jsx(Skeleton, { className: "h-20" }), _jsx(Skeleton, { className: "h-16" })] })) : (_jsxs(_Fragment, { children: [children, _jsx("p", { "data-slot": "kanban-column-empty", "aria-hidden": !empty, className: cn("border border-dashed px-3 py-6 text-center text-xs text-muted-foreground", !empty && "hidden"), children: emptyLabel })] })) })] }));
|
|
127
128
|
}
|
|
128
129
|
const cardChrome = cn("flex border border-border bg-card text-card-foreground");
|
|
129
130
|
const handleChrome = cn("flex shrink-0 items-center self-stretch border-r border-border", "bg-muted/30 px-1.5 text-muted-foreground");
|
|
@@ -192,6 +193,6 @@ function OverflowFade({ side, visible, }) {
|
|
|
192
193
|
}
|
|
193
194
|
/** Board-shaped placeholder for a route `loading` file. */
|
|
194
195
|
function KanbanSkeleton({ columnCount = 4, className, columnClassName, ...props }) {
|
|
195
|
-
return (_jsx("div", { "data-slot": "kanban-skeleton", className: cn("flex gap-4 overflow-hidden", className), ...props, children: Array.from({ length: columnCount }, (_, index) => (_jsx(Skeleton, { className: cn(
|
|
196
|
+
return (_jsx("div", { "data-slot": "kanban-skeleton", className: cn("flex gap-4 overflow-hidden", className), ...props, children: Array.from({ length: columnCount }, (_, index) => (_jsx(Skeleton, { className: cn(columnSize, columnClassName) }, index))) }));
|
|
196
197
|
}
|
|
197
198
|
export { KanbanBoard, KanbanSkeleton };
|
package/dist/nav-link.d.ts
CHANGED
|
@@ -7,17 +7,25 @@ import type { AsChildProps } from "./internal/as-child.js";
|
|
|
7
7
|
* Apps pass `active` — the kit has no router.
|
|
8
8
|
*/
|
|
9
9
|
declare const navLinkVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
10
|
+
/**
|
|
11
|
+
* Whether `href` is the current page for a nav link: the path itself, or a
|
|
12
|
+
* page under it (`/tasks` is active on `/tasks/42`, not on `/tasks-archive`).
|
|
13
|
+
* The kit has no router, so the app passes the pathname:
|
|
14
|
+
* `active={isNavActive(pathname, href)}`.
|
|
15
|
+
*/
|
|
16
|
+
declare function isNavActive(pathname: string, href: string): boolean;
|
|
10
17
|
/**
|
|
11
18
|
* Pill link for app-shell destinations (Calendar, Tasks, Settings). Pass
|
|
12
|
-
* `active` from the route
|
|
13
|
-
*
|
|
19
|
+
* `active` from the route (`isNavActive` compares a pathname with the
|
|
20
|
+
* link's `href`); the kit does not read the URL. `asChild` wraps a router
|
|
21
|
+
* `Link` with these styles, the same Slot pattern as `BreadcrumbLink`.
|
|
14
22
|
*
|
|
15
23
|
* Not a tab: in-page view switches that belong in the URL use `TabsNav`
|
|
16
|
-
* and `
|
|
24
|
+
* and `TabsNavLink`. Next apps can import the optional adapter
|
|
17
25
|
* from `atlas-react-kit/next/nav-link` instead of composing `asChild`.
|
|
18
26
|
*/
|
|
19
27
|
declare function NavLink({ asChild, active, className, ...props }: React.ComponentProps<"a"> & AsChildProps & {
|
|
20
28
|
/** This link is the current page: `aria-current="page"` and the selected chrome. */
|
|
21
29
|
active: boolean;
|
|
22
30
|
}): React.JSX.Element;
|
|
23
|
-
export { NavLink, navLinkVariants };
|
|
31
|
+
export { NavLink, isNavActive, navLinkVariants };
|
package/dist/nav-link.js
CHANGED
|
@@ -15,17 +15,27 @@ const navLinkVariants = cva([
|
|
|
15
15
|
selectedState.active,
|
|
16
16
|
"inline-flex items-center gap-1.5 rounded-none border border-transparent px-3 py-1.5 text-sm text-muted-foreground transition-colors hover:bg-accent data-[state=active]:font-medium",
|
|
17
17
|
]);
|
|
18
|
+
/**
|
|
19
|
+
* Whether `href` is the current page for a nav link: the path itself, or a
|
|
20
|
+
* page under it (`/tasks` is active on `/tasks/42`, not on `/tasks-archive`).
|
|
21
|
+
* The kit has no router, so the app passes the pathname:
|
|
22
|
+
* `active={isNavActive(pathname, href)}`.
|
|
23
|
+
*/
|
|
24
|
+
function isNavActive(pathname, href) {
|
|
25
|
+
return pathname === href || pathname.startsWith(`${href}/`);
|
|
26
|
+
}
|
|
18
27
|
/**
|
|
19
28
|
* Pill link for app-shell destinations (Calendar, Tasks, Settings). Pass
|
|
20
|
-
* `active` from the route
|
|
21
|
-
*
|
|
29
|
+
* `active` from the route (`isNavActive` compares a pathname with the
|
|
30
|
+
* link's `href`); the kit does not read the URL. `asChild` wraps a router
|
|
31
|
+
* `Link` with these styles, the same Slot pattern as `BreadcrumbLink`.
|
|
22
32
|
*
|
|
23
33
|
* Not a tab: in-page view switches that belong in the URL use `TabsNav`
|
|
24
|
-
* and `
|
|
34
|
+
* and `TabsNavLink`. Next apps can import the optional adapter
|
|
25
35
|
* from `atlas-react-kit/next/nav-link` instead of composing `asChild`.
|
|
26
36
|
*/
|
|
27
37
|
function NavLink({ asChild, active, className, ...props }) {
|
|
28
38
|
const Comp = asChild ? Slot.Root : "a";
|
|
29
39
|
return (_jsx(Comp, { "data-slot": "nav-link", "data-state": active ? "active" : "inactive", "aria-current": active ? "page" : undefined, className: cn(navLinkVariants(), className), ...props }));
|
|
30
40
|
}
|
|
31
|
-
export { NavLink, navLinkVariants };
|
|
41
|
+
export { NavLink, isNavActive, navLinkVariants };
|
package/dist/next/nav-link.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type * as React from "react";
|
|
2
2
|
import Link from "next/link.js";
|
|
3
3
|
/**
|
|
4
|
-
* Next.js adapter for the shell `NavLink` pill
|
|
5
|
-
* `
|
|
6
|
-
*
|
|
4
|
+
* Next.js adapter for the shell `NavLink` pill: the base `NavLink` with
|
|
5
|
+
* `asChild` around a Next `Link`, so it renders the same element. Same
|
|
6
|
+
* contract as `TabsNavLink`: Next `Link` props plus a required `active`
|
|
7
|
+
* boolean. The kit still does not read the router; the app passes `active`,
|
|
8
|
+
* usually `active={isNavActive(usePathname(), href)}` with `isNavActive`
|
|
9
|
+
* from `atlas-react-kit/nav-link`.
|
|
7
10
|
*/
|
|
8
11
|
declare function NavLink({ active, className, ...props }: React.ComponentProps<typeof Link> & {
|
|
9
12
|
active: boolean;
|
package/dist/next/nav-link.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import Link from "next/link.js";
|
|
4
|
-
import {
|
|
5
|
-
import { cn } from "../utils.js";
|
|
4
|
+
import { NavLink as BaseNavLink } from "../nav-link.js";
|
|
6
5
|
/**
|
|
7
|
-
* Next.js adapter for the shell `NavLink` pill
|
|
8
|
-
* `
|
|
9
|
-
*
|
|
6
|
+
* Next.js adapter for the shell `NavLink` pill: the base `NavLink` with
|
|
7
|
+
* `asChild` around a Next `Link`, so it renders the same element. Same
|
|
8
|
+
* contract as `TabsNavLink`: Next `Link` props plus a required `active`
|
|
9
|
+
* boolean. The kit still does not read the router; the app passes `active`,
|
|
10
|
+
* usually `active={isNavActive(usePathname(), href)}` with `isNavActive`
|
|
11
|
+
* from `atlas-react-kit/nav-link`.
|
|
10
12
|
*/
|
|
11
13
|
function NavLink({ active, className, ...props }) {
|
|
12
|
-
return (_jsx(
|
|
14
|
+
return (_jsx(BaseNavLink, { asChild: true, active: active, className: className, children: _jsx(Link, { ...props }) }));
|
|
13
15
|
}
|
|
14
16
|
export { NavLink };
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type * as React from "react";
|
|
2
2
|
import Link from "next/link.js";
|
|
3
|
+
/**
|
|
4
|
+
* Next.js adapter for `TabsNavLink`: the base link with `asChild` around a
|
|
5
|
+
* Next `Link`, so it renders the same element. Next `Link` props plus a
|
|
6
|
+
* required `active` boolean (`isNavActive(usePathname(), href)` from
|
|
7
|
+
* `atlas-react-kit/nav-link` works here too).
|
|
8
|
+
*/
|
|
3
9
|
declare function TabsNavLink({ active, className, ...props }: React.ComponentProps<typeof Link> & {
|
|
4
10
|
active: boolean;
|
|
5
11
|
}): React.JSX.Element;
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import Link from "next/link.js";
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import { TabsNavLink as BaseTabsNavLink } from "../tabs.js";
|
|
5
|
+
/**
|
|
6
|
+
* Next.js adapter for `TabsNavLink`: the base link with `asChild` around a
|
|
7
|
+
* Next `Link`, so it renders the same element. Next `Link` props plus a
|
|
8
|
+
* required `active` boolean (`isNavActive(usePathname(), href)` from
|
|
9
|
+
* `atlas-react-kit/nav-link` works here too).
|
|
10
|
+
*/
|
|
6
11
|
function TabsNavLink({ active, className, ...props }) {
|
|
7
|
-
return (_jsx(
|
|
12
|
+
return (_jsx(BaseTabsNavLink, { asChild: true, active: active, className: className, children: _jsx(Link, { ...props }) }));
|
|
8
13
|
}
|
|
9
14
|
export { TabsNavLink };
|
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 {};
|