@robomous/ui-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +38 -0
- package/components.json +13 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +47 -0
- package/dist/lib/button.d.ts +1 -0
- package/dist/lib/button.js +4 -0
- package/dist/lib/cn.d.ts +18 -0
- package/dist/lib/cn.js +21 -0
- package/dist/lib/menu.d.ts +1 -0
- package/dist/lib/menu.js +16 -0
- package/dist/lib/progress.d.ts +3 -0
- package/dist/lib/progress.js +6 -0
- package/dist/lib/select.d.ts +1 -0
- package/dist/lib/select.js +3 -0
- package/dist/primitives/alert.d.ts +10 -0
- package/dist/primitives/alert.js +27 -0
- package/dist/primitives/badge.d.ts +9 -0
- package/dist/primitives/badge.js +28 -0
- package/dist/primitives/button.d.ts +10 -0
- package/dist/primitives/button.js +35 -0
- package/dist/primitives/card.d.ts +11 -0
- package/dist/primitives/card.js +24 -0
- package/dist/primitives/combobox.d.ts +24 -0
- package/dist/primitives/combobox.js +58 -0
- package/dist/primitives/dialog.d.ts +17 -0
- package/dist/primitives/dialog.js +36 -0
- package/dist/primitives/dropdown-menu.d.ts +29 -0
- package/dist/primitives/dropdown-menu.js +50 -0
- package/dist/primitives/field.d.ts +24 -0
- package/dist/primitives/field.js +67 -0
- package/dist/primitives/input-group.d.ts +16 -0
- package/dist/primitives/input-group.js +56 -0
- package/dist/primitives/input.d.ts +3 -0
- package/dist/primitives/input.js +6 -0
- package/dist/primitives/label.d.ts +4 -0
- package/dist/primitives/label.js +7 -0
- package/dist/primitives/progress.d.ts +4 -0
- package/dist/primitives/progress.js +7 -0
- package/dist/primitives/select.d.ts +15 -0
- package/dist/primitives/select.js +35 -0
- package/dist/primitives/separator.d.ts +4 -0
- package/dist/primitives/separator.js +8 -0
- package/dist/primitives/sheet.d.ts +14 -0
- package/dist/primitives/sheet.js +36 -0
- package/dist/primitives/skeleton.d.ts +2 -0
- package/dist/primitives/skeleton.js +6 -0
- package/dist/primitives/sonner.d.ts +4 -0
- package/dist/primitives/sonner.js +40 -0
- package/dist/primitives/table.d.ts +10 -0
- package/dist/primitives/table.js +27 -0
- package/dist/primitives/tabs.d.ts +11 -0
- package/dist/primitives/tabs.js +28 -0
- package/dist/primitives/textarea.d.ts +3 -0
- package/dist/primitives/textarea.js +6 -0
- package/dist/primitives/tooltip.d.ts +7 -0
- package/dist/primitives/tooltip.js +17 -0
- package/dist/statusTone.d.ts +37 -0
- package/dist/statusTone.js +38 -0
- package/dist/tokens.d.ts +26 -0
- package/dist/tokens.js +96 -0
- package/gates/canonical.test.mjs +71 -0
- package/gates/extensions.test.mjs +373 -0
- package/gates/index.d.mts +51 -0
- package/gates/index.mjs +514 -0
- package/gates/tokens.test.mjs +276 -0
- package/package.json +60 -0
- package/shadcn/README.md +13 -0
- package/shadcn/alert.tsx +76 -0
- package/shadcn/badge.tsx +49 -0
- package/shadcn/button.tsx +67 -0
- package/shadcn/card.tsx +103 -0
- package/shadcn/combobox.tsx +299 -0
- package/shadcn/dialog.tsx +166 -0
- package/shadcn/dropdown-menu.tsx +267 -0
- package/shadcn/field.tsx +236 -0
- package/shadcn/input-group.tsx +154 -0
- package/shadcn/input.tsx +19 -0
- package/shadcn/label.tsx +22 -0
- package/shadcn/progress.tsx +29 -0
- package/shadcn/select.tsx +190 -0
- package/shadcn/separator.tsx +28 -0
- package/shadcn/sheet.tsx +145 -0
- package/shadcn/skeleton.tsx +13 -0
- package/shadcn/sonner.tsx +49 -0
- package/shadcn/table.tsx +114 -0
- package/shadcn/tabs.tsx +88 -0
- package/shadcn/textarea.tsx +18 -0
- package/shadcn/tooltip.tsx +57 -0
- package/src/harness.test.tsx +37 -0
- package/src/index.ts +119 -0
- package/src/lib/button.ts +4 -0
- package/src/lib/cn.test.ts +33 -0
- package/src/lib/cn.ts +23 -0
- package/src/lib/menu.ts +16 -0
- package/src/lib/progress.ts +6 -0
- package/src/lib/select.ts +4 -0
- package/src/primitives/alert.tsx +76 -0
- package/src/primitives/badge.tsx +57 -0
- package/src/primitives/button.tsx +67 -0
- package/src/primitives/card.tsx +103 -0
- package/src/primitives/combobox.test.tsx +38 -0
- package/src/primitives/combobox.tsx +299 -0
- package/src/primitives/dialog.tsx +166 -0
- package/src/primitives/dropdown-menu.tsx +267 -0
- package/src/primitives/field.tsx +236 -0
- package/src/primitives/input-group.tsx +154 -0
- package/src/primitives/input.tsx +19 -0
- package/src/primitives/label.tsx +22 -0
- package/src/primitives/primitives.test.tsx +440 -0
- package/src/primitives/progress.tsx +29 -0
- package/src/primitives/select.tsx +190 -0
- package/src/primitives/separator.tsx +28 -0
- package/src/primitives/sheet.tsx +145 -0
- package/src/primitives/skeleton.tsx +13 -0
- package/src/primitives/sonner.test.tsx +41 -0
- package/src/primitives/sonner.tsx +65 -0
- package/src/primitives/table.tsx +114 -0
- package/src/primitives/tabs.tsx +88 -0
- package/src/primitives/textarea.tsx +18 -0
- package/src/primitives/tooltip.tsx +57 -0
- package/src/statusTone.test.ts +20 -0
- package/src/statusTone.ts +52 -0
- package/src/styles.css +204 -0
- package/src/tokens.test.ts +285 -0
- package/src/tokens.ts +102 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Toaster as Sonner } from "sonner";
|
|
5
|
+
import { CircleCheckIcon, InfoIcon, TriangleAlertIcon, OctagonXIcon, Loader2Icon } from "lucide-react";
|
|
6
|
+
// SHADCN FRAMEWORK ADAPTER: shadcn's sonner reads next-themes (a Next.js
|
|
7
|
+
// integration). VisionSet is Vite with one theme source, `.dark` on <html>
|
|
8
|
+
// (styles.css `@custom-variant dark`). This hook is the only difference from
|
|
9
|
+
// frontend/ui-core/shadcn/sonner.tsx; drop it if the app ever adopts a
|
|
10
|
+
// provider that shadcn's file can read.
|
|
11
|
+
function useTheme() {
|
|
12
|
+
const read = () => (typeof document !== "undefined" && document.documentElement.classList.contains("dark") ? "dark" : "light");
|
|
13
|
+
const [theme, setTheme] = React.useState(read);
|
|
14
|
+
React.useEffect(() => {
|
|
15
|
+
const observer = new MutationObserver(() => setTheme(read()));
|
|
16
|
+
observer.observe(document.documentElement, { attributes: true, attributeFilter: ["class"] });
|
|
17
|
+
return () => observer.disconnect();
|
|
18
|
+
}, []);
|
|
19
|
+
return { theme };
|
|
20
|
+
}
|
|
21
|
+
const Toaster = ({ ...props }) => {
|
|
22
|
+
const { theme = "system" } = useTheme();
|
|
23
|
+
return (_jsx(Sonner, { theme: theme, className: "toaster group", icons: {
|
|
24
|
+
success: (_jsx(CircleCheckIcon, { className: "size-4" })),
|
|
25
|
+
info: (_jsx(InfoIcon, { className: "size-4" })),
|
|
26
|
+
warning: (_jsx(TriangleAlertIcon, { className: "size-4" })),
|
|
27
|
+
error: (_jsx(OctagonXIcon, { className: "size-4" })),
|
|
28
|
+
loading: (_jsx(Loader2Icon, { className: "size-4 animate-spin" })),
|
|
29
|
+
}, style: {
|
|
30
|
+
"--normal-bg": "var(--popover)",
|
|
31
|
+
"--normal-text": "var(--popover-foreground)",
|
|
32
|
+
"--normal-border": "var(--border)",
|
|
33
|
+
"--border-radius": "var(--radius)",
|
|
34
|
+
}, toastOptions: {
|
|
35
|
+
classNames: {
|
|
36
|
+
toast: "cn-toast",
|
|
37
|
+
},
|
|
38
|
+
}, ...props }));
|
|
39
|
+
};
|
|
40
|
+
export { Toaster };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
declare function Table({ className, ...props }: React.ComponentProps<"table">): React.JSX.Element;
|
|
3
|
+
declare function TableHeader({ className, ...props }: React.ComponentProps<"thead">): React.JSX.Element;
|
|
4
|
+
declare function TableBody({ className, ...props }: React.ComponentProps<"tbody">): React.JSX.Element;
|
|
5
|
+
declare function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">): React.JSX.Element;
|
|
6
|
+
declare function TableRow({ className, ...props }: React.ComponentProps<"tr">): React.JSX.Element;
|
|
7
|
+
declare function TableHead({ className, ...props }: React.ComponentProps<"th">): React.JSX.Element;
|
|
8
|
+
declare function TableCell({ className, ...props }: React.ComponentProps<"td">): React.JSX.Element;
|
|
9
|
+
declare function TableCaption({ className, ...props }: React.ComponentProps<"caption">): React.JSX.Element;
|
|
10
|
+
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption, };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../lib/cn.js";
|
|
3
|
+
function Table({ className, ...props }) {
|
|
4
|
+
return (_jsx("div", { "data-slot": "table-container", className: "relative w-full overflow-x-auto", children: _jsx("table", { "data-slot": "table", className: cn("w-full caption-bottom text-sm", className), ...props }) }));
|
|
5
|
+
}
|
|
6
|
+
function TableHeader({ className, ...props }) {
|
|
7
|
+
return (_jsx("thead", { "data-slot": "table-header", className: cn("[&_tr]:border-b", className), ...props }));
|
|
8
|
+
}
|
|
9
|
+
function TableBody({ className, ...props }) {
|
|
10
|
+
return (_jsx("tbody", { "data-slot": "table-body", className: cn("[&_tr:last-child]:border-0", className), ...props }));
|
|
11
|
+
}
|
|
12
|
+
function TableFooter({ className, ...props }) {
|
|
13
|
+
return (_jsx("tfoot", { "data-slot": "table-footer", className: cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className), ...props }));
|
|
14
|
+
}
|
|
15
|
+
function TableRow({ className, ...props }) {
|
|
16
|
+
return (_jsx("tr", { "data-slot": "table-row", className: cn("border-b transition-colors hover:bg-muted/50 has-aria-expanded:bg-muted/50 data-[state=selected]:bg-muted", className), ...props }));
|
|
17
|
+
}
|
|
18
|
+
function TableHead({ className, ...props }) {
|
|
19
|
+
return (_jsx("th", { "data-slot": "table-head", className: cn("h-10 px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0", className), ...props }));
|
|
20
|
+
}
|
|
21
|
+
function TableCell({ className, ...props }) {
|
|
22
|
+
return (_jsx("td", { "data-slot": "table-cell", className: cn("p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0", className), ...props }));
|
|
23
|
+
}
|
|
24
|
+
function TableCaption({ className, ...props }) {
|
|
25
|
+
return (_jsx("caption", { "data-slot": "table-caption", className: cn("mt-4 text-sm text-muted-foreground", className), ...props }));
|
|
26
|
+
}
|
|
27
|
+
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption, };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { type VariantProps } from "class-variance-authority";
|
|
3
|
+
import { Tabs as TabsPrimitive } from "radix-ui";
|
|
4
|
+
declare function Tabs({ className, orientation, ...props }: React.ComponentProps<typeof TabsPrimitive.Root>): React.JSX.Element;
|
|
5
|
+
declare const tabsListVariants: (props?: ({
|
|
6
|
+
variant?: "default" | "line" | null | undefined;
|
|
7
|
+
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
8
|
+
declare function TabsList({ className, variant, ...props }: React.ComponentProps<typeof TabsPrimitive.List> & VariantProps<typeof tabsListVariants>): React.JSX.Element;
|
|
9
|
+
declare function TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Trigger>): React.JSX.Element;
|
|
10
|
+
declare function TabsContent({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Content>): React.JSX.Element;
|
|
11
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cva } from "class-variance-authority";
|
|
3
|
+
import { Tabs as TabsPrimitive } from "radix-ui";
|
|
4
|
+
import { cn } from "../lib/cn.js";
|
|
5
|
+
function Tabs({ className, orientation = "horizontal", ...props }) {
|
|
6
|
+
return (_jsx(TabsPrimitive.Root, { "data-slot": "tabs", "data-orientation": orientation, className: cn("group/tabs flex gap-2 data-horizontal:flex-col", className), ...props }));
|
|
7
|
+
}
|
|
8
|
+
const tabsListVariants = cva("group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-8 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none", {
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: "bg-muted",
|
|
12
|
+
line: "gap-1 bg-transparent",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
defaultVariants: {
|
|
16
|
+
variant: "default",
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
function TabsList({ className, variant = "default", ...props }) {
|
|
20
|
+
return (_jsx(TabsPrimitive.List, { "data-slot": "tabs-list", "data-variant": variant, className: cn(tabsListVariants({ variant }), className), ...props }));
|
|
21
|
+
}
|
|
22
|
+
function TabsTrigger({ className, ...props }) {
|
|
23
|
+
return (_jsx(TabsPrimitive.Trigger, { "data-slot": "tabs-trigger", className: cn("relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-1.5 py-0.5 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1 has-data-[icon=inline-start]:pl-1 dark:text-muted-foreground dark:hover:text-foreground group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent", "data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground", "after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100", className), ...props }));
|
|
24
|
+
}
|
|
25
|
+
function TabsContent({ className, ...props }) {
|
|
26
|
+
return (_jsx(TabsPrimitive.Content, { "data-slot": "tabs-content", className: cn("flex-1 text-sm outline-none", className), ...props }));
|
|
27
|
+
}
|
|
28
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../lib/cn.js";
|
|
3
|
+
function Textarea({ className, ...props }) {
|
|
4
|
+
return (_jsx("textarea", { "data-slot": "textarea", className: cn("flex field-sizing-content min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40", className), ...props }));
|
|
5
|
+
}
|
|
6
|
+
export { Textarea };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Tooltip as TooltipPrimitive } from "radix-ui";
|
|
3
|
+
declare function TooltipProvider({ delayDuration, ...props }: React.ComponentProps<typeof TooltipPrimitive.Provider>): React.JSX.Element;
|
|
4
|
+
declare function Tooltip({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Root>): React.JSX.Element;
|
|
5
|
+
declare function TooltipTrigger({ ...props }: React.ComponentProps<typeof TooltipPrimitive.Trigger>): React.JSX.Element;
|
|
6
|
+
declare function TooltipContent({ className, sideOffset, children, ...props }: React.ComponentProps<typeof TooltipPrimitive.Content>): React.JSX.Element;
|
|
7
|
+
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Tooltip as TooltipPrimitive } from "radix-ui";
|
|
4
|
+
import { cn } from "../lib/cn.js";
|
|
5
|
+
function TooltipProvider({ delayDuration = 0, ...props }) {
|
|
6
|
+
return (_jsx(TooltipPrimitive.Provider, { "data-slot": "tooltip-provider", delayDuration: delayDuration, ...props }));
|
|
7
|
+
}
|
|
8
|
+
function Tooltip({ ...props }) {
|
|
9
|
+
return _jsx(TooltipPrimitive.Root, { "data-slot": "tooltip", ...props });
|
|
10
|
+
}
|
|
11
|
+
function TooltipTrigger({ ...props }) {
|
|
12
|
+
return _jsx(TooltipPrimitive.Trigger, { "data-slot": "tooltip-trigger", ...props });
|
|
13
|
+
}
|
|
14
|
+
function TooltipContent({ className, sideOffset = 0, children, ...props }) {
|
|
15
|
+
return (_jsx(TooltipPrimitive.Portal, { children: _jsxs(TooltipPrimitive.Content, { "data-slot": "tooltip-content", sideOffset: sideOffset, className: cn("z-50 inline-flex w-fit max-w-xs origin-(--radix-tooltip-content-transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 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-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 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", className), ...props, children: [children, _jsx(TooltipPrimitive.Arrow, { className: "z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" })] }) }));
|
|
16
|
+
}
|
|
17
|
+
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one home for status colour outside the `Badge`.
|
|
3
|
+
*
|
|
4
|
+
* `badge.tsx` earns its own emerald/amber/sky because a chip's `/10` surface
|
|
5
|
+
* and hairline are a treatment nothing else draws. Everything else that paints
|
|
6
|
+
* a status — a dot, a timeline cell, an inline icon or word — reads its colour
|
|
7
|
+
* from here instead of naming a Tailwind family at its own call site, so a
|
|
8
|
+
* sixth place inventing a fourth spelling of "warning" is a type error, not a
|
|
9
|
+
* diff nobody notices.
|
|
10
|
+
*/
|
|
11
|
+
export type StatusTone = "neutral" | "accent" | "success" | "warning" | "destructive";
|
|
12
|
+
/**
|
|
13
|
+
* A tone's border and its fill, as whole utility names.
|
|
14
|
+
*
|
|
15
|
+
* Whole names rather than a `border-${tone}` template, because Tailwind scans
|
|
16
|
+
* source *text*: a class assembled at runtime is a class the build never saw and
|
|
17
|
+
* therefore a rule that is never emitted. The failure is silent and looks like a
|
|
18
|
+
* styling mistake, which is why these are written out.
|
|
19
|
+
*
|
|
20
|
+
* A status **dot** and a timeline **cell** are solid marks, not `Badge`s — a
|
|
21
|
+
* full-strength `*-500` fill is right there, where the `Badge`'s `/10` surface
|
|
22
|
+
* would vanish at 4px.
|
|
23
|
+
*/
|
|
24
|
+
export declare const TONE_BORDER: Record<StatusTone, string>;
|
|
25
|
+
export declare const TONE_FILL: Record<StatusTone, string>;
|
|
26
|
+
/**
|
|
27
|
+
* Ink for an icon or a run of inline text carrying a status — a `warning`
|
|
28
|
+
* triangle beside a sentence, not a dot or a cell, so it wants a legible
|
|
29
|
+
* foreground rather than `TONE_FILL`'s solid mark. `info` has no `StatusTone`
|
|
30
|
+
* counterpart: it names sky for surfaces that are informational without being
|
|
31
|
+
* any of `neutral`/`accent`/`success`/`warning`/`destructive`.
|
|
32
|
+
*/
|
|
33
|
+
export declare const STATUS_INK: {
|
|
34
|
+
readonly success: "text-emerald-700 dark:text-emerald-400";
|
|
35
|
+
readonly warning: "text-amber-700 dark:text-amber-400";
|
|
36
|
+
readonly info: "text-sky-700 dark:text-sky-400";
|
|
37
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A tone's border and its fill, as whole utility names.
|
|
3
|
+
*
|
|
4
|
+
* Whole names rather than a `border-${tone}` template, because Tailwind scans
|
|
5
|
+
* source *text*: a class assembled at runtime is a class the build never saw and
|
|
6
|
+
* therefore a rule that is never emitted. The failure is silent and looks like a
|
|
7
|
+
* styling mistake, which is why these are written out.
|
|
8
|
+
*
|
|
9
|
+
* A status **dot** and a timeline **cell** are solid marks, not `Badge`s — a
|
|
10
|
+
* full-strength `*-500` fill is right there, where the `Badge`'s `/10` surface
|
|
11
|
+
* would vanish at 4px.
|
|
12
|
+
*/
|
|
13
|
+
export const TONE_BORDER = {
|
|
14
|
+
neutral: "border-border",
|
|
15
|
+
accent: "border-primary",
|
|
16
|
+
success: "border-emerald-500 dark:border-emerald-400",
|
|
17
|
+
warning: "border-amber-500 dark:border-amber-400",
|
|
18
|
+
destructive: "border-destructive",
|
|
19
|
+
};
|
|
20
|
+
export const TONE_FILL = {
|
|
21
|
+
neutral: "bg-muted-foreground",
|
|
22
|
+
accent: "bg-primary",
|
|
23
|
+
success: "bg-emerald-500 dark:bg-emerald-400",
|
|
24
|
+
warning: "bg-amber-500 dark:bg-amber-400",
|
|
25
|
+
destructive: "bg-destructive",
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Ink for an icon or a run of inline text carrying a status — a `warning`
|
|
29
|
+
* triangle beside a sentence, not a dot or a cell, so it wants a legible
|
|
30
|
+
* foreground rather than `TONE_FILL`'s solid mark. `info` has no `StatusTone`
|
|
31
|
+
* counterpart: it names sky for surfaces that are informational without being
|
|
32
|
+
* any of `neutral`/`accent`/`success`/`warning`/`destructive`.
|
|
33
|
+
*/
|
|
34
|
+
export const STATUS_INK = {
|
|
35
|
+
success: "text-emerald-700 dark:text-emerald-400",
|
|
36
|
+
warning: "text-amber-700 dark:text-amber-400",
|
|
37
|
+
info: "text-sky-700 dark:text-sky-400",
|
|
38
|
+
};
|
package/dist/tokens.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The design tokens, as TypeScript.
|
|
3
|
+
*
|
|
4
|
+
* `styles.css` is the one that *runs* — Tailwind reads its `:root`, `.dark`
|
|
5
|
+
* and `@theme inline` blocks and every utility in the package comes out of
|
|
6
|
+
* them. This module exists for the two kinds of caller that cannot read CSS:
|
|
7
|
+
* a `<canvas>`/`<svg>` that needs a colour as a string, and `tokens.test.ts`,
|
|
8
|
+
* which parses the stylesheet and asserts the two agree, declaration for
|
|
9
|
+
* declaration.
|
|
10
|
+
*
|
|
11
|
+
* `LIGHT_THEME`/`DARK_THEME` are the shadcn preset (`b2iH` — style
|
|
12
|
+
* `nova`, base colour `neutral`, chart palette `neutral`) exactly as the CLI
|
|
13
|
+
* 4.19.0 scratch generated it, plus `brand` (Robomous coral — identity only).
|
|
14
|
+
* Consumer extensions such as VisionSet's `stage` and `origin-*` live in the
|
|
15
|
+
* consumer's own token module. Everything else is shadcn's own vocabulary.
|
|
16
|
+
*/
|
|
17
|
+
export declare const LIGHT_THEME: Readonly<Record<string, string>>;
|
|
18
|
+
export declare const DARK_THEME: Readonly<Record<string, string>>;
|
|
19
|
+
/** The two provenance facts a colour string alone cannot carry. */
|
|
20
|
+
export declare const THEME: {
|
|
21
|
+
readonly radius: "0.625rem";
|
|
22
|
+
readonly fontSans: "'Geist Variable', sans-serif";
|
|
23
|
+
readonly fontHeading: "var(--font-sans)";
|
|
24
|
+
};
|
|
25
|
+
/** `cssVar("popover")` → `"var(--popover)"` — for a runtime caller that needs a string. */
|
|
26
|
+
export declare function cssVar(name: string): string;
|
package/dist/tokens.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The design tokens, as TypeScript.
|
|
3
|
+
*
|
|
4
|
+
* `styles.css` is the one that *runs* — Tailwind reads its `:root`, `.dark`
|
|
5
|
+
* and `@theme inline` blocks and every utility in the package comes out of
|
|
6
|
+
* them. This module exists for the two kinds of caller that cannot read CSS:
|
|
7
|
+
* a `<canvas>`/`<svg>` that needs a colour as a string, and `tokens.test.ts`,
|
|
8
|
+
* which parses the stylesheet and asserts the two agree, declaration for
|
|
9
|
+
* declaration.
|
|
10
|
+
*
|
|
11
|
+
* `LIGHT_THEME`/`DARK_THEME` are the shadcn preset (`b2iH` — style
|
|
12
|
+
* `nova`, base colour `neutral`, chart palette `neutral`) exactly as the CLI
|
|
13
|
+
* 4.19.0 scratch generated it, plus `brand` (Robomous coral — identity only).
|
|
14
|
+
* Consumer extensions such as VisionSet's `stage` and `origin-*` live in the
|
|
15
|
+
* consumer's own token module. Everything else is shadcn's own vocabulary.
|
|
16
|
+
*/
|
|
17
|
+
export const LIGHT_THEME = Object.freeze({
|
|
18
|
+
background: "oklch(1 0 0)",
|
|
19
|
+
foreground: "oklch(0.145 0 0)",
|
|
20
|
+
card: "oklch(1 0 0)",
|
|
21
|
+
"card-foreground": "oklch(0.145 0 0)",
|
|
22
|
+
popover: "oklch(1 0 0)",
|
|
23
|
+
"popover-foreground": "oklch(0.145 0 0)",
|
|
24
|
+
primary: "oklch(0.205 0 0)",
|
|
25
|
+
"primary-foreground": "oklch(0.985 0 0)",
|
|
26
|
+
secondary: "oklch(0.97 0 0)",
|
|
27
|
+
"secondary-foreground": "oklch(0.205 0 0)",
|
|
28
|
+
muted: "oklch(0.97 0 0)",
|
|
29
|
+
"muted-foreground": "oklch(0.556 0 0)",
|
|
30
|
+
accent: "oklch(0.97 0 0)",
|
|
31
|
+
"accent-foreground": "oklch(0.205 0 0)",
|
|
32
|
+
destructive: "oklch(0.577 0.245 27.325)",
|
|
33
|
+
border: "oklch(0.922 0 0)",
|
|
34
|
+
input: "oklch(0.922 0 0)",
|
|
35
|
+
ring: "oklch(0.708 0 0)",
|
|
36
|
+
"chart-1": "oklch(0.87 0 0)",
|
|
37
|
+
"chart-2": "oklch(0.556 0 0)",
|
|
38
|
+
"chart-3": "oklch(0.439 0 0)",
|
|
39
|
+
"chart-4": "oklch(0.371 0 0)",
|
|
40
|
+
"chart-5": "oklch(0.269 0 0)",
|
|
41
|
+
sidebar: "oklch(0.985 0 0)",
|
|
42
|
+
"sidebar-foreground": "oklch(0.145 0 0)",
|
|
43
|
+
"sidebar-primary": "oklch(0.205 0 0)",
|
|
44
|
+
"sidebar-primary-foreground": "oklch(0.985 0 0)",
|
|
45
|
+
"sidebar-accent": "oklch(0.97 0 0)",
|
|
46
|
+
"sidebar-accent-foreground": "oklch(0.205 0 0)",
|
|
47
|
+
"sidebar-border": "oklch(0.922 0 0)",
|
|
48
|
+
"sidebar-ring": "oklch(0.708 0 0)",
|
|
49
|
+
// Robomous coral. Identity only — the wordmark and its styleguide swatch
|
|
50
|
+
// — never a functional-UI colour.
|
|
51
|
+
brand: "oklch(0.653 0.178 32.3)",
|
|
52
|
+
});
|
|
53
|
+
export const DARK_THEME = Object.freeze({
|
|
54
|
+
background: "oklch(0.145 0 0)",
|
|
55
|
+
foreground: "oklch(0.985 0 0)",
|
|
56
|
+
card: "oklch(0.205 0 0)",
|
|
57
|
+
"card-foreground": "oklch(0.985 0 0)",
|
|
58
|
+
popover: "oklch(0.205 0 0)",
|
|
59
|
+
"popover-foreground": "oklch(0.985 0 0)",
|
|
60
|
+
primary: "oklch(0.922 0 0)",
|
|
61
|
+
"primary-foreground": "oklch(0.205 0 0)",
|
|
62
|
+
secondary: "oklch(0.269 0 0)",
|
|
63
|
+
"secondary-foreground": "oklch(0.985 0 0)",
|
|
64
|
+
muted: "oklch(0.269 0 0)",
|
|
65
|
+
"muted-foreground": "oklch(0.708 0 0)",
|
|
66
|
+
accent: "oklch(0.269 0 0)",
|
|
67
|
+
"accent-foreground": "oklch(0.985 0 0)",
|
|
68
|
+
destructive: "oklch(0.704 0.191 22.216)",
|
|
69
|
+
border: "oklch(1 0 0 / 10%)",
|
|
70
|
+
input: "oklch(1 0 0 / 15%)",
|
|
71
|
+
ring: "oklch(0.556 0 0)",
|
|
72
|
+
"chart-1": "oklch(0.87 0 0)",
|
|
73
|
+
"chart-2": "oklch(0.556 0 0)",
|
|
74
|
+
"chart-3": "oklch(0.439 0 0)",
|
|
75
|
+
"chart-4": "oklch(0.371 0 0)",
|
|
76
|
+
"chart-5": "oklch(0.269 0 0)",
|
|
77
|
+
sidebar: "oklch(0.205 0 0)",
|
|
78
|
+
"sidebar-foreground": "oklch(0.985 0 0)",
|
|
79
|
+
"sidebar-primary": "oklch(0.488 0.243 264.376)",
|
|
80
|
+
"sidebar-primary-foreground": "oklch(0.985 0 0)",
|
|
81
|
+
"sidebar-accent": "oklch(0.269 0 0)",
|
|
82
|
+
"sidebar-accent-foreground": "oklch(0.985 0 0)",
|
|
83
|
+
"sidebar-border": "oklch(1 0 0 / 10%)",
|
|
84
|
+
"sidebar-ring": "oklch(0.556 0 0)",
|
|
85
|
+
brand: "oklch(0.653 0.178 32.3)",
|
|
86
|
+
});
|
|
87
|
+
/** The two provenance facts a colour string alone cannot carry. */
|
|
88
|
+
export const THEME = {
|
|
89
|
+
radius: "0.625rem",
|
|
90
|
+
fontSans: "'Geist Variable', sans-serif",
|
|
91
|
+
fontHeading: "var(--font-sans)",
|
|
92
|
+
};
|
|
93
|
+
/** `cssVar("popover")` → `"var(--popover)"` — for a runtime caller that needs a string. */
|
|
94
|
+
export function cssVar(name) {
|
|
95
|
+
return `var(--${name})`;
|
|
96
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { test } from "node:test";
|
|
6
|
+
import { relativize } from "../scripts/shadcn_relativize.mjs";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
ADAPTER_REMOVED_LINES,
|
|
10
|
+
additiveOnly,
|
|
11
|
+
checkAdapter,
|
|
12
|
+
withoutLines,
|
|
13
|
+
} from "./index.mjs";
|
|
14
|
+
|
|
15
|
+
const REPO = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
16
|
+
const PRIMITIVES = path.join(REPO, "src/primitives");
|
|
17
|
+
const SNAPSHOTS = path.join(REPO, "shadcn");
|
|
18
|
+
|
|
19
|
+
test("additiveOnly accepts an added line and refuses a changed one", () => {
|
|
20
|
+
assert.equal(additiveOnly("a\nb\n", "a\nx\nb\n").ok, true);
|
|
21
|
+
assert.equal(additiveOnly("a\nb\n", "a\nB\n").ok, false);
|
|
22
|
+
assert.equal(additiveOnly("a\nb\n", "b\na\n").ok, false);
|
|
23
|
+
assert.equal(additiveOnly("a\n\nb\n", "a\nb\n").ok, false);
|
|
24
|
+
assert.equal(additiveOnly("a\r\nb \r\n", "a\nb\n").ok, true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("checkAdapter refuses an unlisted file carrying the marker", () => {
|
|
28
|
+
const result = checkAdapter("unlisted.tsx", "// SHADCN FRAMEWORK ADAPTER\nconst x = 1\n");
|
|
29
|
+
assert.equal(result.ok, false);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("checkAdapter accepts a listed file carrying the marker", () => {
|
|
33
|
+
const result = checkAdapter("sonner.tsx", "// SHADCN FRAMEWORK ADAPTER\nconst x = 1\n");
|
|
34
|
+
assert.deepEqual(result, { ok: true, isAdapter: true });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("checkAdapter ignores a file with no marker even if unlisted", () => {
|
|
38
|
+
const result = checkAdapter("unlisted.tsx", "const x = 1\n");
|
|
39
|
+
assert.deepEqual(result, { ok: true, isAdapter: false });
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("a listed adapter passes when only the two next-themes lines are removed", () => {
|
|
43
|
+
const snapshot = 'import { useTheme } from "next-themes"\nconst x = 1\n const { theme = "system" } = useTheme()\nconst y = 2\n';
|
|
44
|
+
const actual = "// SHADCN FRAMEWORK ADAPTER\nconst x = 1\nconst y = 2\n";
|
|
45
|
+
const result = additiveOnly(withoutLines(relativize(snapshot), ADAPTER_REMOVED_LINES), actual);
|
|
46
|
+
assert.equal(result.ok, true);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("a listed adapter that also drops a different snapshot line still fails", () => {
|
|
50
|
+
const snapshot = 'import { useTheme } from "next-themes"\nconst x = 1\n const { theme = "system" } = useTheme()\nconst y = 2\n';
|
|
51
|
+
const actual = "// SHADCN FRAMEWORK ADAPTER\nconst y = 2\n"; // "const x = 1" went missing too
|
|
52
|
+
const result = additiveOnly(withoutLines(relativize(snapshot), ADAPTER_REMOVED_LINES), actual);
|
|
53
|
+
assert.equal(result.ok, false);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const primitives = readdirSync(PRIMITIVES).filter((f) => f.endsWith(".tsx") && !f.endsWith(".test.tsx"));
|
|
57
|
+
|
|
58
|
+
for (const file of primitives) {
|
|
59
|
+
test(`${file} is shadcn's canonical file plus added lines only`, () => {
|
|
60
|
+
const snapshot = path.join(SNAPSHOTS, file);
|
|
61
|
+
assert.ok(existsSync(snapshot), `${file} has no snapshot in shadcn/ — install it with pnpm shadcn:add`);
|
|
62
|
+
const snapshotText = relativize(readFileSync(snapshot, "utf8"));
|
|
63
|
+
const actualText = readFileSync(path.join(PRIMITIVES, file), "utf8");
|
|
64
|
+
const adapter = checkAdapter(file, actualText);
|
|
65
|
+
assert.ok(adapter.ok, adapter.reason);
|
|
66
|
+
const result = adapter.isAdapter
|
|
67
|
+
? additiveOnly(withoutLines(snapshotText, ADAPTER_REMOVED_LINES), actualText)
|
|
68
|
+
: additiveOnly(snapshotText, actualText);
|
|
69
|
+
assert.ok(result.ok, `${file} diverges from its snapshot at: ${result.missing}`);
|
|
70
|
+
});
|
|
71
|
+
}
|