atlas-react-kit 0.5.0 → 0.6.1
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 +63 -52
- package/DESIGN_RULES.md +32 -15
- package/README.md +6 -3
- package/dist/accordion.js +2 -1
- package/dist/badge.js +7 -2
- package/dist/button.d.ts +2 -2
- package/dist/button.js +14 -4
- package/dist/calendar.js +1 -1
- package/dist/checkbox.js +2 -1
- package/dist/color-legend.d.ts +22 -0
- package/dist/color-legend.js +11 -0
- package/dist/combobox.js +2 -1
- package/dist/dropdown-menu.js +2 -1
- package/dist/form/markdown-field.d.ts +17 -0
- package/dist/form/markdown-field.js +27 -0
- package/dist/info-tip.d.ts +27 -0
- package/dist/info-tip.js +64 -0
- package/dist/input.js +2 -1
- package/dist/internal/form-control.d.ts +1 -1
- package/dist/internal/form-control.js +1 -1
- package/dist/internal/styles.d.ts +50 -0
- package/dist/internal/styles.js +50 -0
- package/dist/internal/tabs-styles.js +6 -3
- package/dist/markdown.d.ts +18 -0
- package/dist/markdown.js +62 -0
- package/dist/meter.d.ts +22 -0
- package/dist/meter.js +17 -0
- package/dist/pagination.js +2 -1
- package/dist/radio-group.js +2 -1
- package/dist/search-picker.d.ts +30 -0
- package/dist/search-picker.js +101 -0
- package/dist/select.js +3 -2
- package/dist/slider.d.ts +20 -0
- package/dist/slider.js +21 -0
- package/dist/stat-tile.d.ts +21 -0
- package/dist/stat-tile.js +17 -0
- package/dist/switch.js +2 -1
- package/dist/table.js +3 -2
- package/dist/textarea.js +2 -1
- package/dist/toggle-group.js +2 -2
- package/dist/toggle.d.ts +7 -1
- package/dist/toggle.js +17 -3
- package/package.json +37 -1
- package/skills/atlas-react-kit/SKILL.md +41 -32
- package/theme.css +23 -9
package/dist/info-tip.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { CircleHelpIcon } from "lucide-react";
|
|
5
|
+
import { Popover, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger, } from "./popover.js";
|
|
6
|
+
import { focusRing } from "./internal/styles.js";
|
|
7
|
+
import { cn } from "./utils.js";
|
|
8
|
+
/**
|
|
9
|
+
* True when a mouse can hover. Touch and coarse pointers must use click;
|
|
10
|
+
* a hover-open popover would trap them after the first tap.
|
|
11
|
+
*/
|
|
12
|
+
function canHoverOpen() {
|
|
13
|
+
return window.matchMedia("(hover: hover) and (pointer: fine)").matches;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Longer help than a Tooltip: a Popover with a 20px glyph and a 44px hit
|
|
17
|
+
* area. Hover opens only on a fine pointer; click, tap, and keyboard always
|
|
18
|
+
* toggle it. Not a glossary or "coming soon" chip — pass that copy in.
|
|
19
|
+
*/
|
|
20
|
+
function InfoTip({ label, title, description, children, layout = "box", side = "top", align = "center", badge, icon, className, }) {
|
|
21
|
+
const [open, setOpen] = React.useState(false);
|
|
22
|
+
const closeTimer = React.useRef(null);
|
|
23
|
+
const hoverOpened = React.useRef(false);
|
|
24
|
+
const inline = layout === "inline";
|
|
25
|
+
const cancelClose = () => {
|
|
26
|
+
if (closeTimer.current !== null) {
|
|
27
|
+
window.clearTimeout(closeTimer.current);
|
|
28
|
+
closeTimer.current = null;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const openFromHover = () => {
|
|
32
|
+
if (!canHoverOpen())
|
|
33
|
+
return;
|
|
34
|
+
cancelClose();
|
|
35
|
+
hoverOpened.current = true;
|
|
36
|
+
setOpen(true);
|
|
37
|
+
};
|
|
38
|
+
const closeFromHover = () => {
|
|
39
|
+
if (!canHoverOpen() || !hoverOpened.current)
|
|
40
|
+
return;
|
|
41
|
+
cancelClose();
|
|
42
|
+
// Leave a beat so the pointer can travel from the glyph into the panel.
|
|
43
|
+
closeTimer.current = window.setTimeout(() => setOpen(false), 120);
|
|
44
|
+
};
|
|
45
|
+
React.useEffect(() => () => cancelClose(), []);
|
|
46
|
+
return (_jsxs(Popover, { open: open, onOpenChange: (next) => {
|
|
47
|
+
// Hover already opened the panel; the following click should pin
|
|
48
|
+
// it, not toggle it closed (Playwright and real mice both hover
|
|
49
|
+
// before they click).
|
|
50
|
+
if (hoverOpened.current && open && !next) {
|
|
51
|
+
hoverOpened.current = false;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
hoverOpened.current = false;
|
|
55
|
+
setOpen(next);
|
|
56
|
+
}, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": label, "data-slot": "info-tip-trigger", "data-layout": layout, className: cn(focusRing, "relative inline-flex shrink-0 items-center justify-center rounded-none text-muted-foreground hover:text-selected-foreground focus-visible:text-selected-foreground", inline
|
|
57
|
+
? "size-5 align-middle after:absolute after:top-1/2 after:left-1/2 after:size-11 after:-translate-x-1/2 after:-translate-y-1/2"
|
|
58
|
+
: "size-11", className), onMouseEnter: openFromHover, onMouseLeave: closeFromHover, children: icon ?? (_jsx(CircleHelpIcon, { className: "size-5", "data-icon": "info-tip", "aria-hidden": true })) }) }), _jsxs(PopoverContent, { side: side, align: align, className: "w-72", onMouseEnter: openFromHover, onMouseLeave: closeFromHover, children: [_jsxs(PopoverHeader, { children: [_jsxs("div", { className: "flex items-start justify-between gap-2", children: [title ? _jsx(PopoverTitle, { children: title }) : null, badge] }), description ? (_jsx(PopoverDescription, { children: description })) : null] }), children] })] }));
|
|
59
|
+
}
|
|
60
|
+
/** InfoTip that sits in a line of text without stretching the line box. */
|
|
61
|
+
function InlineTip(props) {
|
|
62
|
+
return _jsx(InfoTip, { ...props, layout: "inline" });
|
|
63
|
+
}
|
|
64
|
+
export { InfoTip, InlineTip };
|
package/dist/input.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
|
+
import { disabledState, fieldSurface, focusRing, invalidState, } from "./internal/styles.js";
|
|
3
4
|
import { cn } from "./utils.js";
|
|
4
5
|
function Input({ className, type, ...props }) {
|
|
5
|
-
return (_jsx("input", { type: type, "data-slot": "input", className: cn(
|
|
6
|
+
return (_jsx("input", { type: type, "data-slot": "input", className: cn(fieldSurface, focusRing, invalidState, disabledState, "h-10 file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground", className), ...props }));
|
|
6
7
|
}
|
|
7
8
|
export { Input };
|
|
@@ -6,4 +6,4 @@
|
|
|
6
6
|
* control next to real form fields. Hover stays Input-like and cancels the
|
|
7
7
|
* outline Button's purple selected wash.
|
|
8
8
|
*/
|
|
9
|
-
export declare const formControlTriggerClassName = "h-10 w-full border-input bg-background px-3 font-normal text-base tracking-normal shadow-xs normal-case md:text-sm hover:border-input hover:bg-background hover:text-foreground focus-visible:
|
|
9
|
+
export declare const formControlTriggerClassName = "h-10 w-full border-input bg-background px-3 font-normal text-base tracking-normal shadow-xs normal-case md:text-sm hover:border-input hover:bg-background hover:text-foreground focus-visible:text-foreground aria-expanded:border-input aria-expanded:bg-background aria-expanded:text-foreground has-data-[icon=inline-start]:pl-3 has-data-[icon=inline-end]:pr-3 dark:hover:bg-background dark:aria-expanded:bg-background";
|
|
@@ -6,4 +6,4 @@
|
|
|
6
6
|
* control next to real form fields. Hover stays Input-like and cancels the
|
|
7
7
|
* outline Button's purple selected wash.
|
|
8
8
|
*/
|
|
9
|
-
export const formControlTriggerClassName = "h-10 w-full border-input bg-background px-3 font-normal text-base tracking-normal shadow-xs normal-case md:text-sm hover:border-input hover:bg-background hover:text-foreground focus-visible:
|
|
9
|
+
export const formControlTriggerClassName = "h-10 w-full border-input bg-background px-3 font-normal text-base tracking-normal shadow-xs normal-case md:text-sm hover:border-input hover:bg-background hover:text-foreground focus-visible:text-foreground aria-expanded:border-input aria-expanded:bg-background aria-expanded:text-foreground has-data-[icon=inline-start]:pl-3 has-data-[icon=inline-end]:pr-3 dark:hover:bg-background dark:aria-expanded:bg-background";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State classes shared by the kit's controls. Components add these with
|
|
3
|
+
* `cn()` instead of retyping them, so a state looks the same everywhere and
|
|
4
|
+
* a change (such as the WCAG focus ring) happens here once. Tailwind only
|
|
5
|
+
* generates class names it finds as literals, so each state attribute gets
|
|
6
|
+
* its own complete string. `test/dry.test.ts` fails when a component types
|
|
7
|
+
* one of these by hand.
|
|
8
|
+
*/
|
|
9
|
+
/** Keyboard focus: a ring-colored border plus a solid 2px ring (WCAG 1.4.11). */
|
|
10
|
+
export declare const focusRing = "outline-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring";
|
|
11
|
+
/** `aria-invalid`: destructive border and a soft glow; the error text carries the message. */
|
|
12
|
+
export declare const invalidState = "aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40";
|
|
13
|
+
/** Native `disabled`: dimmed, with no hover or click. */
|
|
14
|
+
export declare const disabledState = "disabled:pointer-events-none disabled:opacity-50";
|
|
15
|
+
/** Radix `data-disabled` (menu and list items, slider). */
|
|
16
|
+
export declare const dataDisabledState = "data-disabled:pointer-events-none data-disabled:opacity-50";
|
|
17
|
+
/** The text-field surface, so Input, Textarea, and SelectTrigger line up. */
|
|
18
|
+
export declare const fieldSurface = "w-full min-w-0 rounded-none border border-input bg-background px-3 py-2 text-base shadow-xs transition-[color,box-shadow] placeholder:text-muted-foreground md:text-sm";
|
|
19
|
+
/**
|
|
20
|
+
* Selected chrome: the wash, selected text, and a selected border, because
|
|
21
|
+
* the wash alone is 1.2:1 against the page. One string per state attribute.
|
|
22
|
+
*/
|
|
23
|
+
export declare const selectedState: {
|
|
24
|
+
/**
|
|
25
|
+
* Toggle and ToggleGroupItem. Radix Toggle sets `data-state=on` and
|
|
26
|
+
* `aria-pressed`; group items always set `data-state=on`, add
|
|
27
|
+
* `aria-pressed` in multiple mode, and are radios with `aria-checked` in
|
|
28
|
+
* single mode. All three get the same chrome.
|
|
29
|
+
*/
|
|
30
|
+
toggle: string;
|
|
31
|
+
/** Tab trigger and TabsNav link (`data-state="active"`). */
|
|
32
|
+
active: string;
|
|
33
|
+
/** Current page (`data-active="true"`). */
|
|
34
|
+
current: string;
|
|
35
|
+
/** Trigger whose menu or popover is open. */
|
|
36
|
+
expanded: string;
|
|
37
|
+
/** Quiet buttons and toggles on hover. */
|
|
38
|
+
hover: string;
|
|
39
|
+
};
|
|
40
|
+
/** Options in menus and listboxes. */
|
|
41
|
+
export declare const optionState: {
|
|
42
|
+
/** Highlight as focus moves: grey fill plus a 2px ring bar (the fill alone is 1.1:1). */
|
|
43
|
+
focus: string;
|
|
44
|
+
/** The same highlight for lists that track the active option in state. */
|
|
45
|
+
active: string;
|
|
46
|
+
/** The chosen value (`data-state="checked"`); a check icon marks it too. */
|
|
47
|
+
checked: string;
|
|
48
|
+
/** The chosen value in lists that track selection in state. */
|
|
49
|
+
selected: string;
|
|
50
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State classes shared by the kit's controls. Components add these with
|
|
3
|
+
* `cn()` instead of retyping them, so a state looks the same everywhere and
|
|
4
|
+
* a change (such as the WCAG focus ring) happens here once. Tailwind only
|
|
5
|
+
* generates class names it finds as literals, so each state attribute gets
|
|
6
|
+
* its own complete string. `test/dry.test.ts` fails when a component types
|
|
7
|
+
* one of these by hand.
|
|
8
|
+
*/
|
|
9
|
+
/** Keyboard focus: a ring-colored border plus a solid 2px ring (WCAG 1.4.11). */
|
|
10
|
+
export const focusRing = "outline-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring";
|
|
11
|
+
/** `aria-invalid`: destructive border and a soft glow; the error text carries the message. */
|
|
12
|
+
export const invalidState = "aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40";
|
|
13
|
+
/** Native `disabled`: dimmed, with no hover or click. */
|
|
14
|
+
export const disabledState = "disabled:pointer-events-none disabled:opacity-50";
|
|
15
|
+
/** Radix `data-disabled` (menu and list items, slider). */
|
|
16
|
+
export const dataDisabledState = "data-disabled:pointer-events-none data-disabled:opacity-50";
|
|
17
|
+
/** The text-field surface, so Input, Textarea, and SelectTrigger line up. */
|
|
18
|
+
export const fieldSurface = "w-full min-w-0 rounded-none border border-input bg-background px-3 py-2 text-base shadow-xs transition-[color,box-shadow] placeholder:text-muted-foreground md:text-sm";
|
|
19
|
+
/**
|
|
20
|
+
* Selected chrome: the wash, selected text, and a selected border, because
|
|
21
|
+
* the wash alone is 1.2:1 against the page. One string per state attribute.
|
|
22
|
+
*/
|
|
23
|
+
export const selectedState = {
|
|
24
|
+
/**
|
|
25
|
+
* Toggle and ToggleGroupItem. Radix Toggle sets `data-state=on` and
|
|
26
|
+
* `aria-pressed`; group items always set `data-state=on`, add
|
|
27
|
+
* `aria-pressed` in multiple mode, and are radios with `aria-checked` in
|
|
28
|
+
* single mode. All three get the same chrome.
|
|
29
|
+
*/
|
|
30
|
+
toggle: "data-on:border-selected-foreground data-on:bg-selected data-on:text-selected-foreground aria-pressed:border-selected-foreground aria-pressed:bg-selected aria-pressed:text-selected-foreground aria-checked:border-selected-foreground aria-checked:bg-selected aria-checked:text-selected-foreground",
|
|
31
|
+
/** Tab trigger and TabsNav link (`data-state="active"`). */
|
|
32
|
+
active: "data-[state=active]:border-selected-foreground data-[state=active]:bg-selected data-[state=active]:text-selected-foreground",
|
|
33
|
+
/** Current page (`data-active="true"`). */
|
|
34
|
+
current: "data-[active=true]:border-selected-foreground data-[active=true]:bg-selected data-[active=true]:text-selected-foreground",
|
|
35
|
+
/** Trigger whose menu or popover is open. */
|
|
36
|
+
expanded: "aria-expanded:border-selected-foreground aria-expanded:bg-selected aria-expanded:text-selected-foreground",
|
|
37
|
+
/** Quiet buttons and toggles on hover. */
|
|
38
|
+
hover: "hover:border-selected-foreground hover:bg-selected hover:text-selected-foreground",
|
|
39
|
+
};
|
|
40
|
+
/** Options in menus and listboxes. */
|
|
41
|
+
export const optionState = {
|
|
42
|
+
/** Highlight as focus moves: grey fill plus a 2px ring bar (the fill alone is 1.1:1). */
|
|
43
|
+
focus: "focus:bg-accent focus:text-accent-foreground focus:shadow-[inset_2px_0_0_var(--color-ring)]",
|
|
44
|
+
/** The same highlight for lists that track the active option in state. */
|
|
45
|
+
active: "bg-accent text-accent-foreground shadow-[inset_2px_0_0_var(--color-ring)]",
|
|
46
|
+
/** The chosen value (`data-state="checked"`); a check icon marks it too. */
|
|
47
|
+
checked: "data-[state=checked]:bg-selected data-[state=checked]:text-selected-foreground",
|
|
48
|
+
/** The chosen value in lists that track selection in state. */
|
|
49
|
+
selected: "bg-selected text-selected-foreground",
|
|
50
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { cva } from "class-variance-authority";
|
|
2
|
+
import { disabledState, focusRing, selectedState } from "./styles.js";
|
|
2
3
|
const tabsListVariants = cva('group/tabs-list inline-flex w-fit items-stretch justify-center p-0 text-muted-foreground group-data-[orientation="horizontal"]/tabs:h-11 group-data-[orientation="vertical"]/tabs:h-fit group-data-[orientation="vertical"]/tabs:flex-col', {
|
|
3
4
|
variants: {
|
|
4
5
|
variant: {
|
|
@@ -11,9 +12,11 @@ const tabsListVariants = cva('group/tabs-list inline-flex w-fit items-stretch ju
|
|
|
11
12
|
},
|
|
12
13
|
});
|
|
13
14
|
const tabsTriggerVariants = cva([
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
'data-[
|
|
15
|
+
focusRing,
|
|
16
|
+
disabledState,
|
|
17
|
+
'relative inline-flex h-full min-w-0 flex-1 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 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-3.5',
|
|
18
|
+
'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',
|
|
19
|
+
selectedState.active,
|
|
17
20
|
'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',
|
|
18
21
|
]);
|
|
19
22
|
export { tabsListVariants, tabsTriggerVariants };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drop javascript:/data:/unknown schemes. react-markdown already omits raw
|
|
3
|
+
* HTML (no rehype-raw), so this is the remaining URL hole.
|
|
4
|
+
*/
|
|
5
|
+
export declare function safeMarkdownUrl(url: string): string;
|
|
6
|
+
export type MarkdownProps = {
|
|
7
|
+
content: string;
|
|
8
|
+
className?: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* GitHub-flavoured Markdown, token-styled and sanitized. No Next, no raw
|
|
12
|
+
* HTML, no typography plugin. `react-markdown` and `remark-gfm` stay in this
|
|
13
|
+
* module so other subpaths do not pay for them.
|
|
14
|
+
*/
|
|
15
|
+
declare function Markdown({ content, className }: MarkdownProps): import("react").JSX.Element;
|
|
16
|
+
/** Alias used by apps that imported MarkdownContent. */
|
|
17
|
+
declare const MarkdownContent: typeof Markdown;
|
|
18
|
+
export { Markdown, MarkdownContent };
|
package/dist/markdown.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import ReactMarkdown from "react-markdown";
|
|
3
|
+
import remarkGfm from "remark-gfm";
|
|
4
|
+
import { cn } from "./utils.js";
|
|
5
|
+
/** Stable identity so parent re-renders do not re-parse unchanged content. */
|
|
6
|
+
const REMARK_PLUGINS = [remarkGfm];
|
|
7
|
+
const SAFE_PROTOCOLS = new Set(["http:", "https:", "mailto:"]);
|
|
8
|
+
/**
|
|
9
|
+
* Drop javascript:/data:/unknown schemes. react-markdown already omits raw
|
|
10
|
+
* HTML (no rehype-raw), so this is the remaining URL hole.
|
|
11
|
+
*/
|
|
12
|
+
export function safeMarkdownUrl(url) {
|
|
13
|
+
const value = url.trim();
|
|
14
|
+
if (!value)
|
|
15
|
+
return "";
|
|
16
|
+
if (value.startsWith("#") || value.startsWith("/"))
|
|
17
|
+
return value;
|
|
18
|
+
try {
|
|
19
|
+
const parsed = new URL(value);
|
|
20
|
+
return SAFE_PROTOCOLS.has(parsed.protocol) ? value : "";
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return "";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function headingClass(size) {
|
|
27
|
+
return cn("font-heading font-semibold tracking-tight text-foreground", size);
|
|
28
|
+
}
|
|
29
|
+
const markdownComponents = {
|
|
30
|
+
h1: ({ children }) => _jsx("h1", { className: headingClass("text-xl"), children: children }),
|
|
31
|
+
h2: ({ children }) => _jsx("h2", { className: headingClass("text-lg"), children: children }),
|
|
32
|
+
h3: ({ children }) => (_jsx("h3", { className: headingClass("text-base"), children: children })),
|
|
33
|
+
p: ({ children }) => _jsx("p", { className: "text-sm leading-relaxed", children: children }),
|
|
34
|
+
ul: ({ children }) => (_jsx("ul", { className: "list-disc space-y-1 pl-5 text-sm", children: children })),
|
|
35
|
+
ol: ({ children }) => (_jsx("ol", { className: "list-decimal space-y-1 pl-5 text-sm", children: children })),
|
|
36
|
+
li: ({ children }) => _jsx("li", { className: "leading-relaxed", children: children }),
|
|
37
|
+
blockquote: ({ children }) => (_jsx("blockquote", { className: "border-l-2 border-input pl-3 text-sm text-muted-foreground", children: children })),
|
|
38
|
+
a: ({ href, children }) => href ? (_jsx("a", { href: href, className: "text-selected-foreground underline underline-offset-2", ...(href.startsWith("http")
|
|
39
|
+
? { target: "_blank", rel: "noreferrer noopener" }
|
|
40
|
+
: {}), children: children })) : (_jsx("span", { children: children })),
|
|
41
|
+
code: ({ className, children }) => {
|
|
42
|
+
const fenced = /language-/.test(className ?? "");
|
|
43
|
+
return (_jsx("code", { className: cn("font-mono text-xs", fenced ? "bg-transparent" : "bg-muted px-1 py-0.5"), children: children }));
|
|
44
|
+
},
|
|
45
|
+
pre: ({ children }) => (_jsx("pre", { className: "overflow-x-auto bg-muted p-3 text-xs", children: children })),
|
|
46
|
+
hr: () => _jsx("hr", { className: "border-border" }),
|
|
47
|
+
table: ({ children }) => (_jsx("div", { className: "overflow-x-auto", children: _jsx("table", { className: "w-full text-left text-sm", children: children }) })),
|
|
48
|
+
th: ({ children }) => (_jsx("th", { className: "border-b border-input py-1.5 pr-3 font-medium", children: children })),
|
|
49
|
+
td: ({ children }) => (_jsx("td", { className: "border-b border-border py-1.5 pr-3", children: children })),
|
|
50
|
+
img: ({ src, alt }) => (_jsx("img", { src: src, alt: alt ?? "", className: "max-w-full" })),
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* GitHub-flavoured Markdown, token-styled and sanitized. No Next, no raw
|
|
54
|
+
* HTML, no typography plugin. `react-markdown` and `remark-gfm` stay in this
|
|
55
|
+
* module so other subpaths do not pay for them.
|
|
56
|
+
*/
|
|
57
|
+
function Markdown({ content, className }) {
|
|
58
|
+
return (_jsx("article", { "data-slot": "markdown", className: cn("flex flex-col gap-3 text-foreground", className), children: _jsx(ReactMarkdown, { remarkPlugins: REMARK_PLUGINS, urlTransform: safeMarkdownUrl, components: markdownComponents, children: content }) }));
|
|
59
|
+
}
|
|
60
|
+
/** Alias used by apps that imported MarkdownContent. */
|
|
61
|
+
const MarkdownContent = Markdown;
|
|
62
|
+
export { Markdown, MarkdownContent };
|
package/dist/meter.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type MeterMarker = {
|
|
2
|
+
/** Same scale as `value` / `min` / `max` (not a 0–1 fraction). */
|
|
3
|
+
position: number;
|
|
4
|
+
className?: string;
|
|
5
|
+
};
|
|
6
|
+
export type MeterProps = {
|
|
7
|
+
value: number;
|
|
8
|
+
min?: number;
|
|
9
|
+
max: number;
|
|
10
|
+
/** App-owned fill (status, pace). Defaults to primary. */
|
|
11
|
+
fillClassName?: string;
|
|
12
|
+
markers?: MeterMarker[];
|
|
13
|
+
"aria-label": string;
|
|
14
|
+
className?: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Track, fill, and optional ticks for a current value against a scale.
|
|
18
|
+
* Use `Progress` for a plain 0–100 job bar. The app chooses fill color and
|
|
19
|
+
* marker positions (80% of a cap, 1.0× typical, and so on).
|
|
20
|
+
*/
|
|
21
|
+
declare function Meter({ value, min, max, fillClassName, markers, "aria-label": ariaLabel, className, }: MeterProps): import("react").JSX.Element;
|
|
22
|
+
export { Meter };
|
package/dist/meter.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "./utils.js";
|
|
3
|
+
/**
|
|
4
|
+
* Track, fill, and optional ticks for a current value against a scale.
|
|
5
|
+
* Use `Progress` for a plain 0–100 job bar. The app chooses fill color and
|
|
6
|
+
* marker positions (80% of a cap, 1.0× typical, and so on).
|
|
7
|
+
*/
|
|
8
|
+
function Meter({ value, min = 0, max, fillClassName = "bg-primary", markers, "aria-label": ariaLabel, className, }) {
|
|
9
|
+
const span = max - min;
|
|
10
|
+
const toPercent = (amount) => {
|
|
11
|
+
if (span === 0)
|
|
12
|
+
return 0;
|
|
13
|
+
return Math.min(100, Math.max(0, ((amount - min) / span) * 100));
|
|
14
|
+
};
|
|
15
|
+
return (_jsxs("div", { role: "meter", "data-slot": "meter", "aria-valuemin": min, "aria-valuemax": max, "aria-valuenow": Math.min(max, Math.max(min, value)), "aria-label": ariaLabel, className: cn("relative h-1.5 w-full overflow-hidden bg-muted", className), children: [_jsx("div", { "data-slot": "meter-fill", className: cn("h-full", fillClassName), style: { width: `${toPercent(value)}%` } }), markers?.map((marker) => (_jsx("div", { "aria-hidden": true, "data-slot": "meter-marker", className: cn("absolute inset-y-0 w-px bg-foreground/40", marker.className), style: { left: `${toPercent(marker.position)}%` } }, marker.position)))] }));
|
|
16
|
+
}
|
|
17
|
+
export { Meter };
|
package/dist/pagination.js
CHANGED
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { ChevronLeftIcon, ChevronRightIcon, MoreHorizontalIcon, } from "lucide-react";
|
|
4
4
|
import { buttonVariants } from "./button.js";
|
|
5
|
+
import { selectedState } from "./internal/styles.js";
|
|
5
6
|
import { cn } from "./utils.js";
|
|
6
7
|
/**
|
|
7
8
|
* Page links for long lists and tables. Links are plain anchors so the page
|
|
@@ -21,7 +22,7 @@ function PaginationLink({ className, isActive, size = "icon", ...props }) {
|
|
|
21
22
|
return (_jsx("a", { "aria-current": isActive ? "page" : undefined, "data-slot": "pagination-link", "data-active": isActive, className: cn(buttonVariants({
|
|
22
23
|
variant: isActive ? "outline" : "ghost",
|
|
23
24
|
size: size === "icon" ? "icon-sm" : "sm",
|
|
24
|
-
}), "font-medium tracking-normal normal-case tabular-nums
|
|
25
|
+
}), selectedState.current, "font-medium tracking-normal normal-case tabular-nums", className), ...props }));
|
|
25
26
|
}
|
|
26
27
|
function PaginationPrevious({ className, children = "Previous", ...props }) {
|
|
27
28
|
return (_jsxs(PaginationLink, { "aria-label": "Go to previous page", size: "default", className: cn("gap-1 px-2.5 sm:pl-2", className), ...props, children: [_jsx(ChevronLeftIcon, { "data-icon": "inline-start" }), _jsx("span", { className: "hidden sm:block", children: children })] }));
|
package/dist/radio-group.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { RadioGroup as RadioGroupPrimitive } from "radix-ui";
|
|
5
|
+
import { disabledState, focusRing, invalidState } from "./internal/styles.js";
|
|
5
6
|
import { cn } from "./utils.js";
|
|
6
7
|
/**
|
|
7
8
|
* One choice from a short, visible list. Arrow keys move the selection.
|
|
@@ -15,6 +16,6 @@ function RadioGroup({ className, ...props }) {
|
|
|
15
16
|
* differently from square checkboxes.
|
|
16
17
|
*/
|
|
17
18
|
function RadioGroupItem({ className, ...props }) {
|
|
18
|
-
return (_jsx(RadioGroupPrimitive.Item, { "data-slot": "radio-group-item", className: cn("peer relative flex aspect-square size-4.5 shrink-0 items-center justify-center rounded-full border border-input bg-transparent shadow-xs transition-shadow
|
|
19
|
+
return (_jsx(RadioGroupPrimitive.Item, { "data-slot": "radio-group-item", className: cn(focusRing, invalidState, disabledState, "peer relative flex aspect-square size-4.5 shrink-0 items-center justify-center rounded-full border border-input bg-transparent shadow-xs transition-shadow after:absolute after:-inset-x-3 after:-inset-y-2 data-checked:border-primary", className), ...props, children: _jsx(RadioGroupPrimitive.Indicator, { "data-slot": "radio-group-indicator", className: "flex items-center justify-center", children: _jsx("span", { className: "size-2 rounded-full bg-primary" }) }) }));
|
|
19
20
|
}
|
|
20
21
|
export { RadioGroup, RadioGroupItem };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Case-fold and strip combining marks so "nino" matches "Niño".
|
|
4
|
+
*/
|
|
5
|
+
export declare function foldSearchText(value: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Default SearchPicker filter: the folded label includes the folded query.
|
|
8
|
+
*/
|
|
9
|
+
export declare function defaultSearchFilter<T>(item: T, query: string, getLabel: (item: T) => string): boolean;
|
|
10
|
+
export type SearchPickerProps<T> = {
|
|
11
|
+
items: T[];
|
|
12
|
+
getValue: (item: T) => string;
|
|
13
|
+
getLabel: (item: T) => string;
|
|
14
|
+
filter?: (item: T, query: string, getLabel: (item: T) => string) => boolean;
|
|
15
|
+
value?: string;
|
|
16
|
+
onSelect: (item: T) => void;
|
|
17
|
+
label: string;
|
|
18
|
+
placeholder?: string;
|
|
19
|
+
emptyText?: string;
|
|
20
|
+
listLabel?: string;
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
className?: string;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Always-visible combobox: a labelled input and a listbox. Unlike Combobox,
|
|
26
|
+
* the list is not in a popover. Keyboard: arrows, Home/End, PageUp/PageDown,
|
|
27
|
+
* Enter, Escape.
|
|
28
|
+
*/
|
|
29
|
+
declare function SearchPicker<T>({ items, getValue, getLabel, filter, value, onSelect, label, placeholder, emptyText, listLabel, disabled, className, }: SearchPickerProps<T>): React.JSX.Element;
|
|
30
|
+
export { SearchPicker };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Input } from "./input.js";
|
|
5
|
+
import { Label } from "./label.js";
|
|
6
|
+
import { optionState } from "./internal/styles.js";
|
|
7
|
+
import { cn } from "./utils.js";
|
|
8
|
+
const LISTBOX_PAGE_SIZE = 10;
|
|
9
|
+
/**
|
|
10
|
+
* Case-fold and strip combining marks so "nino" matches "Niño".
|
|
11
|
+
*/
|
|
12
|
+
export function foldSearchText(value) {
|
|
13
|
+
return value.normalize("NFD").replace(/\p{M}/gu, "").toLowerCase();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Default SearchPicker filter: the folded label includes the folded query.
|
|
17
|
+
*/
|
|
18
|
+
export function defaultSearchFilter(item, query, getLabel) {
|
|
19
|
+
const needle = foldSearchText(query.trim());
|
|
20
|
+
if (!needle)
|
|
21
|
+
return true;
|
|
22
|
+
return foldSearchText(getLabel(item)).includes(needle);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Always-visible combobox: a labelled input and a listbox. Unlike Combobox,
|
|
26
|
+
* the list is not in a popover. Keyboard: arrows, Home/End, PageUp/PageDown,
|
|
27
|
+
* Enter, Escape.
|
|
28
|
+
*/
|
|
29
|
+
function SearchPicker({ items, getValue, getLabel, filter = defaultSearchFilter, value, onSelect, label, placeholder = "Search", emptyText = "No results", listLabel, disabled, className, }) {
|
|
30
|
+
const [query, setQuery] = React.useState("");
|
|
31
|
+
const listId = React.useId();
|
|
32
|
+
const inputId = React.useId();
|
|
33
|
+
const filtered = React.useMemo(() => items.filter((item) => filter(item, query, getLabel)), [items, filter, query, getLabel]);
|
|
34
|
+
const selectedIndex = React.useMemo(() => {
|
|
35
|
+
const index = filtered.findIndex((item) => getValue(item) === value);
|
|
36
|
+
return index >= 0 ? index : 0;
|
|
37
|
+
}, [filtered, getValue, value]);
|
|
38
|
+
const [highlight, setHighlight] = React.useState(selectedIndex);
|
|
39
|
+
React.useEffect(() => {
|
|
40
|
+
setHighlight(query ? 0 : selectedIndex);
|
|
41
|
+
}, [query, selectedIndex]);
|
|
42
|
+
const optionId = (index) => `${listId}-option-${index}`;
|
|
43
|
+
const active = filtered[highlight];
|
|
44
|
+
const scrollHighlightedOption = React.useCallback((node) => {
|
|
45
|
+
node?.scrollIntoView({ block: "nearest" });
|
|
46
|
+
}, []);
|
|
47
|
+
const selectItem = (item) => {
|
|
48
|
+
onSelect(item);
|
|
49
|
+
setQuery("");
|
|
50
|
+
};
|
|
51
|
+
const onKeyDown = (event) => {
|
|
52
|
+
if (event.key === "ArrowDown") {
|
|
53
|
+
event.preventDefault();
|
|
54
|
+
setHighlight((index) => filtered.length === 0 ? 0 : Math.min(index + 1, filtered.length - 1));
|
|
55
|
+
}
|
|
56
|
+
if (event.key === "ArrowUp") {
|
|
57
|
+
event.preventDefault();
|
|
58
|
+
setHighlight((index) => Math.max(index - 1, 0));
|
|
59
|
+
}
|
|
60
|
+
if (event.key === "Home") {
|
|
61
|
+
event.preventDefault();
|
|
62
|
+
setHighlight(0);
|
|
63
|
+
}
|
|
64
|
+
if (event.key === "End") {
|
|
65
|
+
event.preventDefault();
|
|
66
|
+
setHighlight(filtered.length === 0 ? 0 : filtered.length - 1);
|
|
67
|
+
}
|
|
68
|
+
if (event.key === "PageDown") {
|
|
69
|
+
event.preventDefault();
|
|
70
|
+
setHighlight((index) => filtered.length === 0
|
|
71
|
+
? 0
|
|
72
|
+
: Math.min(index + LISTBOX_PAGE_SIZE, filtered.length - 1));
|
|
73
|
+
}
|
|
74
|
+
if (event.key === "PageUp") {
|
|
75
|
+
event.preventDefault();
|
|
76
|
+
setHighlight((index) => Math.max(index - LISTBOX_PAGE_SIZE, 0));
|
|
77
|
+
}
|
|
78
|
+
if (event.key === "Enter") {
|
|
79
|
+
event.preventDefault();
|
|
80
|
+
if (active)
|
|
81
|
+
selectItem(active);
|
|
82
|
+
}
|
|
83
|
+
if (event.key === "Escape") {
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
if (query) {
|
|
86
|
+
setQuery("");
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
event.currentTarget.blur();
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
return (_jsxs("div", { "data-slot": "search-picker", className: cn("flex flex-col gap-1.5", className), 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 ? optionId(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: "max-h-60 overflow-y-auto border border-input bg-background", children: filtered.length === 0 ? (_jsx("li", { className: "px-3 py-4 text-center text-sm text-muted-foreground", children: emptyText })) : (filtered.map((item, index) => {
|
|
93
|
+
const itemValue = getValue(item);
|
|
94
|
+
const isSelected = itemValue === value;
|
|
95
|
+
const isActive = index === highlight;
|
|
96
|
+
return (_jsx("li", { role: "none", children: _jsx("button", { ref: isActive ? scrollHighlightedOption : undefined, id: optionId(index), type: "button", role: "option", "aria-selected": isSelected, disabled: disabled, className: cn("relative flex w-full cursor-default items-center rounded-none py-2 pr-3 pl-3 text-left text-sm outline-hidden select-none", isSelected && optionState.selected, isActive && optionState.active, isActive &&
|
|
97
|
+
!isSelected &&
|
|
98
|
+
"bg-accent text-accent-foreground"), onMouseEnter: () => setHighlight(index), onClick: () => selectItem(item), children: _jsx("span", { className: "truncate", children: getLabel(item) }) }) }, itemValue));
|
|
99
|
+
})) })] }));
|
|
100
|
+
}
|
|
101
|
+
export { SearchPicker };
|
package/dist/select.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
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";
|
|
5
6
|
import { cn } from "./utils.js";
|
|
6
7
|
import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react";
|
|
7
8
|
function Select({ ...props }) {
|
|
@@ -14,7 +15,7 @@ function SelectValue({ ...props }) {
|
|
|
14
15
|
return _jsx(SelectPrimitive.Value, { "data-slot": "select-value", ...props });
|
|
15
16
|
}
|
|
16
17
|
function SelectTrigger({ className, size = "default", children, ...props }) {
|
|
17
|
-
return (_jsxs(SelectPrimitive.Trigger, { "data-slot": "select-trigger", "data-size": size, className: cn("flex
|
|
18
|
+
return (_jsxs(SelectPrimitive.Trigger, { "data-slot": "select-trigger", "data-size": size, className: cn(fieldSurface, focusRing, invalidState, disabledState, "flex items-center justify-between gap-1.5 whitespace-nowrap data-placeholder:text-muted-foreground data-[size=default]:h-10 data-[size=sm]:h-9 *: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 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5", className), ...props, children: [children, _jsx(SelectPrimitive.Icon, { asChild: true, children: _jsx(ChevronDownIcon, { className: "pointer-events-none size-3.5 text-muted-foreground" }) })] }));
|
|
18
19
|
}
|
|
19
20
|
function SelectContent({ className, children, position = "popper", align = "center", sideOffset = 4, collisionPadding = 8, ...props }) {
|
|
20
21
|
return (_jsx(SelectPrimitive.Portal, { children: _jsxs(SelectPrimitive.Content, { "data-slot": "select-content", "data-align-trigger": position === "item-aligned", className: cn("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 rounded-none bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", position === "popper" &&
|
|
@@ -24,7 +25,7 @@ function SelectLabel({ className, ...props }) {
|
|
|
24
25
|
return (_jsx(SelectPrimitive.Label, { "data-slot": "select-label", className: cn("px-3 py-2 text-xs font-semibold tracking-wider text-muted-foreground uppercase", className), ...props }));
|
|
25
26
|
}
|
|
26
27
|
function SelectItem({ className, children, ...props }) {
|
|
27
|
-
return (_jsxs(SelectPrimitive.Item, { "data-slot": "select-item", className: cn("relative flex w-full cursor-default items-center gap-2.5 rounded-none py-2 pr-8 pl-3 text-sm outline-hidden select-none
|
|
28
|
+
return (_jsxs(SelectPrimitive.Item, { "data-slot": "select-item", className: cn(optionState.focus, optionState.checked, dataDisabledState, "relative flex w-full cursor-default items-center gap-2.5 rounded-none py-2 pr-8 pl-3 text-sm outline-hidden select-none not-data-[variant=destructive]:focus:**:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5 *:[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, { children: _jsx(CheckIcon, { className: "pointer-events-none" }) }) }), _jsx(SelectPrimitive.ItemText, { children: children })] }));
|
|
28
29
|
}
|
|
29
30
|
function SelectSeparator({ className, ...props }) {
|
|
30
31
|
return (_jsx(SelectPrimitive.Separator, { "data-slot": "select-separator", className: cn("pointer-events-none -mx-1.5 my-1.5 h-px bg-border/50", className), ...props }));
|
package/dist/slider.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type SliderProps = {
|
|
2
|
+
value: number;
|
|
3
|
+
onValueChange: (value: number) => void;
|
|
4
|
+
onValueCommit?: (value: number) => void;
|
|
5
|
+
min?: number;
|
|
6
|
+
max?: number;
|
|
7
|
+
step?: number;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
id?: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
className?: string;
|
|
12
|
+
"aria-label"?: string;
|
|
13
|
+
"aria-labelledby"?: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Single-thumb range on Radix Slider. Square track and thumb, primary fill,
|
|
17
|
+
* solid focus ring, 44px thumb hit area. Pair with a visible `Label`.
|
|
18
|
+
*/
|
|
19
|
+
declare function Slider({ value, onValueChange, onValueCommit, min, max, step, disabled, id, name, className, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, }: SliderProps): import("react").JSX.Element;
|
|
20
|
+
export { Slider };
|
package/dist/slider.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Slider as SliderPrimitive } from "radix-ui";
|
|
4
|
+
import { dataDisabledState, focusRing } from "./internal/styles.js";
|
|
5
|
+
import { cn } from "./utils.js";
|
|
6
|
+
/**
|
|
7
|
+
* Single-thumb range on Radix Slider. Square track and thumb, primary fill,
|
|
8
|
+
* solid focus ring, 44px thumb hit area. Pair with a visible `Label`.
|
|
9
|
+
*/
|
|
10
|
+
function Slider({ value, onValueChange, onValueCommit, min = 0, max = 100, step = 1, disabled, id, name, className, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, }) {
|
|
11
|
+
return (_jsxs(SliderPrimitive.Root, { "data-slot": "slider", name: name, min: min, max: max, step: step, disabled: disabled, value: [value], onValueChange: (next) => {
|
|
12
|
+
const amount = next[0];
|
|
13
|
+
if (amount !== undefined)
|
|
14
|
+
onValueChange(amount);
|
|
15
|
+
}, onValueCommit: (next) => {
|
|
16
|
+
const amount = next[0];
|
|
17
|
+
if (amount !== undefined)
|
|
18
|
+
onValueCommit?.(amount);
|
|
19
|
+
}, className: cn(dataDisabledState, "relative flex w-full touch-none items-center select-none", className), children: [_jsx(SliderPrimitive.Track, { "data-slot": "slider-track", className: "relative h-1.5 w-full grow overflow-hidden rounded-none bg-muted", children: _jsx(SliderPrimitive.Range, { "data-slot": "slider-range", className: "absolute h-full bg-primary" }) }), _jsx(SliderPrimitive.Thumb, { id: id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "data-slot": "slider-thumb", className: cn(focusRing, "relative block size-4 shrink-0 rounded-none border border-primary bg-background shadow-xs after:absolute after:top-1/2 after:left-1/2 after:size-11 after:-translate-x-1/2 after:-translate-y-1/2") })] }));
|
|
20
|
+
}
|
|
21
|
+
export { Slider };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Uppercase muted label used on tiles and thin section chrome. Apps that
|
|
4
|
+
* only needed a heading class can import this instead of a wrapper.
|
|
5
|
+
*/
|
|
6
|
+
export declare const sectionLabel = "text-xs font-semibold tracking-widest text-muted-foreground uppercase";
|
|
7
|
+
/** Row of tiles: stacked on phones, divided columns from `sm`. */
|
|
8
|
+
export declare const statTileGrid = "grid ring-1 ring-foreground/5 sm:divide-x";
|
|
9
|
+
export type StatTileProps = {
|
|
10
|
+
label: string;
|
|
11
|
+
value: string;
|
|
12
|
+
sub?: React.ReactNode;
|
|
13
|
+
children?: React.ReactNode;
|
|
14
|
+
className?: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Label, big tabular figure, optional extra (a meter), muted sub line.
|
|
18
|
+
* Domain copy and status colors stay in the app.
|
|
19
|
+
*/
|
|
20
|
+
declare function StatTile({ label, value, sub, children, className }: StatTileProps): React.JSX.Element;
|
|
21
|
+
export { StatTile };
|