@voltro/ui-shadcn 0.1.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 +52 -0
- package/LICENSE +57 -0
- package/README.md +26 -0
- package/SECURITY.md +56 -0
- package/THIRD-PARTY-NOTICES.md +3016 -0
- package/dist/brand.d.ts +73 -0
- package/dist/brand.js +177 -0
- package/dist/cn.d.ts +11 -0
- package/dist/cn.js +6 -0
- package/dist/index.d.ts +1183 -0
- package/dist/index.js +2636 -0
- package/dist/tokens.css +532 -0
- package/package.json +64 -0
- package/src/brand/README.md +60 -0
- package/src/brand/assets/voltro-favicon.svg +30 -0
- package/src/brand/assets/voltro-icon-dark.svg +26 -0
- package/src/brand/assets/voltro-icon.svg +14 -0
- package/src/brand/assets/voltro-mark-mono.svg +5 -0
- package/src/brand/assets/voltro-mark.svg +14 -0
- package/src/brand/voltroLogo.tsx +244 -0
- package/src/cn.ts +10 -0
- package/src/compositions/appShell.tsx +72 -0
- package/src/compositions/codeCompare.tsx +88 -0
- package/src/compositions/docShell.tsx +112 -0
- package/src/compositions/docsLayout.tsx +577 -0
- package/src/compositions/featureBento.tsx +103 -0
- package/src/compositions/featureGrid.tsx +41 -0
- package/src/compositions/heroSection.tsx +55 -0
- package/src/compositions/landingCta.tsx +85 -0
- package/src/compositions/landingHero.tsx +174 -0
- package/src/compositions/landingStats.tsx +99 -0
- package/src/compositions/loginCard.tsx +139 -0
- package/src/compositions/pageHeader.tsx +58 -0
- package/src/compositions/profileMenu.tsx +316 -0
- package/src/compositions/siteFooter.tsx +250 -0
- package/src/compositions/themeToggle.tsx +82 -0
- package/src/cookies.ts +109 -0
- package/src/index.ts +160 -0
- package/src/primitives/animatedNumber.tsx +73 -0
- package/src/primitives/avatar.tsx +39 -0
- package/src/primitives/badge.tsx +39 -0
- package/src/primitives/button.tsx +53 -0
- package/src/primitives/callout.tsx +97 -0
- package/src/primitives/card.tsx +68 -0
- package/src/primitives/checkbox.tsx +55 -0
- package/src/primitives/codeBlock.tsx +134 -0
- package/src/primitives/codeWindow.tsx +84 -0
- package/src/primitives/dialog.tsx +43 -0
- package/src/primitives/docCard.tsx +109 -0
- package/src/primitives/docIcons.tsx +268 -0
- package/src/primitives/dropdownMenu.tsx +162 -0
- package/src/primitives/gridOverlay.tsx +51 -0
- package/src/primitives/highlightedCode.tsx +112 -0
- package/src/primitives/input.tsx +25 -0
- package/src/primitives/label.tsx +19 -0
- package/src/primitives/localeSwitcher.tsx +90 -0
- package/src/primitives/meshBackdrop.tsx +62 -0
- package/src/primitives/scrollReveal.tsx +70 -0
- package/src/primitives/searchModal.tsx +304 -0
- package/src/primitives/select.tsx +24 -0
- package/src/primitives/separator.tsx +24 -0
- package/src/primitives/skeleton.tsx +12 -0
- package/src/primitives/sparkles.tsx +105 -0
- package/src/primitives/steps.tsx +55 -0
- package/src/primitives/tabs.tsx +102 -0
- package/src/primitives/textarea.tsx +24 -0
- package/src/primitives/toast.tsx +44 -0
- package/src/primitives/tocScrollSpy.tsx +110 -0
- package/src/primitives/toggle.tsx +50 -0
- package/src/primitives/toggleGroup.tsx +62 -0
- package/src/tokens.css +532 -0
- package/src/widgets.tsx +297 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
// Lucide-style stroke icons for doc landing cards. All icons share the
|
|
2
|
+
// same visual language: 24×24 viewBox, 1.5px stroke, rounded caps,
|
|
3
|
+
// `currentColor` for fill/stroke so they pick up `text-*` Tailwind
|
|
4
|
+
// utilities + the theme tokens.
|
|
5
|
+
//
|
|
6
|
+
// Hand-rolled (vs depending on `lucide-react`) because we only need a
|
|
7
|
+
// dozen icons here and a 200KB dep for that is silly. Add more shapes
|
|
8
|
+
// to this file as the doc catalogue grows.
|
|
9
|
+
|
|
10
|
+
import type { ReactNode } from 'react'
|
|
11
|
+
|
|
12
|
+
interface DocIconProps {
|
|
13
|
+
readonly className?: string
|
|
14
|
+
/** Accessible label. When omitted the icon is `aria-hidden` and
|
|
15
|
+
* should sit next to text. */
|
|
16
|
+
readonly title?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const Icon = ({
|
|
20
|
+
children, className, title,
|
|
21
|
+
}: DocIconProps & { children: ReactNode }): ReactNode => (
|
|
22
|
+
<svg
|
|
23
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
24
|
+
width="24" height="24"
|
|
25
|
+
viewBox="0 0 24 24"
|
|
26
|
+
fill="none"
|
|
27
|
+
stroke="currentColor"
|
|
28
|
+
strokeWidth="1.5"
|
|
29
|
+
strokeLinecap="round"
|
|
30
|
+
strokeLinejoin="round"
|
|
31
|
+
className={className}
|
|
32
|
+
{...(title ? { role: 'img', 'aria-label': title } : { 'aria-hidden': true })}
|
|
33
|
+
>
|
|
34
|
+
{title ? <title>{title}</title> : null}
|
|
35
|
+
{children}
|
|
36
|
+
</svg>
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
export const RocketIcon = (p: DocIconProps): ReactNode => (
|
|
40
|
+
<Icon {...p}>
|
|
41
|
+
<path d="M4.5 16.5 c-1.5 1.26 -2 5 -2 5 s3.74 -.5 5 -2 c.71 -.84 .7 -2.13 -.09 -2.91 a2.18 2.18 0 0 0 -2.91 -.09Z" />
|
|
42
|
+
<path d="m12 15 -3 -3 a22 22 0 0 1 2 -3.95 A12.88 12.88 0 0 1 22 2 c0 2.72 -.78 7.5 -6 11 a22.35 22.35 0 0 1 -4 2Z" />
|
|
43
|
+
<path d="M9 12 H4 s.55 -3.03 2 -4 c1.62 -1.08 5 0 5 0" />
|
|
44
|
+
<path d="M12 15 v5 s3.03 -.55 4 -2 c1.08 -1.62 0 -5 0 -5" />
|
|
45
|
+
</Icon>
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
export const CompassIcon = (p: DocIconProps): ReactNode => (
|
|
49
|
+
<Icon {...p}>
|
|
50
|
+
<circle cx="12" cy="12" r="10" />
|
|
51
|
+
<polygon points="16.24,7.76 14.12,14.12 7.76,16.24 9.88,9.88" />
|
|
52
|
+
</Icon>
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
export const PackageIcon = (p: DocIconProps): ReactNode => (
|
|
56
|
+
<Icon {...p}>
|
|
57
|
+
<path d="m7.5 4.27 9 5.15" />
|
|
58
|
+
<path d="M21 8 a2 2 0 0 0 -1 -1.73 l-7 -4 a2 2 0 0 0 -2 0 l-7 4 A2 2 0 0 0 3 8 v8 a2 2 0 0 0 1 1.73 l7 4 a2 2 0 0 0 2 0 l7 -4 A2 2 0 0 0 21 16 Z" />
|
|
59
|
+
<path d="M3.3 7 12 12 l8.7 -5" />
|
|
60
|
+
<path d="M12 22 V12" />
|
|
61
|
+
</Icon>
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
export const FolderIcon = (p: DocIconProps): ReactNode => (
|
|
65
|
+
<Icon {...p}>
|
|
66
|
+
<path d="M20 20 H4 a2 2 0 0 1 -2 -2 V6 a2 2 0 0 1 2 -2 h5.93 a2 2 0 0 1 1.66 .9 l.82 1.2 a2 2 0 0 0 1.66 .9 H20 a2 2 0 0 1 2 2 v8 a2 2 0 0 1 -2 2 z" />
|
|
67
|
+
</Icon>
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
export const FileIcon = (p: DocIconProps): ReactNode => (
|
|
71
|
+
<Icon {...p}>
|
|
72
|
+
<path d="M14.5 2 H6 a2 2 0 0 0 -2 2 v16 a2 2 0 0 0 2 2 h12 a2 2 0 0 0 2 -2 V7.5 z" />
|
|
73
|
+
<polyline points="14 2 14 8 20 8" />
|
|
74
|
+
</Icon>
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
export const BroadcastIcon = (p: DocIconProps): ReactNode => (
|
|
78
|
+
<Icon {...p}>
|
|
79
|
+
<path d="M4.93 4.93 a10 10 0 0 0 0 14.14" />
|
|
80
|
+
<path d="M19.07 4.93 a10 10 0 0 1 0 14.14" />
|
|
81
|
+
<path d="M7.76 7.76 a6 6 0 0 0 0 8.49" />
|
|
82
|
+
<path d="M16.24 7.76 a6 6 0 0 1 0 8.49" />
|
|
83
|
+
<circle cx="12" cy="12" r="2" />
|
|
84
|
+
</Icon>
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
export const PencilIcon = (p: DocIconProps): ReactNode => (
|
|
88
|
+
<Icon {...p}>
|
|
89
|
+
<path d="M17 3 a2.85 2.83 0 1 1 4 4 L7.5 20.5 2 22 l1.5 -5.5 z" />
|
|
90
|
+
</Icon>
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
export const BellIcon = (p: DocIconProps): ReactNode => (
|
|
94
|
+
<Icon {...p}>
|
|
95
|
+
<path d="M6 8 a6 6 0 0 1 12 0 c0 7 3 9 3 9 H3 s3 -2 3 -9" />
|
|
96
|
+
<path d="M10.3 21 a1.94 1.94 0 0 0 3.4 0" />
|
|
97
|
+
</Icon>
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
export const CogIcon = (p: DocIconProps): ReactNode => (
|
|
101
|
+
<Icon {...p}>
|
|
102
|
+
<path d="M12 2 v4 M12 18 v4 M4.93 4.93 l2.83 2.83 M16.24 16.24 l2.83 2.83 M2 12 h4 M18 12 h4 M4.93 19.07 l2.83 -2.83 M16.24 7.76 l2.83 -2.83" />
|
|
103
|
+
<circle cx="12" cy="12" r="4" />
|
|
104
|
+
</Icon>
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
export const BuildingIcon = (p: DocIconProps): ReactNode => (
|
|
108
|
+
<Icon {...p}>
|
|
109
|
+
<rect x="4" y="2" width="16" height="20" rx="2" />
|
|
110
|
+
<path d="M9 22 V12 h6 v10" />
|
|
111
|
+
<path d="M8 6 h.01 M12 6 h.01 M16 6 h.01 M8 10 h.01 M16 10 h.01" />
|
|
112
|
+
</Icon>
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
export const DatabaseIcon = (p: DocIconProps): ReactNode => (
|
|
116
|
+
<Icon {...p}>
|
|
117
|
+
<ellipse cx="12" cy="5" rx="9" ry="3" />
|
|
118
|
+
<path d="M3 5 v14 a9 3 0 0 0 18 0 V5" />
|
|
119
|
+
<path d="M3 12 a9 3 0 0 0 18 0" />
|
|
120
|
+
</Icon>
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
export const LockIcon = (p: DocIconProps): ReactNode => (
|
|
124
|
+
<Icon {...p}>
|
|
125
|
+
<rect x="3" y="11" width="18" height="11" rx="2" />
|
|
126
|
+
<path d="M7 11 V7 a5 5 0 0 1 10 0 v4" />
|
|
127
|
+
</Icon>
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
export const BotIcon = (p: DocIconProps): ReactNode => (
|
|
131
|
+
<Icon {...p}>
|
|
132
|
+
<path d="M12 8 V4 H8" />
|
|
133
|
+
<rect x="2" y="8" width="20" height="14" rx="2" />
|
|
134
|
+
<path d="M2 14 h2 M20 14 h2 M15 13 v2 M9 13 v2" />
|
|
135
|
+
</Icon>
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
export const TerminalIcon = (p: DocIconProps): ReactNode => (
|
|
139
|
+
<Icon {...p}>
|
|
140
|
+
<polyline points="4 17 10 11 4 5" />
|
|
141
|
+
<line x1="12" y1="19" x2="20" y2="19" />
|
|
142
|
+
</Icon>
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
export const LayersIcon = (p: DocIconProps): ReactNode => (
|
|
146
|
+
<Icon {...p}>
|
|
147
|
+
<polygon points="12 2 2 7 12 12 22 7 12 2" />
|
|
148
|
+
<polyline points="2 17 12 22 22 17" />
|
|
149
|
+
<polyline points="2 12 12 17 22 12" />
|
|
150
|
+
</Icon>
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
export const PuzzleIcon = (p: DocIconProps): ReactNode => (
|
|
154
|
+
<Icon {...p}>
|
|
155
|
+
<path d="M19.43 12.98 a8 8 0 0 0 0 -1.96 l2.11 -1.65 -2 -3.46 -2.49 1 a8 8 0 0 0 -1.7 -.98 L15 3 H9 l-.38 2.93 a8 8 0 0 0 -1.7 .98 L4.43 5.91 l-2 3.46 2.11 1.65 a8 8 0 0 0 0 1.96 L2.43 14.63 l2 3.46 2.49 -1 a8 8 0 0 0 1.7 .98 L9 21 h6 l.38 -2.93 a8 8 0 0 0 1.7 -.98 l2.49 1 2 -3.46 z" />
|
|
156
|
+
<circle cx="12" cy="12" r="3" />
|
|
157
|
+
</Icon>
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
export const HookIcon = (p: DocIconProps): ReactNode => (
|
|
161
|
+
<Icon {...p}>
|
|
162
|
+
<path d="M9 9 a3 3 0 0 1 6 0 v9 a3 3 0 0 1 -6 0 V12 a1 1 0 0 1 2 0 v6 a1 1 0 0 0 2 0 V9 a1 1 0 0 0 -2 0" />
|
|
163
|
+
</Icon>
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
export const GlobeIcon = (p: DocIconProps): ReactNode => (
|
|
167
|
+
<Icon {...p}>
|
|
168
|
+
<circle cx="12" cy="12" r="10" />
|
|
169
|
+
<line x1="2" y1="12" x2="22" y2="12" />
|
|
170
|
+
<path d="M12 2 a15.3 15.3 0 0 1 4 10 a15.3 15.3 0 0 1 -4 10 a15.3 15.3 0 0 1 -4 -10 a15.3 15.3 0 0 1 4 -10 z" />
|
|
171
|
+
</Icon>
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
export const CloudIcon = (p: DocIconProps): ReactNode => (
|
|
175
|
+
<Icon {...p}>
|
|
176
|
+
<path d="M17.5 19 a4.5 4.5 0 1 0 -1.41 -8.78 a6 6 0 1 0 -11.05 4.5" />
|
|
177
|
+
</Icon>
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
export const HomeIcon = (p: DocIconProps): ReactNode => (
|
|
181
|
+
<Icon {...p}>
|
|
182
|
+
<path d="M3 9 l9 -7 9 7 v11 a2 2 0 0 1 -2 2 H5 a2 2 0 0 1 -2 -2 z" />
|
|
183
|
+
<polyline points="9 22 9 12 15 12 15 22" />
|
|
184
|
+
</Icon>
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
export const ChevronLeftIcon = (p: DocIconProps): ReactNode => (
|
|
188
|
+
<Icon {...p}><polyline points="15 18 9 12 15 6" /></Icon>
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
export const ChevronRightIcon = (p: DocIconProps): ReactNode => (
|
|
192
|
+
<Icon {...p}><polyline points="9 18 15 12 9 6" /></Icon>
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
export const SearchIcon = (p: DocIconProps): ReactNode => (
|
|
196
|
+
<Icon {...p}>
|
|
197
|
+
<circle cx="11" cy="11" r="8" />
|
|
198
|
+
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
|
199
|
+
</Icon>
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
export const ArrowReturnIcon = (p: DocIconProps): ReactNode => (
|
|
203
|
+
<Icon {...p}>
|
|
204
|
+
<polyline points="9 10 4 15 9 20" />
|
|
205
|
+
<path d="M20 4 v7 a4 4 0 0 1 -4 4 H4" />
|
|
206
|
+
</Icon>
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
// ---- Social / brand icons ----
|
|
210
|
+
//
|
|
211
|
+
// Filled (not stroked) — that's the convention for brand marks since
|
|
212
|
+
// the official lockups are filled. Same 24×24 viewBox + currentColor
|
|
213
|
+
// fill so they pick up text-* utilities and theme tokens.
|
|
214
|
+
|
|
215
|
+
const FilledIcon = ({
|
|
216
|
+
children, className, title,
|
|
217
|
+
}: DocIconProps & { children: ReactNode }): ReactNode => (
|
|
218
|
+
<svg
|
|
219
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
220
|
+
width="24" height="24"
|
|
221
|
+
viewBox="0 0 24 24"
|
|
222
|
+
fill="currentColor"
|
|
223
|
+
className={className}
|
|
224
|
+
{...(title ? { role: 'img', 'aria-label': title } : { 'aria-hidden': true })}
|
|
225
|
+
>
|
|
226
|
+
{title ? <title>{title}</title> : null}
|
|
227
|
+
{children}
|
|
228
|
+
</svg>
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
export const GitHubIcon = (p: DocIconProps): ReactNode => (
|
|
232
|
+
<FilledIcon {...p}>
|
|
233
|
+
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.3 3.44 9.8 8.21 11.39.6.11.82-.26.82-.58 0-.29-.01-1.04-.02-2.05-3.34.72-4.04-1.61-4.04-1.61-.55-1.39-1.34-1.76-1.34-1.76-1.09-.74.08-.73.08-.73 1.2.09 1.84 1.24 1.84 1.24 1.07 1.84 2.81 1.31 3.5 1 .11-.78.42-1.31.76-1.61-2.67-.3-5.47-1.33-5.47-5.93 0-1.31.47-2.38 1.24-3.22-.12-.3-.54-1.52.12-3.18 0 0 1.01-.32 3.31 1.23.96-.27 1.99-.4 3.02-.41 1.02.01 2.05.14 3.02.41 2.3-1.55 3.31-1.23 3.31-1.23.66 1.66.24 2.88.12 3.18.77.84 1.24 1.91 1.24 3.22 0 4.61-2.81 5.63-5.49 5.93.43.37.81 1.1.81 2.22 0 1.6-.01 2.89-.01 3.29 0 .32.21.7.83.58C20.57 21.8 24 17.3 24 12c0-6.63-5.37-12-12-12z" />
|
|
234
|
+
</FilledIcon>
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
export const XIcon = (p: DocIconProps): ReactNode => (
|
|
238
|
+
<FilledIcon {...p}>
|
|
239
|
+
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
|
|
240
|
+
</FilledIcon>
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
export const DiscordIcon = (p: DocIconProps): ReactNode => (
|
|
244
|
+
<FilledIcon {...p}>
|
|
245
|
+
<path d="M20.317 4.37a19.79 19.79 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.74 19.74 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.84 19.84 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z" />
|
|
246
|
+
</FilledIcon>
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
export const LinkedInIcon = (p: DocIconProps): ReactNode => (
|
|
250
|
+
<FilledIcon {...p}>
|
|
251
|
+
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 0 1-2.063-2.065 2.063 2.063 0 1 1 2.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" />
|
|
252
|
+
</FilledIcon>
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
export const RssIcon = (p: DocIconProps): ReactNode => (
|
|
256
|
+
<Icon {...p}>
|
|
257
|
+
<path d="M4 11 a9 9 0 0 1 9 9" />
|
|
258
|
+
<path d="M4 4 a16 16 0 0 1 16 16" />
|
|
259
|
+
<circle cx="5" cy="19" r="1" />
|
|
260
|
+
</Icon>
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
export const ChartIcon = (p: DocIconProps): ReactNode => (
|
|
264
|
+
<Icon {...p}>
|
|
265
|
+
<path d="M3 3 v18 h18" />
|
|
266
|
+
<path d="M7 14 l3 -3 l4 4 l6 -7" />
|
|
267
|
+
</Icon>
|
|
268
|
+
)
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// shadcn-style DropdownMenu — Radix wrappers with kit token styling.
|
|
2
|
+
//
|
|
3
|
+
// 1:1 API with shadcn/ui reference so docs + examples lifted from
|
|
4
|
+
// there work without rename:
|
|
5
|
+
//
|
|
6
|
+
// <DropdownMenu>
|
|
7
|
+
// <DropdownMenuTrigger asChild><Button … /></DropdownMenuTrigger>
|
|
8
|
+
// <DropdownMenuContent align="end">
|
|
9
|
+
// <DropdownMenuLabel>Account</DropdownMenuLabel>
|
|
10
|
+
// <DropdownMenuItem onSelect={…}>Settings</DropdownMenuItem>
|
|
11
|
+
// <DropdownMenuSeparator />
|
|
12
|
+
// <DropdownMenuRadioGroup value={theme} onValueChange={setTheme}>
|
|
13
|
+
// <DropdownMenuRadioItem value="system">System</DropdownMenuRadioItem>
|
|
14
|
+
// <DropdownMenuRadioItem value="light">Light</DropdownMenuRadioItem>
|
|
15
|
+
// <DropdownMenuRadioItem value="dark">Dark</DropdownMenuRadioItem>
|
|
16
|
+
// </DropdownMenuRadioGroup>
|
|
17
|
+
// </DropdownMenuContent>
|
|
18
|
+
// </DropdownMenu>
|
|
19
|
+
//
|
|
20
|
+
// Not yet wrapped: CheckboxItem, Sub / SubTrigger / SubContent.
|
|
21
|
+
// Add when something needs them — keeps the surface lean.
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
forwardRef,
|
|
25
|
+
type ComponentPropsWithoutRef,
|
|
26
|
+
type ComponentRef,
|
|
27
|
+
type ReactNode,
|
|
28
|
+
} from 'react'
|
|
29
|
+
import * as RDM from '@radix-ui/react-dropdown-menu'
|
|
30
|
+
import { cn } from '../cn'
|
|
31
|
+
|
|
32
|
+
// ---- Pass-through wrappers ----
|
|
33
|
+
|
|
34
|
+
export const DropdownMenu = RDM.Root
|
|
35
|
+
export const DropdownMenuTrigger = RDM.Trigger
|
|
36
|
+
export const DropdownMenuGroup = RDM.Group
|
|
37
|
+
export const DropdownMenuPortal = RDM.Portal
|
|
38
|
+
export const DropdownMenuRadioGroup = RDM.RadioGroup
|
|
39
|
+
|
|
40
|
+
// ---- Content ----
|
|
41
|
+
|
|
42
|
+
export const DropdownMenuContent = forwardRef<
|
|
43
|
+
ComponentRef<typeof RDM.Content>,
|
|
44
|
+
ComponentPropsWithoutRef<typeof RDM.Content>
|
|
45
|
+
>(({ className, sideOffset = 6, ...props }, ref) => (
|
|
46
|
+
<RDM.Portal>
|
|
47
|
+
<RDM.Content
|
|
48
|
+
ref={ref}
|
|
49
|
+
sideOffset={sideOffset}
|
|
50
|
+
className={cn(
|
|
51
|
+
'z-50 min-w-[10rem] overflow-hidden rounded-md border border-border',
|
|
52
|
+
'bg-popover text-popover-foreground shadow-lg p-1',
|
|
53
|
+
'data-[state=open]:animate-in data-[state=closed]:animate-out',
|
|
54
|
+
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
|
55
|
+
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
|
|
56
|
+
'data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2',
|
|
57
|
+
'data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2',
|
|
58
|
+
className,
|
|
59
|
+
)}
|
|
60
|
+
{...props}
|
|
61
|
+
/>
|
|
62
|
+
</RDM.Portal>
|
|
63
|
+
))
|
|
64
|
+
DropdownMenuContent.displayName = 'DropdownMenuContent'
|
|
65
|
+
|
|
66
|
+
// ---- Label ----
|
|
67
|
+
|
|
68
|
+
export const DropdownMenuLabel = forwardRef<
|
|
69
|
+
ComponentRef<typeof RDM.Label>,
|
|
70
|
+
ComponentPropsWithoutRef<typeof RDM.Label> & { readonly inset?: boolean }
|
|
71
|
+
>(({ className, inset, ...props }, ref) => (
|
|
72
|
+
<RDM.Label
|
|
73
|
+
ref={ref}
|
|
74
|
+
className={cn(
|
|
75
|
+
'px-2 py-1.5 text-[0.65rem] font-semibold uppercase tracking-wider text-muted-foreground',
|
|
76
|
+
inset && 'pl-8',
|
|
77
|
+
className,
|
|
78
|
+
)}
|
|
79
|
+
{...props}
|
|
80
|
+
/>
|
|
81
|
+
))
|
|
82
|
+
DropdownMenuLabel.displayName = 'DropdownMenuLabel'
|
|
83
|
+
|
|
84
|
+
// ---- Separator ----
|
|
85
|
+
|
|
86
|
+
export const DropdownMenuSeparator = forwardRef<
|
|
87
|
+
ComponentRef<typeof RDM.Separator>,
|
|
88
|
+
ComponentPropsWithoutRef<typeof RDM.Separator>
|
|
89
|
+
>(({ className, ...props }, ref) => (
|
|
90
|
+
<RDM.Separator
|
|
91
|
+
ref={ref}
|
|
92
|
+
className={cn('-mx-1 my-1 h-px bg-border', className)}
|
|
93
|
+
{...props}
|
|
94
|
+
/>
|
|
95
|
+
))
|
|
96
|
+
DropdownMenuSeparator.displayName = 'DropdownMenuSeparator'
|
|
97
|
+
|
|
98
|
+
// ---- Item ----
|
|
99
|
+
//
|
|
100
|
+
// `destructive` opts into the destructive token — shorthand for the
|
|
101
|
+
// common "Sign out / Delete" use case so callers don't repeat the
|
|
102
|
+
// same Tailwind chain.
|
|
103
|
+
|
|
104
|
+
export const DropdownMenuItem = forwardRef<
|
|
105
|
+
ComponentRef<typeof RDM.Item>,
|
|
106
|
+
ComponentPropsWithoutRef<typeof RDM.Item> & {
|
|
107
|
+
readonly inset?: boolean
|
|
108
|
+
readonly destructive?: boolean
|
|
109
|
+
}
|
|
110
|
+
>(({ className, inset, destructive, ...props }, ref) => (
|
|
111
|
+
<RDM.Item
|
|
112
|
+
ref={ref}
|
|
113
|
+
className={cn(
|
|
114
|
+
'relative flex items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none',
|
|
115
|
+
'transition-colors select-none cursor-pointer',
|
|
116
|
+
'focus:bg-accent focus:text-accent-foreground',
|
|
117
|
+
'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
118
|
+
"[&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 [&_svg]:pointer-events-none",
|
|
119
|
+
inset && 'pl-8',
|
|
120
|
+
destructive
|
|
121
|
+
? 'text-destructive focus:bg-destructive/10 focus:text-destructive'
|
|
122
|
+
: 'text-foreground',
|
|
123
|
+
className,
|
|
124
|
+
)}
|
|
125
|
+
{...props}
|
|
126
|
+
/>
|
|
127
|
+
))
|
|
128
|
+
DropdownMenuItem.displayName = 'DropdownMenuItem'
|
|
129
|
+
|
|
130
|
+
// ---- RadioItem ----
|
|
131
|
+
//
|
|
132
|
+
// Indicator: filled dot on the left, slot reserved (pl-8) so
|
|
133
|
+
// unselected items stay aligned with selected ones.
|
|
134
|
+
|
|
135
|
+
const RadioDot = (): ReactNode => (
|
|
136
|
+
<span className="size-2 rounded-full bg-current" />
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
export const DropdownMenuRadioItem = forwardRef<
|
|
140
|
+
ComponentRef<typeof RDM.RadioItem>,
|
|
141
|
+
ComponentPropsWithoutRef<typeof RDM.RadioItem>
|
|
142
|
+
>(({ className, children, ...props }, ref) => (
|
|
143
|
+
<RDM.RadioItem
|
|
144
|
+
ref={ref}
|
|
145
|
+
className={cn(
|
|
146
|
+
'relative flex items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-none',
|
|
147
|
+
'transition-colors select-none cursor-pointer text-foreground',
|
|
148
|
+
'focus:bg-accent focus:text-accent-foreground',
|
|
149
|
+
'data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
150
|
+
className,
|
|
151
|
+
)}
|
|
152
|
+
{...props}
|
|
153
|
+
>
|
|
154
|
+
<span className="absolute left-2.5 flex size-3.5 items-center justify-center">
|
|
155
|
+
<RDM.ItemIndicator>
|
|
156
|
+
<RadioDot />
|
|
157
|
+
</RDM.ItemIndicator>
|
|
158
|
+
</span>
|
|
159
|
+
{children}
|
|
160
|
+
</RDM.RadioItem>
|
|
161
|
+
))
|
|
162
|
+
DropdownMenuRadioItem.displayName = 'DropdownMenuRadioItem'
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// GridOverlay — subtle dot or line grid pattern, faded with a radial
|
|
2
|
+
// mask so it brightest at the centre and disappears toward the
|
|
3
|
+
// edges. The classic "engineering blueprint" tech vibe without
|
|
4
|
+
// fighting the foreground content.
|
|
5
|
+
//
|
|
6
|
+
// Stacks on top of MeshBackdrop, beneath foreground content. Pointer-
|
|
7
|
+
// events off; aria-hidden.
|
|
8
|
+
|
|
9
|
+
import type { ReactNode } from 'react'
|
|
10
|
+
import { cn } from '../cn'
|
|
11
|
+
|
|
12
|
+
interface GridOverlayProps {
|
|
13
|
+
readonly className?: string
|
|
14
|
+
/** Grid style — dotted (default, gentler) or lined (stronger
|
|
15
|
+
* blueprint feel). */
|
|
16
|
+
readonly variant?: 'dots' | 'lines'
|
|
17
|
+
/** Cell size in px. Default 32. */
|
|
18
|
+
readonly size?: number
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const GridOverlay = ({
|
|
22
|
+
className, variant = 'dots', size = 32,
|
|
23
|
+
}: GridOverlayProps): ReactNode => {
|
|
24
|
+
// For dots: a single radial gradient repeating in a tiny tile.
|
|
25
|
+
// For lines: two linear gradients (h + v) over the same tile.
|
|
26
|
+
const dotsBg = `radial-gradient(circle, var(--foreground) 1px, transparent 1.5px)`
|
|
27
|
+
const linesBg
|
|
28
|
+
= `linear-gradient(to right, var(--foreground) 1px, transparent 1px),`
|
|
29
|
+
+ `linear-gradient(to bottom, var(--foreground) 1px, transparent 1px)`
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div
|
|
33
|
+
aria-hidden="true"
|
|
34
|
+
className={cn(
|
|
35
|
+
'absolute inset-0 pointer-events-none opacity-[0.06]',
|
|
36
|
+
className,
|
|
37
|
+
)}
|
|
38
|
+
style={{
|
|
39
|
+
backgroundImage: variant === 'lines' ? linesBg : dotsBg,
|
|
40
|
+
backgroundSize: `${size}px ${size}px`,
|
|
41
|
+
// Radial mask: full opacity at centre, fades to 0 at edges.
|
|
42
|
+
// Tailwind doesn't have a mask utility in stable v4 so this
|
|
43
|
+
// stays inline.
|
|
44
|
+
WebkitMaskImage:
|
|
45
|
+
'radial-gradient(ellipse at center, black 0%, transparent 75%)',
|
|
46
|
+
maskImage:
|
|
47
|
+
'radial-gradient(ellipse at center, black 0%, transparent 75%)',
|
|
48
|
+
}}
|
|
49
|
+
/>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// HighlightedCode — Shiki-powered syntax highlighting as a reusable
|
|
2
|
+
// primitive. Apps drop `<HighlightedCode code lang />` anywhere and
|
|
3
|
+
// get dual-theme (light + dark) output that flips with the
|
|
4
|
+
// `.dark` class on <html>.
|
|
5
|
+
//
|
|
6
|
+
// Lazy + shared singleton: the first usage triggers an async Shiki
|
|
7
|
+
// load (~200ms WASM bootstrap), subsequent renders share the same
|
|
8
|
+
// highlighter instance. Until Shiki is ready, the component renders
|
|
9
|
+
// plain escaped code so the layout doesn't jump.
|
|
10
|
+
//
|
|
11
|
+
// Browser-safe via dynamic import; runs on the Node side too (SSR /
|
|
12
|
+
// `voltro build` waits for it via `whenReady`).
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
useEffect, useMemo, useState, type ReactNode,
|
|
16
|
+
} from 'react'
|
|
17
|
+
import type { Highlighter } from 'shiki'
|
|
18
|
+
import { cn } from '../cn'
|
|
19
|
+
|
|
20
|
+
declare global {
|
|
21
|
+
var __voltroShikiHighlighter: Highlighter | null | undefined
|
|
22
|
+
var __voltroShikiHighlighterPromise: Promise<Highlighter | null> | null | undefined
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Languages we ship support for. Add to this list as new doc + landing
|
|
26
|
+
// snippets need them; loading is one-shot at highlighter init.
|
|
27
|
+
export const SHIKI_LANGS = [
|
|
28
|
+
'text', 'ts', 'tsx', 'typescript', 'js', 'jsx', 'json', 'bash', 'sh',
|
|
29
|
+
'shell', 'sql', 'yaml', 'toml', 'md', 'mdx', 'html', 'css', 'diff',
|
|
30
|
+
] as const
|
|
31
|
+
export type ShikiLang = typeof SHIKI_LANGS[number]
|
|
32
|
+
|
|
33
|
+
// Global singleton — shared across HighlightedCode calls, HMR reloads,
|
|
34
|
+
// and docs-content rendering so we only pay the WASM bootstrap once.
|
|
35
|
+
const ensure = async (): Promise<Highlighter | null> => {
|
|
36
|
+
if (globalThis.__voltroShikiHighlighter) return globalThis.__voltroShikiHighlighter
|
|
37
|
+
if (globalThis.__voltroShikiHighlighterPromise) {
|
|
38
|
+
return globalThis.__voltroShikiHighlighterPromise
|
|
39
|
+
}
|
|
40
|
+
globalThis.__voltroShikiHighlighterPromise = (async () => {
|
|
41
|
+
const { createHighlighter } = await import('shiki')
|
|
42
|
+
const highlighter = await createHighlighter({
|
|
43
|
+
themes: ['github-dark-dimmed', 'github-light'],
|
|
44
|
+
langs: SHIKI_LANGS as unknown as string[],
|
|
45
|
+
})
|
|
46
|
+
globalThis.__voltroShikiHighlighter = highlighter
|
|
47
|
+
return highlighter
|
|
48
|
+
})()
|
|
49
|
+
return globalThis.__voltroShikiHighlighterPromise
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Promise that resolves once Shiki has loaded — for SSR/build callers
|
|
53
|
+
* that want to await before rendering. */
|
|
54
|
+
export const whenHighlighterReady = (): Promise<void> =>
|
|
55
|
+
ensure().then(() => { /* discard */ })
|
|
56
|
+
|
|
57
|
+
const escapeHtml = (s: string): string =>
|
|
58
|
+
s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
59
|
+
|
|
60
|
+
interface HighlightedCodeProps {
|
|
61
|
+
readonly code: string
|
|
62
|
+
readonly lang?: ShikiLang
|
|
63
|
+
/** Outer wrapper class. */
|
|
64
|
+
readonly className?: string
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const HighlightedCode = ({
|
|
68
|
+
code, lang = 'ts', className,
|
|
69
|
+
}: HighlightedCodeProps): ReactNode => {
|
|
70
|
+
const [html, setHtml] = useState<string | null>(null)
|
|
71
|
+
|
|
72
|
+
// Render the highlighted HTML when Shiki is ready. Re-runs only when
|
|
73
|
+
// code/lang changes — the highlighter itself is stable.
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
let cancelled = false
|
|
76
|
+
ensure().then((hl) => {
|
|
77
|
+
if (cancelled || !hl) return
|
|
78
|
+
try {
|
|
79
|
+
const out = hl.codeToHtml(code, {
|
|
80
|
+
lang,
|
|
81
|
+
themes: { light: 'github-light', dark: 'github-dark-dimmed' },
|
|
82
|
+
defaultColor: false,
|
|
83
|
+
})
|
|
84
|
+
setHtml(out)
|
|
85
|
+
} catch {
|
|
86
|
+
// Lang missing → fall through to plain escaped code.
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
return () => { cancelled = true }
|
|
90
|
+
}, [code, lang])
|
|
91
|
+
|
|
92
|
+
// Cached escaped fallback — what renders before Shiki ships its
|
|
93
|
+
// first highlighted batch.
|
|
94
|
+
const fallback = useMemo(() => escapeHtml(code), [code])
|
|
95
|
+
|
|
96
|
+
if (html) {
|
|
97
|
+
return (
|
|
98
|
+
<div
|
|
99
|
+
className={cn(
|
|
100
|
+
'text-sm leading-relaxed [&_pre]:!m-0 [&_pre]:!bg-transparent [&_pre]:p-5 overflow-x-auto',
|
|
101
|
+
className,
|
|
102
|
+
)}
|
|
103
|
+
dangerouslySetInnerHTML={{ __html: html }}
|
|
104
|
+
/>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
return (
|
|
108
|
+
<pre className={cn('p-5 text-sm leading-relaxed overflow-x-auto font-mono', className)}>
|
|
109
|
+
<code dangerouslySetInnerHTML={{ __html: fallback }} />
|
|
110
|
+
</pre>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// shadcn Input — minimal HTML input with theme-aware styles.
|
|
2
|
+
|
|
3
|
+
import type { InputHTMLAttributes, ReactNode } from 'react'
|
|
4
|
+
import { cn } from '../cn'
|
|
5
|
+
|
|
6
|
+
export const Input = ({ className, type = 'text', ...props }: InputHTMLAttributes<HTMLInputElement>): ReactNode => (
|
|
7
|
+
<input
|
|
8
|
+
type={type}
|
|
9
|
+
data-slot="input"
|
|
10
|
+
className={cn(
|
|
11
|
+
'flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs',
|
|
12
|
+
'transition-[color,box-shadow] outline-none',
|
|
13
|
+
'file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium',
|
|
14
|
+
'placeholder:text-muted-foreground',
|
|
15
|
+
'selection:bg-primary selection:text-primary-foreground',
|
|
16
|
+
'dark:bg-input/30',
|
|
17
|
+
'disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50',
|
|
18
|
+
'md:text-sm',
|
|
19
|
+
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
|
|
20
|
+
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
|
|
21
|
+
className,
|
|
22
|
+
)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// shadcn Label — paired with form inputs for accessible naming.
|
|
2
|
+
|
|
3
|
+
import type { LabelHTMLAttributes, ReactNode } from 'react'
|
|
4
|
+
import { cn } from '../cn'
|
|
5
|
+
|
|
6
|
+
export const Label = ({ className, children, ...props }: LabelHTMLAttributes<HTMLLabelElement>): ReactNode => (
|
|
7
|
+
<label
|
|
8
|
+
data-slot="label"
|
|
9
|
+
className={cn(
|
|
10
|
+
'flex items-center gap-2 text-sm leading-none font-medium select-none',
|
|
11
|
+
'group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50',
|
|
12
|
+
'peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
|
|
13
|
+
className,
|
|
14
|
+
)}
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
{children}
|
|
18
|
+
</label>
|
|
19
|
+
)
|