shapes-ui 0.1.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.
Files changed (117) hide show
  1. package/.changeset/README.md +8 -0
  2. package/.changeset/config.json +11 -0
  3. package/.cta.json +12 -0
  4. package/.github/workflows/release.yml +44 -0
  5. package/.oxfmtrc.json +21 -0
  6. package/.oxlintrc.json +40 -0
  7. package/.vscode/settings.json +11 -0
  8. package/CHANGELOG.md +22 -0
  9. package/LICENSE +21 -0
  10. package/README.md +1 -0
  11. package/content/components/accordion.mdx +41 -0
  12. package/content/components/alert-dialog.mdx +25 -0
  13. package/content/components/alert.mdx +53 -0
  14. package/content/components/badge.mdx +27 -0
  15. package/content/components/button.mdx +55 -0
  16. package/content-collections.ts +67 -0
  17. package/dist/cli.d.ts +1 -0
  18. package/dist/cli.js +15255 -0
  19. package/dist/client/.assetsignore +2 -0
  20. package/dist/server/.vite/manifest.json +2435 -0
  21. package/examples/__index.tsx +201 -0
  22. package/examples/accordion-demo.tsx +30 -0
  23. package/examples/accordion-icon.tsx +40 -0
  24. package/examples/accordion-multiple.tsx +27 -0
  25. package/examples/accordion-small.tsx +30 -0
  26. package/examples/accordion-surface.tsx +30 -0
  27. package/examples/alert-action.tsx +18 -0
  28. package/examples/alert-demo.tsx +10 -0
  29. package/examples/alert-description.tsx +16 -0
  30. package/examples/alert-destructive.tsx +13 -0
  31. package/examples/alert-dialog-demo.tsx +33 -0
  32. package/examples/alert-dialog-destructive.tsx +33 -0
  33. package/examples/alert-dialog-icon.tsx +38 -0
  34. package/examples/alert-info.tsx +13 -0
  35. package/examples/alert-link.tsx +21 -0
  36. package/examples/alert-success.tsx +13 -0
  37. package/examples/alert-title.tsx +12 -0
  38. package/examples/alert-warning.tsx +13 -0
  39. package/examples/badge-demo.tsx +5 -0
  40. package/examples/badge-status-icon.tsx +26 -0
  41. package/examples/badge-status.tsx +12 -0
  42. package/examples/badge-variants.tsx +12 -0
  43. package/examples/button-default.tsx +15 -0
  44. package/examples/button-demo.tsx +14 -0
  45. package/examples/button-destructive.tsx +15 -0
  46. package/examples/button-ghost.tsx +15 -0
  47. package/examples/button-info.tsx +15 -0
  48. package/examples/button-link.tsx +15 -0
  49. package/examples/button-loading.tsx +14 -0
  50. package/examples/button-outline.tsx +15 -0
  51. package/examples/button-sizes.tsx +34 -0
  52. package/examples/button-success.tsx +15 -0
  53. package/examples/button-warning.tsx +15 -0
  54. package/package.json +81 -0
  55. package/public/apple-touch-icon.png +0 -0
  56. package/public/circuit-board.svg +1 -0
  57. package/public/favicon-16x16.png +0 -0
  58. package/public/favicon-32x32.png +0 -0
  59. package/public/favicon.ico +0 -0
  60. package/public/favicon.svg +3 -0
  61. package/public/logo192.png +0 -0
  62. package/public/logo512.png +0 -0
  63. package/public/manifest.json +25 -0
  64. package/public/r/accordion.json +16 -0
  65. package/public/r/alert-dialog.json +17 -0
  66. package/public/r/alert.json +15 -0
  67. package/public/r/badge.json +15 -0
  68. package/public/r/button.json +16 -0
  69. package/public/r/index.json +7 -0
  70. package/public/robots.txt +3 -0
  71. package/public/shps_black.png +0 -0
  72. package/public/shps_black.svg +3 -0
  73. package/public/shps_white.png +0 -0
  74. package/public/shps_white.svg +3 -0
  75. package/scripts/generate-examples.mts +118 -0
  76. package/scripts/generate-registry.mts +129 -0
  77. package/src/commands/add.ts +125 -0
  78. package/src/commands/cli.ts +27 -0
  79. package/src/commands/init.ts +128 -0
  80. package/src/components/docs/docs-button.tsx +60 -0
  81. package/src/components/docs/layout/circuit-board.tsx +22 -0
  82. package/src/components/docs/layout/footer.tsx +11 -0
  83. package/src/components/docs/layout/header.tsx +49 -0
  84. package/src/components/docs/layout/mobile-menu.tsx +86 -0
  85. package/src/components/docs/layout/nav-list.tsx +43 -0
  86. package/src/components/docs/layout/page-header.tsx +33 -0
  87. package/src/components/docs/layout/split-layout.tsx +21 -0
  88. package/src/components/docs/layout/suspense-fallback.tsx +12 -0
  89. package/src/components/docs/markdown/components.tsx +324 -0
  90. package/src/components/docs/markdown/installation-block.tsx +146 -0
  91. package/src/components/docs/markdown/render-preview.tsx +152 -0
  92. package/src/components/docs/theme-provider.tsx +48 -0
  93. package/src/components/ui/accordion.tsx +83 -0
  94. package/src/components/ui/alert-dialog.tsx +162 -0
  95. package/src/components/ui/alert.tsx +72 -0
  96. package/src/components/ui/badge.tsx +34 -0
  97. package/src/components/ui/button.tsx +64 -0
  98. package/src/lib/utils.ts +6 -0
  99. package/src/routeTree.gen.ts +131 -0
  100. package/src/router.tsx +17 -0
  101. package/src/routes/__root.tsx +72 -0
  102. package/src/routes/components.$slug.tsx +62 -0
  103. package/src/routes/components.index.tsx +55 -0
  104. package/src/routes/components.tsx +15 -0
  105. package/src/routes/index.tsx +16 -0
  106. package/src/styles/globals.css +66 -0
  107. package/src/styles/styles.css +113 -0
  108. package/src/types/registry-item.ts +13 -0
  109. package/src/utils/cli-utils.ts +46 -0
  110. package/src/utils/package-manager.ts +27 -0
  111. package/src/utils/schema.ts +25 -0
  112. package/tests/generate-registry.test.ts +60 -0
  113. package/tsconfig.json +29 -0
  114. package/tsup.config.ts +11 -0
  115. package/vite.config.ts +31 -0
  116. package/vitest.config.ts +8 -0
  117. package/wrangler.jsonc +7 -0
