@umami/shiso 0.54.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,171 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { Badge as BadgePrimitive } from '@/components/ui/badge';
|
|
3
|
+
import { cn } from '@/lib/utils';
|
|
4
|
+
import { resolveIcon } from './utils';
|
|
5
|
+
|
|
6
|
+
export type BadgeColor =
|
|
7
|
+
| 'gray'
|
|
8
|
+
| 'blue'
|
|
9
|
+
| 'green'
|
|
10
|
+
| 'yellow'
|
|
11
|
+
| 'orange'
|
|
12
|
+
| 'red'
|
|
13
|
+
| 'purple'
|
|
14
|
+
| 'white'
|
|
15
|
+
| 'surface'
|
|
16
|
+
| 'white-destructive'
|
|
17
|
+
| 'surface-destructive';
|
|
18
|
+
|
|
19
|
+
export type BadgeSize = 'xs' | 'sm' | 'md' | 'lg';
|
|
20
|
+
export type BadgeShape = 'rounded' | 'pill';
|
|
21
|
+
|
|
22
|
+
export interface BadgeProps {
|
|
23
|
+
color?: BadgeColor;
|
|
24
|
+
size?: BadgeSize;
|
|
25
|
+
shape?: BadgeShape;
|
|
26
|
+
icon?: ReactNode;
|
|
27
|
+
stroke?: boolean;
|
|
28
|
+
disabled?: boolean;
|
|
29
|
+
className?: string;
|
|
30
|
+
/** @deprecated Use `color` instead. */
|
|
31
|
+
tone?: 'muted' | 'primary';
|
|
32
|
+
children?: ReactNode;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const colorClasses: Record<BadgeColor | 'primary', { filled: string; stroke: string }> = {
|
|
36
|
+
gray: {
|
|
37
|
+
filled: 'border-transparent bg-muted text-muted-foreground',
|
|
38
|
+
stroke: 'border-border bg-transparent text-muted-foreground',
|
|
39
|
+
},
|
|
40
|
+
blue: {
|
|
41
|
+
filled: 'border-transparent bg-blue-100 text-blue-800 dark:bg-blue-400/15 dark:text-blue-300',
|
|
42
|
+
stroke:
|
|
43
|
+
'border-blue-300 bg-transparent text-blue-700 dark:border-blue-400/50 dark:text-blue-300',
|
|
44
|
+
},
|
|
45
|
+
green: {
|
|
46
|
+
filled:
|
|
47
|
+
'border-transparent bg-green-100 text-green-800 dark:bg-green-400/15 dark:text-green-300',
|
|
48
|
+
stroke:
|
|
49
|
+
'border-green-300 bg-transparent text-green-700 dark:border-green-400/50 dark:text-green-300',
|
|
50
|
+
},
|
|
51
|
+
yellow: {
|
|
52
|
+
filled:
|
|
53
|
+
'border-transparent bg-yellow-100 text-yellow-800 dark:bg-yellow-400/15 dark:text-yellow-300',
|
|
54
|
+
stroke:
|
|
55
|
+
'border-yellow-300 bg-transparent text-yellow-700 dark:border-yellow-400/50 dark:text-yellow-300',
|
|
56
|
+
},
|
|
57
|
+
orange: {
|
|
58
|
+
filled:
|
|
59
|
+
'border-transparent bg-orange-100 text-orange-800 dark:bg-orange-400/15 dark:text-orange-300',
|
|
60
|
+
stroke:
|
|
61
|
+
'border-orange-300 bg-transparent text-orange-700 dark:border-orange-400/50 dark:text-orange-300',
|
|
62
|
+
},
|
|
63
|
+
red: {
|
|
64
|
+
filled: 'border-transparent bg-red-100 text-red-800 dark:bg-red-400/15 dark:text-red-300',
|
|
65
|
+
stroke: 'border-red-300 bg-transparent text-red-700 dark:border-red-400/50 dark:text-red-300',
|
|
66
|
+
},
|
|
67
|
+
purple: {
|
|
68
|
+
filled:
|
|
69
|
+
'border-transparent bg-purple-100 text-purple-800 dark:bg-purple-400/15 dark:text-purple-300',
|
|
70
|
+
stroke:
|
|
71
|
+
'border-purple-300 bg-transparent text-purple-700 dark:border-purple-400/50 dark:text-purple-300',
|
|
72
|
+
},
|
|
73
|
+
white: {
|
|
74
|
+
filled: 'border-border bg-white text-zinc-900',
|
|
75
|
+
stroke: 'border-white bg-transparent text-foreground dark:text-white',
|
|
76
|
+
},
|
|
77
|
+
surface: {
|
|
78
|
+
filled: 'border-border bg-background text-foreground',
|
|
79
|
+
stroke: 'border-border bg-transparent text-foreground',
|
|
80
|
+
},
|
|
81
|
+
'white-destructive': {
|
|
82
|
+
filled: 'border-border bg-white text-destructive',
|
|
83
|
+
stroke: 'border-white bg-transparent text-destructive',
|
|
84
|
+
},
|
|
85
|
+
'surface-destructive': {
|
|
86
|
+
filled: 'border-destructive/30 bg-background text-destructive',
|
|
87
|
+
stroke: 'border-destructive/50 bg-transparent text-destructive',
|
|
88
|
+
},
|
|
89
|
+
primary: {
|
|
90
|
+
filled: 'border-transparent bg-primary/10 text-primary',
|
|
91
|
+
stroke: 'border-primary/50 bg-transparent text-primary',
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const sizeClasses: Record<BadgeSize, string> = {
|
|
96
|
+
xs: 'gap-1 px-1.5 py-0 text-[0.625rem] leading-4',
|
|
97
|
+
sm: 'gap-1 px-2 py-0.5 text-xs leading-4',
|
|
98
|
+
md: 'gap-1.5 px-2.5 py-0.5 text-sm leading-5',
|
|
99
|
+
lg: 'gap-1.5 px-3 py-1 text-base leading-5',
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const iconSizes: Record<BadgeSize, number> = {
|
|
103
|
+
xs: 10,
|
|
104
|
+
sm: 12,
|
|
105
|
+
md: 14,
|
|
106
|
+
lg: 16,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
function isImageSource(value: string): boolean {
|
|
110
|
+
return (
|
|
111
|
+
/^(?:https?:|data:image\/|\/|\.\.?\/)/i.test(value) ||
|
|
112
|
+
/\.(?:avif|gif|jpe?g|png|svg|webp)(?:[?#].*)?$/i.test(value)
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function BadgeIcon({ icon, size }: { icon: ReactNode; size: number }) {
|
|
117
|
+
const content =
|
|
118
|
+
typeof icon === 'string' && isImageSource(icon) ? (
|
|
119
|
+
<img src={icon} alt="" width={size} height={size} />
|
|
120
|
+
) : (
|
|
121
|
+
resolveIcon(icon, size)
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
if (!content) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<span
|
|
130
|
+
className="inline-flex shrink-0 items-center justify-center [&>img]:size-full [&>svg]:size-full"
|
|
131
|
+
style={{ width: size, height: size }}
|
|
132
|
+
aria-hidden="true"
|
|
133
|
+
>
|
|
134
|
+
{content}
|
|
135
|
+
</span>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function Badge({
|
|
140
|
+
color,
|
|
141
|
+
size = 'md',
|
|
142
|
+
shape = 'rounded',
|
|
143
|
+
icon,
|
|
144
|
+
stroke = false,
|
|
145
|
+
disabled = false,
|
|
146
|
+
className,
|
|
147
|
+
tone,
|
|
148
|
+
children,
|
|
149
|
+
}: BadgeProps) {
|
|
150
|
+
const resolvedColor = color || (tone === 'primary' ? 'primary' : 'gray');
|
|
151
|
+
const palette = colorClasses[resolvedColor];
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<BadgePrimitive
|
|
155
|
+
variant="secondary"
|
|
156
|
+
className={cn(
|
|
157
|
+
'h-auto justify-start overflow-visible whitespace-normal border align-middle transition-none',
|
|
158
|
+
sizeClasses[size],
|
|
159
|
+
stroke ? palette.stroke : palette.filled,
|
|
160
|
+
shape === 'pill' ? 'rounded-full' : 'rounded-md',
|
|
161
|
+
disabled && 'cursor-not-allowed opacity-50',
|
|
162
|
+
className,
|
|
163
|
+
)}
|
|
164
|
+
aria-disabled={disabled || undefined}
|
|
165
|
+
data-disabled={disabled ? '' : undefined}
|
|
166
|
+
>
|
|
167
|
+
{icon ? <BadgeIcon icon={icon} size={iconSizes[size]} /> : null}
|
|
168
|
+
{children}
|
|
169
|
+
</BadgePrimitive>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Check as CheckIcon,
|
|
4
|
+
CircleAlert,
|
|
5
|
+
InfoIcon,
|
|
6
|
+
Lightbulb,
|
|
7
|
+
TriangleAlert,
|
|
8
|
+
} from '@/components/icons';
|
|
9
|
+
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
|
10
|
+
import { styles } from './styles';
|
|
11
|
+
import { resolveIcon } from './utils';
|
|
12
|
+
|
|
13
|
+
export interface CalloutProps {
|
|
14
|
+
title?: ReactNode;
|
|
15
|
+
icon?: ReactNode | string;
|
|
16
|
+
children?: ReactNode;
|
|
17
|
+
variant?: 'note' | 'tip' | 'warning' | 'info' | 'check' | 'danger';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const CALLOUT_ICONS = {
|
|
21
|
+
note: <InfoIcon size={14} />,
|
|
22
|
+
info: <InfoIcon size={14} />,
|
|
23
|
+
warning: <TriangleAlert size={14} />,
|
|
24
|
+
tip: <Lightbulb size={14} />,
|
|
25
|
+
check: <CheckIcon size={14} />,
|
|
26
|
+
danger: <CircleAlert size={14} />,
|
|
27
|
+
} as const;
|
|
28
|
+
|
|
29
|
+
export function Callout({ title, icon, children, variant = 'note' }: CalloutProps) {
|
|
30
|
+
return (
|
|
31
|
+
<Alert role="note" className={`${styles.callout} ${styles[variant]}`}>
|
|
32
|
+
<span className={styles.calloutIcon} aria-hidden={true}>
|
|
33
|
+
{resolveIcon(icon) || CALLOUT_ICONS[variant]}
|
|
34
|
+
</span>
|
|
35
|
+
<div className={styles.calloutBody}>
|
|
36
|
+
{title ? <AlertTitle className={styles.calloutTitle}>{title}</AlertTitle> : null}
|
|
37
|
+
<AlertDescription className="text-sm text-inherit leading-6">{children}</AlertDescription>
|
|
38
|
+
</div>
|
|
39
|
+
</Alert>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function Note({ children }: { children?: ReactNode }) {
|
|
44
|
+
return <Callout variant="note">{children}</Callout>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function Tip({ children }: { children?: ReactNode }) {
|
|
48
|
+
return <Callout variant="tip">{children}</Callout>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function Warning({ children }: { children?: ReactNode }) {
|
|
52
|
+
return <Callout variant="warning">{children}</Callout>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function Info({ children }: { children?: ReactNode }) {
|
|
56
|
+
return <Callout variant="info">{children}</Callout>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function Check({ children }: { children?: ReactNode }) {
|
|
60
|
+
return <Callout variant="check">{children}</Callout>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function Danger({ children }: { children?: ReactNode }) {
|
|
64
|
+
return <Callout variant="danger">{children}</Callout>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function WarningBanner({ children }: { children?: ReactNode }) {
|
|
68
|
+
return (
|
|
69
|
+
<Callout variant="warning" icon={<CircleAlert size={14} />}>
|
|
70
|
+
{children}
|
|
71
|
+
</Callout>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { Link } from 'react-router';
|
|
3
|
+
import {
|
|
4
|
+
Check as CheckIcon,
|
|
5
|
+
ChevronRight,
|
|
6
|
+
CircleAlert,
|
|
7
|
+
InfoIcon,
|
|
8
|
+
Lightbulb,
|
|
9
|
+
TriangleAlert,
|
|
10
|
+
} from '@/components/icons';
|
|
11
|
+
import { CardContent, Card as CardPrimitive } from '@/components/ui/card';
|
|
12
|
+
import { styles } from './styles';
|
|
13
|
+
import { gridColsClass, resolveIcon } from './utils';
|
|
14
|
+
|
|
15
|
+
export interface CardProps {
|
|
16
|
+
title?: ReactNode;
|
|
17
|
+
href?: string;
|
|
18
|
+
icon?: ReactNode | string;
|
|
19
|
+
color?: string;
|
|
20
|
+
img?: string;
|
|
21
|
+
cta?: string;
|
|
22
|
+
horizontal?: boolean;
|
|
23
|
+
arrow?: boolean;
|
|
24
|
+
type?: CardType;
|
|
25
|
+
children?: ReactNode;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type CardType = 'note' | 'info' | 'warning' | 'tip' | 'check' | 'danger';
|
|
29
|
+
|
|
30
|
+
const CARD_TYPE_ICONS: Record<CardType, ReactNode> = {
|
|
31
|
+
note: <InfoIcon size={16} />,
|
|
32
|
+
info: <InfoIcon size={16} />,
|
|
33
|
+
warning: <TriangleAlert size={16} />,
|
|
34
|
+
tip: <Lightbulb size={16} />,
|
|
35
|
+
check: <CheckIcon size={16} />,
|
|
36
|
+
danger: <CircleAlert size={16} />,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const CARD_TYPE_HOVER: Record<CardType, string> = {
|
|
40
|
+
note: 'hover:border-border',
|
|
41
|
+
info: 'hover:border-blue-300 dark:hover:border-blue-400/40',
|
|
42
|
+
warning: 'hover:border-amber-300 dark:hover:border-amber-400/40',
|
|
43
|
+
tip: 'hover:border-green-300 dark:hover:border-green-400/40',
|
|
44
|
+
check: 'hover:border-emerald-300 dark:hover:border-emerald-400/40',
|
|
45
|
+
danger: 'hover:border-destructive/40',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
function DocsCardContent({
|
|
49
|
+
title,
|
|
50
|
+
icon,
|
|
51
|
+
color,
|
|
52
|
+
img,
|
|
53
|
+
cta,
|
|
54
|
+
arrow,
|
|
55
|
+
type,
|
|
56
|
+
children,
|
|
57
|
+
}: Omit<CardProps, 'href'>) {
|
|
58
|
+
const resolvedIcon = resolveIcon(icon, 16) || (type ? CARD_TYPE_ICONS[type] : null);
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<CardContent className="p-0">
|
|
62
|
+
{img ? <img src={img} alt="" className={styles.cardImage} /> : null}
|
|
63
|
+
<div className={styles.cardInner} data-slot="card-inner">
|
|
64
|
+
<div className={styles.cardMain} data-slot="card-main">
|
|
65
|
+
<div className={styles.cardHeader}>
|
|
66
|
+
{resolvedIcon ? (
|
|
67
|
+
<span
|
|
68
|
+
className={`${styles.cardIcon} ${type ? 'text-inherit' : 'text-primary'}`}
|
|
69
|
+
style={color ? { color } : undefined}
|
|
70
|
+
>
|
|
71
|
+
{resolvedIcon}
|
|
72
|
+
</span>
|
|
73
|
+
) : null}
|
|
74
|
+
{title ? (
|
|
75
|
+
<div className={styles.cardTitle} data-slot="card-title">
|
|
76
|
+
{title}
|
|
77
|
+
</div>
|
|
78
|
+
) : null}
|
|
79
|
+
</div>
|
|
80
|
+
{children ? (
|
|
81
|
+
<div className={styles.cardBody} data-slot="card-body">
|
|
82
|
+
{children}
|
|
83
|
+
</div>
|
|
84
|
+
) : null}
|
|
85
|
+
</div>
|
|
86
|
+
{cta || arrow ? (
|
|
87
|
+
<div className={styles.cardCta} data-slot="card-cta">
|
|
88
|
+
{cta ? <span>{cta}</span> : null}
|
|
89
|
+
{arrow ? (
|
|
90
|
+
<ChevronRight size={14} className={styles.cardArrow} data-slot="card-arrow" />
|
|
91
|
+
) : null}
|
|
92
|
+
</div>
|
|
93
|
+
) : null}
|
|
94
|
+
</div>
|
|
95
|
+
</CardContent>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function Card({
|
|
100
|
+
title,
|
|
101
|
+
href,
|
|
102
|
+
icon,
|
|
103
|
+
color,
|
|
104
|
+
img,
|
|
105
|
+
cta,
|
|
106
|
+
horizontal,
|
|
107
|
+
arrow,
|
|
108
|
+
type,
|
|
109
|
+
children,
|
|
110
|
+
}: CardProps) {
|
|
111
|
+
const external = typeof href === 'string' && /^https?:\/\//i.test(href);
|
|
112
|
+
const showArrow = arrow ?? external;
|
|
113
|
+
const className = `${styles.card} ${type ? `${styles.cardTyped} ${styles[type]} ${CARD_TYPE_HOVER[type]}` : ''} ${horizontal ? styles.cardHorizontal : ''} ${img ? styles.cardImageLayout : ''}`;
|
|
114
|
+
const content = (
|
|
115
|
+
<CardPrimitive className={className}>
|
|
116
|
+
<DocsCardContent
|
|
117
|
+
title={title}
|
|
118
|
+
icon={icon}
|
|
119
|
+
color={color}
|
|
120
|
+
img={img}
|
|
121
|
+
cta={cta}
|
|
122
|
+
arrow={showArrow}
|
|
123
|
+
type={type}
|
|
124
|
+
>
|
|
125
|
+
{children}
|
|
126
|
+
</DocsCardContent>
|
|
127
|
+
</CardPrimitive>
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
if (!href) {
|
|
131
|
+
return content;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const linkClassName = 'block h-full no-underline hover:no-underline active:no-underline';
|
|
135
|
+
|
|
136
|
+
if (external) {
|
|
137
|
+
return (
|
|
138
|
+
<a href={href} className={linkClassName} target="_blank" rel="noreferrer">
|
|
139
|
+
{content}
|
|
140
|
+
</a>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<Link to={href} className={linkClassName}>
|
|
146
|
+
{content}
|
|
147
|
+
</Link>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface CardGroupProps {
|
|
152
|
+
children?: ReactNode;
|
|
153
|
+
cols?: 1 | 2 | 3 | 4;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function CardGroup({ children, cols = 2 }: CardGroupProps) {
|
|
157
|
+
return <div className={`${styles.grid} ${styles[gridColsClass(cols)]}`}>{children}</div>;
|
|
158
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { useMemo, useState } from 'react';
|
|
3
|
+
import { TabsContent, TabsList, Tabs as TabsPrimitive, TabsTrigger } from '@/components/ui/tabs';
|
|
4
|
+
import { styles } from './styles';
|
|
5
|
+
import { findCodeLanguage, findCodeTitle, slugify, toElementArray } from './utils';
|
|
6
|
+
|
|
7
|
+
export interface CodeGroupProps {
|
|
8
|
+
children?: ReactNode;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function CodeGroup({ children }: CodeGroupProps) {
|
|
12
|
+
const blocks = useMemo(() => {
|
|
13
|
+
const counts = new Map<string, number>();
|
|
14
|
+
|
|
15
|
+
return toElementArray<{ title?: string }>(children).map((child, index) => {
|
|
16
|
+
const explicitTitle = child.props?.title;
|
|
17
|
+
const titleValue =
|
|
18
|
+
(typeof explicitTitle === 'string' && explicitTitle.trim()) ||
|
|
19
|
+
findCodeTitle(child) ||
|
|
20
|
+
findCodeLanguage(child) ||
|
|
21
|
+
`snippet ${index + 1}`;
|
|
22
|
+
const keyBase = slugify(titleValue, `snippet-${index + 1}`);
|
|
23
|
+
const count = counts.get(keyBase) || 0;
|
|
24
|
+
counts.set(keyBase, count + 1);
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
id: count ? `${keyBase}-${count + 1}` : keyBase,
|
|
28
|
+
title: count ? `${titleValue} ${count + 1}` : titleValue,
|
|
29
|
+
content: child,
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
}, [children]);
|
|
33
|
+
|
|
34
|
+
const [selectedKey, setSelectedKey] = useState<string | undefined>(blocks[0]?.id);
|
|
35
|
+
const selected = blocks.some(block => block.id === selectedKey) ? selectedKey : blocks[0]?.id;
|
|
36
|
+
|
|
37
|
+
if (!blocks.length) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (blocks.length === 1) {
|
|
42
|
+
return <div className={styles.tabs}>{blocks[0].content}</div>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<TabsPrimitive
|
|
47
|
+
value={selected}
|
|
48
|
+
onValueChange={setSelectedKey}
|
|
49
|
+
className="my-4 gap-0 overflow-hidden rounded-lg bg-muted/50 [&_[data-slot=code-block]]:my-0 [&_[data-slot=code-block]]:rounded-none [&_[data-slot=code-block]]:border-0 [&_[data-slot=code-block]]:bg-transparent"
|
|
50
|
+
>
|
|
51
|
+
<TabsList
|
|
52
|
+
variant="line"
|
|
53
|
+
className="h-9 w-full justify-start gap-5 overflow-x-auto overflow-y-hidden rounded-none px-3 py-0 shadow-[inset_0_-1px_0_var(--border)] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
|
54
|
+
aria-label="Code snippets"
|
|
55
|
+
>
|
|
56
|
+
{blocks.map(block => (
|
|
57
|
+
<TabsTrigger
|
|
58
|
+
key={block.id}
|
|
59
|
+
value={block.id}
|
|
60
|
+
className="flex-none px-0 text-muted-foreground after:bg-primary data-active:text-foreground"
|
|
61
|
+
>
|
|
62
|
+
{block.title}
|
|
63
|
+
</TabsTrigger>
|
|
64
|
+
))}
|
|
65
|
+
</TabsList>
|
|
66
|
+
{blocks.map(block => (
|
|
67
|
+
<TabsContent key={block.id} value={block.id}>
|
|
68
|
+
{block.content}
|
|
69
|
+
</TabsContent>
|
|
70
|
+
))}
|
|
71
|
+
</TabsPrimitive>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { styles } from './styles';
|
|
3
|
+
import { gridColsClass } from './utils';
|
|
4
|
+
|
|
5
|
+
export interface ColumnsProps {
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
cols?: 1 | 2 | 3 | 4;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function Columns({ children, cols = 2 }: ColumnsProps) {
|
|
11
|
+
return <div className={`${styles.grid} ${styles[gridColsClass(cols)]}`}>{children}</div>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ColumnProps {
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function Column({ children }: ColumnProps) {
|
|
19
|
+
return <div className={styles.column}>{children}</div>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { ChevronRight } from '@/components/icons';
|
|
3
|
+
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
|
4
|
+
import { styles } from './styles';
|
|
5
|
+
|
|
6
|
+
export interface ExpandableProps {
|
|
7
|
+
title: ReactNode;
|
|
8
|
+
children?: ReactNode;
|
|
9
|
+
defaultOpen?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function Expandable({ title, children, defaultOpen = false }: ExpandableProps) {
|
|
13
|
+
return (
|
|
14
|
+
<Collapsible defaultOpen={defaultOpen} className={styles.accordion}>
|
|
15
|
+
<div className={styles.accordionItem}>
|
|
16
|
+
<CollapsibleTrigger
|
|
17
|
+
className={`${styles.expandableTrigger} group/collapsible-trigger flex w-full justify-start gap-2 text-left outline-none focus-visible:ring-3 focus-visible:ring-ring/50`}
|
|
18
|
+
>
|
|
19
|
+
<ChevronRight className="size-4 shrink-0 text-muted-foreground transition-transform group-aria-expanded/collapsible-trigger:rotate-90" />
|
|
20
|
+
{title}
|
|
21
|
+
</CollapsibleTrigger>
|
|
22
|
+
<CollapsibleContent>
|
|
23
|
+
<div className={styles.accordionContent}>{children}</div>
|
|
24
|
+
</CollapsibleContent>
|
|
25
|
+
</div>
|
|
26
|
+
</Collapsible>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Children,
|
|
3
|
+
cloneElement,
|
|
4
|
+
isValidElement,
|
|
5
|
+
type ReactElement,
|
|
6
|
+
type ReactNode,
|
|
7
|
+
type VideoHTMLAttributes,
|
|
8
|
+
} from 'react';
|
|
9
|
+
import { Hand } from '@/components/icons';
|
|
10
|
+
import { renderInlineMarkdown } from '@/lib/inline-markdown';
|
|
11
|
+
import { styles } from './styles';
|
|
12
|
+
|
|
13
|
+
export interface FrameProps {
|
|
14
|
+
caption?: ReactNode;
|
|
15
|
+
hint?: ReactNode;
|
|
16
|
+
children?: ReactNode;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function renderFrameText(value: ReactNode) {
|
|
20
|
+
return typeof value === 'string' ? renderInlineMarkdown(value) : value;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function enhanceAutoplayVideos(children: ReactNode) {
|
|
24
|
+
return Children.map(children, child => {
|
|
25
|
+
if (!isValidElement(child) || child.type !== 'video') {
|
|
26
|
+
return child;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const video = child as ReactElement<VideoHTMLAttributes<HTMLVideoElement>, 'video'>;
|
|
30
|
+
|
|
31
|
+
return video.props.autoPlay
|
|
32
|
+
? cloneElement(video, { playsInline: true, loop: true, muted: true })
|
|
33
|
+
: video;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function Frame({ caption, hint, children }: FrameProps) {
|
|
38
|
+
return (
|
|
39
|
+
<div className={styles.frameWrapper}>
|
|
40
|
+
{hint ? (
|
|
41
|
+
<div className={styles.frameHint}>
|
|
42
|
+
<span className={styles.frameHintIcon} aria-hidden={true}>
|
|
43
|
+
<Hand />
|
|
44
|
+
</span>
|
|
45
|
+
<span>{renderFrameText(hint)}</span>
|
|
46
|
+
</div>
|
|
47
|
+
) : null}
|
|
48
|
+
<figure className={styles.frame}>
|
|
49
|
+
<div className={styles.frameContent}>{enhanceAutoplayVideos(children)}</div>
|
|
50
|
+
{caption ? (
|
|
51
|
+
<figcaption className={styles.frameCaption}>{renderFrameText(caption)}</figcaption>
|
|
52
|
+
) : null}
|
|
53
|
+
</figure>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { getIcon } from '@/lib/icons';
|
|
2
|
+
import { styles } from './styles';
|
|
3
|
+
|
|
4
|
+
export interface IconProps {
|
|
5
|
+
icon?: string;
|
|
6
|
+
src?: string;
|
|
7
|
+
color?: string;
|
|
8
|
+
size?: number;
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function Icon({ icon, src, color, size = 16, className }: IconProps) {
|
|
13
|
+
const classes = className ? `${styles.icon} ${className}` : styles.icon;
|
|
14
|
+
|
|
15
|
+
if (src) {
|
|
16
|
+
return <img src={src} alt="" width={size} height={size} className={classes} />;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!icon) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const Component = getIcon(icon);
|
|
24
|
+
|
|
25
|
+
if (!Component) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return <Component size={size} color={color} className={classes} />;
|
|
30
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { Badge } from './Badge';
|
|
3
|
+
import { styles } from './styles';
|
|
4
|
+
import { decodeHtmlEntities } from './utils';
|
|
5
|
+
|
|
6
|
+
export interface ParamFieldProps {
|
|
7
|
+
name?: string;
|
|
8
|
+
query?: string;
|
|
9
|
+
path?: string;
|
|
10
|
+
header?: string;
|
|
11
|
+
body?: string;
|
|
12
|
+
type?: string;
|
|
13
|
+
required?: boolean;
|
|
14
|
+
children?: ReactNode;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function ParamField({
|
|
18
|
+
name,
|
|
19
|
+
query,
|
|
20
|
+
path,
|
|
21
|
+
header,
|
|
22
|
+
body,
|
|
23
|
+
type,
|
|
24
|
+
required,
|
|
25
|
+
children,
|
|
26
|
+
}: ParamFieldProps) {
|
|
27
|
+
const label = name || query || path || header || body || 'parameter';
|
|
28
|
+
const location = query ? 'query' : path ? 'path' : header ? 'header' : body ? 'body' : undefined;
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div className={styles.paramField}>
|
|
32
|
+
<div className={styles.fieldHeader}>
|
|
33
|
+
<span className={styles.fieldName}>{label}</span>
|
|
34
|
+
{location ? <Badge>{location}</Badge> : null}
|
|
35
|
+
{type ? <span className={styles.fieldType}>{decodeHtmlEntities(type)}</span> : null}
|
|
36
|
+
{required ? <Badge tone="primary">required</Badge> : null}
|
|
37
|
+
</div>
|
|
38
|
+
{children ? <div className={styles.fieldBody}>{children}</div> : null}
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function Param({ children }: { children?: ReactNode }) {
|
|
44
|
+
return <code className={styles.code}>{children}</code>;
|
|
45
|
+
}
|