shapes-ui 0.4.2 → 0.6.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.
Files changed (180) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.yml +47 -0
  2. package/.github/ISSUE_TEMPLATE/config.yml +1 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.yml +31 -0
  4. package/.github/pull_request_template.md +14 -0
  5. package/.github/workflows/pr-preview.yml +75 -0
  6. package/.github/workflows/release.yml +8 -0
  7. package/CHANGELOG.md +30 -0
  8. package/CODE_OF_CONDUCT.md +41 -0
  9. package/CONTRIBUTING.md +52 -0
  10. package/README.md +18 -0
  11. package/SECURITY.md +0 -0
  12. package/content/components/accordion.mdx +13 -0
  13. package/content/components/alert-dialog.mdx +34 -0
  14. package/content/components/autocomplete.mdx +62 -0
  15. package/content/components/avatar.mdx +11 -0
  16. package/content/components/button.mdx +8 -0
  17. package/content/components/checkbox.mdx +11 -0
  18. package/content/components/collapsible.mdx +11 -0
  19. package/content/components/combobox.mdx +33 -0
  20. package/content/components/context-menu.mdx +29 -0
  21. package/content/components/dialog.mdx +33 -0
  22. package/content/components/drawer.mdx +38 -0
  23. package/content/components/field.mdx +23 -2
  24. package/content/components/fieldset.mdx +11 -1
  25. package/content/components/form.mdx +8 -0
  26. package/content/components/input.mdx +4 -0
  27. package/content/components/menu.mdx +27 -0
  28. package/content/components/menubar.mdx +31 -0
  29. package/content/components/meter.mdx +14 -0
  30. package/content/components/navigation-menu.mdx +28 -0
  31. package/content/components/number-field.mdx +25 -0
  32. package/content/components/popover.mdx +22 -0
  33. package/content/components/preview-card.mdx +14 -2
  34. package/content/components/progress.mdx +15 -1
  35. package/content/components/radio.mdx +31 -0
  36. package/content/components/scroll-area.mdx +23 -0
  37. package/content/components/select.mdx +57 -0
  38. package/content/components/separator.mdx +29 -0
  39. package/content/components/slider.mdx +52 -0
  40. package/content/components/switch.mdx +30 -0
  41. package/content/components/tabs.mdx +47 -0
  42. package/content/components/toast.mdx +70 -0
  43. package/content/components/toggle-group.mdx +37 -0
  44. package/content/components/toggle.mdx +46 -2
  45. package/content/components/toolbar.mdx +48 -0
  46. package/content/components/tooltip.mdx +38 -0
  47. package/content/docs/installation.mdx +30 -0
  48. package/content-collections.ts +65 -1
  49. package/dist/cli.js +947 -101
  50. package/examples/__index.tsx +320 -66
  51. package/examples/autocomplete-align.tsx +39 -0
  52. package/examples/autocomplete-controlled.tsx +44 -0
  53. package/examples/autocomplete-groups.tsx +65 -0
  54. package/examples/autocomplete-no-clear.tsx +40 -0
  55. package/examples/avatar-demo.tsx +3 -3
  56. package/examples/checkbox-demo.tsx +1 -1
  57. package/examples/checkbox-form.tsx +3 -3
  58. package/examples/field-custom-control.tsx +33 -9
  59. package/examples/form-demo.tsx +5 -10
  60. package/examples/input-group-with-button.tsx +1 -1
  61. package/examples/menu-advanced.tsx +1 -3
  62. package/examples/menu-align.tsx +19 -16
  63. package/examples/menu-checkbox.tsx +2 -3
  64. package/examples/menu-demo.tsx +1 -3
  65. package/examples/menu-group.tsx +1 -3
  66. package/examples/menu-radio.tsx +1 -3
  67. package/examples/menu-submenu.tsx +2 -3
  68. package/examples/meter-demo.tsx +10 -2
  69. package/examples/meter-flip.tsx +8 -8
  70. package/examples/meter-no-label.tsx +9 -2
  71. package/examples/meter-no-value.tsx +7 -8
  72. package/examples/radio-card.tsx +28 -0
  73. package/examples/radio-demo.tsx +19 -1
  74. package/examples/radio-description.tsx +26 -0
  75. package/examples/radio-orientation.tsx +21 -0
  76. package/examples/select-alignment.tsx +51 -0
  77. package/examples/select-demo.tsx +36 -1
  78. package/examples/select-disabled.tsx +38 -0
  79. package/examples/select-groups.tsx +54 -0
  80. package/examples/select-invalid.tsx +41 -0
  81. package/examples/select-scrollable.tsx +112 -0
  82. package/examples/separator-demo.tsx +13 -0
  83. package/examples/separator-horizontal.tsx +18 -0
  84. package/examples/slider-controlled.tsx +28 -0
  85. package/examples/slider-demo.tsx +3 -1
  86. package/examples/slider-disabled.tsx +7 -0
  87. package/examples/slider-edge.tsx +13 -0
  88. package/examples/slider-multiple.tsx +7 -0
  89. package/examples/slider-range.tsx +5 -0
  90. package/examples/slider-vertical.tsx +10 -0
  91. package/examples/switch-demo.tsx +19 -1
  92. package/examples/switch-disabled.tsx +20 -0
  93. package/examples/switch-sizes.tsx +24 -0
  94. package/examples/switch-with-label.tsx +16 -0
  95. package/examples/tabs-demo.tsx +14 -1
  96. package/examples/tabs-disabled.tsx +21 -0
  97. package/examples/tabs-line.tsx +18 -0
  98. package/examples/tabs-vertical.tsx +13 -0
  99. package/examples/toast-action.tsx +39 -0
  100. package/examples/toast-anchored.tsx +36 -0
  101. package/examples/toast-demo.tsx +27 -1
  102. package/examples/toast-positions.tsx +54 -0
  103. package/examples/toast-promise.tsx +51 -0
  104. package/examples/toast-stacked.tsx +30 -0
  105. package/examples/toast-timeout.tsx +43 -0
  106. package/examples/toast-update.tsx +38 -0
  107. package/examples/toast-variants.tsx +54 -0
  108. package/examples/toggle-controlled.tsx +20 -0
  109. package/examples/toggle-demo.tsx +7 -51
  110. package/examples/toggle-group-demo.tsx +19 -0
  111. package/examples/toggle-group-multiple.tsx +19 -0
  112. package/examples/toggle-icon-fill.tsx +12 -0
  113. package/examples/toolbar-demo.tsx +45 -21
  114. package/examples/toolbar-input-link.tsx +35 -0
  115. package/examples/toolbar-menu.tsx +53 -0
  116. package/examples/tooltip-demo.tsx +48 -0
  117. package/examples/tooltip-positions.tsx +60 -0
  118. package/package.json +19 -18
  119. package/public/base-ui.svg +1 -0
  120. package/public/r/drawer.json +1 -1
  121. package/public/r/field.json +1 -1
  122. package/public/r/meter.json +1 -1
  123. package/public/r/number-field.json +1 -1
  124. package/public/r/progress.json +1 -1
  125. package/public/r/radio.json +1 -1
  126. package/public/r/select.json +1 -1
  127. package/public/r/slider.json +1 -1
  128. package/public/r/switch.json +1 -1
  129. package/public/r/tabs.json +1 -1
  130. package/public/r/toast.json +2 -1
  131. package/public/r/toggle.json +1 -1
  132. package/public/r/toolbar.json +1 -1
  133. package/public/r/tooltip.json +15 -0
  134. package/src/assets/base-ui.svg +1 -0
  135. package/src/commands/add.ts +79 -38
  136. package/src/commands/cli.ts +50 -3
  137. package/src/commands/create.ts +262 -0
  138. package/src/commands/init.ts +45 -12
  139. package/src/commands/palette.ts +55 -0
  140. package/src/components/docs/layout/footer.tsx +2 -2
  141. package/src/components/docs/layout/header.tsx +7 -19
  142. package/src/components/docs/layout/mobile-menu.tsx +26 -78
  143. package/src/components/docs/layout/nav-list.tsx +27 -21
  144. package/src/components/docs/layout/page-header.tsx +52 -7
  145. package/src/components/docs/layout/split-layout.tsx +11 -9
  146. package/src/components/docs/layout/table-of-content.tsx +145 -0
  147. package/src/components/docs/markdown/components.tsx +142 -29
  148. package/src/components/docs/markdown/copy-button.tsx +41 -0
  149. package/src/components/docs/markdown/installation-block.tsx +5 -24
  150. package/src/components/docs/markdown/render-preview.tsx +1 -1
  151. package/src/components/ui/badge.tsx +1 -1
  152. package/src/components/ui/button-group.tsx +1 -1
  153. package/src/components/ui/checkbox.tsx +1 -1
  154. package/src/components/ui/drawer.tsx +1 -1
  155. package/src/components/ui/field.tsx +9 -28
  156. package/src/components/ui/form.tsx +1 -1
  157. package/src/components/ui/meter.tsx +12 -26
  158. package/src/components/ui/number-field.tsx +1 -1
  159. package/src/components/ui/radio.tsx +32 -19
  160. package/src/components/ui/scroll-area.tsx +11 -2
  161. package/src/components/ui/select.tsx +6 -6
  162. package/src/components/ui/slider.tsx +8 -5
  163. package/src/components/ui/switch.tsx +13 -17
  164. package/src/components/ui/tabs.tsx +23 -10
  165. package/src/components/ui/toast.tsx +190 -29
  166. package/src/components/ui/toggle.tsx +1 -1
  167. package/src/components/ui/toolbar.tsx +17 -4
  168. package/src/components/ui/tooltip.tsx +54 -0
  169. package/src/lib/docs-headings.ts +72 -0
  170. package/src/routeTree.gen.ts +60 -3
  171. package/src/routes/__root.tsx +3 -5
  172. package/src/routes/components.$slug.tsx +20 -4
  173. package/src/routes/docs.$slug.tsx +78 -0
  174. package/src/routes/docs.tsx +15 -0
  175. package/src/styles/styles.css +1 -1
  176. package/src/utils/cli-utils.ts +8 -8
  177. package/src/utils/dependency-installer.ts +30 -0
  178. package/src/utils/package-manager.ts +4 -4
  179. package/src/utils/palette.ts +666 -0
  180. package/src/utils/schema.ts +6 -0
@@ -1,8 +1,10 @@
1
1
  import React from "react";
2
2
  import type { ComponentProps } from "react";
3
3
 
4
+ import { slugifyHeading } from "@/lib/docs-headings";
4
5
  import { cn } from "@/lib/utils";
5
6
 
7
+ import { CopyButton } from "./copy-button";
6
8
  import { InstallationBlock } from "./installation-block";
7
9
 
8
10
  // Lightweight helpers used by the markdown components.
@@ -11,28 +13,6 @@ function getIconForLanguageExtension(_ext: string) {
11
13
  return null;
12
14
  }
13
15
 
14
- function CopyButton({ value }: { value: string }) {
15
- const handleCopy = async () => {
16
- if (typeof navigator === "undefined" || !navigator.clipboard?.writeText) return;
17
- try {
18
- await navigator.clipboard.writeText(value);
19
- } catch {
20
- /* no-op */
21
- }
22
- };
23
-
24
- return (
25
- <button
26
- onClick={handleCopy}
27
- type="button"
28
- aria-label="Copy"
29
- className="ml-2 inline-flex items-center rounded-md bg-muted/30 px-2 py-1 text-sm"
30
- >
31
- Copy
32
- </button>
33
- );
34
- }
35
-
36
16
  function CodeBlockCommand({
37
17
  __npm__,
38
18
  __yarn__,
@@ -68,6 +48,113 @@ function CodeBlockCommand({
68
48
  );
69
49
  }
70
50
 
51
+ function UsageCodeBlock({ value, lang = "tsx" }: { value: string; lang?: "tsx" | "typescript" }) {
52
+ const [highlighted, setHighlighted] = React.useState<string | null>(null);
53
+
54
+ React.useEffect(() => {
55
+ let isActive = true;
56
+
57
+ async function highlight() {
58
+ try {
59
+ const { codeToHtml } = await import("shiki");
60
+ const html = await codeToHtml(value, {
61
+ lang,
62
+ themes: {
63
+ light: "github-light",
64
+ dark: "github-dark",
65
+ },
66
+ defaultColor: false,
67
+ transformers: [
68
+ {
69
+ name: "line-numbers",
70
+ pre(node: any) {
71
+ node.properties["data-line-numbers"] = "";
72
+ },
73
+ line(node: any, line: number) {
74
+ node.properties.className = ["line"];
75
+ node.children.unshift({
76
+ type: "element",
77
+ tagName: "span",
78
+ properties: {
79
+ className: ["line-number"],
80
+ },
81
+ children: [{ type: "text", value: String(line) }],
82
+ });
83
+ },
84
+ },
85
+ ],
86
+ });
87
+
88
+ if (isActive) {
89
+ setHighlighted(html);
90
+ }
91
+ } catch {
92
+ if (isActive) {
93
+ setHighlighted(null);
94
+ }
95
+ }
96
+ }
97
+
98
+ highlight();
99
+
100
+ return () => {
101
+ isActive = false;
102
+ };
103
+ }, [value, lang]);
104
+
105
+ const lines = value.trim().split("\n");
106
+
107
+ return (
108
+ <div className="not-prose overflow-hidden border ">
109
+ <div className="relative">
110
+ <CopyButton
111
+ value={value}
112
+ ariaLabel="Copy code"
113
+ className="absolute top-2 right-2 z-10 ml-0"
114
+ />
115
+
116
+ <div className="no-scrollbar max-h-112 min-w-0 overflow-auto px-4 py-3.5 font-mono text-xs">
117
+ {highlighted ? (
118
+ <div className="shiki-wrapper" dangerouslySetInnerHTML={{ __html: highlighted }} />
119
+ ) : (
120
+ <pre className="min-w-0 overflow-x-auto">
121
+ {lines.map((line, index) => (
122
+ <div key={`${index}-${line}`} className="grid grid-cols-[1.75rem_1fr] gap-4">
123
+ <span className="text-right text-muted-foreground/50 select-none">
124
+ {index + 1}
125
+ </span>
126
+ <code className="text-code-foreground block font-mono whitespace-pre">
127
+ {line || " "}
128
+ </code>
129
+ </div>
130
+ ))}
131
+ </pre>
132
+ )}
133
+ </div>
134
+ </div>
135
+ </div>
136
+ );
137
+ }
138
+
139
+ function UsageSection({
140
+ from,
141
+ imports,
142
+ anatomy,
143
+ }: {
144
+ from: string;
145
+ imports: string[];
146
+ anatomy: string;
147
+ }) {
148
+ const importCode = `import {\n${imports.map((item) => ` ${item},`).join("\n")}\n} from "${from}"`;
149
+
150
+ return (
151
+ <div className="my-6 space-y-5">
152
+ <UsageCodeBlock value={importCode} lang="typescript" />
153
+ <UsageCodeBlock value={anatomy} lang="tsx" />
154
+ </div>
155
+ );
156
+ }
157
+
71
158
  type TextProps = ComponentProps<"p">;
72
159
  type LinkProps = ComponentProps<"a">;
73
160
  type ListProps = ComponentProps<"ul">;
@@ -88,9 +175,34 @@ type TableRowProps = ComponentProps<"tr">;
88
175
  type TableHeaderProps = ComponentProps<"th">;
89
176
  type TableCellProps = ComponentProps<"td">;
90
177
 
178
+ function getNodeText(node: React.ReactNode): string {
179
+ if (typeof node === "string" || typeof node === "number") {
180
+ return String(node);
181
+ }
182
+
183
+ if (Array.isArray(node)) {
184
+ return node.map(getNodeText).join("");
185
+ }
186
+
187
+ if (React.isValidElement(node)) {
188
+ return getNodeText(node.props.children);
189
+ }
190
+
191
+ return "";
192
+ }
193
+
194
+ function getHeadingId(id: string | undefined, children: React.ReactNode) {
195
+ if (id) {
196
+ return id;
197
+ }
198
+
199
+ return slugifyHeading(getNodeText(children));
200
+ }
201
+
91
202
  const H1 = ({ className, ...props }: ComponentProps<"h1">) => (
92
203
  <h1
93
- className={cn("mt-2 scroll-m-28 font-heading text-3xl font-bold tracking-tight", className)}
204
+ id={getHeadingId(props.id, props.children)}
205
+ className={cn("mt-2 scroll-m-28 font-heading text-2xl font-bold tracking-tight", className)}
94
206
  {...props}
95
207
  />
96
208
  );
@@ -98,12 +210,7 @@ const H1 = ({ className, ...props }: ComponentProps<"h1">) => (
98
210
  const H2 = ({ className, ...props }: ComponentProps<"h2">) => {
99
211
  return (
100
212
  <h2
101
- id={props.children
102
- ?.toString()
103
- .replace(/ /g, "-")
104
- .replace(/'/g, "")
105
- .replace(/\?/g, "")
106
- .toLowerCase()}
213
+ id={getHeadingId(props.id, props.children)}
107
214
  className={cn(
108
215
  "[&+]*:[code]:text-xl mt-10 scroll-m-28 font-heading text-xl font-medium tracking-tight first:mt-0 lg:mt-12 [&+.steps]:!mt-0 [&+.steps>h3]:!mt-4 [&+h3]:!mt-6 [&+p]:!mt-4",
109
216
  className,
@@ -115,6 +222,7 @@ const H2 = ({ className, ...props }: ComponentProps<"h2">) => {
115
222
 
116
223
  const H3 = ({ className, ...props }: ComponentProps<"h3">) => (
117
224
  <h3
225
+ id={getHeadingId(props.id, props.children)}
118
226
  className={cn(
119
227
  "mt-12 scroll-m-28 font-heading text-lg font-medium tracking-tight [&+p]:!mt-4 *:[code]:text-xl",
120
228
  className,
@@ -125,6 +233,7 @@ const H3 = ({ className, ...props }: ComponentProps<"h3">) => (
125
233
 
126
234
  const H4 = ({ className, ...props }: ComponentProps<"h4">) => (
127
235
  <h4
236
+ id={getHeadingId(props.id, props.children)}
128
237
  className={cn("mt-8 scroll-m-28 font-heading text-base font-medium tracking-tight", className)}
129
238
  {...props}
130
239
  />
@@ -132,6 +241,7 @@ const H4 = ({ className, ...props }: ComponentProps<"h4">) => (
132
241
 
133
242
  const H5 = ({ className, ...props }: ComponentProps<"h5">) => (
134
243
  <h5
244
+ id={getHeadingId(props.id, props.children)}
135
245
  className={cn("mt-8 scroll-m-28 text-base font-medium tracking-tight", className)}
136
246
  {...props}
137
247
  />
@@ -139,6 +249,7 @@ const H5 = ({ className, ...props }: ComponentProps<"h5">) => (
139
249
 
140
250
  const H6 = ({ className, ...props }: ComponentProps<"h6">) => (
141
251
  <h6
252
+ id={getHeadingId(props.id, props.children)}
142
253
  className={cn("mt-8 scroll-m-28 text-base font-medium tracking-tight", className)}
143
254
  {...props}
144
255
  />
@@ -297,7 +408,9 @@ const Code = ({
297
408
  };
298
409
 
299
410
  export const mdxComponents = {
411
+ CopyButton,
300
412
  InstallationBlock,
413
+ UsageSection,
301
414
  h1: H1,
302
415
  h2: H2,
303
416
  h3: H3,
@@ -0,0 +1,41 @@
1
+ import { CheckIcon, CopyIcon } from "lucide-react";
2
+ import { useState } from "react";
3
+
4
+ import { Button } from "@/components/ui/button";
5
+ import { cn } from "@/lib/utils";
6
+
7
+ export function CopyButton({
8
+ value,
9
+ className,
10
+ ariaLabel = "Copy",
11
+ }: {
12
+ value: string;
13
+ className?: string;
14
+ children?: React.ReactNode;
15
+ ariaLabel?: string;
16
+ }) {
17
+ const [copied, setCopied] = useState(false);
18
+
19
+ const handleCopy = async () => {
20
+ try {
21
+ await navigator.clipboard.writeText(value);
22
+ setCopied(true);
23
+ setTimeout(() => setCopied(false), 2000);
24
+ } catch (err) {
25
+ console.error("Failed to copy!", err);
26
+ }
27
+ };
28
+
29
+ return (
30
+ <Button
31
+ variant={"ghost"}
32
+ size={"icon-xs"}
33
+ onClick={handleCopy}
34
+ type="button"
35
+ aria-label={ariaLabel}
36
+ className={cn(className)}
37
+ >
38
+ {copied ? <CheckIcon className="text-success" /> : <CopyIcon />}
39
+ </Button>
40
+ );
41
+ }
@@ -1,11 +1,13 @@
1
1
  "use client";
2
2
 
3
3
  import { Tabs } from "@base-ui/react/tabs";
4
- import { TerminalIcon, CheckIcon, CopyIcon } from "lucide-react";
4
+ import { TerminalIcon } from "lucide-react";
5
5
  import * as React from "react";
6
6
 
7
7
  import { cn } from "@/lib/utils";
8
8
 
9
+ import { CopyButton } from "./copy-button";
10
+
9
11
  interface InstallationBlockProps {
10
12
  name: string;
11
13
  }
@@ -18,7 +20,6 @@ const PACKAGE_MANAGERS = [
18
20
  ];
19
21
 
20
22
  export function InstallationBlock({ name }: InstallationBlockProps) {
21
- const [copied, setCopied] = React.useState(false);
22
23
  const [activeTab, setActiveTab] = React.useState("npx");
23
24
  const [highlighted, setHighlighted] = React.useState<string | null>(null);
24
25
 
@@ -75,18 +76,8 @@ export function InstallationBlock({ name }: InstallationBlockProps) {
75
76
  };
76
77
  }, [currentCommand]);
77
78
 
78
- const copyToClipboard = async () => {
79
- try {
80
- await navigator.clipboard.writeText(currentCommand);
81
- setCopied(true);
82
- setTimeout(() => setCopied(false), 2000);
83
- } catch (err) {
84
- console.error("Failed to copy!", err);
85
- }
86
- };
87
-
88
79
  return (
89
- <div className="not-prose my-6 w-full overflow-hidden rounded-xl border bg-background">
80
+ <div className="not-prose my-6 w-full overflow-hidden border bg-background">
90
81
  <Tabs.Root value={activeTab} onValueChange={setActiveTab}>
91
82
  <div className="flex h-10 items-center justify-between border-b px-3">
92
83
  <div className="flex items-center gap-3">
@@ -109,17 +100,7 @@ export function InstallationBlock({ name }: InstallationBlockProps) {
109
100
  ))}
110
101
  </Tabs.List>
111
102
  </div>
112
- <button
113
- onClick={copyToClipboard}
114
- className="flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted/80 hover:text-foreground"
115
- aria-label="Copy command"
116
- >
117
- {copied ? (
118
- <CheckIcon className="h-3.5 w-3.5 text-success" />
119
- ) : (
120
- <CopyIcon className="h-3.5 w-3.5" />
121
- )}
122
- </button>
103
+ <CopyButton value={currentCommand} ariaLabel="Copy command" />
123
104
  </div>
124
105
 
125
106
  {PACKAGE_MANAGERS.map((pm) => (
@@ -104,7 +104,7 @@ export function RenderPreview({ name }: { name: string }) {
104
104
  const ExampleComponent = example.code;
105
105
 
106
106
  return (
107
- <Tabs.Root className={"my-2 flex flex-col gap-2"}>
107
+ <Tabs.Root className={"my-2 flex max-w-sm flex-col gap-2 md:max-w-full"}>
108
108
  <Tabs.List className={"flex gap-2"}>
109
109
  <Tabs.Tab
110
110
  value="preview"
@@ -2,7 +2,7 @@ import { cva, VariantProps } from "class-variance-authority";
2
2
  import { ComponentProps } from "react";
3
3
 
4
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!",
5
+ "group/badge inline-flex h-5 min-h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full 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
6
  {
7
7
  variants: {
8
8
  variant: {
@@ -43,7 +43,7 @@ function ButtonGroupText({ className, ...props }: React.ComponentProps<"div">) {
43
43
  <div
44
44
  data-slot="button-group-text"
45
45
  className={cn(
46
- "flex items-center gap-2 rounded-lg border bg-muted px-2.5 text-sm font-medium [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
46
+ "flex items-center gap-2 rounded-lg bg-muted px-2.5 text-sm font-medium [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
47
47
  className,
48
48
  )}
49
49
  {...props}
@@ -10,7 +10,7 @@ function Checkbox({ className, ...props }: CheckboxPrimitive.Root.Props) {
10
10
  <CheckboxPrimitive.Root
11
11
  data-slot="checkbox"
12
12
  className={cn(
13
- "peer inline-flex size-4 max-w-4 shrink-0 items-center justify-center rounded border border-input bg-background transition-colors group-data-invalid/field:border-destructive focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-none group-data-invalid/field:focus-visible:border-destructive group-data-invalid/field:focus-visible:ring-destructive/20 disabled:cursor-not-allowed disabled:opacity-50 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground data-indeterminate:border-primary data-indeterminate:bg-primary data-indeterminate:text-primary-foreground dark:group-data-invalid/field:border-destructive/50 dark:group-data-invalid/field:focus-visible:ring-destructive/40",
13
+ "peer inline-flex size-4 max-w-4 min-w-4 shrink-0 items-center justify-center rounded border border-input bg-background transition-colors group-data-invalid/field:border-destructive focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-none group-data-invalid/field:focus-visible:border-destructive group-data-invalid/field:focus-visible:ring-destructive/20 disabled:cursor-not-allowed disabled:opacity-50 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground data-indeterminate:border-primary data-indeterminate:bg-primary data-indeterminate:text-primary-foreground dark:group-data-invalid/field:border-destructive/50 dark:group-data-invalid/field:focus-visible:ring-destructive/40",
14
14
  className,
15
15
  )}
16
16
  {...props}
@@ -89,7 +89,7 @@ function DrawerPopup({
89
89
  data-slot="drawer-popup"
90
90
  data-position={position}
91
91
  className={cn(
92
- "pointer-events-auto grid w-full gap-4 overflow-y-auto overscroll-contain bg-background p-6 text-sm text-foreground shadow-lg ring-1 ring-foreground/10 duration-150 outline-none data-closed:animate-out data-closed:fade-out-0 data-open:animate-in data-open:fade-in-0 data-swiping:transition-none data-swiping:select-none data-[position=bottom]:max-h-dvh data-[position=bottom]:w-dvw data-[position=bottom]:translate-y-[calc(var(--drawer-snap-point-offset)+var(--drawer-swipe-movement-y))] data-[position=bottom]:slide-in-from-bottom-10 data-[position=left]:h-dvh data-[position=left]:w-[min(24rem,calc(100vw-1rem))] data-[position=left]:translate-x-(--drawer-swipe-movement-x) data-[position=left]:slide-in-from-left-10 data-[position=right]:h-dvh data-[position=right]:w-[min(24rem,calc(100vw-1rem))] data-[position=right]:translate-x-(--drawer-swipe-movement-x) data-[position=right]:slide-in-from-right-10 data-[position=top]:max-h-dvh data-[position=top]:w-dvw data-[position=top]:translate-y-[calc(var(--drawer-snap-point-offset)+var(--drawer-swipe-movement-y))] data-[position=top]:slide-in-from-top-10",
92
+ "pointer-events-auto grid w-full gap-4 overflow-y-auto overscroll-contain bg-background p-6 text-sm text-foreground shadow-lg ring-1 ring-foreground/10 duration-150 outline-none data-closed:animate-out data-closed:fade-out-0 data-open:animate-in data-open:fade-in-0 data-swiping:transition-none data-swiping:select-none data-[position=bottom]:max-h-dvh data-[position=bottom]:w-dvw data-[position=bottom]:translate-y-[calc(var(--drawer-snap-point-offset)+var(--drawer-swipe-movement-y))] data-[position=bottom]:slide-in-from-bottom-10 data-[position=bottom]:[data-ending-style]:slide--from-bottom-10 data-[position=left]:h-dvh data-[position=left]:w-[min(24rem,calc(100vw-1rem))] data-[position=left]:translate-x-(--drawer-swipe-movement-x) data-[position=left]:slide-in-from-left-10 data-[position=right]:h-dvh data-[position=right]:w-[min(24rem,calc(100vw-1rem))] data-[position=right]:translate-x-(--drawer-swipe-movement-x) data-[position=right]:slide-in-from-right-10 data-[position=top]:max-h-dvh data-[position=top]:w-dvw data-[position=top]:translate-y-[calc(var(--drawer-snap-point-offset)+var(--drawer-swipe-movement-y))] data-[position=top]:slide-in-from-top-10 ",
93
93
 
94
94
  className,
95
95
  )}
@@ -1,36 +1,18 @@
1
1
  "use client";
2
2
 
3
3
  import { Field as FieldPrimitive } from "@base-ui/react/field";
4
- import { cva, VariantProps } from "class-variance-authority";
5
4
  import { useMemo } from "react";
6
5
 
7
6
  import { cn } from "@/lib/utils";
8
7
 
9
- const fieldVariants = cva("group/field flex gap-2 data-[invalid=true]:text-destructive", {
10
- variants: {
11
- orientation: {
12
- vertical: "flex-col *:w-full [&>.sr-only]:w-auto",
13
- horizontal:
14
- "flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
15
- responsive:
16
- "flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
17
- },
18
- },
19
- defaultVariants: {
20
- orientation: "vertical",
21
- },
22
- });
23
-
24
- function Field({
25
- className,
26
- orientation = "vertical",
27
- ...props
28
- }: FieldPrimitive.Root.Props & VariantProps<typeof fieldVariants>) {
8
+ function Field({ className, ...props }: FieldPrimitive.Root.Props) {
29
9
  return (
30
10
  <FieldPrimitive.Root
31
11
  data-slot="field-root"
32
- data-orientation={orientation}
33
- className={cn(fieldVariants({ orientation }), className)}
12
+ className={cn(
13
+ "group/field flex flex-col gap-2 *:w-full data-invalid:text-destructive [&>.sr-only]:w-auto",
14
+ className,
15
+ )}
34
16
  {...props}
35
17
  />
36
18
  );
@@ -41,8 +23,7 @@ function FieldLabel({ className, ...props }: FieldPrimitive.Label.Props) {
41
23
  <FieldPrimitive.Label
42
24
  data-slot="field-label"
43
25
  className={cn(
44
- "group/field-label peer/field-label flex w-fit gap-2 text-sm leading-snug group-data-[disabled=true]/field:opacity-50 has-data-checked:border-primary/30 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:rounded-lg has-[>[data-slot=field]]:border *:data-[slot=field]:p-2.5 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10",
45
- "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",
26
+ "group/field-label peer/field-label flex w-fit gap-1 text-sm leading-snug group-data-disabled/field:opacity-50 has-data-checked:border-primary/30 has-data-checked:bg-primary/5 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10",
46
27
  className,
47
28
  )}
48
29
  {...props}
@@ -72,7 +53,7 @@ function FieldDescription({ className, ...props }: FieldPrimitive.Description.Pr
72
53
  <FieldPrimitive.Description
73
54
  data-slot="field-description"
74
55
  className={cn(
75
- "text-left text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
56
+ "text-left text-xs leading-normal font-normal text-muted-foreground [[data-variant=legend]+&]:-mt-1.5",
76
57
  "last:mt-0 nth-last-2:-mt-1",
77
58
  "[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
78
59
  className,
@@ -116,7 +97,7 @@ function FieldError({
116
97
  <FieldPrimitive.Error
117
98
  role="alert"
118
99
  data-slot="field-error"
119
- className={cn("text-sm font-normal text-destructive", className)}
100
+ className={cn("text-xs font-normal text-destructive", className)}
120
101
  {...props}
121
102
  >
122
103
  {content}
@@ -128,7 +109,7 @@ function FieldItem({ className, ...props }: FieldPrimitive.Item.Props) {
128
109
  return (
129
110
  <FieldPrimitive.Item
130
111
  data-slot="field-item"
131
- className={cn("flex items-center gap-2", className)}
112
+ className={cn("flex flex-col gap-1", className)}
132
113
  {...props}
133
114
  />
134
115
  );
@@ -5,7 +5,7 @@ import { Form as FormPrimitive } from "@base-ui/react/form";
5
5
  import { cn } from "@/lib/utils";
6
6
 
7
7
  function Form({ className, ...props }: FormPrimitive.Props) {
8
- return <FormPrimitive className={cn("", className)} {...props} />;
8
+ return <FormPrimitive data-slot="form-root" className={cn(className)} {...props} />;
9
9
  }
10
10
 
11
11
  export { Form };
@@ -4,46 +4,32 @@ import { Meter as MeterPrimitive } from "@base-ui/react/meter";
4
4
 
5
5
  import { cn } from "@/lib/utils";
6
6
 
7
- function Meter({
8
- label,
9
- showLabel,
10
- showValue,
11
- className,
12
- ...props
13
- }: MeterPrimitive.Root.Props & {
14
- showValue?: boolean;
15
- showLabel?: boolean;
16
- label?: string;
17
- }) {
18
- return (
19
- <MeterPrimitive.Root className={cn("flex flex-col gap-1", className)} {...props}>
20
- <div className="flex items-center justify-between">
21
- {showLabel && label && <MeterLabel>{label}</MeterLabel>}
22
- {showValue && <MeterValue />}
23
- </div>
24
- <MeterTrack>
25
- <MeterIndicator />
26
- </MeterTrack>
27
- </MeterPrimitive.Root>
28
- );
7
+ function Meter({ className, ...props }: MeterPrimitive.Root.Props) {
8
+ return <MeterPrimitive.Root className={cn("grid grid-cols-2 gap-1", className)} {...props} />;
29
9
  }
30
10
 
31
11
  function MeterLabel({ className, ...props }: MeterPrimitive.Label.Props) {
32
12
  return (
33
- <MeterPrimitive.Label className={cn("text-xs font-bold text-primary", className)} {...props} />
13
+ <MeterPrimitive.Label
14
+ className={cn("col-span-1 text-xs font-bold text-primary", className)}
15
+ {...props}
16
+ />
34
17
  );
35
18
  }
36
19
 
37
20
  function MeterValue({ className, ...props }: MeterPrimitive.Value.Props) {
38
21
  return (
39
- <MeterPrimitive.Value className={cn("text-right text-xs text-primary", className)} {...props} />
22
+ <MeterPrimitive.Value
23
+ className={cn("col-span-1 text-right text-xs text-primary", className)}
24
+ {...props}
25
+ />
40
26
  );
41
27
  }
42
28
 
43
29
  function MeterTrack({ className, ...props }: MeterPrimitive.Track.Props) {
44
30
  return (
45
31
  <MeterPrimitive.Track
46
- className={cn("h-2 w-full overflow-hidden rounded-full bg-primary/20", className)}
32
+ className={cn("col-span-2 h-2 w-full overflow-hidden rounded-full bg-primary/20", className)}
47
33
  {...props}
48
34
  />
49
35
  );
@@ -52,7 +38,7 @@ function MeterTrack({ className, ...props }: MeterPrimitive.Track.Props) {
52
38
  function MeterIndicator({ className, ...props }: MeterPrimitive.Indicator.Props) {
53
39
  return (
54
40
  <MeterPrimitive.Indicator
55
- className={cn("block rounded-full rounded-r-xl bg-primary", className)}
41
+ className={cn("block rounded-full bg-primary", className)}
56
42
  {...props}
57
43
  />
58
44
  );
@@ -35,7 +35,7 @@ function NumberFieldGroup({ className, ...props }: NumberFieldPrimitive.Group.Pr
35
35
  <NumberFieldPrimitive.Group
36
36
  data-slot="number-field-group"
37
37
  className={cn(
38
- "relative inline-flex h-9 w-full items-stretch overflow-hidden rounded-lg border border-input focus-within:border-border focus-within:ring-1 focus-within:ring-ring has-[>[data-slot=number-field-decrement]]:*:data-[slot=number-field-input]:rounded-none has-[>[data-slot=number-field-decrement]]:*:data-[slot=number-field-input]:border-0 has-[>[data-slot=number-field-decrement]]:*:data-[slot=number-field-input]:outline-none has-[>[data-slot=number-field-decrement]]:*:data-[slot=number-field-input]:focus:ring-0 has-[>[data-slot=number-field-decrement]]:*:data-[slot=number-field-input]:focus:outline-none",
38
+ "relative inline-flex h-9 w-full items-stretch overflow-hidden rounded-lg border border-input focus-within:border-ring focus-within:ring-3 focus-within:ring-ring/50 has-[>[data-slot=number-field-decrement]]:*:data-[slot=number-field-input]:rounded-none has-[>[data-slot=number-field-decrement]]:*:data-[slot=number-field-input]:border-0 has-[>[data-slot=number-field-decrement]]:*:data-[slot=number-field-input]:outline-none has-[>[data-slot=number-field-decrement]]:*:data-[slot=number-field-input]:focus:ring-0 has-[>[data-slot=number-field-decrement]]:*:data-[slot=number-field-input]:focus:outline-none",
39
39
  "has-[>[data-slot=number-field-decrement][data-align=end]]:*:data-[slot=number-field-decrement]:border-l has-[>[data-slot=number-field-decrement][data-align=start]]:*:data-[slot=number-field-decrement]:border-r has-[>[data-slot=number-field-increment][data-align=end]]:*:data-[slot=number-field-increment]:border-l ",
40
40
  className,
41
41
  )}
@@ -6,31 +6,44 @@ import { CircleIcon } from "lucide-react";
6
6
 
7
7
  import { cn } from "@/lib/utils";
8
8
 
9
- function Radio({ children, className, ...props }: RadioPrimitive.Root.Props) {
9
+ function Radio({ className, ...props }: RadioPrimitive.Root.Props) {
10
10
  return (
11
- <label
12
- className={cn("group flex items-center gap-2 text-sm text-foreground", className)}
13
- htmlFor={props.id}
11
+ <RadioPrimitive.Root
12
+ data-slot="radio"
13
+ className={cn(
14
+ "group/radio-group-item peer relative flex aspect-square size-4 shrink-0 rounded-full border border-input text-primary outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-checked:ring data-checked:ring-primary/40 dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
15
+ className,
16
+ )}
17
+ {...props}
14
18
  >
15
- <RadioPrimitive.Root
16
- className={
17
- "flex size-4 items-center justify-center border border-input bg-transparent outline outline-input transition duration-50 ease-in group-hover:bg-accent/80"
18
- }
19
- {...props}
19
+ <RadioPrimitive.Indicator
20
+ data-slot="radio-indicator"
21
+ className="flex size-4 items-center justify-center text-primary group-aria-invalid/radio-group-item:text-destructive"
20
22
  >
21
- <RadioPrimitive.Indicator
22
- className={"flex items-center justify-center data-checked:bg-primary"}
23
- >
24
- <CircleIcon className="size-2 bg-primary" />
25
- </RadioPrimitive.Indicator>
26
- </RadioPrimitive.Root>
27
- {children}
28
- </label>
23
+ <CircleIcon className="absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 fill-current" />
24
+ </RadioPrimitive.Indicator>
25
+ </RadioPrimitive.Root>
29
26
  );
30
27
  }
31
28
 
32
- function RadioGroup({ className, ...props }: RadioGroupPrimitive.Props) {
33
- return <RadioGroupPrimitive className={cn("flex flex-col gap-1", className)} {...props} />;
29
+ function RadioGroup({
30
+ className,
31
+ orientation = "vertical",
32
+ ...props
33
+ }: RadioGroupPrimitive.Props & {
34
+ orientation?: "horizontal" | "vertical";
35
+ }) {
36
+ return (
37
+ <RadioGroupPrimitive
38
+ data-slot="radio-group"
39
+ data-orientation={orientation}
40
+ className={cn(
41
+ "flex h-fit gap-4 data-[orientation=horizontal]:flex-row data-[orientation=vertical]:flex-col",
42
+ className,
43
+ )}
44
+ {...props}
45
+ />
46
+ );
34
47
  }
35
48
 
36
49
  export { Radio, RadioGroup };
@@ -1,17 +1,26 @@
1
1
  "use client";
2
2
 
3
3
  import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area";
4
+ import type { ReactNode, Ref } from "react";
4
5
 
5
6
  import { cn } from "@/lib/utils";
6
7
 
7
8
  function ScrollArea({
8
9
  className,
9
10
  children,
11
+ viewportRef,
10
12
  ...props
11
- }: ScrollAreaPrimitive.Root.Props & { children?: React.ReactNode }) {
13
+ }: ScrollAreaPrimitive.Root.Props & {
14
+ children?: ReactNode;
15
+ viewportRef?: Ref<HTMLDivElement>;
16
+ }) {
12
17
  return (
13
18
  <ScrollAreaPrimitive.Root className={cn("relative", className)} {...props}>
14
- <ScrollAreaPrimitive.Viewport className="h-full w-full">
19
+ <ScrollAreaPrimitive.Viewport
20
+ ref={viewportRef}
21
+ data-scroll-area-viewport
22
+ className="h-full w-full"
23
+ >
15
24
  <ScrollAreaPrimitive.Content>{children}</ScrollAreaPrimitive.Content>
16
25
  </ScrollAreaPrimitive.Viewport>
17
26
  <ScrollAreaScrollbar>