@umami/shiso 0.55.0 → 0.61.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/CHANGELOG.md +12 -0
- package/README.md +9 -49
- package/bin/shiso.mjs +132 -0
- package/docs.schema.json +835 -0
- package/mdx.config.ts +143 -0
- package/package.json +73 -83
- package/scripts/check-package.mjs +18 -0
- package/scripts/generate-icon-registry.mjs +196 -0
- package/scripts/generate-last-modified.mjs +128 -0
- package/scripts/generate-search-index.mjs +252 -0
- package/scripts/load-docs-config.mjs +244 -0
- package/scripts/prerender.mjs +187 -0
- package/scripts/validate-config.mjs +104 -0
- package/scripts/vite-docs-config.mjs +60 -0
- package/src/App.tsx +39 -0
- package/src/components/Banner.tsx +69 -0
- package/src/components/CodeBlock.tsx +46 -0
- package/src/components/ConfiguredIcon.tsx +15 -0
- package/src/components/ContextualMenu.tsx +93 -0
- package/src/components/DocContent.tsx +100 -0
- package/src/components/Docs.tsx +126 -0
- package/src/components/Footer.tsx +84 -0
- package/src/components/Header.tsx +72 -0
- package/src/components/Layout.tsx +16 -0
- package/src/components/PageLinks.tsx +71 -0
- package/src/components/Search.tsx +208 -0
- package/src/components/SideNav.tsx +347 -0
- package/src/components/SocialIcon.tsx +88 -0
- package/src/components/ThemeToggle.tsx +34 -0
- package/src/components/TopNav.tsx +127 -0
- package/src/components/docs/Accordion.tsx +68 -0
- package/src/components/docs/Badge.tsx +171 -0
- package/src/components/docs/Callout.tsx +73 -0
- package/src/components/docs/Card.tsx +158 -0
- package/src/components/docs/CodeGroup.tsx +73 -0
- package/src/components/docs/Columns.tsx +20 -0
- package/src/components/docs/Expandable.tsx +28 -0
- package/src/components/docs/Frame.tsx +56 -0
- package/src/components/docs/Icon.tsx +30 -0
- package/src/components/docs/ParamField.tsx +45 -0
- package/src/components/docs/PropertiesTable.tsx +84 -0
- package/src/components/docs/ResponseField.tsx +36 -0
- package/src/components/docs/Steps.tsx +47 -0
- package/src/components/docs/Tabs.tsx +116 -0
- package/src/components/docs/Tooltip.tsx +21 -0
- package/src/components/docs/index.ts +15 -0
- package/src/components/docs/styles.ts +82 -0
- package/src/components/docs/utils.ts +118 -0
- package/src/components/icons/index.ts +17 -0
- package/src/components/ui/accordion.tsx +69 -0
- package/src/components/ui/alert.tsx +69 -0
- package/src/components/ui/badge.tsx +49 -0
- package/src/components/ui/button.tsx +58 -0
- package/src/components/ui/card.tsx +88 -0
- package/src/components/ui/collapsible.tsx +15 -0
- package/src/components/ui/command.tsx +173 -0
- package/src/components/ui/dialog.tsx +137 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/scroll-area.tsx +71 -0
- package/src/components/ui/sheet.tsx +124 -0
- package/src/components/ui/tabs.tsx +73 -0
- package/src/components/ui/tooltip.tsx +52 -0
- package/src/declarations.d.ts +9 -0
- package/src/entry-client.tsx +17 -0
- package/src/entry-server.tsx +77 -0
- package/src/generated/last-modified.ts +2 -0
- package/src/lib/content.ts +44 -0
- package/src/lib/docs-config.ts +682 -0
- package/src/lib/head.ts +220 -0
- package/src/lib/icon-registry.generated.ts +4 -0
- package/src/lib/icons.ts +29 -0
- package/src/lib/inline-markdown.tsx +86 -0
- package/src/lib/mdast.ts +56 -0
- package/src/lib/paths.ts +86 -0
- package/src/lib/remark-toc.ts +71 -0
- package/src/lib/search/config.ts +43 -0
- package/src/lib/search/provider.ts +80 -0
- package/src/lib/search/providers/local.ts +15 -0
- package/src/lib/search-index.generated.ts +4 -0
- package/src/lib/search.ts +100 -0
- package/src/lib/site-config.ts +104 -0
- package/src/lib/site-model.ts +221 -0
- package/src/lib/slug.ts +38 -0
- package/src/lib/types.ts +478 -0
- package/src/lib/utils.ts +6 -0
- package/src/pages/DocPage.tsx +33 -0
- package/src/styles/global.css +268 -0
- package/src/styles/tokens.css +114 -0
- package/types/client.d.ts +3 -0
- package/types/search.d.ts +17 -0
- package/vite.config.ts +342 -0
- package/LICENSE +0 -21
- package/dist/index.css +0 -189
- package/dist/index.d.ts +0 -57
- package/dist/index.js +0 -464
- package/dist/index.mjs +0 -437
- package/server/index.d.ts +0 -30
- package/server/index.js +0 -189
- package/styles.css +0 -4766
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { Badge } from './Badge';
|
|
3
|
+
import type { ResponseFieldProps } from './ResponseField';
|
|
4
|
+
import { decodeHtmlEntities, toElementArray } from './utils';
|
|
5
|
+
|
|
6
|
+
export interface PropertiesTableProps {
|
|
7
|
+
children?: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function displayValue(value: ResponseFieldProps['type']): string | undefined {
|
|
11
|
+
if (typeof value === 'string') {
|
|
12
|
+
return decodeHtmlEntities(value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return value === undefined || value === null ? undefined : String(value);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function PropertiesTable({ children }: PropertiesTableProps) {
|
|
19
|
+
const rows = toElementArray<ResponseFieldProps>(children);
|
|
20
|
+
|
|
21
|
+
if (!rows.length) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className="my-4 overflow-x-auto rounded-lg border border-border p-6">
|
|
27
|
+
<table className="m-0 min-w-[40rem] table-fixed border-collapse">
|
|
28
|
+
<thead>
|
|
29
|
+
<tr>
|
|
30
|
+
<th className="w-1/3 border-x-0 border-t-0 border-b border-border bg-transparent px-0 py-3 pr-6 text-left text-sm font-semibold text-foreground">
|
|
31
|
+
Name
|
|
32
|
+
</th>
|
|
33
|
+
<th className="w-1/3 border-x-0 border-t-0 border-b border-border bg-transparent px-0 py-3 pr-6 text-left text-sm font-semibold text-foreground">
|
|
34
|
+
Type
|
|
35
|
+
</th>
|
|
36
|
+
<th className="w-1/3 border-x-0 border-t-0 border-b border-border bg-transparent px-0 py-3 text-left text-sm font-semibold text-foreground">
|
|
37
|
+
Description
|
|
38
|
+
</th>
|
|
39
|
+
</tr>
|
|
40
|
+
</thead>
|
|
41
|
+
<tbody>
|
|
42
|
+
{rows.map((row, index) => {
|
|
43
|
+
const { name, type, required, deprecated, children: description } = row.props;
|
|
44
|
+
const defaultValue = displayValue(row.props.default);
|
|
45
|
+
const borderClass = index === rows.length - 1 ? 'border-b-0' : 'border-b';
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<tr key={row.key ?? name}>
|
|
49
|
+
<td
|
|
50
|
+
className={`${borderClass} border-x-0 border-t-0 border-border px-0 py-3 pr-6 align-top text-sm text-foreground`}
|
|
51
|
+
>
|
|
52
|
+
<span className="inline-flex flex-wrap items-center gap-2">
|
|
53
|
+
{name}
|
|
54
|
+
{required ? (
|
|
55
|
+
<Badge size="xs" tone="primary">
|
|
56
|
+
required
|
|
57
|
+
</Badge>
|
|
58
|
+
) : null}
|
|
59
|
+
{deprecated ? <Badge size="xs">deprecated</Badge> : null}
|
|
60
|
+
</span>
|
|
61
|
+
</td>
|
|
62
|
+
<td
|
|
63
|
+
className={`${borderClass} border-x-0 border-t-0 border-border px-0 py-3 pr-6 align-top text-sm text-foreground`}
|
|
64
|
+
>
|
|
65
|
+
{displayValue(type)}
|
|
66
|
+
</td>
|
|
67
|
+
<td
|
|
68
|
+
className={`${borderClass} border-x-0 border-t-0 border-border px-0 py-3 align-top text-sm leading-6 text-foreground [&_p]:m-0`}
|
|
69
|
+
>
|
|
70
|
+
{description}
|
|
71
|
+
{defaultValue ? (
|
|
72
|
+
<span className="mt-1 block text-sm text-muted-foreground">
|
|
73
|
+
Default: {defaultValue}
|
|
74
|
+
</span>
|
|
75
|
+
) : null}
|
|
76
|
+
</td>
|
|
77
|
+
</tr>
|
|
78
|
+
);
|
|
79
|
+
})}
|
|
80
|
+
</tbody>
|
|
81
|
+
</table>
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { Badge } from './Badge';
|
|
3
|
+
import { styles } from './styles';
|
|
4
|
+
import { decodeHtmlEntities } from './utils';
|
|
5
|
+
|
|
6
|
+
type FieldValue = string | number | boolean | null | undefined;
|
|
7
|
+
|
|
8
|
+
export interface ResponseFieldProps {
|
|
9
|
+
name: string;
|
|
10
|
+
type?: FieldValue;
|
|
11
|
+
default?: FieldValue;
|
|
12
|
+
required?: boolean;
|
|
13
|
+
deprecated?: boolean;
|
|
14
|
+
children?: ReactNode;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function ResponseField({ name, type, required, deprecated, children }: ResponseFieldProps) {
|
|
18
|
+
const normalizedType =
|
|
19
|
+
typeof type === 'string'
|
|
20
|
+
? decodeHtmlEntities(type)
|
|
21
|
+
: type === undefined || type === null
|
|
22
|
+
? undefined
|
|
23
|
+
: String(type);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className={styles.field}>
|
|
27
|
+
<div className={styles.fieldHeader}>
|
|
28
|
+
<span className={styles.fieldName}>{name}</span>
|
|
29
|
+
{normalizedType ? <span className={styles.fieldType}>{normalizedType}</span> : null}
|
|
30
|
+
{required ? <Badge tone="primary">required</Badge> : null}
|
|
31
|
+
{deprecated ? <Badge>deprecated</Badge> : null}
|
|
32
|
+
</div>
|
|
33
|
+
{children ? <div className={styles.fieldBody}>{children}</div> : null}
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import { styles } from './styles';
|
|
4
|
+
import { toElementArray } from './utils';
|
|
5
|
+
|
|
6
|
+
interface StepChildProps {
|
|
7
|
+
title?: ReactNode;
|
|
8
|
+
children?: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function Step({ children }: StepChildProps) {
|
|
12
|
+
return <>{children}</>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface StepsProps {
|
|
16
|
+
children?: ReactNode;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function Steps({ children }: StepsProps) {
|
|
20
|
+
const steps = useMemo(() => {
|
|
21
|
+
return toElementArray<StepChildProps>(children).map((child, index) => {
|
|
22
|
+
return {
|
|
23
|
+
title: child.props.title || `Step ${index + 1}`,
|
|
24
|
+
content: child.props.children,
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
}, [children]);
|
|
28
|
+
|
|
29
|
+
if (!steps.length) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className={`${styles.steps} ${steps.length === 1 ? 'before:hidden' : ''}`}>
|
|
35
|
+
{steps.map((step, index) => (
|
|
36
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: steps are static MDX content
|
|
37
|
+
<div key={`step-${index + 1}`} className={styles.step}>
|
|
38
|
+
<div className={styles.stepNumber}>{index + 1}</div>
|
|
39
|
+
<div className={styles.stepContent}>
|
|
40
|
+
<div className={styles.stepTitle}>{step.title}</div>
|
|
41
|
+
<div>{step.content}</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
))}
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { TabsContent, TabsList, Tabs as TabsPrimitive, TabsTrigger } from '@/components/ui/tabs';
|
|
4
|
+
import { slugify, toElementArray } from './utils';
|
|
5
|
+
|
|
6
|
+
interface TabChildProps {
|
|
7
|
+
title?: ReactNode;
|
|
8
|
+
children?: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function Tab({ children }: TabChildProps) {
|
|
12
|
+
return <>{children}</>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface TabsProps {
|
|
16
|
+
children?: ReactNode;
|
|
17
|
+
group?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function Tabs({ children, group }: TabsProps) {
|
|
21
|
+
const tabs = useMemo(() => {
|
|
22
|
+
const counts = new Map<string, number>();
|
|
23
|
+
|
|
24
|
+
return toElementArray<TabChildProps>(children).map((child, index) => {
|
|
25
|
+
const title = child.props.title || `Tab ${index + 1}`;
|
|
26
|
+
const keyBase = slugify(title, `tab-${index + 1}`);
|
|
27
|
+
const count = counts.get(keyBase) || 0;
|
|
28
|
+
counts.set(keyBase, count + 1);
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
id: count ? `${keyBase}-${count + 1}` : keyBase,
|
|
32
|
+
title,
|
|
33
|
+
content: child.props.children,
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
}, [children]);
|
|
37
|
+
|
|
38
|
+
const defaultTab = tabs[0]?.id;
|
|
39
|
+
const [selectedKey, setSelectedKey] = useState<string | undefined>(defaultTab);
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (!group || typeof window === 'undefined') {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const storedKey = window.localStorage.getItem(`shiso-tabs:${group}`);
|
|
47
|
+
if (storedKey && tabs.some(tab => tab.id === storedKey)) {
|
|
48
|
+
setSelectedKey(storedKey);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (defaultTab) {
|
|
53
|
+
setSelectedKey(defaultTab);
|
|
54
|
+
}
|
|
55
|
+
}, [defaultTab, group, tabs]);
|
|
56
|
+
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (!tabs.length) {
|
|
59
|
+
setSelectedKey(undefined);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!selectedKey || !tabs.some(tab => tab.id === selectedKey)) {
|
|
64
|
+
setSelectedKey(tabs[0].id);
|
|
65
|
+
}
|
|
66
|
+
}, [selectedKey, tabs]);
|
|
67
|
+
|
|
68
|
+
const handleSelect = (id: string) => {
|
|
69
|
+
setSelectedKey(id);
|
|
70
|
+
|
|
71
|
+
if (group && typeof window !== 'undefined') {
|
|
72
|
+
window.localStorage.setItem(`shiso-tabs:${group}`, id);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
if (!tabs.length) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (tabs.length === 1) {
|
|
81
|
+
return <div className="my-4">{tabs[0].content}</div>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<TabsPrimitive
|
|
86
|
+
className="my-4 gap-0"
|
|
87
|
+
value={selectedKey}
|
|
88
|
+
onValueChange={value => {
|
|
89
|
+
if (typeof value === 'string') {
|
|
90
|
+
handleSelect(value);
|
|
91
|
+
}
|
|
92
|
+
}}
|
|
93
|
+
>
|
|
94
|
+
<TabsList
|
|
95
|
+
variant="line"
|
|
96
|
+
className="w-full justify-start gap-5 overflow-x-auto overflow-y-hidden p-0 shadow-[inset_0_-1px_0_var(--border)] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
|
97
|
+
aria-label="Content tabs"
|
|
98
|
+
>
|
|
99
|
+
{tabs.map(tab => (
|
|
100
|
+
<TabsTrigger
|
|
101
|
+
key={tab.id}
|
|
102
|
+
value={tab.id}
|
|
103
|
+
className="flex-none px-0 text-muted-foreground after:bg-primary data-active:text-foreground"
|
|
104
|
+
>
|
|
105
|
+
{tab.title}
|
|
106
|
+
</TabsTrigger>
|
|
107
|
+
))}
|
|
108
|
+
</TabsList>
|
|
109
|
+
{tabs.map(tab => (
|
|
110
|
+
<TabsContent key={tab.id} value={tab.id} className="pt-4 text-base">
|
|
111
|
+
{tab.content}
|
|
112
|
+
</TabsContent>
|
|
113
|
+
))}
|
|
114
|
+
</TabsPrimitive>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
TooltipContent,
|
|
4
|
+
Tooltip as TooltipPrimitive,
|
|
5
|
+
TooltipTrigger,
|
|
6
|
+
} from '@/components/ui/tooltip';
|
|
7
|
+
import { styles } from './styles';
|
|
8
|
+
|
|
9
|
+
export interface TooltipProps {
|
|
10
|
+
tip?: ReactNode;
|
|
11
|
+
children?: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function Tooltip({ tip, children }: TooltipProps) {
|
|
15
|
+
return (
|
|
16
|
+
<TooltipPrimitive>
|
|
17
|
+
<TooltipTrigger className={styles.tooltipTrigger}>{children}</TooltipTrigger>
|
|
18
|
+
<TooltipContent>{tip}</TooltipContent>
|
|
19
|
+
</TooltipPrimitive>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export * from './Accordion';
|
|
2
|
+
export * from './Badge';
|
|
3
|
+
export * from './Callout';
|
|
4
|
+
export * from './Card';
|
|
5
|
+
export * from './CodeGroup';
|
|
6
|
+
export * from './Columns';
|
|
7
|
+
export * from './Expandable';
|
|
8
|
+
export * from './Frame';
|
|
9
|
+
export * from './Icon';
|
|
10
|
+
export * from './ParamField';
|
|
11
|
+
export * from './PropertiesTable';
|
|
12
|
+
export * from './ResponseField';
|
|
13
|
+
export * from './Steps';
|
|
14
|
+
export * from './Tabs';
|
|
15
|
+
export * from './Tooltip';
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/** Shared Tailwind utility groups for the built-in MDX components. */
|
|
2
|
+
export const styles = {
|
|
3
|
+
accordion: 'my-3',
|
|
4
|
+
accordionGroup:
|
|
5
|
+
'my-4 overflow-hidden rounded-lg border border-border [&_[data-slot=accordion-item]]:rounded-none [&_[data-slot=accordion-item]]:border-0 [&_[data-slot=accordion-item]+[data-slot=accordion-item]]:border-t [&_[data-slot=accordion-trigger]]:rounded-none',
|
|
6
|
+
accordionItem: 'rounded-lg border border-border bg-transparent',
|
|
7
|
+
accordionTrigger:
|
|
8
|
+
'items-center px-4 py-3 text-base font-semibold text-foreground hover:no-underline',
|
|
9
|
+
accordionContent: 'px-4 pb-4 text-base leading-7',
|
|
10
|
+
expandableTrigger:
|
|
11
|
+
'items-center px-4 py-3 text-sm font-normal text-foreground hover:no-underline',
|
|
12
|
+
|
|
13
|
+
callout: 'my-4 flex items-start gap-3 rounded-lg border px-3 py-2.5 text-sm leading-6',
|
|
14
|
+
calloutIcon:
|
|
15
|
+
'mt-[calc((1.5em-1.25rem)/2)] inline-flex size-5 shrink-0 items-center justify-center [&_svg]:block [&_svg]:size-4',
|
|
16
|
+
calloutBody:
|
|
17
|
+
'flex min-w-0 flex-1 flex-col gap-[0.35rem] [&>:first-child]:mt-0 [&>:last-child]:mb-0 [&_:where(p,ul,ol)]:my-[0.35rem] [&>:where(p,ul,ol):first-child]:mt-0 [&>:where(p,ul,ol):last-child]:mb-0',
|
|
18
|
+
calloutTitle: 'font-semibold leading-6',
|
|
19
|
+
note: 'border-border bg-muted/50 text-foreground',
|
|
20
|
+
info: 'border-blue-300 bg-blue-50 text-blue-900 dark:border-blue-400/40 dark:bg-blue-400/10 dark:text-blue-300',
|
|
21
|
+
warning:
|
|
22
|
+
'border-amber-300 bg-amber-50 text-amber-900 dark:border-amber-400/40 dark:bg-amber-400/10 dark:text-amber-300',
|
|
23
|
+
tip: 'border-green-300 bg-green-50 text-green-900 dark:border-green-400/40 dark:bg-green-400/10 dark:text-green-300',
|
|
24
|
+
check:
|
|
25
|
+
'border-emerald-300 bg-emerald-50 text-emerald-900 dark:border-emerald-400/40 dark:bg-emerald-400/10 dark:text-emerald-300',
|
|
26
|
+
danger: 'border-destructive/40 bg-destructive/10 text-destructive',
|
|
27
|
+
|
|
28
|
+
card: 'block h-full gap-0 rounded-lg border border-border bg-transparent p-4 text-base ring-0 hover:border-input',
|
|
29
|
+
cardHorizontal:
|
|
30
|
+
'[&_[data-slot=card-inner]]:items-center [&_[data-slot=card-main]]:flex-row [&_[data-slot=card-main]]:items-center [&_[data-slot=card-main]]:gap-3 [&_[data-slot=card-body]]:m-0',
|
|
31
|
+
cardTyped:
|
|
32
|
+
'[&_[data-slot=card-title]]:text-inherit [&_[data-slot=card-body]]:text-inherit [&_[data-slot=card-cta]]:text-inherit [&_[data-slot=card-arrow]]:text-inherit',
|
|
33
|
+
cardImageLayout: 'p-0 [&_[data-slot=card-inner]]:p-6',
|
|
34
|
+
cardImage: 'block aspect-video w-full border-border border-b object-cover',
|
|
35
|
+
cardInner: 'flex items-start justify-between gap-3',
|
|
36
|
+
cardMain: 'flex min-w-0 flex-col gap-2',
|
|
37
|
+
cardHeader: 'flex items-center gap-2',
|
|
38
|
+
cardIcon: 'inline-flex shrink-0 items-center justify-center [&_img]:size-4 [&_svg]:size-4',
|
|
39
|
+
cardTitle: 'text-base font-semibold text-foreground',
|
|
40
|
+
cardBody: 'text-muted-foreground [&>:first-child]:mt-0 [&>:last-child]:mb-0',
|
|
41
|
+
cardCta:
|
|
42
|
+
'flex shrink-0 items-center gap-[0.35rem] text-[0.9rem] font-medium text-muted-foreground',
|
|
43
|
+
cardArrow: 'shrink-0 text-muted-foreground',
|
|
44
|
+
|
|
45
|
+
grid: 'my-4 grid grid-cols-1 gap-4',
|
|
46
|
+
gridCols1: 'md:grid-cols-1',
|
|
47
|
+
gridCols2: 'md:grid-cols-2',
|
|
48
|
+
gridCols3: 'md:grid-cols-3',
|
|
49
|
+
gridCols4: 'md:grid-cols-4',
|
|
50
|
+
column: 'min-w-0 [&>:first-child]:mt-0 [&>:last-child]:mb-0',
|
|
51
|
+
icon: 'inline-block align-[-0.145em]',
|
|
52
|
+
|
|
53
|
+
tabs: 'my-4 gap-0',
|
|
54
|
+
|
|
55
|
+
frameWrapper: 'my-4',
|
|
56
|
+
frameHint: 'mb-4 flex items-start gap-2 text-sm font-medium leading-5 text-foreground',
|
|
57
|
+
frameHintIcon:
|
|
58
|
+
'flex h-5 shrink-0 items-center text-muted-foreground [&_svg]:size-4 [&_svg]:fill-current',
|
|
59
|
+
frame: 'relative overflow-hidden rounded-2xl border border-border bg-muted/25 p-2',
|
|
60
|
+
frameContent:
|
|
61
|
+
'relative flex justify-center overflow-hidden rounded-xl [&_p]:m-0 [&_img]:w-full [&_video]:w-full',
|
|
62
|
+
frameCaption:
|
|
63
|
+
'relative mt-3 px-4 pb-1 text-center text-sm leading-5 text-muted-foreground [&_p]:m-0 [&_a]:font-semibold',
|
|
64
|
+
|
|
65
|
+
field: 'my-3',
|
|
66
|
+
paramField: 'my-3',
|
|
67
|
+
fieldHeader: 'flex flex-wrap items-center gap-2',
|
|
68
|
+
fieldBody: 'mt-3',
|
|
69
|
+
fieldType: 'text-[0.9rem] text-muted-foreground',
|
|
70
|
+
fieldName: 'font-bold text-foreground',
|
|
71
|
+
code: 'rounded-sm bg-[color-mix(in_srgb,currentColor_4%,transparent)] px-[0.35rem] py-[0.1rem] text-sm text-foreground font-mono',
|
|
72
|
+
steps:
|
|
73
|
+
"relative my-4 flex flex-col gap-5 before:absolute before:top-4 before:bottom-4 before:left-4 before:w-px before:-translate-x-1/2 before:bg-border before:content-['']",
|
|
74
|
+
step: 'relative flex items-start gap-4',
|
|
75
|
+
stepNumber:
|
|
76
|
+
'relative z-[1] flex size-8 shrink-0 items-center justify-center rounded-full border border-border bg-card text-[0.9rem] font-semibold text-foreground',
|
|
77
|
+
stepContent: 'flex min-w-0 grow flex-col gap-2',
|
|
78
|
+
stepTitle: 'text-[1.1rem] font-semibold text-foreground',
|
|
79
|
+
|
|
80
|
+
tooltipTrigger:
|
|
81
|
+
'peer cursor-help border-0 border-muted-foreground border-b border-dotted bg-transparent p-0 text-inherit [font:inherit]',
|
|
82
|
+
} as const;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Children, createElement, isValidElement, type ReactElement, type ReactNode } from 'react';
|
|
2
|
+
import { getIcon } from '@/lib/icons';
|
|
3
|
+
|
|
4
|
+
interface NodeWithClassName {
|
|
5
|
+
className?: string;
|
|
6
|
+
'data-title'?: string;
|
|
7
|
+
children?: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function toElementArray<T>(children: ReactNode): ReactElement<T>[] {
|
|
11
|
+
return Children.toArray(children).filter(isValidElement) as ReactElement<T>[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Icon props accept either a rendered node or a lucide icon name, since MDX authors
|
|
16
|
+
* write `icon="rocket"` while first-party components pass elements directly.
|
|
17
|
+
*/
|
|
18
|
+
export function resolveIcon(icon: ReactNode | string, size = 14): ReactNode {
|
|
19
|
+
if (typeof icon !== 'string') {
|
|
20
|
+
return icon;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (
|
|
24
|
+
/^(?:https?:|data:image\/|\/|\.\.?\/)/i.test(icon) ||
|
|
25
|
+
/\.(?:avif|gif|jpe?g|png|svg|webp)(?:[?#].*)?$/i.test(icon)
|
|
26
|
+
) {
|
|
27
|
+
return createElement('img', { src: icon, alt: '', width: size, height: size });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const Component = getIcon(icon);
|
|
31
|
+
|
|
32
|
+
return Component ? createElement(Component, { size }) : null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function slugify(value: ReactNode, fallback: string): string {
|
|
36
|
+
const text = typeof value === 'string' ? value : '';
|
|
37
|
+
const normalized = text
|
|
38
|
+
.trim()
|
|
39
|
+
.toLowerCase()
|
|
40
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
41
|
+
.replace(/^-+|-+$/g, '');
|
|
42
|
+
|
|
43
|
+
return normalized || fallback;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const GRID_COLS_CLASSES = {
|
|
47
|
+
1: 'gridCols1',
|
|
48
|
+
2: 'gridCols2',
|
|
49
|
+
3: 'gridCols3',
|
|
50
|
+
4: 'gridCols4',
|
|
51
|
+
} as const;
|
|
52
|
+
|
|
53
|
+
/** Shared by CardGroup and Columns: clamps a column count to a CSS module class name. */
|
|
54
|
+
export function gridColsClass(cols: number): (typeof GRID_COLS_CLASSES)[1 | 2 | 3 | 4] {
|
|
55
|
+
const columns = Math.min(Math.max(Math.round(cols) || 1, 1), 4) as 1 | 2 | 3 | 4;
|
|
56
|
+
|
|
57
|
+
return GRID_COLS_CLASSES[columns];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function decodeHtmlEntities(value: string): string {
|
|
61
|
+
return value
|
|
62
|
+
.replace(/&#x([0-9a-fA-F]+);/g, (_, hex) => {
|
|
63
|
+
return String.fromCodePoint(Number.parseInt(hex, 16));
|
|
64
|
+
})
|
|
65
|
+
.replace(/&#([0-9]+);/g, (_, decimal) => {
|
|
66
|
+
return String.fromCodePoint(Number.parseInt(decimal, 10));
|
|
67
|
+
})
|
|
68
|
+
.replace(/"/g, '"')
|
|
69
|
+
.replace(/'/g, "'")
|
|
70
|
+
.replace(/</g, '<')
|
|
71
|
+
.replace(/>/g, '>')
|
|
72
|
+
.replace(/&/g, '&');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function normalizeLanguage(value: string): string {
|
|
76
|
+
const label = value.toLowerCase();
|
|
77
|
+
const aliases: Record<string, string> = {
|
|
78
|
+
js: 'javascript',
|
|
79
|
+
ts: 'typescript',
|
|
80
|
+
sh: 'shell',
|
|
81
|
+
zsh: 'shell',
|
|
82
|
+
shell: 'shell',
|
|
83
|
+
bash: 'bash',
|
|
84
|
+
yml: 'yaml',
|
|
85
|
+
md: 'markdown',
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
return aliases[label] || label;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function findCodeLanguage(node: ReactNode): string | undefined {
|
|
92
|
+
if (!isValidElement<NodeWithClassName>(node)) {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const className = node.props.className;
|
|
97
|
+
if (typeof className === 'string') {
|
|
98
|
+
const match = className.match(/language-([a-z0-9-]+)/i);
|
|
99
|
+
if (match?.[1]) {
|
|
100
|
+
return normalizeLanguage(match[1]);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return findCodeLanguage(node.props.children);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function findCodeTitle(node: ReactNode): string | undefined {
|
|
108
|
+
if (!isValidElement<NodeWithClassName>(node)) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const title = node.props['data-title'];
|
|
113
|
+
if (typeof title === 'string' && title.trim()) {
|
|
114
|
+
return title.trim();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return findCodeTitle(node.props.children);
|
|
118
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Accordion as AccordionPrimitive } from '@base-ui/react/accordion';
|
|
2
|
+
import { ChevronDownIcon, ChevronUpIcon } from 'lucide-react';
|
|
3
|
+
import { cn } from '@/lib/utils';
|
|
4
|
+
|
|
5
|
+
function Accordion({ className, ...props }: AccordionPrimitive.Root.Props) {
|
|
6
|
+
return (
|
|
7
|
+
<AccordionPrimitive.Root
|
|
8
|
+
data-slot="accordion"
|
|
9
|
+
className={cn('flex w-full flex-col', className)}
|
|
10
|
+
{...props}
|
|
11
|
+
/>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function AccordionItem({ className, ...props }: AccordionPrimitive.Item.Props) {
|
|
16
|
+
return (
|
|
17
|
+
<AccordionPrimitive.Item
|
|
18
|
+
data-slot="accordion-item"
|
|
19
|
+
className={cn('not-last:border-b', className)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function AccordionTrigger({ className, children, ...props }: AccordionPrimitive.Trigger.Props) {
|
|
26
|
+
return (
|
|
27
|
+
<AccordionPrimitive.Header className="m-0 flex">
|
|
28
|
+
<AccordionPrimitive.Trigger
|
|
29
|
+
data-slot="accordion-trigger"
|
|
30
|
+
className={cn(
|
|
31
|
+
'group/accordion-trigger relative flex flex-1 items-start justify-between rounded-lg border border-transparent py-2.5 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:after:border-ring aria-disabled:pointer-events-none aria-disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-muted-foreground',
|
|
32
|
+
className,
|
|
33
|
+
)}
|
|
34
|
+
{...props}
|
|
35
|
+
>
|
|
36
|
+
{children}
|
|
37
|
+
<ChevronDownIcon
|
|
38
|
+
data-slot="accordion-trigger-icon"
|
|
39
|
+
className="pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden"
|
|
40
|
+
/>
|
|
41
|
+
<ChevronUpIcon
|
|
42
|
+
data-slot="accordion-trigger-icon"
|
|
43
|
+
className="pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline"
|
|
44
|
+
/>
|
|
45
|
+
</AccordionPrimitive.Trigger>
|
|
46
|
+
</AccordionPrimitive.Header>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function AccordionContent({ className, children, ...props }: AccordionPrimitive.Panel.Props) {
|
|
51
|
+
return (
|
|
52
|
+
<AccordionPrimitive.Panel
|
|
53
|
+
data-slot="accordion-content"
|
|
54
|
+
className="overflow-hidden text-sm data-open:animate-accordion-down data-closed:animate-accordion-up"
|
|
55
|
+
{...props}
|
|
56
|
+
>
|
|
57
|
+
<div
|
|
58
|
+
className={cn(
|
|
59
|
+
'h-(--accordion-panel-height) pt-0 pb-2.5 data-ending-style:h-0 data-starting-style:h-0 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4',
|
|
60
|
+
className,
|
|
61
|
+
)}
|
|
62
|
+
>
|
|
63
|
+
{children}
|
|
64
|
+
</div>
|
|
65
|
+
</AccordionPrimitive.Panel>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
2
|
+
import type * as React from 'react';
|
|
3
|
+
|
|
4
|
+
import { cn } from '@/lib/utils';
|
|
5
|
+
|
|
6
|
+
const alertVariants = cva(
|
|
7
|
+
"group/alert relative grid w-full gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: 'bg-card text-card-foreground',
|
|
12
|
+
destructive:
|
|
13
|
+
'bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
defaultVariants: {
|
|
17
|
+
variant: 'default',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
function Alert({
|
|
23
|
+
className,
|
|
24
|
+
variant,
|
|
25
|
+
...props
|
|
26
|
+
}: React.ComponentProps<'div'> & VariantProps<typeof alertVariants>) {
|
|
27
|
+
return (
|
|
28
|
+
<div
|
|
29
|
+
data-slot="alert"
|
|
30
|
+
role="alert"
|
|
31
|
+
className={cn(alertVariants({ variant }), className)}
|
|
32
|
+
{...props}
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function AlertTitle({ className, ...props }: React.ComponentProps<'div'>) {
|
|
38
|
+
return (
|
|
39
|
+
<div
|
|
40
|
+
data-slot="alert-title"
|
|
41
|
+
className={cn(
|
|
42
|
+
'font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground',
|
|
43
|
+
className,
|
|
44
|
+
)}
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function AlertDescription({ className, ...props }: React.ComponentProps<'div'>) {
|
|
51
|
+
return (
|
|
52
|
+
<div
|
|
53
|
+
data-slot="alert-description"
|
|
54
|
+
className={cn(
|
|
55
|
+
'text-sm text-balance text-muted-foreground md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4',
|
|
56
|
+
className,
|
|
57
|
+
)}
|
|
58
|
+
{...props}
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function AlertAction({ className, ...props }: React.ComponentProps<'div'>) {
|
|
64
|
+
return (
|
|
65
|
+
<div data-slot="alert-action" className={cn('absolute top-2 right-2', className)} {...props} />
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export { Alert, AlertAction, AlertDescription, AlertTitle };
|