shapes-ui 0.5.0 → 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/workflows/pr-preview.yml +9 -2
- package/CHANGELOG.md +11 -0
- package/README.md +13 -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 +21 -0
- package/content/components/fieldset.mdx +10 -0
- 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 +11 -0
- package/content/components/scroll-area.mdx +23 -0
- package/content/components/select.mdx +27 -0
- package/content/components/separator.mdx +29 -0
- package/content/components/slider.mdx +4 -0
- package/content/components/switch.mdx +4 -0
- package/content/components/tabs.mdx +15 -0
- package/content/components/toast.mdx +10 -0
- package/content/components/toggle-group.mdx +37 -0
- package/content/components/toggle.mdx +12 -0
- package/content/components/toolbar.mdx +22 -0
- package/content/components/tooltip.mdx +13 -0
- package/content/docs/installation.mdx +30 -0
- package/content-collections.ts +65 -1
- package/dist/cli.js +947 -101
- package/examples/__index.tsx +136 -68
- 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/input-group-with-button.tsx +1 -1
- package/examples/separator-demo.tsx +13 -0
- package/examples/separator-horizontal.tsx +18 -0
- package/package.json +19 -18
- package/public/base-ui.svg +1 -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 -9
- package/src/components/docs/layout/mobile-menu.tsx +0 -1
- package/src/components/docs/layout/nav-list.tsx +2 -2
- package/src/components/docs/layout/page-header.tsx +52 -7
- package/src/components/docs/layout/split-layout.tsx +9 -10
- 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/button-group.tsx +1 -1
- package/src/components/ui/scroll-area.tsx +11 -2
- package/src/lib/docs-headings.ts +72 -0
- package/src/routeTree.gen.ts +60 -3
- package/src/routes/__root.tsx +2 -2
- 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"
|
|
@@ -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}
|
|
@@ -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>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export type TocHeading = {
|
|
2
|
+
id: string;
|
|
3
|
+
title: string;
|
|
4
|
+
level: number;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export function slugifyHeading(value: string) {
|
|
8
|
+
return value
|
|
9
|
+
.trim()
|
|
10
|
+
.toLowerCase()
|
|
11
|
+
.replace(/`/g, "")
|
|
12
|
+
.replace(/\[(.*?)\]\((.*?)\)/g, "$1")
|
|
13
|
+
.replace(/<[^>]+>/g, "")
|
|
14
|
+
.replace(/&[a-z0-9#]+;/gi, "")
|
|
15
|
+
.replace(/[^a-z0-9\s-]/g, "")
|
|
16
|
+
.replace(/\s+/g, "-")
|
|
17
|
+
.replace(/-+/g, "-")
|
|
18
|
+
.replace(/^-|-$/g, "");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function sanitizeHeadingTitle(value: string) {
|
|
22
|
+
return value
|
|
23
|
+
.replace(/\s+#+\s*$/, "")
|
|
24
|
+
.replace(/`/g, "")
|
|
25
|
+
.replace(/\[(.*?)\]\((.*?)\)/g, "$1")
|
|
26
|
+
.replace(/[*_~]/g, "")
|
|
27
|
+
.replace(/<[^>]+>/g, "")
|
|
28
|
+
.replace(/&[a-z0-9#]+;/gi, "")
|
|
29
|
+
.trim();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function extractHeadingsFromMarkdown(content: string): TocHeading[] {
|
|
33
|
+
const headings: TocHeading[] = [];
|
|
34
|
+
const lines = content.split("\n");
|
|
35
|
+
let inCodeFence = false;
|
|
36
|
+
|
|
37
|
+
for (const rawLine of lines) {
|
|
38
|
+
const line = rawLine.trim();
|
|
39
|
+
|
|
40
|
+
if (line.startsWith("```")) {
|
|
41
|
+
inCodeFence = !inCodeFence;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (inCodeFence) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const match = line.match(/^(#{1,6})\s+(.+)$/);
|
|
50
|
+
|
|
51
|
+
if (!match) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const level = match[1]?.length ?? 0;
|
|
56
|
+
const title = sanitizeHeadingTitle(match[2] ?? "");
|
|
57
|
+
|
|
58
|
+
if (!title) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const id = slugifyHeading(title);
|
|
63
|
+
|
|
64
|
+
if (!id) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
headings.push({ id, title, level });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return headings;
|
|
72
|
+
}
|
package/src/routeTree.gen.ts
CHANGED
|
@@ -9,11 +9,18 @@
|
|
|
9
9
|
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
|
10
10
|
|
|
11
11
|
import { Route as rootRouteImport } from './routes/__root'
|
|
12
|
+
import { Route as DocsRouteImport } from './routes/docs'
|
|
12
13
|
import { Route as ComponentsRouteImport } from './routes/components'
|
|
13
14
|
import { Route as IndexRouteImport } from './routes/index'
|
|
14
15
|
import { Route as ComponentsIndexRouteImport } from './routes/components.index'
|
|
16
|
+
import { Route as DocsSlugRouteImport } from './routes/docs.$slug'
|
|
15
17
|
import { Route as ComponentsSlugRouteImport } from './routes/components.$slug'
|
|
16
18
|
|
|
19
|
+
const DocsRoute = DocsRouteImport.update({
|
|
20
|
+
id: '/docs',
|
|
21
|
+
path: '/docs',
|
|
22
|
+
getParentRoute: () => rootRouteImport,
|
|
23
|
+
} as any)
|
|
17
24
|
const ComponentsRoute = ComponentsRouteImport.update({
|
|
18
25
|
id: '/components',
|
|
19
26
|
path: '/components',
|
|
@@ -29,6 +36,11 @@ const ComponentsIndexRoute = ComponentsIndexRouteImport.update({
|
|
|
29
36
|
path: '/',
|
|
30
37
|
getParentRoute: () => ComponentsRoute,
|
|
31
38
|
} as any)
|
|
39
|
+
const DocsSlugRoute = DocsSlugRouteImport.update({
|
|
40
|
+
id: '/$slug',
|
|
41
|
+
path: '/$slug',
|
|
42
|
+
getParentRoute: () => DocsRoute,
|
|
43
|
+
} as any)
|
|
32
44
|
const ComponentsSlugRoute = ComponentsSlugRouteImport.update({
|
|
33
45
|
id: '/$slug',
|
|
34
46
|
path: '/$slug',
|
|
@@ -38,36 +50,63 @@ const ComponentsSlugRoute = ComponentsSlugRouteImport.update({
|
|
|
38
50
|
export interface FileRoutesByFullPath {
|
|
39
51
|
'/': typeof IndexRoute
|
|
40
52
|
'/components': typeof ComponentsRouteWithChildren
|
|
53
|
+
'/docs': typeof DocsRouteWithChildren
|
|
41
54
|
'/components/$slug': typeof ComponentsSlugRoute
|
|
55
|
+
'/docs/$slug': typeof DocsSlugRoute
|
|
42
56
|
'/components/': typeof ComponentsIndexRoute
|
|
43
57
|
}
|
|
44
58
|
export interface FileRoutesByTo {
|
|
45
59
|
'/': typeof IndexRoute
|
|
60
|
+
'/docs': typeof DocsRouteWithChildren
|
|
46
61
|
'/components/$slug': typeof ComponentsSlugRoute
|
|
62
|
+
'/docs/$slug': typeof DocsSlugRoute
|
|
47
63
|
'/components': typeof ComponentsIndexRoute
|
|
48
64
|
}
|
|
49
65
|
export interface FileRoutesById {
|
|
50
66
|
__root__: typeof rootRouteImport
|
|
51
67
|
'/': typeof IndexRoute
|
|
52
68
|
'/components': typeof ComponentsRouteWithChildren
|
|
69
|
+
'/docs': typeof DocsRouteWithChildren
|
|
53
70
|
'/components/$slug': typeof ComponentsSlugRoute
|
|
71
|
+
'/docs/$slug': typeof DocsSlugRoute
|
|
54
72
|
'/components/': typeof ComponentsIndexRoute
|
|
55
73
|
}
|
|
56
74
|
export interface FileRouteTypes {
|
|
57
75
|
fileRoutesByFullPath: FileRoutesByFullPath
|
|
58
|
-
fullPaths:
|
|
76
|
+
fullPaths:
|
|
77
|
+
| '/'
|
|
78
|
+
| '/components'
|
|
79
|
+
| '/docs'
|
|
80
|
+
| '/components/$slug'
|
|
81
|
+
| '/docs/$slug'
|
|
82
|
+
| '/components/'
|
|
59
83
|
fileRoutesByTo: FileRoutesByTo
|
|
60
|
-
to: '/' | '/components/$slug' | '/components'
|
|
61
|
-
id:
|
|
84
|
+
to: '/' | '/docs' | '/components/$slug' | '/docs/$slug' | '/components'
|
|
85
|
+
id:
|
|
86
|
+
| '__root__'
|
|
87
|
+
| '/'
|
|
88
|
+
| '/components'
|
|
89
|
+
| '/docs'
|
|
90
|
+
| '/components/$slug'
|
|
91
|
+
| '/docs/$slug'
|
|
92
|
+
| '/components/'
|
|
62
93
|
fileRoutesById: FileRoutesById
|
|
63
94
|
}
|
|
64
95
|
export interface RootRouteChildren {
|
|
65
96
|
IndexRoute: typeof IndexRoute
|
|
66
97
|
ComponentsRoute: typeof ComponentsRouteWithChildren
|
|
98
|
+
DocsRoute: typeof DocsRouteWithChildren
|
|
67
99
|
}
|
|
68
100
|
|
|
69
101
|
declare module '@tanstack/react-router' {
|
|
70
102
|
interface FileRoutesByPath {
|
|
103
|
+
'/docs': {
|
|
104
|
+
id: '/docs'
|
|
105
|
+
path: '/docs'
|
|
106
|
+
fullPath: '/docs'
|
|
107
|
+
preLoaderRoute: typeof DocsRouteImport
|
|
108
|
+
parentRoute: typeof rootRouteImport
|
|
109
|
+
}
|
|
71
110
|
'/components': {
|
|
72
111
|
id: '/components'
|
|
73
112
|
path: '/components'
|
|
@@ -89,6 +128,13 @@ declare module '@tanstack/react-router' {
|
|
|
89
128
|
preLoaderRoute: typeof ComponentsIndexRouteImport
|
|
90
129
|
parentRoute: typeof ComponentsRoute
|
|
91
130
|
}
|
|
131
|
+
'/docs/$slug': {
|
|
132
|
+
id: '/docs/$slug'
|
|
133
|
+
path: '/$slug'
|
|
134
|
+
fullPath: '/docs/$slug'
|
|
135
|
+
preLoaderRoute: typeof DocsSlugRouteImport
|
|
136
|
+
parentRoute: typeof DocsRoute
|
|
137
|
+
}
|
|
92
138
|
'/components/$slug': {
|
|
93
139
|
id: '/components/$slug'
|
|
94
140
|
path: '/$slug'
|
|
@@ -113,9 +159,20 @@ const ComponentsRouteWithChildren = ComponentsRoute._addFileChildren(
|
|
|
113
159
|
ComponentsRouteChildren,
|
|
114
160
|
)
|
|
115
161
|
|
|
162
|
+
interface DocsRouteChildren {
|
|
163
|
+
DocsSlugRoute: typeof DocsSlugRoute
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const DocsRouteChildren: DocsRouteChildren = {
|
|
167
|
+
DocsSlugRoute: DocsSlugRoute,
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const DocsRouteWithChildren = DocsRoute._addFileChildren(DocsRouteChildren)
|
|
171
|
+
|
|
116
172
|
const rootRouteChildren: RootRouteChildren = {
|
|
117
173
|
IndexRoute: IndexRoute,
|
|
118
174
|
ComponentsRoute: ComponentsRouteWithChildren,
|
|
175
|
+
DocsRoute: DocsRouteWithChildren,
|
|
119
176
|
}
|
|
120
177
|
export const routeTree = rootRouteImport
|
|
121
178
|
._addFileChildren(rootRouteChildren)
|
package/src/routes/__root.tsx
CHANGED
|
@@ -46,9 +46,9 @@ function RootDocument({ children }: { children: React.ReactNode }) {
|
|
|
46
46
|
<head>
|
|
47
47
|
<HeadContent />
|
|
48
48
|
</head>
|
|
49
|
-
<body className="flex h-dvh flex-col overflow-hidden">
|
|
49
|
+
<body className="flex h-dvh flex-col overflow-hidden bg-muted">
|
|
50
50
|
<Header />
|
|
51
|
-
<main className=" container mx-auto flex min-h-0 flex-1
|
|
51
|
+
<main className=" container mx-auto flex min-h-0 xl:bg-card flex-1 px-4 xl:p-0 ">{children}</main>
|
|
52
52
|
<Footer />
|
|
53
53
|
<TanStackDevtools
|
|
54
54
|
config={{
|
|
@@ -7,16 +7,27 @@ import { PageHeader } from "@/components/docs/layout/page-header";
|
|
|
7
7
|
import { mdxComponents } from "@/components/docs/markdown/components";
|
|
8
8
|
import { RenderPreview } from "@/components/docs/markdown/render-preview";
|
|
9
9
|
|
|
10
|
+
const markdownFiles = import.meta.glob("../../content/components/*.mdx", {
|
|
11
|
+
query: "?url",
|
|
12
|
+
import: "default",
|
|
13
|
+
eager: true,
|
|
14
|
+
}) as Record<string, string>;
|
|
15
|
+
|
|
10
16
|
export const Route = createFileRoute("/components/$slug")({
|
|
11
17
|
loader: ({ params }) => {
|
|
12
18
|
const { slug } = params;
|
|
13
|
-
const component =
|
|
19
|
+
const component = allComponents?.find((c) => c.slug === slug);
|
|
14
20
|
|
|
15
21
|
if (!component) {
|
|
16
22
|
throw notFound();
|
|
17
23
|
}
|
|
18
24
|
|
|
19
|
-
|
|
25
|
+
const markdownLink = markdownFiles[`../../content/components/${component.slug}.mdx`];
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
...component,
|
|
29
|
+
markdownLink,
|
|
30
|
+
};
|
|
20
31
|
},
|
|
21
32
|
head: ({ loaderData }) => ({
|
|
22
33
|
meta: [
|
|
@@ -51,8 +62,13 @@ function RouteComponent() {
|
|
|
51
62
|
|
|
52
63
|
return (
|
|
53
64
|
<div className=" flex flex-col gap-6 ">
|
|
54
|
-
<PageHeader
|
|
55
|
-
|
|
65
|
+
<PageHeader
|
|
66
|
+
title={component?.title}
|
|
67
|
+
subtitle={component?.description}
|
|
68
|
+
baseUILink={component?.referenceLink}
|
|
69
|
+
markdownLink={component?.markdownLink}
|
|
70
|
+
/>
|
|
71
|
+
<div data-docs-content className="container mx-auto max-w-4xl min-w-0 p-6">
|
|
56
72
|
{isMounted ? (
|
|
57
73
|
<MDXContent code={component.mdx} components={{ ...mdxComponents, RenderPreview }} />
|
|
58
74
|
) : null}
|