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/src/dev/App.tsx CHANGED
@@ -1,4 +1,5 @@
1
- import React, { Suspense, lazy, useCallback, useEffect, useState } from "react";
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 categoryOrder = [
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
- setTimeout(() => {
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
- const el = document.getElementById(comp);
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
- const compDef = COMPONENTS.find((c) => c.id === active);
170
- if (compDef) {
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 — Search bar (opens Command Palette) */}
204
- <button
205
- id="cmd-search-trigger"
206
- onClick={() => setCmdOpen(true)}
207
- className="hidden md:flex h-9 items-center gap-2.5 px-4 rounded-lg border border-border bg-muted/30 text-sm text-muted-foreground hover:bg-muted/60 transition-colors w-64 xl:w-80"
208
- aria-label="Tìm component (⌘K)"
209
- >
210
- <SearchIcon className="size-3.5 shrink-0" />
211
- <span className="flex-1 text-left text-[13px]">
212
- Tìm component...
213
- </span>
214
- <kbd className="text-[10px] bg-background border border-border/80 rounded px-1.5 py-0.5 font-sans leading-none">
215
- ⌘K
216
- </kbd>
217
- </button>
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={`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:block md:h-[calc(100vh-4rem)] md:w-56 md:translate-x-0 md:pt-6 md:z-0 overflow-y-auto custom-scrollbar ${
280
- isMobileMenuOpen ? "translate-x-0 shadow-2xl" : "-translate-x-full"
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={`w-full flex items-center gap-2.5 rounded-md px-2.5 py-1.5 text-[13px] transition-colors text-left mb-2 ${
287
- active === "overview"
288
- ? "bg-accent text-accent-foreground font-medium"
289
- : "text-muted-foreground hover:bg-muted/60 hover:text-foreground"
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
- {categoryOrder.map((cat) => {
299
- const comps = groupedComponents[cat];
300
- if (!comps || comps.length === 0) return null;
301
- return (
302
- <div key={cat} className="mb-4">
303
- <p className="mb-2 px-2 text-[10px] font-bold uppercase tracking-wider text-foreground/70">
304
- {cat}
305
- </p>
306
- <nav className="space-y-0.5">
307
- {comps.map(({ id, label }) => (
308
- <button
309
- key={id}
310
- onClick={() => setActive(id)}
311
- className={`w-full flex items-center justify-between gap-2.5 rounded-md px-2.5 py-1.5 text-[13px] transition-colors text-left ${
312
- active === id
313
- ? "bg-accent text-accent-foreground font-medium"
314
- : "text-muted-foreground hover:bg-muted/60 hover:text-foreground"
315
- }`}
316
- >
317
- <span>{label}</span>
318
- </button>
319
- ))}
320
- </nav>
321
- </div>
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
- {COMPONENTS.find((c) => c.id === active)?.hasSize && (
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
- <div
63
- ref={containerRef}
64
- className="code-highlighter overflow-x-auto text-[13px] leading-relaxed"
65
- dangerouslySetInnerHTML={{ __html: html }}
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
+ }