@@ -0,0 +1,152 @@
1
+ import { Tabs } from "@base-ui/react/tabs";
2
+ import { examples } from "@examples/__index";
3
+ import { Suspense, useEffect, useState } from "react";
4
+
5
+ import { SuspenseFallback } from "../layout/suspense-fallback";
6
+
7
+ type RegistryEntry = (typeof examples)[keyof typeof examples];
8
+ type ExampleEntry = RegistryEntry["examples"][number];
9
+
10
+ function findExample(name: string): ExampleEntry | null {
11
+ const componentEntry = examples[name];
12
+ if (componentEntry?.examples?.length) {
13
+ return componentEntry.examples[0];
14
+ }
15
+
16
+ for (const entry of Object.values(examples)) {
17
+ const match = entry.examples.find((example) => example.name === name);
18
+ if (match) {
19
+ return match;
20
+ }
21
+ }
22
+
23
+ return null;
24
+ }
25
+
26
+ export function RenderPreview({ name }: { name: string }) {
27
+ const [code, setCode] = useState<string | null>(null);
28
+ const [highlighted, setHighlighted] = useState<string | null>(null);
29
+ const example = findExample(name);
30
+
31
+ useEffect(() => {
32
+ if (!example) return;
33
+ let isActive = true;
34
+
35
+ const rawExamples = import.meta.glob("/examples/**/*.tsx", {
36
+ query: "?raw",
37
+ import: "default",
38
+ });
39
+
40
+ const matchingKey = Object.keys(rawExamples).find((key) => key.includes(example.name));
41
+
42
+ if (matchingKey) {
43
+ rawExamples[matchingKey]().then(async (mod: any) => {
44
+ const source = mod.default || mod;
45
+ if (!isActive) return;
46
+
47
+ setCode(source);
48
+
49
+ try {
50
+ const { codeToHtml } = await import("shiki");
51
+
52
+ const html = await codeToHtml(source, {
53
+ lang: "tsx",
54
+ themes: {
55
+ light: "github-light",
56
+ dark: "github-dark",
57
+ },
58
+ defaultColor: false,
59
+ transformers: [
60
+ {
61
+ name: "line-numbers",
62
+ pre(node: any) {
63
+ node.properties["data-line-numbers"] = "";
64
+ },
65
+ line(node: any, line: number) {
66
+ node.properties.className = ["line"];
67
+ node.children.unshift({
68
+ type: "element",
69
+ tagName: "span",
70
+ properties: {
71
+ className: ["line-number"],
72
+ },
73
+ children: [{ type: "text", value: String(line) }],
74
+ });
75
+ },
76
+ },
77
+ ],
78
+ });
79
+
80
+ if (isActive) {
81
+ setHighlighted(html);
82
+ }
83
+ } catch {
84
+ if (isActive) {
85
+ setHighlighted(null);
86
+ }
87
+ }
88
+ });
89
+ }
90
+
91
+ return () => {
92
+ isActive = false;
93
+ };
94
+ }, [example]);
95
+
96
+ if (!example) {
97
+ return (
98
+ <div className="rounded border border-dashed p-4 text-sm text-muted-foreground">
99
+ Example not found.
100
+ </div>
101
+ );
102
+ }
103
+
104
+ const ExampleComponent = example.code;
105
+
106
+ return (
107
+ <Tabs.Root className={"my-2 flex flex-col gap-2"}>
108
+ <Tabs.List className={"flex gap-2"}>
109
+ <Tabs.Tab
110
+ value="preview"
111
+ className={
112
+ "px-2 py-1 text-xs text-foreground/80 data-active:bg-accent data-active:font-medium data-active:text-accent-foreground"
113
+ }
114
+ >
115
+ Preview
116
+ </Tabs.Tab>
117
+ <Tabs.Tab
118
+ value="code"
119
+ className={
120
+ "px-2 py-1 text-xs text-foreground/80 data-active:bg-accent data-active:font-medium data-active:text-accent-foreground"
121
+ }
122
+ >
123
+ Code
124
+ </Tabs.Tab>
125
+ </Tabs.List>
126
+
127
+ <Tabs.Panel
128
+ value={"preview"}
129
+ className={"flex h-90 items-center justify-center overflow-x-auto border p-4 "}
130
+ >
131
+ <Suspense fallback={<SuspenseFallback />}>
132
+ <ExampleComponent />
133
+ </Suspense>
134
+ </Tabs.Panel>
135
+
136
+ <Tabs.Panel
137
+ value={"code"}
138
+ className={
139
+ "flex h-90 items-start justify-start overflow-y-auto border bg-muted/40 p-4 font-mono text-xs "
140
+ }
141
+ >
142
+ {highlighted ? (
143
+ <div className="shiki-wrapper" dangerouslySetInnerHTML={{ __html: highlighted }} />
144
+ ) : (
145
+ <pre className="overflow-x-auto">
146
+ <code>{code ?? "// Loading code..."}</code>
147
+ </pre>
148
+ )}
149
+ </Tabs.Panel>
150
+ </Tabs.Root>
151
+ );
152
+ }
@@ -0,0 +1,48 @@
1
+ import React, { createContext, useContext, useEffect, useState } from "react";
2
+
3
+ type Theme = "light" | "dark";
4
+
5
+ interface ThemeContextType {
6
+ theme: Theme;
7
+ toggleTheme: () => void;
8
+ }
9
+
10
+ const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
11
+
12
+ export function ThemeProvider({ children }: { children: React.ReactNode }) {
13
+ const [theme, setTheme] = useState<Theme>("light");
14
+
15
+ useEffect(() => {
16
+ const savedTheme = localStorage.getItem("theme") as Theme;
17
+ if (savedTheme) {
18
+ setTheme(savedTheme);
19
+ } else {
20
+ const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
21
+ setTheme(prefersDark ? "dark" : "light");
22
+ }
23
+ }, []);
24
+
25
+ useEffect(() => {
26
+ const root = document.documentElement;
27
+ if (theme === "dark") {
28
+ root.classList.add("dark");
29
+ } else {
30
+ root.classList.remove("dark");
31
+ }
32
+ localStorage.setItem("theme", theme);
33
+ }, [theme]);
34
+
35
+ const toggleTheme = () => {
36
+ setTheme((prev) => (prev === "light" ? "dark" : "light"));
37
+ };
38
+
39
+ return <ThemeContext.Provider value={{ theme, toggleTheme }}>{children}</ThemeContext.Provider>;
40
+ }
41
+
42
+ export function useTheme() {
43
+ const context = useContext(ThemeContext);
44
+ if (context === undefined) {
45
+ throw new Error("useTheme must be used within a ThemeProvider");
46
+ }
47
+ return context;
48
+ }
@@ -0,0 +1,83 @@
1
+ "use client";
2
+
3
+ import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion";
4
+ import { MinusIcon, PlusIcon } from "lucide-react";
5
+
6
+ import { cn } from "@/lib/utils";
7
+
8
+ function Accordion({
9
+ size,
10
+ variant,
11
+ className,
12
+ ...props
13
+ }: AccordionPrimitive.Root.Props & {
14
+ size?: "default" | "small";
15
+ variant?: "default" | "surface";
16
+ }) {
17
+ return (
18
+ <AccordionPrimitive.Root
19
+ data-slot="accordion-root"
20
+ data-variant={variant}
21
+ data-size={size}
22
+ className={cn(
23
+ "group/accordion-root flex w-full flex-col",
24
+ "data-[variant=surface]:space-y-1",
25
+ className,
26
+ )}
27
+ {...props}
28
+ />
29
+ );
30
+ }
31
+
32
+ function AccordionItem({ className, ...props }: AccordionPrimitive.Item.Props) {
33
+ return (
34
+ <AccordionPrimitive.Item
35
+ data-slot="accordion-item"
36
+ className={cn(
37
+ "group/accordion-item not-last:border-b group-data-[variant=surface]/accordion-root:bg-accent group-data-[variant=surface]/accordion-root:px-4",
38
+ className,
39
+ )}
40
+ {...props}
41
+ />
42
+ );
43
+ }
44
+
45
+ function AccordionTrigger({ className, children, ...props }: AccordionPrimitive.Trigger.Props) {
46
+ return (
47
+ <AccordionPrimitive.Header className={"flex"}>
48
+ <AccordionPrimitive.Trigger
49
+ data-slot="accordion-trigger"
50
+ className={cn(
51
+ "&_svg]:shrink-0 group/accordion-trigger flex flex-1 items-center gap-2 rounded-lg py-2.5 text-sm group-data-[size=small]/accordion-root:py-1.5 hover:underline focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:after:border-ring disabled:pointer-events-none disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:text-muted-foreground [&_svg:not([class*='size-'])]:size-4 ",
52
+ className,
53
+ )}
54
+ {...props}
55
+ >
56
+ {children}
57
+ <PlusIcon
58
+ data-slot="accordion-trigger-icon"
59
+ className=" group-data-panel-open/accordion-trigger:hidden"
60
+ />
61
+ <MinusIcon
62
+ data-slot="accordion-trigger-icon"
63
+ className="hidden group-data-panel-open/accordion-trigger:inline-flex"
64
+ />
65
+ </AccordionPrimitive.Trigger>
66
+ </AccordionPrimitive.Header>
67
+ );
68
+ }
69
+
70
+ function AccordionPanel({ className, ...props }: AccordionPrimitive.Panel.Props) {
71
+ return (
72
+ <AccordionPrimitive.Panel
73
+ data-slot="accordion-panel"
74
+ className={cn(
75
+ "h-[--accordion-panel-height] overflow-hidden text-sm transition-all duration-150 ease-out data-ending-style:h-0 group-data-open/accordion-item:data-open:pb-2 data-starting-style:h-0 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4",
76
+ className,
77
+ )}
78
+ {...props}
79
+ />
80
+ );
81
+ }
82
+
83
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionPanel };
@@ -0,0 +1,162 @@
1
+ "use client";
2
+
3
+ import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog";
4
+ import * as React from "react";
5
+
6
+ import { Button } from "@/components/ui/button";
7
+ import { cn } from "@/lib/utils";
8
+
9
+ function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) {
10
+ return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
11
+ }
12
+
13
+ function AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) {
14
+ return <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />;
15
+ }
16
+
17
+ function AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) {
18
+ return <AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />;
19
+ }
20
+
21
+ function AlertDialogOverlay({ className, ...props }: AlertDialogPrimitive.Backdrop.Props) {
22
+ return (
23
+ <AlertDialogPrimitive.Backdrop
24
+ data-slot="alert-dialog-overlay"
25
+ className={cn(
26
+ "fixed inset-0 isolate z-50 bg-black/10 duration-100 data-closed:animate-out data-closed:fade-out-0 data-open:animate-in data-open:fade-in-0 supports-backdrop-filter:backdrop-blur-xs",
27
+ className,
28
+ )}
29
+ {...props}
30
+ />
31
+ );
32
+ }
33
+
34
+ function AlertDialogPopup({
35
+ className,
36
+ size = "default",
37
+ ...props
38
+ }: AlertDialogPrimitive.Popup.Props & {
39
+ size?: "default" | "sm";
40
+ }) {
41
+ return (
42
+ <AlertDialogPortal>
43
+ <AlertDialogOverlay />
44
+ <AlertDialogPrimitive.Popup
45
+ data-slot="alert-dialog-content"
46
+ data-size={size}
47
+ className={cn(
48
+ "group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-background p-4 ring-1 ring-foreground/10 duration-100 outline-none data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm",
49
+ className,
50
+ )}
51
+ {...props}
52
+ />
53
+ </AlertDialogPortal>
54
+ );
55
+ }
56
+
57
+ function AlertDialogHeader({ className, ...props }: React.ComponentProps<"div">) {
58
+ return (
59
+ <div
60
+ data-slot="alert-dialog-header"
61
+ className={cn(
62
+ "grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
63
+ className,
64
+ )}
65
+ {...props}
66
+ />
67
+ );
68
+ }
69
+
70
+ function AlertDialogFooter({ className, ...props }: React.ComponentProps<"div">) {
71
+ return (
72
+ <div
73
+ data-slot="alert-dialog-footer"
74
+ className={cn(
75
+ "-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t p-4 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
76
+ className,
77
+ )}
78
+ {...props}
79
+ />
80
+ );
81
+ }
82
+
83
+ function AlertDialogMedia({ className, ...props }: React.ComponentProps<"div">) {
84
+ return (
85
+ <div
86
+ data-slot="alert-dialog-media"
87
+ className={cn(
88
+ "mb-2 inline-flex size-10 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6",
89
+ className,
90
+ )}
91
+ {...props}
92
+ />
93
+ );
94
+ }
95
+
96
+ function AlertDialogTitle({
97
+ className,
98
+ ...props
99
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
100
+ return (
101
+ <AlertDialogPrimitive.Title
102
+ data-slot="alert-dialog-title"
103
+ className={cn(
104
+ " text-base font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
105
+ className,
106
+ )}
107
+ {...props}
108
+ />
109
+ );
110
+ }
111
+
112
+ function AlertDialogDescription({
113
+ className,
114
+ ...props
115
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
116
+ return (
117
+ <AlertDialogPrimitive.Description
118
+ data-slot="alert-dialog-description"
119
+ className={cn(
120
+ "text-sm text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
121
+ className,
122
+ )}
123
+ {...props}
124
+ />
125
+ );
126
+ }
127
+
128
+ function AlertDialogAction({ className, ...props }: React.ComponentProps<typeof Button>) {
129
+ return <Button data-slot="alert-dialog-action" className={cn(className)} {...props} />;
130
+ }
131
+
132
+ function AlertDialogCancel({
133
+ className,
134
+ variant = "outline",
135
+ size = "default",
136
+ ...props
137
+ }: AlertDialogPrimitive.Close.Props &
138
+ Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
139
+ return (
140
+ <AlertDialogPrimitive.Close
141
+ data-slot="alert-dialog-cancel"
142
+ className={cn(className)}
143
+ render={<Button variant={variant} size={size} />}
144
+ {...props}
145
+ />
146
+ );
147
+ }
148
+
149
+ export {
150
+ AlertDialog,
151
+ AlertDialogAction,
152
+ AlertDialogCancel,
153
+ AlertDialogPopup,
154
+ AlertDialogDescription,
155
+ AlertDialogFooter,
156
+ AlertDialogHeader,
157
+ AlertDialogMedia,
158
+ AlertDialogOverlay,
159
+ AlertDialogPortal,
160
+ AlertDialogTitle,
161
+ AlertDialogTrigger,
162
+ };
@@ -0,0 +1,72 @@
1
+ import { cva, VariantProps } from "class-variance-authority";
2
+ import { ComponentProps } from "react";
3
+
4
+ import { cn } from "@/lib/utils";
5
+
6
+ const alertVariants = cva(
7
+ "relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg px-4 py-3 text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "border bg-card text-card-foreground",
12
+ destructive: "bg-destructive/10 text-destructive [&_>svg]:text-destructive-foreground",
13
+ success: "bg-success/10 text-success [&_>svg]:text-success-foreground",
14
+ warning: "bg-warning/10 text-warning [&_>svg]:text-warning-foreground",
15
+ info: "bg-info/10 text-info [&_>svg]:text-info-foreground",
16
+ },
17
+ },
18
+ defaultVariants: {
19
+ variant: "default",
20
+ },
21
+ },
22
+ );
23
+
24
+ function Alert({
25
+ className,
26
+ variant,
27
+ ...props
28
+ }: ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
29
+ return (
30
+ <div
31
+ data-slot="alert-root"
32
+ role="alert"
33
+ className={alertVariants({ variant, className })}
34
+ {...props}
35
+ />
36
+ );
37
+ }
38
+
39
+ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
40
+ return (
41
+ <div
42
+ data-slot="alert-title"
43
+ className={cn("col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight", className)}
44
+ {...props}
45
+ />
46
+ );
47
+ }
48
+
49
+ function AlertDescription({ className, ...props }: React.ComponentProps<"div">) {
50
+ return (
51
+ <div
52
+ data-slot="alert-description"
53
+ className={cn(
54
+ "col-start-2 grid justify-items-start gap-1 text-sm text-muted-foreground [&_p]:leading-relaxed",
55
+ className,
56
+ )}
57
+ {...props}
58
+ />
59
+ );
60
+ }
61
+
62
+ function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
63
+ return (
64
+ <div
65
+ data-slot="alert-action"
66
+ className={cn("col-start-2 flex items-center justify-end gap-2", className)}
67
+ {...props}
68
+ />
69
+ );
70
+ }
71
+
72
+ export { Alert, AlertTitle, AlertDescription, AlertAction };
@@ -0,0 +1,34 @@
1
+ import { cva, VariantProps } from "class-variance-authority";
2
+ import { ComponentProps } from "react";
3
+
4
+ const badgeVariants = cva(
5
+ "group/badge inline-flex h-5 min-h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-lg border border-transparent px-2.5 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=end]:pr-1.5 has-data-[icon=start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
6
+ {
7
+ variants: {
8
+ variant: {
9
+ default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
10
+ secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
11
+ ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
12
+ outline:
13
+ "border-border! text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
14
+ success:
15
+ "bg-success/20 text-success-foreground hover:bg-success/25 [a]:hover:bg-success/80",
16
+ warning:
17
+ "bg-warning/20 text-warning-foreground hover:bg-warning/25 [a]:hover:bg-warning/80",
18
+ info: "bg-info/20 text-info-foreground hover:bg-info/25 [a]:hover:bg-info/80",
19
+ destructive:
20
+ "bg-destructive/20 text-destructive-foreground hover:bg-destructive/25 [a]:hover:bg-destructive/80",
21
+ },
22
+ },
23
+ },
24
+ );
25
+
26
+ function Badge({
27
+ className,
28
+ variant = "default",
29
+ ...props
30
+ }: ComponentProps<"span"> & VariantProps<typeof badgeVariants>) {
31
+ return <span data-slot="badge" className={badgeVariants({ variant, className })} {...props} />;
32
+ }
33
+
34
+ export { Badge, badgeVariants };
@@ -0,0 +1,64 @@
1
+ "use client";
2
+
3
+ import { Button as ButtonPrimitive } from "@base-ui/react/button";
4
+ import { cva, type VariantProps } from "class-variance-authority";
5
+
6
+ import { cn } from "@/lib/utils";
7
+
8
+ const buttonVariants = cva(
9
+ "group/button inline-flex h-8 shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
10
+ {
11
+ variants: {
12
+ variant: {
13
+ default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
14
+ secondary:
15
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
16
+ outline:
17
+ "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
18
+ ghost:
19
+ "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
20
+ link: "text-primary underline-offset-4 hover:underline",
21
+ success:
22
+ "bg-success/10 text-success hover:bg-success/20 focus-visible:border-success/40 focus-visible:ring-success/20 dark:bg-success/20 dark:hover:bg-success/30 dark:focus-visible:ring-success/40",
23
+ info: "bg-info/10 text-info hover:bg-info/20 focus-visible:border-info/40 focus-visible:ring-info/20 dark:bg-info/20 dark:hover:bg-info/30 dark:focus-visible:ring-info/40",
24
+ warning:
25
+ "bg-warning/10 text-warning hover:bg-warning/20 focus-visible:border-warning/40 focus-visible:ring-warning/20 dark:bg-warning/20 dark:hover:bg-warning/30 dark:focus-visible:ring-warning/40",
26
+ destructive:
27
+ "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
28
+ },
29
+ size: {
30
+ default: "gap-1.5 px-3 has-data-[icon=end]:pr-2 has-data-[icon=start]:pl-2",
31
+ xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=end]:pr-1.5 has-data-[icon=start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
32
+ sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=end]:pr-1.5 has-data-[icon=start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
33
+ lg: "h-9 gap-1.5 px-2.5 has-data-[icon=end]:pr-3 has-data-[icon=start]:pl-3",
34
+ icon: "size-8",
35
+ "icon-xs":
36
+ "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
37
+ "icon-sm":
38
+ "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
39
+ "icon-lg": "size-9",
40
+ },
41
+ },
42
+ defaultVariants: {
43
+ variant: "default",
44
+ size: "default",
45
+ },
46
+ },
47
+ );
48
+
49
+ function Button({
50
+ className,
51
+ variant,
52
+ size,
53
+ ...props
54
+ }: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {
55
+ return (
56
+ <ButtonPrimitive
57
+ data-slot="button"
58
+ className={cn(buttonVariants({ variant, size, className }))}
59
+ {...props}
60
+ />
61
+ );
62
+ }
63
+
64
+ export { Button, buttonVariants };
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }