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.
- package/.github/ISSUE_TEMPLATE/bug_report.yml +47 -0
- package/.github/ISSUE_TEMPLATE/config.yml +1 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +31 -0
- package/.github/pull_request_template.md +14 -0
- package/.github/workflows/pr-preview.yml +75 -0
- package/.github/workflows/release.yml +8 -0
- package/CHANGELOG.md +30 -0
- package/CODE_OF_CONDUCT.md +41 -0
- package/CONTRIBUTING.md +52 -0
- package/README.md +18 -0
- package/SECURITY.md +0 -0
- package/content/components/accordion.mdx +13 -0
- package/content/components/alert-dialog.mdx +34 -0
- package/content/components/autocomplete.mdx +62 -0
- package/content/components/avatar.mdx +11 -0
- package/content/components/button.mdx +8 -0
- package/content/components/checkbox.mdx +11 -0
- package/content/components/collapsible.mdx +11 -0
- package/content/components/combobox.mdx +33 -0
- package/content/components/context-menu.mdx +29 -0
- package/content/components/dialog.mdx +33 -0
- package/content/components/drawer.mdx +38 -0
- package/content/components/field.mdx +23 -2
- package/content/components/fieldset.mdx +11 -1
- package/content/components/form.mdx +8 -0
- package/content/components/input.mdx +4 -0
- package/content/components/menu.mdx +27 -0
- package/content/components/menubar.mdx +31 -0
- package/content/components/meter.mdx +14 -0
- package/content/components/navigation-menu.mdx +28 -0
- package/content/components/number-field.mdx +25 -0
- package/content/components/popover.mdx +22 -0
- package/content/components/preview-card.mdx +14 -2
- package/content/components/progress.mdx +15 -1
- package/content/components/radio.mdx +31 -0
- package/content/components/scroll-area.mdx +23 -0
- package/content/components/select.mdx +57 -0
- package/content/components/separator.mdx +29 -0
- package/content/components/slider.mdx +52 -0
- package/content/components/switch.mdx +30 -0
- package/content/components/tabs.mdx +47 -0
- package/content/components/toast.mdx +70 -0
- package/content/components/toggle-group.mdx +37 -0
- package/content/components/toggle.mdx +46 -2
- package/content/components/toolbar.mdx +48 -0
- package/content/components/tooltip.mdx +38 -0
- package/content/docs/installation.mdx +30 -0
- package/content-collections.ts +65 -1
- package/dist/cli.js +947 -101
- package/examples/__index.tsx +320 -66
- package/examples/autocomplete-align.tsx +39 -0
- package/examples/autocomplete-controlled.tsx +44 -0
- package/examples/autocomplete-groups.tsx +65 -0
- package/examples/autocomplete-no-clear.tsx +40 -0
- package/examples/avatar-demo.tsx +3 -3
- package/examples/checkbox-demo.tsx +1 -1
- package/examples/checkbox-form.tsx +3 -3
- package/examples/field-custom-control.tsx +33 -9
- package/examples/form-demo.tsx +5 -10
- package/examples/input-group-with-button.tsx +1 -1
- package/examples/menu-advanced.tsx +1 -3
- package/examples/menu-align.tsx +19 -16
- package/examples/menu-checkbox.tsx +2 -3
- package/examples/menu-demo.tsx +1 -3
- package/examples/menu-group.tsx +1 -3
- package/examples/menu-radio.tsx +1 -3
- package/examples/menu-submenu.tsx +2 -3
- package/examples/meter-demo.tsx +10 -2
- package/examples/meter-flip.tsx +8 -8
- package/examples/meter-no-label.tsx +9 -2
- package/examples/meter-no-value.tsx +7 -8
- package/examples/radio-card.tsx +28 -0
- package/examples/radio-demo.tsx +19 -1
- package/examples/radio-description.tsx +26 -0
- package/examples/radio-orientation.tsx +21 -0
- package/examples/select-alignment.tsx +51 -0
- package/examples/select-demo.tsx +36 -1
- package/examples/select-disabled.tsx +38 -0
- package/examples/select-groups.tsx +54 -0
- package/examples/select-invalid.tsx +41 -0
- package/examples/select-scrollable.tsx +112 -0
- package/examples/separator-demo.tsx +13 -0
- package/examples/separator-horizontal.tsx +18 -0
- package/examples/slider-controlled.tsx +28 -0
- package/examples/slider-demo.tsx +3 -1
- package/examples/slider-disabled.tsx +7 -0
- package/examples/slider-edge.tsx +13 -0
- package/examples/slider-multiple.tsx +7 -0
- package/examples/slider-range.tsx +5 -0
- package/examples/slider-vertical.tsx +10 -0
- package/examples/switch-demo.tsx +19 -1
- package/examples/switch-disabled.tsx +20 -0
- package/examples/switch-sizes.tsx +24 -0
- package/examples/switch-with-label.tsx +16 -0
- package/examples/tabs-demo.tsx +14 -1
- package/examples/tabs-disabled.tsx +21 -0
- package/examples/tabs-line.tsx +18 -0
- package/examples/tabs-vertical.tsx +13 -0
- package/examples/toast-action.tsx +39 -0
- package/examples/toast-anchored.tsx +36 -0
- package/examples/toast-demo.tsx +27 -1
- package/examples/toast-positions.tsx +54 -0
- package/examples/toast-promise.tsx +51 -0
- package/examples/toast-stacked.tsx +30 -0
- package/examples/toast-timeout.tsx +43 -0
- package/examples/toast-update.tsx +38 -0
- package/examples/toast-variants.tsx +54 -0
- package/examples/toggle-controlled.tsx +20 -0
- package/examples/toggle-demo.tsx +7 -51
- package/examples/toggle-group-demo.tsx +19 -0
- package/examples/toggle-group-multiple.tsx +19 -0
- package/examples/toggle-icon-fill.tsx +12 -0
- package/examples/toolbar-demo.tsx +45 -21
- package/examples/toolbar-input-link.tsx +35 -0
- package/examples/toolbar-menu.tsx +53 -0
- package/examples/tooltip-demo.tsx +48 -0
- package/examples/tooltip-positions.tsx +60 -0
- package/package.json +19 -18
- package/public/base-ui.svg +1 -0
- package/public/r/drawer.json +1 -1
- package/public/r/field.json +1 -1
- package/public/r/meter.json +1 -1
- package/public/r/number-field.json +1 -1
- package/public/r/progress.json +1 -1
- package/public/r/radio.json +1 -1
- package/public/r/select.json +1 -1
- package/public/r/slider.json +1 -1
- package/public/r/switch.json +1 -1
- package/public/r/tabs.json +1 -1
- package/public/r/toast.json +2 -1
- package/public/r/toggle.json +1 -1
- package/public/r/toolbar.json +1 -1
- package/public/r/tooltip.json +15 -0
- package/src/assets/base-ui.svg +1 -0
- package/src/commands/add.ts +79 -38
- package/src/commands/cli.ts +50 -3
- package/src/commands/create.ts +262 -0
- package/src/commands/init.ts +45 -12
- package/src/commands/palette.ts +55 -0
- package/src/components/docs/layout/footer.tsx +2 -2
- package/src/components/docs/layout/header.tsx +7 -19
- package/src/components/docs/layout/mobile-menu.tsx +26 -78
- package/src/components/docs/layout/nav-list.tsx +27 -21
- package/src/components/docs/layout/page-header.tsx +52 -7
- package/src/components/docs/layout/split-layout.tsx +11 -9
- package/src/components/docs/layout/table-of-content.tsx +145 -0
- package/src/components/docs/markdown/components.tsx +142 -29
- package/src/components/docs/markdown/copy-button.tsx +41 -0
- package/src/components/docs/markdown/installation-block.tsx +5 -24
- package/src/components/docs/markdown/render-preview.tsx +1 -1
- package/src/components/ui/badge.tsx +1 -1
- package/src/components/ui/button-group.tsx +1 -1
- package/src/components/ui/checkbox.tsx +1 -1
- package/src/components/ui/drawer.tsx +1 -1
- package/src/components/ui/field.tsx +9 -28
- package/src/components/ui/form.tsx +1 -1
- package/src/components/ui/meter.tsx +12 -26
- package/src/components/ui/number-field.tsx +1 -1
- package/src/components/ui/radio.tsx +32 -19
- package/src/components/ui/scroll-area.tsx +11 -2
- package/src/components/ui/select.tsx +6 -6
- package/src/components/ui/slider.tsx +8 -5
- package/src/components/ui/switch.tsx +13 -17
- package/src/components/ui/tabs.tsx +23 -10
- package/src/components/ui/toast.tsx +190 -29
- package/src/components/ui/toggle.tsx +1 -1
- package/src/components/ui/toolbar.tsx +17 -4
- package/src/components/ui/tooltip.tsx +54 -0
- package/src/lib/docs-headings.ts +72 -0
- package/src/routeTree.gen.ts +60 -3
- package/src/routes/__root.tsx +3 -5
- package/src/routes/components.$slug.tsx +20 -4
- package/src/routes/docs.$slug.tsx +78 -0
- package/src/routes/docs.tsx +15 -0
- package/src/styles/styles.css +1 -1
- package/src/utils/cli-utils.ts +8 -8
- package/src/utils/dependency-installer.ts +30 -0
- package/src/utils/package-manager.ts +4 -4
- package/src/utils/palette.ts +666 -0
- 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
|
-
|
|
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
|
|
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
|
|
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
|
-
<
|
|
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-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
33
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
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({
|
|
9
|
+
function Radio({ className, ...props }: RadioPrimitive.Root.Props) {
|
|
10
10
|
return (
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
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.
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
<
|
|
22
|
-
|
|
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({
|
|
33
|
-
|
|
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 & {
|
|
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
|
|
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>
|