@theruntime/components 0.0.0 → 0.0.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/LICENSE +21 -0
- package/package.json +113 -4
- package/src/accordion.tsx +62 -0
- package/src/alert-dialog.tsx +177 -0
- package/src/alert.tsx +60 -0
- package/src/aspect-ratio.tsx +7 -0
- package/src/attachment.tsx +192 -0
- package/src/avatar.tsx +94 -0
- package/src/badge.tsx +46 -0
- package/src/breadcrumb.tsx +102 -0
- package/src/bubble.tsx +125 -0
- package/src/button-group.tsx +82 -0
- package/src/button.tsx +62 -0
- package/src/calendar.tsx +178 -0
- package/src/card.tsx +75 -0
- package/src/carousel.tsx +228 -0
- package/src/chart/container.tsx +72 -0
- package/src/chart/context.tsx +39 -0
- package/src/chart/index.ts +4 -0
- package/src/chart/legend.tsx +63 -0
- package/src/chart/payload.ts +31 -0
- package/src/chart/tooltip.tsx +149 -0
- package/src/checkbox.tsx +27 -0
- package/src/cn.ts +36 -0
- package/src/collapsible.tsx +19 -0
- package/src/combobox/chips.tsx +71 -0
- package/src/combobox/content.tsx +123 -0
- package/src/combobox/index.ts +13 -0
- package/src/combobox/input.tsx +39 -0
- package/src/combobox/trigger.tsx +44 -0
- package/src/command.tsx +153 -0
- package/src/context-menu.tsx +222 -0
- package/src/dialog.tsx +142 -0
- package/src/direction.tsx +18 -0
- package/src/drawer.tsx +122 -0
- package/src/dropdown-menu.tsx +226 -0
- package/src/empty.tsx +94 -0
- package/src/field.tsx +232 -0
- package/src/form.tsx +153 -0
- package/src/hover-card.tsx +36 -0
- package/src/index.ts +90 -0
- package/src/input-group.tsx +156 -0
- package/src/input-otp.tsx +68 -0
- package/src/input.tsx +21 -0
- package/src/item.tsx +172 -0
- package/src/kbd.tsx +28 -0
- package/src/label.tsx +19 -0
- package/src/marker.tsx +66 -0
- package/src/menubar.tsx +250 -0
- package/src/message-scroller.tsx +128 -0
- package/src/message.tsx +85 -0
- package/src/native-select.tsx +56 -0
- package/src/navigation-menu.tsx +161 -0
- package/src/pagination.tsx +106 -0
- package/src/popover.tsx +72 -0
- package/src/progress.tsx +26 -0
- package/src/questionnaire/actions.tsx +118 -0
- package/src/questionnaire/choices.tsx +113 -0
- package/src/questionnaire/index.ts +21 -0
- package/src/questionnaire/root.tsx +75 -0
- package/src/radio-group.tsx +43 -0
- package/src/resizable.tsx +45 -0
- package/src/scroll-area.tsx +54 -0
- package/src/select.tsx +173 -0
- package/src/separator.tsx +26 -0
- package/src/sheet.tsx +132 -0
- package/src/shell/app-tab.tsx +67 -0
- package/src/shell/entry-row.tsx +137 -0
- package/src/shell/field.tsx +83 -0
- package/src/shell/group-head.tsx +19 -0
- package/src/shell/scope-chip.tsx +32 -0
- package/src/shell/suggest-panel.tsx +92 -0
- package/src/sidebar/context.tsx +128 -0
- package/src/sidebar/index.ts +23 -0
- package/src/sidebar/menu.tsx +239 -0
- package/src/sidebar/sections.tsx +118 -0
- package/src/sidebar/sidebar.tsx +183 -0
- package/src/skeleton.tsx +13 -0
- package/src/slider.tsx +56 -0
- package/src/sonner.tsx +41 -0
- package/src/spinner.tsx +16 -0
- package/src/styles.css +149 -0
- package/src/switch.tsx +33 -0
- package/src/table.tsx +90 -0
- package/src/tabs.tsx +79 -0
- package/src/textarea.tsx +18 -0
- package/src/toggle-group.tsx +81 -0
- package/src/toggle.tsx +44 -0
- package/src/tooltip.tsx +51 -0
- package/src/use-mobile.ts +21 -0
- package/README.md +0 -3
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { SidebarSimpleIcon } from "@phosphor-icons/react";
|
|
3
|
+
|
|
4
|
+
import { cn, type CSSVarStyle } from "../cn";
|
|
5
|
+
import { Button } from "../button";
|
|
6
|
+
import { Input } from "../input";
|
|
7
|
+
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "../sheet";
|
|
8
|
+
import { SIDEBAR_WIDTH_MOBILE, useSidebar } from "./context";
|
|
9
|
+
|
|
10
|
+
export function Sidebar({
|
|
11
|
+
side = "left",
|
|
12
|
+
variant = "sidebar",
|
|
13
|
+
collapsible = "offcanvas",
|
|
14
|
+
className,
|
|
15
|
+
children,
|
|
16
|
+
...props
|
|
17
|
+
}: React.ComponentProps<"div"> & {
|
|
18
|
+
side?: "left" | "right";
|
|
19
|
+
variant?: "sidebar" | "floating" | "inset";
|
|
20
|
+
collapsible?: "offcanvas" | "icon" | "none";
|
|
21
|
+
}) {
|
|
22
|
+
const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
|
|
23
|
+
|
|
24
|
+
if (collapsible === "none") {
|
|
25
|
+
return (
|
|
26
|
+
<div
|
|
27
|
+
data-slot="sidebar"
|
|
28
|
+
className={cn(
|
|
29
|
+
"flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground",
|
|
30
|
+
className,
|
|
31
|
+
)}
|
|
32
|
+
{...props}
|
|
33
|
+
>
|
|
34
|
+
{children}
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (isMobile) {
|
|
40
|
+
const mobileStyle: CSSVarStyle = { "--sidebar-width": SIDEBAR_WIDTH_MOBILE };
|
|
41
|
+
return (
|
|
42
|
+
<Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
|
|
43
|
+
<SheetContent
|
|
44
|
+
data-sidebar="sidebar"
|
|
45
|
+
data-slot="sidebar"
|
|
46
|
+
data-mobile="true"
|
|
47
|
+
className="w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
|
|
48
|
+
style={mobileStyle}
|
|
49
|
+
side={side}
|
|
50
|
+
>
|
|
51
|
+
<SheetHeader className="sr-only">
|
|
52
|
+
<SheetTitle>Sidebar</SheetTitle>
|
|
53
|
+
<SheetDescription>Displays the mobile sidebar.</SheetDescription>
|
|
54
|
+
</SheetHeader>
|
|
55
|
+
<div className="flex h-full w-full flex-col">{children}</div>
|
|
56
|
+
</SheetContent>
|
|
57
|
+
</Sheet>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div
|
|
63
|
+
className="group peer hidden text-sidebar-foreground md:block"
|
|
64
|
+
data-state={state}
|
|
65
|
+
data-collapsible={state === "collapsed" ? collapsible : ""}
|
|
66
|
+
data-variant={variant}
|
|
67
|
+
data-side={side}
|
|
68
|
+
data-slot="sidebar"
|
|
69
|
+
>
|
|
70
|
+
{/* This is what handles the sidebar gap on desktop */}
|
|
71
|
+
<div
|
|
72
|
+
data-slot="sidebar-gap"
|
|
73
|
+
className={cn(
|
|
74
|
+
"relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear",
|
|
75
|
+
"group-data-[collapsible=offcanvas]:w-0",
|
|
76
|
+
"group-data-[side=right]:rotate-180",
|
|
77
|
+
variant === "floating" || variant === "inset"
|
|
78
|
+
? "group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]"
|
|
79
|
+
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon)",
|
|
80
|
+
)}
|
|
81
|
+
/>
|
|
82
|
+
<div
|
|
83
|
+
data-slot="sidebar-container"
|
|
84
|
+
className={cn(
|
|
85
|
+
"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex",
|
|
86
|
+
side === "left"
|
|
87
|
+
? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
|
|
88
|
+
: "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
|
|
89
|
+
// Adjust the padding for floating and inset variants.
|
|
90
|
+
variant === "floating" || variant === "inset"
|
|
91
|
+
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
|
|
92
|
+
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",
|
|
93
|
+
className,
|
|
94
|
+
)}
|
|
95
|
+
{...props}
|
|
96
|
+
>
|
|
97
|
+
<div
|
|
98
|
+
data-sidebar="sidebar"
|
|
99
|
+
data-slot="sidebar-inner"
|
|
100
|
+
className="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow-sm"
|
|
101
|
+
>
|
|
102
|
+
{children}
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function SidebarTrigger({
|
|
110
|
+
className,
|
|
111
|
+
onClick,
|
|
112
|
+
...props
|
|
113
|
+
}: React.ComponentProps<typeof Button>) {
|
|
114
|
+
const { toggleSidebar } = useSidebar();
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<Button
|
|
118
|
+
data-sidebar="trigger"
|
|
119
|
+
data-slot="sidebar-trigger"
|
|
120
|
+
variant="ghost"
|
|
121
|
+
size="icon"
|
|
122
|
+
className={cn("size-7", className)}
|
|
123
|
+
onClick={(event) => {
|
|
124
|
+
onClick?.(event);
|
|
125
|
+
toggleSidebar();
|
|
126
|
+
}}
|
|
127
|
+
{...props}
|
|
128
|
+
>
|
|
129
|
+
<SidebarSimpleIcon />
|
|
130
|
+
<span className="sr-only">Toggle Sidebar</span>
|
|
131
|
+
</Button>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
|
|
136
|
+
const { toggleSidebar } = useSidebar();
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<button
|
|
140
|
+
data-sidebar="rail"
|
|
141
|
+
data-slot="sidebar-rail"
|
|
142
|
+
aria-label="Toggle Sidebar"
|
|
143
|
+
tabIndex={-1}
|
|
144
|
+
onClick={toggleSidebar}
|
|
145
|
+
title="Toggle Sidebar"
|
|
146
|
+
className={cn(
|
|
147
|
+
"absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border sm:flex",
|
|
148
|
+
"in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
|
|
149
|
+
"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
|
|
150
|
+
"group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full hover:group-data-[collapsible=offcanvas]:bg-sidebar",
|
|
151
|
+
"[[data-side=left][data-collapsible=offcanvas]_&]:-right-2",
|
|
152
|
+
"[[data-side=right][data-collapsible=offcanvas]_&]:-left-2",
|
|
153
|
+
className,
|
|
154
|
+
)}
|
|
155
|
+
{...props}
|
|
156
|
+
/>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
|
|
161
|
+
return (
|
|
162
|
+
<main
|
|
163
|
+
data-slot="sidebar-inset"
|
|
164
|
+
className={cn(
|
|
165
|
+
"relative flex w-full flex-1 flex-col bg-background",
|
|
166
|
+
"md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2",
|
|
167
|
+
className,
|
|
168
|
+
)}
|
|
169
|
+
{...props}
|
|
170
|
+
/>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function SidebarInput({ className, ...props }: React.ComponentProps<typeof Input>) {
|
|
175
|
+
return (
|
|
176
|
+
<Input
|
|
177
|
+
data-slot="sidebar-input"
|
|
178
|
+
data-sidebar="input"
|
|
179
|
+
className={cn("h-8 w-full bg-background shadow-none", className)}
|
|
180
|
+
{...props}
|
|
181
|
+
/>
|
|
182
|
+
);
|
|
183
|
+
}
|
package/src/skeleton.tsx
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { cn } from "./cn";
|
|
2
|
+
|
|
3
|
+
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
|
4
|
+
return (
|
|
5
|
+
<div
|
|
6
|
+
data-slot="skeleton"
|
|
7
|
+
className={cn("animate-pulse rounded-md bg-accent", className)}
|
|
8
|
+
{...props}
|
|
9
|
+
/>
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { Skeleton };
|
package/src/slider.tsx
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Slider as SliderPrimitive } from "radix-ui";
|
|
3
|
+
|
|
4
|
+
import { cn } from "./cn";
|
|
5
|
+
|
|
6
|
+
function Slider({
|
|
7
|
+
className,
|
|
8
|
+
defaultValue,
|
|
9
|
+
value,
|
|
10
|
+
min = 0,
|
|
11
|
+
max = 100,
|
|
12
|
+
...props
|
|
13
|
+
}: React.ComponentProps<typeof SliderPrimitive.Root>) {
|
|
14
|
+
const _values = React.useMemo(
|
|
15
|
+
() => (Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min, max]),
|
|
16
|
+
[value, defaultValue, min, max],
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<SliderPrimitive.Root
|
|
21
|
+
data-slot="slider"
|
|
22
|
+
defaultValue={defaultValue}
|
|
23
|
+
value={value}
|
|
24
|
+
min={min}
|
|
25
|
+
max={max}
|
|
26
|
+
className={cn(
|
|
27
|
+
"relative flex w-full touch-none items-center select-none data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col",
|
|
28
|
+
className,
|
|
29
|
+
)}
|
|
30
|
+
{...props}
|
|
31
|
+
>
|
|
32
|
+
<SliderPrimitive.Track
|
|
33
|
+
data-slot="slider-track"
|
|
34
|
+
className={cn(
|
|
35
|
+
"relative grow overflow-hidden rounded-full bg-muted data-[orientation=horizontal]:h-1.5 data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5",
|
|
36
|
+
)}
|
|
37
|
+
>
|
|
38
|
+
<SliderPrimitive.Range
|
|
39
|
+
data-slot="slider-range"
|
|
40
|
+
className={cn(
|
|
41
|
+
"absolute bg-primary data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full",
|
|
42
|
+
)}
|
|
43
|
+
/>
|
|
44
|
+
</SliderPrimitive.Track>
|
|
45
|
+
{Array.from({ length: _values.length }, (_, index) => (
|
|
46
|
+
<SliderPrimitive.Thumb
|
|
47
|
+
data-slot="slider-thumb"
|
|
48
|
+
key={index}
|
|
49
|
+
className="block size-4 shrink-0 rounded-full border border-primary bg-white shadow-sm ring-ring/50 transition-[color,box-shadow] hover:ring-4 focus-visible:ring-4 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50"
|
|
50
|
+
/>
|
|
51
|
+
))}
|
|
52
|
+
</SliderPrimitive.Root>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export { Slider };
|
package/src/sonner.tsx
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CheckCircleIcon,
|
|
3
|
+
CircleNotchIcon,
|
|
4
|
+
InfoIcon,
|
|
5
|
+
WarningIcon,
|
|
6
|
+
XCircleIcon,
|
|
7
|
+
} from "@phosphor-icons/react";
|
|
8
|
+
import { useTheme } from "next-themes";
|
|
9
|
+
import { Toaster as Sonner, type ToasterProps } from "sonner";
|
|
10
|
+
|
|
11
|
+
import type { CSSVarStyle } from "./cn";
|
|
12
|
+
|
|
13
|
+
const Toaster = ({ ...props }: ToasterProps) => {
|
|
14
|
+
const { theme = "system" } = useTheme();
|
|
15
|
+
const sonnerTheme: ToasterProps["theme"] =
|
|
16
|
+
theme === "light" || theme === "dark" ? theme : "system";
|
|
17
|
+
const style: CSSVarStyle = {
|
|
18
|
+
"--normal-bg": "var(--popover)",
|
|
19
|
+
"--normal-text": "var(--popover-foreground)",
|
|
20
|
+
"--normal-border": "var(--border)",
|
|
21
|
+
"--border-radius": "var(--radius)",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<Sonner
|
|
26
|
+
theme={sonnerTheme}
|
|
27
|
+
className="toaster group"
|
|
28
|
+
icons={{
|
|
29
|
+
success: <CheckCircleIcon className="size-4" />,
|
|
30
|
+
info: <InfoIcon className="size-4" />,
|
|
31
|
+
warning: <WarningIcon className="size-4" />,
|
|
32
|
+
error: <XCircleIcon className="size-4" />,
|
|
33
|
+
loading: <CircleNotchIcon className="size-4 animate-spin" />,
|
|
34
|
+
}}
|
|
35
|
+
style={style}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export { Toaster };
|
package/src/spinner.tsx
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CircleNotchIcon } from "@phosphor-icons/react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "./cn";
|
|
4
|
+
|
|
5
|
+
function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
|
|
6
|
+
return (
|
|
7
|
+
<CircleNotchIcon
|
|
8
|
+
role="status"
|
|
9
|
+
aria-label="Loading"
|
|
10
|
+
className={cn("size-4 animate-spin", className)}
|
|
11
|
+
{...props}
|
|
12
|
+
/>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { Spinner };
|
package/src/styles.css
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The name map every runtime component resolves against, and the base rules they assume.
|
|
3
|
+
*
|
|
4
|
+
* It ships with the components rather than with the SDK because an app imports those components
|
|
5
|
+
* live, from this package, and a utility they write compiles to nothing unless this map reached
|
|
6
|
+
* the app's Tailwind root. The app reaches it by @import from app.css.
|
|
7
|
+
*
|
|
8
|
+
* This file opens no Tailwind root. It is reached FROM a root, never one itself, and a second
|
|
9
|
+
* root is refused. @theme, @custom-variant and @layer base all merge across that hop, which
|
|
10
|
+
* packages/daemon/test/tailwind-facts.test.ts holds against the installed Tailwind.
|
|
11
|
+
*
|
|
12
|
+
* VALUES are not here. A token's value comes from the app's own tokens.css and fonts.css.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/* tw-animate-css: the v4 successor to tailwindcss-animate. shadcn v4 components use
|
|
16
|
+
animate-in / fade-in-0 / zoom-in-95 / slide-in-* (Dialog, Popover, Tooltip, Sheet, Dropdown…) and the
|
|
17
|
+
accordion-up/down + collapsible-up/down keyframes (Accordion, Collapsible), all of which live here, not
|
|
18
|
+
in core Tailwind v4. Without this they silently no-op (no error; component shows but never animates open). */
|
|
19
|
+
@import "tw-animate-css";
|
|
20
|
+
|
|
21
|
+
/* `dark:` is a CLASS variant, not a media query, so a register's own `.dark` overrides decide
|
|
22
|
+
what it means rather than the browser deciding for them. The marker is stamped on <html> by
|
|
23
|
+
followColorScheme(), from the OS preference. Selector is shadcn v4's, so a hand-set `.dark`
|
|
24
|
+
works too. */
|
|
25
|
+
@custom-variant dark (&:is(.dark *));
|
|
26
|
+
|
|
27
|
+
@theme inline {
|
|
28
|
+
/* Scale-axis type utilities (register Tier 3): text-title/body/caption/meta/control
|
|
29
|
+
resolve per scope (.scale-content default / .scale-chrome / .scale-touch). Heights
|
|
30
|
+
use the shorthand h-(--control-h) at call sites, no spacing key needed. */
|
|
31
|
+
--text-title: var(--type-title, 16px);
|
|
32
|
+
--text-title--line-height: 1.3;
|
|
33
|
+
--text-title--font-weight: var(--w-title, 600);
|
|
34
|
+
--text-body: var(--type-body, 14px);
|
|
35
|
+
--text-body--line-height: var(--lh-body, 1.5);
|
|
36
|
+
--text-caption: var(--type-caption, 12px);
|
|
37
|
+
--text-caption--line-height: 1.4;
|
|
38
|
+
--text-meta: var(--type-meta, 11px);
|
|
39
|
+
--text-meta--line-height: 1;
|
|
40
|
+
--text-control: var(--type-control, 14px);
|
|
41
|
+
--text-control--line-height: 1;
|
|
42
|
+
--text-stat: var(--type-stat, 24px);
|
|
43
|
+
--text-stat--line-height: 1.15;
|
|
44
|
+
--text-stat--font-weight: 600;
|
|
45
|
+
/* Radius: shadcn v4's four rungs, derived from --radius. Without these mappings the
|
|
46
|
+
rounded-* utilities compile to Tailwind's stock literals and --radius is inert, a
|
|
47
|
+
theme could define it eight times and every card still rendered off-register. */
|
|
48
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
49
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
50
|
+
--radius-lg: var(--radius);
|
|
51
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
52
|
+
--color-background: var(--background);
|
|
53
|
+
--color-foreground: var(--foreground);
|
|
54
|
+
--color-card: var(--card);
|
|
55
|
+
--color-card-foreground: var(--card-foreground);
|
|
56
|
+
--color-muted: var(--muted);
|
|
57
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
58
|
+
--color-border: var(--border);
|
|
59
|
+
--color-input: var(--input);
|
|
60
|
+
--color-primary: var(--primary);
|
|
61
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
62
|
+
--color-accent: var(--accent);
|
|
63
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
64
|
+
--color-ring: var(--ring);
|
|
65
|
+
/* shadcn full token set, added so bg-secondary/bg-destructive/bg-popover/bg-chart-* generate as
|
|
66
|
+
utilities. Values come from each theme block below (undefined → inert for themes that omit them). */
|
|
67
|
+
--color-secondary: var(--secondary);
|
|
68
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
69
|
+
--color-destructive: var(--destructive);
|
|
70
|
+
--color-destructive-foreground: var(--destructive-foreground);
|
|
71
|
+
--color-popover: var(--popover);
|
|
72
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
73
|
+
--color-chart-1: var(--chart-1);
|
|
74
|
+
--color-chart-2: var(--chart-2);
|
|
75
|
+
--color-chart-3: var(--chart-3);
|
|
76
|
+
--color-chart-4: var(--chart-4);
|
|
77
|
+
--color-chart-5: var(--chart-5);
|
|
78
|
+
--color-chart-6: var(--chart-6);
|
|
79
|
+
/* Sidebar token family. shadcn's Sidebar is the one component with its own colour
|
|
80
|
+
vocabulary, bg-sidebar, text-sidebar-foreground, bg-sidebar-accent, border-sidebar-border,
|
|
81
|
+
ring-sidebar-ring. Without these keys those utilities never generate and the sidebar renders
|
|
82
|
+
transparent on the page ground. Every theme block below defines the values by aliasing its own
|
|
83
|
+
register tokens, so a re-theme carries the navigation surface with it. */
|
|
84
|
+
--color-sidebar: var(--sidebar);
|
|
85
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
86
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
87
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
88
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
89
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
90
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
91
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
92
|
+
|
|
93
|
+
--font-sans: "Inter Variable", ui-sans-serif, system-ui, sans-serif;
|
|
94
|
+
--font-serif: "Source Serif 4 Variable", Palatino, Georgia, serif;
|
|
95
|
+
--font-mono: "JetBrains Mono Variable", ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
96
|
+
|
|
97
|
+
--radius: 0.625rem;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* Display face for headings: a register sets --font-display to override, otherwise headings fall
|
|
101
|
+
back to the sans body face. In @layer base, not unlayered: unlayered CSS beats every layer at
|
|
102
|
+
any specificity, so an unlayered rule here silently outranked the register's own reading
|
|
103
|
+
surface, whose heading face is set in @layer components. Utilities still win over both. */
|
|
104
|
+
@layer base {
|
|
105
|
+
h1,
|
|
106
|
+
h2,
|
|
107
|
+
h3,
|
|
108
|
+
h4 {
|
|
109
|
+
font-family: var(--font-display, var(--font-sans));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* shadcn base, default border color. Tailwind v4 changed the bare `border` utility's default
|
|
114
|
+
color to currentColor (it was gray-200 in v3), so an uncolored `border`/`border-b`/`border-t`
|
|
115
|
+
falls through to the ink text color. shadcn's globals.css fixes this with exactly this base rule
|
|
116
|
+
so every uncolored border/divider resolves to the active theme's hairline (--border). Without it,
|
|
117
|
+
Table dividers, Card edges, Separator, etc. render as ink. Components that name their own border
|
|
118
|
+
token (border-input, border-primary…) are unaffected, those win on specificity/cascade. */
|
|
119
|
+
@layer base {
|
|
120
|
+
*,
|
|
121
|
+
::after,
|
|
122
|
+
::before {
|
|
123
|
+
border-color: var(--border);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* The app-root contract. Every app root fills the viewport, so an app's h-full, min-h-full and
|
|
128
|
+
height:100% resolve against it. In @layer base, so a utility at a call site still wins.
|
|
129
|
+
|
|
130
|
+
The themed ground extends onto html and #root, not body alone: the overscroll area paints from
|
|
131
|
+
html, so an app on a dark ground would otherwise flash the white default there. */
|
|
132
|
+
@layer base {
|
|
133
|
+
html,
|
|
134
|
+
body,
|
|
135
|
+
#root {
|
|
136
|
+
height: 100%;
|
|
137
|
+
overscroll-behavior: none;
|
|
138
|
+
}
|
|
139
|
+
html,
|
|
140
|
+
#root {
|
|
141
|
+
background-color: var(--background);
|
|
142
|
+
}
|
|
143
|
+
body {
|
|
144
|
+
background-color: var(--background);
|
|
145
|
+
color: var(--foreground);
|
|
146
|
+
font-family: var(--font-sans);
|
|
147
|
+
-webkit-font-smoothing: antialiased;
|
|
148
|
+
}
|
|
149
|
+
}
|
package/src/switch.tsx
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Switch as SwitchPrimitive } from "radix-ui";
|
|
3
|
+
|
|
4
|
+
import { cn } from "./cn";
|
|
5
|
+
|
|
6
|
+
function Switch({
|
|
7
|
+
className,
|
|
8
|
+
size = "default",
|
|
9
|
+
...props
|
|
10
|
+
}: React.ComponentProps<typeof SwitchPrimitive.Root> & {
|
|
11
|
+
size?: "sm" | "default";
|
|
12
|
+
}) {
|
|
13
|
+
return (
|
|
14
|
+
<SwitchPrimitive.Root
|
|
15
|
+
data-slot="switch"
|
|
16
|
+
data-size={size}
|
|
17
|
+
className={cn(
|
|
18
|
+
"peer group/switch inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input dark:data-[state=unchecked]:bg-input/80",
|
|
19
|
+
className,
|
|
20
|
+
)}
|
|
21
|
+
{...props}
|
|
22
|
+
>
|
|
23
|
+
<SwitchPrimitive.Thumb
|
|
24
|
+
data-slot="switch-thumb"
|
|
25
|
+
className={cn(
|
|
26
|
+
"pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0 dark:data-[state=checked]:bg-primary-foreground dark:data-[state=unchecked]:bg-foreground",
|
|
27
|
+
)}
|
|
28
|
+
/>
|
|
29
|
+
</SwitchPrimitive.Root>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { Switch };
|
package/src/table.tsx
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "./cn";
|
|
4
|
+
|
|
5
|
+
function Table({ className, ...props }: React.ComponentProps<"table">) {
|
|
6
|
+
return (
|
|
7
|
+
<div data-slot="table-container" className="relative w-full overflow-x-auto">
|
|
8
|
+
<table
|
|
9
|
+
data-slot="table"
|
|
10
|
+
className={cn("w-full caption-bottom text-sm", className)}
|
|
11
|
+
{...props}
|
|
12
|
+
/>
|
|
13
|
+
</div>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
|
|
18
|
+
return <thead data-slot="table-header" className={cn("[&_tr]:border-b", className)} {...props} />;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
|
|
22
|
+
return (
|
|
23
|
+
<tbody
|
|
24
|
+
data-slot="table-body"
|
|
25
|
+
className={cn("[&_tr:last-child]:border-0", className)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
|
|
32
|
+
return (
|
|
33
|
+
<tfoot
|
|
34
|
+
data-slot="table-footer"
|
|
35
|
+
className={cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
|
42
|
+
return (
|
|
43
|
+
<tr
|
|
44
|
+
data-slot="table-row"
|
|
45
|
+
className={cn(
|
|
46
|
+
"border-b transition-colors hover:bg-muted/50 has-aria-expanded:bg-muted/50 data-[state=selected]:bg-muted",
|
|
47
|
+
className,
|
|
48
|
+
)}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
|
55
|
+
return (
|
|
56
|
+
<th
|
|
57
|
+
data-slot="table-head"
|
|
58
|
+
className={cn(
|
|
59
|
+
"h-10 px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
|
60
|
+
className,
|
|
61
|
+
)}
|
|
62
|
+
{...props}
|
|
63
|
+
/>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
|
68
|
+
return (
|
|
69
|
+
<td
|
|
70
|
+
data-slot="table-cell"
|
|
71
|
+
className={cn(
|
|
72
|
+
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
|
73
|
+
className,
|
|
74
|
+
)}
|
|
75
|
+
{...props}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function TableCaption({ className, ...props }: React.ComponentProps<"caption">) {
|
|
81
|
+
return (
|
|
82
|
+
<caption
|
|
83
|
+
data-slot="table-caption"
|
|
84
|
+
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
|
85
|
+
{...props}
|
|
86
|
+
/>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };
|
package/src/tabs.tsx
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import { Tabs as TabsPrimitive } from "radix-ui";
|
|
4
|
+
|
|
5
|
+
import { cn } from "./cn";
|
|
6
|
+
|
|
7
|
+
function Tabs({
|
|
8
|
+
className,
|
|
9
|
+
orientation = "horizontal",
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
|
|
12
|
+
return (
|
|
13
|
+
<TabsPrimitive.Root
|
|
14
|
+
data-slot="tabs"
|
|
15
|
+
data-orientation={orientation}
|
|
16
|
+
orientation={orientation}
|
|
17
|
+
className={cn("group/tabs flex gap-2 data-[orientation=horizontal]:flex-col", className)}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const tabsListVariants = cva(
|
|
24
|
+
"group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-[orientation=horizontal]/tabs:h-9 group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col data-[variant=line]:rounded-none",
|
|
25
|
+
{
|
|
26
|
+
variants: {
|
|
27
|
+
variant: {
|
|
28
|
+
default: "bg-muted",
|
|
29
|
+
line: "gap-1 bg-transparent",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
defaultVariants: {
|
|
33
|
+
variant: "default",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
function TabsList({
|
|
39
|
+
className,
|
|
40
|
+
variant = "default",
|
|
41
|
+
...props
|
|
42
|
+
}: React.ComponentProps<typeof TabsPrimitive.List> & VariantProps<typeof tabsListVariants>) {
|
|
43
|
+
return (
|
|
44
|
+
<TabsPrimitive.List
|
|
45
|
+
data-slot="tabs-list"
|
|
46
|
+
data-variant={variant}
|
|
47
|
+
className={cn(tabsListVariants({ variant }), className)}
|
|
48
|
+
{...props}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
|
|
54
|
+
return (
|
|
55
|
+
<TabsPrimitive.Trigger
|
|
56
|
+
data-slot="tabs-trigger"
|
|
57
|
+
className={cn(
|
|
58
|
+
"relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=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 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none dark:text-muted-foreground dark:hover:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
59
|
+
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent",
|
|
60
|
+
"data-[state=active]:bg-background data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 dark:data-[state=active]:text-foreground",
|
|
61
|
+
"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",
|
|
62
|
+
className,
|
|
63
|
+
)}
|
|
64
|
+
{...props}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function TabsContent({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Content>) {
|
|
70
|
+
return (
|
|
71
|
+
<TabsPrimitive.Content
|
|
72
|
+
data-slot="tabs-content"
|
|
73
|
+
className={cn("flex-1 outline-none", className)}
|
|
74
|
+
{...props}
|
|
75
|
+
/>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants };
|
package/src/textarea.tsx
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "./cn";
|
|
4
|
+
|
|
5
|
+
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|
6
|
+
return (
|
|
7
|
+
<textarea
|
|
8
|
+
data-slot="textarea"
|
|
9
|
+
className={cn(
|
|
10
|
+
"flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:ring-destructive/40",
|
|
11
|
+
className,
|
|
12
|
+
)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { Textarea };
|