gladvn 0.2.32 → 0.2.34
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/bin/cli.js +41 -3
- package/package.json +1 -1
- package/src/blocks/auth-form.tsx +105 -0
- package/src/blocks/auth-recovery.tsx +5 -5
- package/src/blocks/auth-split.tsx +8 -8
- package/src/blocks/dashboard.tsx +162 -54
- package/src/blocks/settings.tsx +197 -68
- package/src/components/macro/field-preset.tsx +14 -17
- package/src/components/macro/input-preset.tsx +1 -1
- package/src/components/macro/textarea-preset.tsx +1 -1
- package/src/dev/App.tsx +192 -72
- package/src/dev/components/BlockViewer.tsx +136 -0
- package/src/dev/components/code-highlighter.tsx +17 -5
- package/src/dev/data.ts +40 -0
- package/src/dev/showcase/auth-form-block.tsx +13 -0
- package/src/dev/showcase/auth-recovery-block.tsx +13 -0
- package/src/dev/showcase/auth-split-block.tsx +13 -0
- package/src/dev/showcase/dashboard-block.tsx +77 -0
- package/src/dev/showcase/overview.tsx +53 -30
- package/src/dev/showcase/settings-block.tsx +13 -0
- package/src/blocks/auth-card.tsx +0 -104
- package/src/blocks/auth-dialog.tsx +0 -116
package/src/dev/App.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { cn } from "../lib/utils";
|
|
2
|
+
import React, { Suspense, lazy, useCallback, useEffect, useState, useMemo } from "react";
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
LayersIcon,
|
|
@@ -42,7 +43,7 @@ const groupedComponents = COMPONENTS.reduce(
|
|
|
42
43
|
{} as Record<string, (typeof COMPONENTS)[number][]>,
|
|
43
44
|
);
|
|
44
45
|
|
|
45
|
-
const
|
|
46
|
+
const componentCategories = [
|
|
46
47
|
"Layout & Structure",
|
|
47
48
|
"Forms & Inputs",
|
|
48
49
|
"Feedback & Overlays",
|
|
@@ -51,6 +52,12 @@ const categoryOrder = [
|
|
|
51
52
|
"Other",
|
|
52
53
|
];
|
|
53
54
|
|
|
55
|
+
const blockCategories = [
|
|
56
|
+
"Dashboards",
|
|
57
|
+
"Settings",
|
|
58
|
+
"Authentication",
|
|
59
|
+
];
|
|
60
|
+
|
|
54
61
|
function ComponentViewer({ id }: { id: string }) {
|
|
55
62
|
const compDef = COMPONENTS.find((c) => c.id === id);
|
|
56
63
|
if (!compDef) return null;
|
|
@@ -70,7 +77,24 @@ function ComponentViewer({ id }: { id: string }) {
|
|
|
70
77
|
);
|
|
71
78
|
}
|
|
72
79
|
|
|
80
|
+
function PreviewViewer({ blockId }: { blockId: string }) {
|
|
81
|
+
const Block = useMemo(() => lazy(() => import(`../blocks/${blockId}.tsx`)), [blockId]);
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<Suspense fallback={<div className="p-12 text-center text-muted-foreground animate-pulse">Loading preview...</div>}>
|
|
85
|
+
<div className="w-full min-h-screen bg-background antialiased">
|
|
86
|
+
<Block />
|
|
87
|
+
</div>
|
|
88
|
+
</Suspense>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
73
92
|
export default function App() {
|
|
93
|
+
if (typeof window !== "undefined" && window.location.pathname.startsWith("/preview/")) {
|
|
94
|
+
const blockId = window.location.pathname.replace(/^\/preview\//, "");
|
|
95
|
+
return <PreviewViewer blockId={blockId} />;
|
|
96
|
+
}
|
|
97
|
+
|
|
74
98
|
const theme = useTheme();
|
|
75
99
|
const { size, setSize } = useDevContext();
|
|
76
100
|
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
|
@@ -90,6 +114,8 @@ export default function App() {
|
|
|
90
114
|
return "overview";
|
|
91
115
|
});
|
|
92
116
|
|
|
117
|
+
const activeComponent = useMemo(() => COMPONENTS.find((c) => c.id === active), [active]);
|
|
118
|
+
|
|
93
119
|
const setActive = useCallback((id: string) => {
|
|
94
120
|
setActiveState(id);
|
|
95
121
|
setIsMobileMenuOpen(false);
|
|
@@ -99,13 +125,7 @@ export default function App() {
|
|
|
99
125
|
url.searchParams.delete("component");
|
|
100
126
|
url.pathname = id === "overview" ? "/" : `/${id}`;
|
|
101
127
|
window.history.pushState({}, "", url);
|
|
102
|
-
|
|
103
|
-
setTimeout(() => {
|
|
104
|
-
const el = document.getElementById(id);
|
|
105
|
-
if (el) {
|
|
106
|
-
el.scrollIntoView({ behavior: "smooth" });
|
|
107
|
-
}
|
|
108
|
-
}, 50);
|
|
128
|
+
window.scrollTo({ top: 0, behavior: "smooth" });
|
|
109
129
|
}
|
|
110
130
|
}, []);
|
|
111
131
|
|
|
@@ -114,12 +134,9 @@ export default function App() {
|
|
|
114
134
|
const params = new URLSearchParams(window.location.search);
|
|
115
135
|
const queryComp = params.get("component");
|
|
116
136
|
const comp = queryComp || window.location.pathname.replace(/^\/+/, "");
|
|
117
|
-
|
|
137
|
+
|
|
118
138
|
if (comp && comp !== "overview") {
|
|
119
|
-
|
|
120
|
-
const el = document.getElementById(comp);
|
|
121
|
-
if (el) el.scrollIntoView({ behavior: "smooth" });
|
|
122
|
-
}, 100);
|
|
139
|
+
window.scrollTo({ top: 0, behavior: "smooth" });
|
|
123
140
|
}
|
|
124
141
|
}
|
|
125
142
|
}, []);
|
|
@@ -130,8 +147,7 @@ export default function App() {
|
|
|
130
147
|
const queryComp = params.get("component");
|
|
131
148
|
const comp = queryComp || window.location.pathname.replace(/^\/+/, "") || "overview";
|
|
132
149
|
setActiveState(comp);
|
|
133
|
-
|
|
134
|
-
if (el) el.scrollIntoView({ behavior: "smooth" });
|
|
150
|
+
window.scrollTo({ top: 0, behavior: "smooth" });
|
|
135
151
|
};
|
|
136
152
|
window.addEventListener("popstate", handlePopState);
|
|
137
153
|
return () => window.removeEventListener("popstate", handlePopState);
|
|
@@ -166,9 +182,8 @@ export default function App() {
|
|
|
166
182
|
if (active === "overview") {
|
|
167
183
|
document.title = "gladvn — Tailwind CSS React Components";
|
|
168
184
|
} else {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
document.title = `${compDef.label} — gladvn Components`;
|
|
185
|
+
if (activeComponent) {
|
|
186
|
+
document.title = `${activeComponent.label} — gladvn Components`;
|
|
172
187
|
}
|
|
173
188
|
}
|
|
174
189
|
}
|
|
@@ -178,7 +193,7 @@ export default function App() {
|
|
|
178
193
|
<div className="min-h-screen bg-background text-foreground">
|
|
179
194
|
{/* Top nav */}
|
|
180
195
|
<header className="sticky top-0 z-50 border-b border-border/40 bg-background/80 backdrop-blur-md">
|
|
181
|
-
<div className="mx-auto flex h-16 max-w-[1440px] items-center justify-between px-6">
|
|
196
|
+
<div className="mx-auto flex h-16 max-w-[1440px] items-center justify-between px-4 md:px-6">
|
|
182
197
|
{/* Left — Logo + version */}
|
|
183
198
|
<div className="flex items-center gap-3">
|
|
184
199
|
<button
|
|
@@ -200,21 +215,70 @@ export default function App() {
|
|
|
200
215
|
</a>
|
|
201
216
|
</div>
|
|
202
217
|
|
|
203
|
-
{/* Center —
|
|
204
|
-
<
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
</
|
|
217
|
-
|
|
218
|
+
{/* Center — Navigation Tabs & Search */}
|
|
219
|
+
<div className="flex-1 justify-center gap-6 hidden md:flex items-center mx-4">
|
|
220
|
+
<button
|
|
221
|
+
onClick={() => setActive("overview")}
|
|
222
|
+
className={cn(
|
|
223
|
+
"text-[14px] font-medium transition-colors",
|
|
224
|
+
{
|
|
225
|
+
"text-foreground": active === "overview",
|
|
226
|
+
"text-muted-foreground hover:text-foreground": !(active === "overview")
|
|
227
|
+
}
|
|
228
|
+
)}
|
|
229
|
+
>
|
|
230
|
+
Overview
|
|
231
|
+
</button>
|
|
232
|
+
<button
|
|
233
|
+
onClick={() => {
|
|
234
|
+
if (active === "overview" || blockCategories.includes(activeComponent?.category || "")) {
|
|
235
|
+
setActive("accordion");
|
|
236
|
+
}
|
|
237
|
+
}}
|
|
238
|
+
className={cn(
|
|
239
|
+
"text-[14px] font-medium transition-colors",
|
|
240
|
+
{
|
|
241
|
+
"text-foreground": active !== "overview" && !blockCategories.includes(activeComponent?.category || ""),
|
|
242
|
+
"text-muted-foreground hover:text-foreground": !(active !== "overview" && !blockCategories.includes(activeComponent?.category || ""))
|
|
243
|
+
}
|
|
244
|
+
)}
|
|
245
|
+
>
|
|
246
|
+
Components
|
|
247
|
+
</button>
|
|
248
|
+
<button
|
|
249
|
+
onClick={() => {
|
|
250
|
+
if (!blockCategories.includes(activeComponent?.category || "")) {
|
|
251
|
+
setActive("dashboard-block");
|
|
252
|
+
}
|
|
253
|
+
}}
|
|
254
|
+
className={cn(
|
|
255
|
+
"text-[14px] font-medium transition-colors",
|
|
256
|
+
{
|
|
257
|
+
"text-foreground": blockCategories.includes(activeComponent?.category || ""),
|
|
258
|
+
"text-muted-foreground hover:text-foreground": !(blockCategories.includes(activeComponent?.category || ""))
|
|
259
|
+
}
|
|
260
|
+
)}
|
|
261
|
+
>
|
|
262
|
+
Blocks
|
|
263
|
+
</button>
|
|
264
|
+
|
|
265
|
+
<div className="mx-2 h-4 w-px bg-border/50"></div>
|
|
266
|
+
|
|
267
|
+
<button
|
|
268
|
+
id="cmd-search-trigger"
|
|
269
|
+
onClick={() => setCmdOpen(true)}
|
|
270
|
+
className="flex h-8 items-center gap-2 px-3 rounded-md border border-border bg-muted/30 text-[13px] text-muted-foreground hover:bg-muted/60 transition-colors w-56"
|
|
271
|
+
aria-label="Tìm component (⌘K)"
|
|
272
|
+
>
|
|
273
|
+
<SearchIcon className="size-3.5 shrink-0" />
|
|
274
|
+
<span className="flex-1 text-left">
|
|
275
|
+
Search...
|
|
276
|
+
</span>
|
|
277
|
+
<kbd className="text-[10px] bg-background border border-border/80 rounded px-1.5 py-0.5 font-sans leading-none">
|
|
278
|
+
⌘K
|
|
279
|
+
</kbd>
|
|
280
|
+
</button>
|
|
281
|
+
</div>
|
|
218
282
|
|
|
219
283
|
{/* Right — GitHub + npx + theme toggle */}
|
|
220
284
|
<div className="flex items-center gap-1.5">
|
|
@@ -276,56 +340,112 @@ export default function App() {
|
|
|
276
340
|
|
|
277
341
|
{/* Sidebar */}
|
|
278
342
|
<aside
|
|
279
|
-
className={
|
|
280
|
-
|
|
281
|
-
|
|
343
|
+
className={cn(
|
|
344
|
+
"fixed inset-y-0 left-0 z-50 w-64 transform border-r bg-background border-border pt-4 px-3 transition-transform duration-200 ease-in-out md:sticky md:top-16 md:h-[calc(100vh-4rem)] md:w-56 md:translate-x-0 md:pt-6 md:z-0 overflow-y-auto custom-scrollbar",
|
|
345
|
+
{
|
|
346
|
+
"translate-x-0 shadow-2xl": isMobileMenuOpen,
|
|
347
|
+
"-translate-x-full": !isMobileMenuOpen,
|
|
348
|
+
"md:hidden": active === "overview",
|
|
349
|
+
"md:block": active !== "overview"
|
|
350
|
+
}
|
|
351
|
+
)}
|
|
282
352
|
>
|
|
283
|
-
<nav className="space-y-0.5">
|
|
353
|
+
<nav className="space-y-0.5 md:hidden">
|
|
284
354
|
<button
|
|
285
355
|
onClick={() => setActive("overview")}
|
|
286
|
-
className={
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
356
|
+
className={cn(
|
|
357
|
+
"w-full flex items-center gap-2.5 rounded-md px-2.5 py-1.5 text-[13px] transition-colors text-left mb-2",
|
|
358
|
+
{
|
|
359
|
+
"bg-accent text-accent-foreground font-medium": active === "overview",
|
|
360
|
+
"text-muted-foreground hover:bg-muted/60 hover:text-foreground": !(active === "overview")
|
|
361
|
+
}
|
|
362
|
+
)}
|
|
291
363
|
>
|
|
292
364
|
<LayersIcon className="size-3.5" />
|
|
293
365
|
Overview
|
|
294
366
|
</button>
|
|
367
|
+
<div className="h-px bg-border/50 my-2 mx-1"></div>
|
|
368
|
+
<button
|
|
369
|
+
onClick={() => setActive("accordion")}
|
|
370
|
+
className={cn(
|
|
371
|
+
"w-full flex items-center gap-2.5 rounded-md px-2.5 py-1.5 text-[13px] transition-colors text-left",
|
|
372
|
+
{
|
|
373
|
+
"bg-accent text-accent-foreground font-medium": active !== "overview" && !blockCategories.includes(activeComponent?.category || ""),
|
|
374
|
+
"text-muted-foreground hover:bg-muted/60 hover:text-foreground": !(active !== "overview" && !blockCategories.includes(activeComponent?.category || ""))
|
|
375
|
+
}
|
|
376
|
+
)}
|
|
377
|
+
>
|
|
378
|
+
Components
|
|
379
|
+
</button>
|
|
380
|
+
<button
|
|
381
|
+
onClick={() => setActive("dashboard-block")}
|
|
382
|
+
className={cn(
|
|
383
|
+
"w-full flex items-center gap-2.5 rounded-md px-2.5 py-1.5 text-[13px] transition-colors text-left mb-2",
|
|
384
|
+
{
|
|
385
|
+
"bg-accent text-accent-foreground font-medium": blockCategories.includes(activeComponent?.category || ""),
|
|
386
|
+
"text-muted-foreground hover:bg-muted/60 hover:text-foreground": !(blockCategories.includes(activeComponent?.category || ""))
|
|
387
|
+
}
|
|
388
|
+
)}
|
|
389
|
+
>
|
|
390
|
+
Blocks
|
|
391
|
+
</button>
|
|
392
|
+
<div className="h-px bg-border/50 my-2 mx-1"></div>
|
|
295
393
|
</nav>
|
|
296
394
|
|
|
297
395
|
<div className="mt-6">
|
|
298
|
-
{
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
{
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
396
|
+
{blockCategories.includes(activeComponent?.category || "") ? (
|
|
397
|
+
<nav className="space-y-0.5">
|
|
398
|
+
{COMPONENTS.filter(c => blockCategories.includes(c.category)).map(({ id, label }) => (
|
|
399
|
+
<button
|
|
400
|
+
key={id}
|
|
401
|
+
onClick={() => setActive(id)}
|
|
402
|
+
className={cn(
|
|
403
|
+
"w-full flex items-center justify-between gap-2.5 rounded-md px-2.5 py-1.5 text-[13px] transition-colors text-left",
|
|
404
|
+
{
|
|
405
|
+
"bg-accent text-accent-foreground font-medium": active === id,
|
|
406
|
+
"text-muted-foreground hover:bg-muted/60 hover:text-foreground": !(active === id)
|
|
407
|
+
}
|
|
408
|
+
)}
|
|
409
|
+
>
|
|
410
|
+
<span>{label}</span>
|
|
411
|
+
</button>
|
|
412
|
+
))}
|
|
413
|
+
</nav>
|
|
414
|
+
) : (
|
|
415
|
+
componentCategories.map((cat) => {
|
|
416
|
+
const comps = groupedComponents[cat];
|
|
417
|
+
if (!comps || comps.length === 0) return null;
|
|
418
|
+
return (
|
|
419
|
+
<div key={cat} className="mb-4">
|
|
420
|
+
<p className="mb-2 px-2 text-[10px] font-bold uppercase tracking-wider text-foreground/70">
|
|
421
|
+
{cat}
|
|
422
|
+
</p>
|
|
423
|
+
<nav className="space-y-0.5">
|
|
424
|
+
{comps.map(({ id, label }) => (
|
|
425
|
+
<button
|
|
426
|
+
key={id}
|
|
427
|
+
onClick={() => setActive(id)}
|
|
428
|
+
className={cn(
|
|
429
|
+
"w-full flex items-center justify-between gap-2.5 rounded-md px-2.5 py-1.5 text-[13px] transition-colors text-left",
|
|
430
|
+
{
|
|
431
|
+
"bg-accent text-accent-foreground font-medium": active === id,
|
|
432
|
+
"text-muted-foreground hover:bg-muted/60 hover:text-foreground": !(active === id)
|
|
433
|
+
}
|
|
434
|
+
)}
|
|
435
|
+
>
|
|
436
|
+
<span>{label}</span>
|
|
437
|
+
</button>
|
|
438
|
+
))}
|
|
439
|
+
</nav>
|
|
440
|
+
</div>
|
|
441
|
+
);
|
|
442
|
+
})
|
|
443
|
+
)}
|
|
324
444
|
</div>
|
|
325
445
|
</aside>
|
|
326
446
|
|
|
327
447
|
{/* Main */}
|
|
328
|
-
<main className="flex-1 min-w-0 px-6 py-8">
|
|
448
|
+
<main className="flex-1 min-w-0 px-3 md:px-6 py-6 md:py-8">
|
|
329
449
|
<div className="pb-24">
|
|
330
450
|
{active === "overview" && <OverviewSection />}
|
|
331
451
|
{active !== "overview" && <ComponentViewer id={active} />}
|
|
@@ -334,7 +454,7 @@ export default function App() {
|
|
|
334
454
|
</div>
|
|
335
455
|
|
|
336
456
|
{/* Floating Size Toggle */}
|
|
337
|
-
{
|
|
457
|
+
{activeComponent?.hasSize && (
|
|
338
458
|
<div className="fixed bottom-6 right-6 z-50 flex flex-col gap-1.5 items-end animate-in fade-in slide-in-from-bottom-4 duration-300">
|
|
339
459
|
<div className="rounded-xl border border-border/50 bg-background/80 p-1 shadow-2xl backdrop-blur-xl">
|
|
340
460
|
<SizeToggle value={size} onValueChange={setSize} />
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { Maximize2Icon, MonitorIcon, SmartphoneIcon, TabletIcon } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
import { Button } from "../../components/micro/button";
|
|
5
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../components/micro/tabs";
|
|
6
|
+
import { SectionHeader } from "./showcase";
|
|
7
|
+
import { CodeHighlighter } from "./code-highlighter";
|
|
8
|
+
import { cn } from "../../lib/utils";
|
|
9
|
+
|
|
10
|
+
export function BlockViewer({
|
|
11
|
+
blockId,
|
|
12
|
+
title,
|
|
13
|
+
description,
|
|
14
|
+
codeString,
|
|
15
|
+
}: {
|
|
16
|
+
blockId: string;
|
|
17
|
+
title: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
codeString: string;
|
|
20
|
+
}) {
|
|
21
|
+
const [width, setWidth] = useState<"100%" | "768px" | "375px">("100%");
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className="mb-12">
|
|
25
|
+
<SectionHeader title={title} description={description} />
|
|
26
|
+
|
|
27
|
+
<Tabs defaultValue="preview" className="w-full relative group">
|
|
28
|
+
<div className="flex items-center justify-between mb-6 pb-4 border-b border-border/50">
|
|
29
|
+
<TabsList className="bg-muted/50 p-1 rounded-lg h-auto">
|
|
30
|
+
<TabsTrigger value="preview" className="data-[state=active]:bg-background data-[state=active]:shadow-sm border-transparent rounded-md h-7 px-3 text-xs">Preview</TabsTrigger>
|
|
31
|
+
<TabsTrigger value="code" className="data-[state=active]:bg-background data-[state=active]:shadow-sm border-transparent rounded-md h-7 px-3 text-xs">Code</TabsTrigger>
|
|
32
|
+
</TabsList>
|
|
33
|
+
|
|
34
|
+
<div className="flex items-center gap-1.5 opacity-100 md:opacity-60 md:group-hover:opacity-100 transition-opacity">
|
|
35
|
+
<Button
|
|
36
|
+
variant="ghost"
|
|
37
|
+
size="sm"
|
|
38
|
+
iconOnly
|
|
39
|
+
onClick={() => setWidth("100%")}
|
|
40
|
+
className={cn("size-7", width === "100%" ? "bg-muted text-foreground" : "text-muted-foreground")}
|
|
41
|
+
aria-label="Desktop view"
|
|
42
|
+
>
|
|
43
|
+
<MonitorIcon className="size-3.5" />
|
|
44
|
+
</Button>
|
|
45
|
+
<Button
|
|
46
|
+
variant="ghost"
|
|
47
|
+
size="sm"
|
|
48
|
+
iconOnly
|
|
49
|
+
onClick={() => setWidth("768px")}
|
|
50
|
+
className={cn("size-7 hidden sm:flex", width === "768px" ? "bg-muted text-foreground" : "text-muted-foreground")}
|
|
51
|
+
aria-label="Tablet view"
|
|
52
|
+
>
|
|
53
|
+
<TabletIcon className="size-3.5" />
|
|
54
|
+
</Button>
|
|
55
|
+
<Button
|
|
56
|
+
variant="ghost"
|
|
57
|
+
size="sm"
|
|
58
|
+
iconOnly
|
|
59
|
+
onClick={() => setWidth("375px")}
|
|
60
|
+
className={cn("size-7", width === "375px" ? "bg-muted text-foreground" : "text-muted-foreground")}
|
|
61
|
+
aria-label="Mobile view"
|
|
62
|
+
>
|
|
63
|
+
<SmartphoneIcon className="size-3.5" />
|
|
64
|
+
</Button>
|
|
65
|
+
|
|
66
|
+
<div className="w-px h-4 bg-border mx-1" />
|
|
67
|
+
|
|
68
|
+
<Button
|
|
69
|
+
variant="ghost"
|
|
70
|
+
size="sm"
|
|
71
|
+
iconOnly
|
|
72
|
+
render={<a href={`/preview/${blockId}`} target="_blank" rel="noreferrer" />}
|
|
73
|
+
nativeButton={false}
|
|
74
|
+
className="size-7 text-muted-foreground hover:text-foreground"
|
|
75
|
+
title="Open full screen"
|
|
76
|
+
>
|
|
77
|
+
<Maximize2Icon className="size-3.5" />
|
|
78
|
+
</Button>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<TabsContent value="preview" className="mt-4">
|
|
83
|
+
<div className="flex justify-center w-full">
|
|
84
|
+
<div
|
|
85
|
+
className="bg-background rounded-xl overflow-hidden border border-border shadow-sm transition-all duration-500 ease-in-out relative flex flex-col"
|
|
86
|
+
style={{ width, height: "750px", maxWidth: "100%" }}
|
|
87
|
+
>
|
|
88
|
+
<div className="h-10 border-b border-border bg-muted/30 flex items-center px-4 gap-2 shrink-0">
|
|
89
|
+
<div className="flex gap-1.5">
|
|
90
|
+
<div className="size-2.5 rounded-full bg-border/80" />
|
|
91
|
+
<div className="size-2.5 rounded-full bg-border/80" />
|
|
92
|
+
<div className="size-2.5 rounded-full bg-border/80" />
|
|
93
|
+
</div>
|
|
94
|
+
<div className="flex-1 text-center text-[10px] text-muted-foreground/60 font-medium font-mono select-none">
|
|
95
|
+
localhost:5173/preview/{blockId}
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<iframe
|
|
100
|
+
src={`/preview/${blockId}`}
|
|
101
|
+
className="w-full flex-1 border-0"
|
|
102
|
+
title={`${title} Preview`}
|
|
103
|
+
/>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</TabsContent>
|
|
107
|
+
|
|
108
|
+
<TabsContent value="code" className="mt-4 relative group/code">
|
|
109
|
+
<div className="rounded-2xl border border-border overflow-hidden [&_.shiki]:!m-0 [&_.shiki]:!rounded-none [&_.shiki]:!p-6 [&_.shiki]:max-h-[750px] overflow-auto custom-scrollbar">
|
|
110
|
+
<CodeHighlighter code={codeString} />
|
|
111
|
+
</div>
|
|
112
|
+
<Button
|
|
113
|
+
size="sm"
|
|
114
|
+
iconOnly
|
|
115
|
+
variant="soft"
|
|
116
|
+
className="absolute top-4 right-4 z-10 bg-background/80 backdrop-blur-sm text-muted-foreground hover:text-foreground shadow-sm"
|
|
117
|
+
onClick={() => {
|
|
118
|
+
navigator.clipboard.writeText(codeString);
|
|
119
|
+
const el = document.getElementById(`copy-${blockId}`);
|
|
120
|
+
if (el) {
|
|
121
|
+
el.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check size-4 text-green-500"><path d="M20 6 9 17l-5-5"/></svg>';
|
|
122
|
+
setTimeout(() => {
|
|
123
|
+
el.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy size-4"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>';
|
|
124
|
+
}, 2000);
|
|
125
|
+
}
|
|
126
|
+
}}
|
|
127
|
+
>
|
|
128
|
+
<span id={`copy-${blockId}`}>
|
|
129
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" className="lucide lucide-copy size-4"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
|
|
130
|
+
</span>
|
|
131
|
+
</Button>
|
|
132
|
+
</TabsContent>
|
|
133
|
+
</Tabs>
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
@@ -59,10 +59,22 @@ export function CodeHighlighter({
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
return (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
<>
|
|
63
|
+
<style>{`
|
|
64
|
+
html.dark .shiki,
|
|
65
|
+
html.dark .shiki span {
|
|
66
|
+
color: var(--shiki-dark) !important;
|
|
67
|
+
background-color: var(--shiki-dark-bg) !important;
|
|
68
|
+
font-style: var(--shiki-dark-font-style) !important;
|
|
69
|
+
font-weight: var(--shiki-dark-font-weight) !important;
|
|
70
|
+
text-decoration: var(--shiki-dark-text-decoration) !important;
|
|
71
|
+
}
|
|
72
|
+
`}</style>
|
|
73
|
+
<div
|
|
74
|
+
ref={containerRef}
|
|
75
|
+
className="code-highlighter overflow-x-auto text-[13px] leading-relaxed"
|
|
76
|
+
dangerouslySetInnerHTML={{ __html: html }}
|
|
77
|
+
/>
|
|
78
|
+
</>
|
|
67
79
|
);
|
|
68
80
|
}
|
package/src/dev/data.ts
CHANGED
|
@@ -484,6 +484,46 @@ export const COMPONENTS = [
|
|
|
484
484
|
status: "stable",
|
|
485
485
|
hasSize: true,
|
|
486
486
|
},
|
|
487
|
+
{
|
|
488
|
+
id: "dashboard-block",
|
|
489
|
+
category: "Dashboards",
|
|
490
|
+
label: "Dashboard",
|
|
491
|
+
hasMicro: false,
|
|
492
|
+
hasMacro: false,
|
|
493
|
+
status: "new",
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
id: "settings-block",
|
|
497
|
+
category: "Settings",
|
|
498
|
+
label: "Settings Layout",
|
|
499
|
+
hasMicro: false,
|
|
500
|
+
hasMacro: false,
|
|
501
|
+
status: "new",
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
id: "auth-form-block",
|
|
505
|
+
category: "Authentication",
|
|
506
|
+
label: "Login Form",
|
|
507
|
+
hasMicro: false,
|
|
508
|
+
hasMacro: false,
|
|
509
|
+
status: "new",
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
id: "auth-recovery-block",
|
|
513
|
+
category: "Authentication",
|
|
514
|
+
label: "Password Recovery",
|
|
515
|
+
hasMicro: false,
|
|
516
|
+
hasMacro: false,
|
|
517
|
+
status: "new",
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
id: "auth-split-block",
|
|
521
|
+
category: "Authentication",
|
|
522
|
+
label: "Login (Split Screen)",
|
|
523
|
+
hasMicro: false,
|
|
524
|
+
hasMacro: false,
|
|
525
|
+
status: "new",
|
|
526
|
+
},
|
|
487
527
|
{
|
|
488
528
|
id: "why",
|
|
489
529
|
category: "Other",
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BlockViewer } from "../components/BlockViewer";
|
|
2
|
+
import codeString from "../../blocks/auth-form.tsx?raw";
|
|
3
|
+
|
|
4
|
+
export default function AuthFormBlockShowcase() {
|
|
5
|
+
return (
|
|
6
|
+
<BlockViewer
|
|
7
|
+
blockId="auth-form"
|
|
8
|
+
title="Login Form"
|
|
9
|
+
description="A clean, simple authentication form layout for login and registration."
|
|
10
|
+
codeString={codeString}
|
|
11
|
+
/>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BlockViewer } from "../components/BlockViewer";
|
|
2
|
+
import codeString from "../../blocks/auth-recovery.tsx?raw";
|
|
3
|
+
|
|
4
|
+
export default function AuthRecoveryBlockShowcase() {
|
|
5
|
+
return (
|
|
6
|
+
<BlockViewer
|
|
7
|
+
blockId="auth-recovery"
|
|
8
|
+
title="Auth Recovery"
|
|
9
|
+
description="A simple password recovery and reset flow."
|
|
10
|
+
codeString={codeString}
|
|
11
|
+
/>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BlockViewer } from "../components/BlockViewer";
|
|
2
|
+
import codeString from "../../blocks/auth-split.tsx?raw";
|
|
3
|
+
|
|
4
|
+
export default function AuthSplitBlockShowcase() {
|
|
5
|
+
return (
|
|
6
|
+
<BlockViewer
|
|
7
|
+
blockId="auth-split"
|
|
8
|
+
title="Auth Split Layout"
|
|
9
|
+
description="A split-screen authentication page with an image or branding area."
|
|
10
|
+
codeString={codeString}
|
|
11
|
+
/>
|
|
12
|
+
);
|
|
13
|
+
}
|