gladvn 0.2.36 → 0.2.37

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.
Files changed (134) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/scripts/strip-comments.ts +79 -0
  4. package/src/blocks/auth-form.tsx +34 -30
  5. package/src/blocks/auth-recovery.tsx +9 -13
  6. package/src/blocks/auth-split.tsx +46 -36
  7. package/src/blocks/settings.tsx +84 -117
  8. package/src/components/macro/alert-preset.tsx +3 -1
  9. package/src/components/macro/carousel-preset.tsx +0 -1
  10. package/src/components/macro/checkbox-preset.test.tsx +2 -2
  11. package/src/components/macro/checkbox-preset.tsx +1 -2
  12. package/src/components/macro/command-preset.tsx +1 -1
  13. package/src/components/macro/date-picker.tsx +5 -18
  14. package/src/components/macro/dialog-preset.tsx +5 -3
  15. package/src/components/macro/input-preset.tsx +0 -1
  16. package/src/components/macro/select-preset.tsx +1 -1
  17. package/src/components/macro/switch-preset.test.tsx +1 -1
  18. package/src/components/micro/accordion.test.tsx +6 -12
  19. package/src/components/micro/alert-dialog.tsx +0 -2
  20. package/src/components/micro/alert.test.tsx +5 -8
  21. package/src/components/micro/alert.tsx +0 -2
  22. package/src/components/micro/badge.test.tsx +5 -7
  23. package/src/components/micro/badge.tsx +1 -3
  24. package/src/components/micro/button.test.tsx +19 -26
  25. package/src/components/micro/button.tsx +3 -11
  26. package/src/components/micro/calendar.tsx +12 -13
  27. package/src/components/micro/card.test.tsx +3 -5
  28. package/src/components/micro/carousel.tsx +0 -1
  29. package/src/components/micro/chart.tsx +3 -2
  30. package/src/components/micro/checkbox.test.tsx +9 -18
  31. package/src/components/micro/combobox.tsx +1 -3
  32. package/src/components/micro/command.tsx +23 -11
  33. package/src/components/micro/context-menu.tsx +0 -2
  34. package/src/components/micro/dialog.test.tsx +6 -14
  35. package/src/components/micro/dialog.tsx +0 -2
  36. package/src/components/micro/drawer.tsx +0 -3
  37. package/src/components/micro/dropdown-menu.test.tsx +8 -14
  38. package/src/components/micro/dropdown-menu.tsx +0 -2
  39. package/src/components/micro/field.tsx +3 -3
  40. package/src/components/micro/hover-card.tsx +0 -2
  41. package/src/components/micro/input-group.tsx +9 -42
  42. package/src/components/micro/input.test.tsx +15 -24
  43. package/src/components/micro/navigation-menu.tsx +0 -2
  44. package/src/components/micro/popover.test.tsx +5 -9
  45. package/src/components/micro/progress.tsx +5 -1
  46. package/src/components/micro/select.test.tsx +5 -18
  47. package/src/components/micro/select.tsx +3 -5
  48. package/src/components/micro/separator.tsx +1 -1
  49. package/src/components/micro/sheet.test.tsx +6 -14
  50. package/src/components/micro/sheet.tsx +0 -2
  51. package/src/components/micro/sidebar.tsx +5 -6
  52. package/src/components/micro/sonner.tsx +0 -1
  53. package/src/components/micro/switch.test.tsx +9 -18
  54. package/src/components/micro/switch.tsx +1 -1
  55. package/src/components/micro/tabs.test.tsx +5 -11
  56. package/src/components/micro/theme-provider.tsx +0 -12
  57. package/src/components/micro/toggle-group.tsx +1 -12
  58. package/src/components/micro/tooltip.test.tsx +7 -12
  59. package/src/dev/App.tsx +6 -6
  60. package/src/dev/components/BlockViewer.tsx +15 -4
  61. package/src/dev/components/Container.tsx +20 -0
  62. package/src/dev/components/GladvnLogo.tsx +3 -9
  63. package/src/dev/components/code-highlighter.tsx +5 -3
  64. package/src/dev/components/showcase.tsx +116 -168
  65. package/src/dev/sections/buttons.tsx +4 -9
  66. package/src/dev/sections/display.tsx +68 -72
  67. package/src/dev/sections/feedback.tsx +304 -314
  68. package/src/dev/sections/forms.tsx +10 -14
  69. package/src/dev/sections/interactive.tsx +32 -32
  70. package/src/dev/sections/overview.tsx +2 -10
  71. package/src/dev/showcase/accordion.tsx +512 -554
  72. package/src/dev/showcase/alert-dialog.tsx +525 -565
  73. package/src/dev/showcase/alert.tsx +352 -402
  74. package/src/dev/showcase/aspect-ratio.tsx +167 -193
  75. package/src/dev/showcase/avatar.tsx +231 -269
  76. package/src/dev/showcase/badge.tsx +92 -140
  77. package/src/dev/showcase/breadcrumb.tsx +113 -135
  78. package/src/dev/showcase/button.tsx +140 -173
  79. package/src/dev/showcase/calendar.tsx +187 -237
  80. package/src/dev/showcase/card.tsx +229 -261
  81. package/src/dev/showcase/carousel.tsx +288 -315
  82. package/src/dev/showcase/chart.tsx +44 -50
  83. package/src/dev/showcase/checkbox.tsx +286 -330
  84. package/src/dev/showcase/collapsible.tsx +143 -160
  85. package/src/dev/showcase/combobox.tsx +327 -369
  86. package/src/dev/showcase/command.tsx +106 -120
  87. package/src/dev/showcase/context-menu.tsx +93 -107
  88. package/src/dev/showcase/dialog.tsx +585 -630
  89. package/src/dev/showcase/drawer.tsx +176 -196
  90. package/src/dev/showcase/dropdown-menu.tsx +183 -201
  91. package/src/dev/showcase/empty.tsx +232 -274
  92. package/src/dev/showcase/hover-card.tsx +153 -180
  93. package/src/dev/showcase/input-group.tsx +221 -253
  94. package/src/dev/showcase/input-otp.tsx +204 -250
  95. package/src/dev/showcase/input.tsx +246 -325
  96. package/src/dev/showcase/item.tsx +134 -152
  97. package/src/dev/showcase/kbd.tsx +149 -172
  98. package/src/dev/showcase/label.tsx +50 -68
  99. package/src/dev/showcase/menubar.tsx +194 -213
  100. package/src/dev/showcase/navigation-menu.tsx +119 -133
  101. package/src/dev/showcase/overview-components/ArchitectureSection.tsx +126 -0
  102. package/src/dev/showcase/overview-components/CodeBlock.tsx +50 -0
  103. package/src/dev/showcase/overview-components/ColorPaletteSection.tsx +44 -0
  104. package/src/dev/showcase/overview-components/DashboardPreview.tsx +159 -0
  105. package/src/dev/showcase/overview-components/FeatureGridSection.tsx +535 -0
  106. package/src/dev/showcase/overview-components/FooterCTA.tsx +137 -0
  107. package/src/dev/showcase/overview-components/HeroSection.tsx +65 -0
  108. package/src/dev/showcase/overview-components/PrinciplesSection.tsx +128 -0
  109. package/src/dev/showcase/overview.tsx +21 -1163
  110. package/src/dev/showcase/pagination.tsx +144 -190
  111. package/src/dev/showcase/popover.tsx +181 -196
  112. package/src/dev/showcase/progress.tsx +141 -189
  113. package/src/dev/showcase/radio-group.tsx +272 -332
  114. package/src/dev/showcase/resizable.tsx +119 -141
  115. package/src/dev/showcase/scroll-area.tsx +21 -36
  116. package/src/dev/showcase/select.tsx +262 -322
  117. package/src/dev/showcase/separator.tsx +75 -73
  118. package/src/dev/showcase/sheet.tsx +333 -361
  119. package/src/dev/showcase/sidebar.tsx +53 -59
  120. package/src/dev/showcase/skeleton.tsx +93 -112
  121. package/src/dev/showcase/slider.tsx +244 -296
  122. package/src/dev/showcase/sonner.tsx +251 -258
  123. package/src/dev/showcase/spinner.tsx +46 -67
  124. package/src/dev/showcase/switch.tsx +298 -362
  125. package/src/dev/showcase/table.tsx +76 -80
  126. package/src/dev/showcase/tabs.tsx +302 -355
  127. package/src/dev/showcase/textarea.tsx +240 -293
  128. package/src/dev/showcase/toast.tsx +48 -54
  129. package/src/dev/showcase/toggle-group.tsx +104 -129
  130. package/src/dev/showcase/toggle.tsx +82 -102
  131. package/src/dev/showcase/tooltip.tsx +173 -192
  132. package/src/dev/showcase/why.tsx +6 -12
  133. package/src/hooks/use-mobile.test.ts +3 -2
  134. package/src/lib/utils.test.ts +5 -4
@@ -4,9 +4,6 @@ import * as React from "react";
4
4
 
5
5
  import { cn } from "../../lib/utils";
6
6
 
7
- // ─────────────────────────────────────────────────────────────────────────────
8
- // 1. Sidebar (Wrapper)
9
- // ─────────────────────────────────────────────────────────────────────────────
10
7
  const Sidebar = React.forwardRef<
11
8
  HTMLDivElement,
12
9
  React.ComponentProps<"div"> & {
@@ -27,7 +24,7 @@ const Sidebar = React.forwardRef<
27
24
  },
28
25
  ref,
29
26
  ) => {
30
- // will-change only during animation, not always-on
27
+
31
28
  const handleTransitionStart = (
32
29
  e: React.TransitionEvent<HTMLDivElement>,
33
30
  ) => {
@@ -39,7 +36,7 @@ const Sidebar = React.forwardRef<
39
36
  };
40
37
 
41
38
  return (
42
- // Outer: only this div animates width. No layout children to reflow.
39
+
43
40
  <div
44
41
  ref={ref}
45
42
  role="navigation"
@@ -55,7 +52,9 @@ const Sidebar = React.forwardRef<
55
52
  "transition-[width,border-width] duration-[400ms]",
56
53
  "data-[state=expanded]:w-[var(--sb-expanded)]",
57
54
  "data-[state=collapsed]:w-[var(--sb-collapsed)]",
58
- collapsedWidth === 0 && "data-[state=collapsed]:border-r-0",
55
+ {
56
+ "data-[state=collapsed]:border-r-0": collapsedWidth === 0
57
+ },
59
58
  className,
60
59
  )}
61
60
  data-state={defaultState}
@@ -43,7 +43,6 @@ function Toaster({ ...props }: ToasterProps) {
43
43
  "--normal-border": "var(--border)",
44
44
  "--border-radius": "var(--radius)",
45
45
 
46
- /* Map Sonner's internal variables to our design system variables */
47
46
  "--success-bg":
48
47
  "color-mix(in srgb, var(--success) 10%, var(--popover))",
49
48
  "--success-text": "var(--success)",
@@ -8,20 +8,20 @@ import { Switch, SwitchThumb } from "./switch";
8
8
 
9
9
  describe("Switch", () => {
10
10
  it("[3C.7-01] [P1] renders correctly", () => {
11
- // Given
11
+
12
12
  render(
13
13
  <Switch aria-label="Toggle setting">
14
14
  <SwitchThumb />
15
15
  </Switch>,
16
16
  );
17
17
  const switchEl = screen.getByRole("switch", { name: "Toggle setting" });
18
- // Then
18
+
19
19
  expect(switchEl).toBeInTheDocument();
20
20
  expect(switchEl).toHaveAttribute("data-slot", "switch");
21
21
  });
22
22
 
23
23
  it("[3C.7-02] [P1] handles user click to toggle state", async () => {
24
- // Given
24
+
25
25
  const user = userEvent.setup();
26
26
  const handleCheckedChange = vi.fn();
27
27
  render(
@@ -32,24 +32,19 @@ describe("Switch", () => {
32
32
 
33
33
  const switchEl = screen.getByRole("switch", { name: "Airplane mode" });
34
34
 
35
- // Initially unchecked (base-ui uses aria-checked)
36
- // Then
37
35
  expect(switchEl).toHaveAttribute("aria-checked", "false");
38
36
 
39
- // Click to check
40
- // When
41
37
  await user.click(switchEl);
42
38
  expect(switchEl).toHaveAttribute("aria-checked", "true");
43
39
  expect(handleCheckedChange).toHaveBeenCalledWith(true, expect.anything());
44
40
 
45
- // Click to uncheck
46
41
  await user.click(switchEl);
47
42
  expect(switchEl).toHaveAttribute("aria-checked", "false");
48
43
  expect(handleCheckedChange).toHaveBeenCalledWith(false, expect.anything());
49
44
  });
50
45
 
51
46
  it("[3C.7-03] [P1] can be focused and toggled via keyboard (Space)", async () => {
52
- // Given
47
+
53
48
  const user = userEvent.setup();
54
49
  render(
55
50
  <Switch aria-label="Keyboard toggle">
@@ -59,19 +54,16 @@ describe("Switch", () => {
59
54
 
60
55
  const switchEl = screen.getByRole("switch", { name: "Keyboard toggle" });
61
56
 
62
- // Test focus
63
- // When
64
57
  await user.tab();
65
- // Then
58
+
66
59
  expect(switchEl).toHaveFocus();
67
60
 
68
- // Test keyboard activation (Space for switch/checkbox)
69
61
  await user.keyboard("[Space]");
70
62
  expect(switchEl).toHaveAttribute("aria-checked", "true");
71
63
  });
72
64
 
73
65
  it("[3C.7-04] [P1] handles disabled state correctly and blocks clicks", async () => {
74
- // Given
66
+
75
67
  const user = userEvent.setup();
76
68
  const handleCheckedChange = vi.fn();
77
69
  render(
@@ -85,24 +77,23 @@ describe("Switch", () => {
85
77
  );
86
78
 
87
79
  const switchEl = screen.getByRole("switch", { name: "Disabled switch" });
88
- // Then
80
+
89
81
  expect(switchEl).toHaveAttribute("aria-disabled", "true");
90
82
 
91
- // When
92
83
  await user.click(switchEl);
93
84
  expect(handleCheckedChange).not.toHaveBeenCalled();
94
85
  expect(switchEl).toHaveAttribute("aria-checked", "false");
95
86
  });
96
87
 
97
88
  it("[3C.7-05] [P1] forwards the ref to the underlying DOM element", () => {
98
- // Given
89
+
99
90
  const ref = React.createRef<HTMLSpanElement>();
100
91
  render(
101
92
  <Switch ref={ref} aria-label="Ref switch">
102
93
  <SwitchThumb />
103
94
  </Switch>,
104
95
  );
105
- // Then
96
+
106
97
  expect(ref.current).toBeInstanceOf(HTMLSpanElement);
107
98
  });
108
99
  });
@@ -41,7 +41,7 @@ const Switch = React.forwardRef<
41
41
  <SwitchPrimitive.Root
42
42
  ref={ref}
43
43
  data-slot="switch"
44
- /* Expand touch target area (Hit Area) minimally to 44x44px via pseudo-element (after:-inset-*) */
44
+
45
45
  className={cn(switchVariants({ size, className }))}
46
46
  {...props}
47
47
  />
@@ -7,7 +7,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "./tabs";
7
7
 
8
8
  describe("Tabs", () => {
9
9
  it("[3C.8-01] [P1] renders correctly and shows default content", () => {
10
- // Given
10
+
11
11
  render(
12
12
  <Tabs defaultValue="tab1">
13
13
  <TabsList>
@@ -19,19 +19,16 @@ describe("Tabs", () => {
19
19
  </Tabs>,
20
20
  );
21
21
 
22
- // Then
23
22
  expect(screen.getByRole("tab", { name: "Tab 1" })).toBeInTheDocument();
24
23
  expect(screen.getByRole("tab", { name: "Tab 2" })).toBeInTheDocument();
25
24
 
26
- // Tab 1 content should be visible
27
25
  expect(screen.getByText("Content 1")).toBeInTheDocument();
28
26
 
29
- // Tab 2 content should NOT be visible
30
27
  expect(screen.queryByText("Content 2")).not.toBeInTheDocument();
31
28
  });
32
29
 
33
30
  it("[3C.8-02] [P1] switches content when another tab is clicked", async () => {
34
- // Given
31
+
35
32
  const user = userEvent.setup();
36
33
  render(
37
34
  <Tabs defaultValue="tab1">
@@ -45,16 +42,15 @@ describe("Tabs", () => {
45
42
  );
46
43
 
47
44
  const tab2 = screen.getByRole("tab", { name: "Tab 2" });
48
- // When
45
+
49
46
  await user.click(tab2);
50
47
 
51
- // Then
52
48
  expect(screen.queryByText("Content 1")).not.toBeInTheDocument();
53
49
  expect(screen.getByText("Content 2")).toBeInTheDocument();
54
50
  });
55
51
 
56
52
  it("[3C.8-03] [P1] supports keyboard navigation", async () => {
57
- // Given
53
+
58
54
  const user = userEvent.setup();
59
55
  render(
60
56
  <Tabs defaultValue="tab1">
@@ -70,12 +66,10 @@ describe("Tabs", () => {
70
66
  const tab1 = screen.getByRole("tab", { name: "Tab 1" });
71
67
  const tab2 = screen.getByRole("tab", { name: "Tab 2" });
72
68
 
73
- // When
74
69
  await user.tab();
75
- // Then
70
+
76
71
  expect(tab1).toHaveFocus();
77
72
 
78
- // ArrowRight should move focus and potentially activate the next tab
79
73
  await user.keyboard("[ArrowRight]");
80
74
  await waitFor(() => {
81
75
  expect(tab2).toHaveFocus();
@@ -15,8 +15,6 @@
15
15
 
16
16
  import * as React from "react";
17
17
 
18
- // ─── Types ────────────────────────────────────────────────────────────────────
19
-
20
18
  type ThemeMode = "light" | "dark";
21
19
 
22
20
  type ThemeContextValue = {
@@ -26,14 +24,10 @@ type ThemeContextValue = {
26
24
  setMode: (mode: ThemeMode) => void;
27
25
  };
28
26
 
29
- // ─── Context ──────────────────────────────────────────────────────────────────
30
-
31
27
  const ThemeContext = React.createContext<ThemeContextValue | undefined>(
32
28
  undefined,
33
29
  );
34
30
 
35
- // ─── useTheme ─────────────────────────────────────────────────────────────────
36
-
37
31
  /**
38
32
  * Read and control the nearest ThemeProvider.
39
33
  * Returns `undefined` when no provider is found in the tree.
@@ -49,8 +43,6 @@ function useTheme(): ThemeContextValue | undefined {
49
43
  return React.useContext(ThemeContext);
50
44
  }
51
45
 
52
- // ─── ThemeProvider ────────────────────────────────────────────────────────────
53
-
54
46
  type ThemeProviderProps = {
55
47
  children: React.ReactNode;
56
48
  } & (
@@ -131,8 +123,6 @@ function ThemeProvider({
131
123
 
132
124
  ThemeProvider.displayName = "ThemeProvider";
133
125
 
134
- // ─── ThemeWrapper ─────────────────────────────────────────────────────────────
135
-
136
126
  /**
137
127
  * ThemeWrapper — re-applies the current theme class inside a Portal.
138
128
  * Used internally by Dialog, Tooltip, Popover, etc. to tunnel the theme
@@ -157,7 +147,5 @@ function ThemeWrapper({ children }: { children: React.ReactNode }) {
157
147
 
158
148
  ThemeWrapper.displayName = "ThemeWrapper";
159
149
 
160
- // ─── Exports ──────────────────────────────────────────────────────────────────
161
-
162
150
  export { ThemeProvider, ThemeWrapper, useTheme };
163
151
  export type { ThemeMode };
@@ -16,9 +16,6 @@ import { type VariantProps } from "class-variance-authority";
16
16
  import { toggleVariants } from "../../components/micro/toggle";
17
17
  import { cn } from "../../lib/utils";
18
18
 
19
- // ──────────────────────────────────────────────────────────
20
- // Context
21
- // ──────────────────────────────────────────────────────────
22
19
  type ToggleGroupContextValue = VariantProps<typeof toggleVariants> & {
23
20
  spacing?: number;
24
21
  orientation?: "horizontal" | "vertical";
@@ -31,9 +28,6 @@ const ToggleGroupContext = React.createContext<ToggleGroupContextValue>({
31
28
  orientation: "horizontal",
32
29
  });
33
30
 
34
- // ──────────────────────────────────────────────────────────
35
- // Types
36
- // ──────────────────────────────────────────────────────────
37
31
  type ToggleGroupProps = ToggleGroupPrimitive.Props &
38
32
  VariantProps<typeof toggleVariants> & {
39
33
  spacing?: number;
@@ -43,10 +37,6 @@ type ToggleGroupProps = ToggleGroupPrimitive.Props &
43
37
  type ToggleGroupItemProps = TogglePrimitive.Props &
44
38
  VariantProps<typeof toggleVariants>;
45
39
 
46
- // ──────────────────────────────────────────────────────────
47
- // Components
48
- // ──────────────────────────────────────────────────────────
49
-
50
40
  /**
51
41
  * @description A set of two-state buttons that can be toggled on or off.
52
42
  * @requires ToggleGroupItem
@@ -59,8 +49,7 @@ function ToggleGroup({
59
49
  className,
60
50
  variant,
61
51
  size,
62
- // spacing is a dynamic numeric value — CSS variable is the only viable approach
63
- // since Tailwind cannot generate arbitrary gap-{n} utilities at runtime.
52
+
64
53
  spacing = 2,
65
54
  orientation = "horizontal",
66
55
  children,
@@ -12,7 +12,7 @@ import {
12
12
 
13
13
  describe("Tooltip", () => {
14
14
  it("[3C.9-01] [P1] renders trigger and hides content initially", () => {
15
- // Given
15
+
16
16
  render(
17
17
  <TooltipProvider>
18
18
  <Tooltip>
@@ -22,17 +22,15 @@ describe("Tooltip", () => {
22
22
  </TooltipProvider>,
23
23
  );
24
24
 
25
- // Then
26
25
  expect(
27
26
  screen.getByRole("button", { name: "Hover me" }),
28
27
  ).toBeInTheDocument();
29
28
 
30
- // Content should not be visible initially
31
29
  expect(screen.queryByText("Tooltip info")).not.toBeInTheDocument();
32
30
  });
33
31
 
34
32
  it("[3C.9-02] [P1] shows tooltip content on hover", async () => {
35
- // Given
33
+
36
34
  const user = userEvent.setup();
37
35
  render(
38
36
  <TooltipProvider delay={0}>
@@ -44,17 +42,16 @@ describe("Tooltip", () => {
44
42
  );
45
43
 
46
44
  const trigger = screen.getByRole("button", { name: "Hover me" });
47
- // When
45
+
48
46
  await user.hover(trigger);
49
47
 
50
- // Then
51
48
  await waitFor(() => {
52
49
  expect(screen.getByText("Tooltip info")).toBeVisible();
53
50
  });
54
51
  });
55
52
 
56
53
  it("[3C.9-03] [P1] shows tooltip content on focus", async () => {
57
- // Given
54
+
58
55
  const user = userEvent.setup();
59
56
  render(
60
57
  <TooltipProvider delay={0}>
@@ -67,9 +64,8 @@ describe("Tooltip", () => {
67
64
 
68
65
  const trigger = screen.getByRole("button", { name: "Hover me" });
69
66
 
70
- // When
71
67
  await user.tab();
72
- // Then
68
+
73
69
  expect(trigger).toHaveFocus();
74
70
 
75
71
  await waitFor(() => {
@@ -78,7 +74,7 @@ describe("Tooltip", () => {
78
74
  });
79
75
 
80
76
  it("[3C.9-04] [P1] hides tooltip when Escape is pressed", async () => {
81
- // Given
77
+
82
78
  const user = userEvent.setup();
83
79
  render(
84
80
  <TooltipProvider delay={0}>
@@ -90,10 +86,9 @@ describe("Tooltip", () => {
90
86
  );
91
87
 
92
88
  const trigger = screen.getByRole("button", { name: "Hover me" });
93
- // When
89
+
94
90
  await user.hover(trigger);
95
91
 
96
- // Then
97
92
  await waitFor(() => {
98
93
  expect(screen.getByText("Tooltip info")).toBeVisible();
99
94
  });
package/src/dev/App.tsx CHANGED
@@ -199,7 +199,7 @@ export default function App() {
199
199
  <button
200
200
  onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
201
201
  className="md:hidden -ml-2 p-1.5 rounded-md hover:bg-muted text-muted-foreground transition-colors"
202
- aria-label="Toggle Menu"
202
+ aria-label="Bật/tắt menu"
203
203
  >
204
204
  {isMobileMenuOpen ? (
205
205
  <XIcon className="size-5" />
@@ -227,7 +227,7 @@ export default function App() {
227
227
  }
228
228
  )}
229
229
  >
230
- Overview
230
+ Tổng quan
231
231
  </button>
232
232
  <button
233
233
  onClick={() => {
@@ -272,7 +272,7 @@ export default function App() {
272
272
  >
273
273
  <SearchIcon className="size-3.5 shrink-0" />
274
274
  <span className="flex-1 text-left">
275
- Search...
275
+ Tìm kiếm...
276
276
  </span>
277
277
  <kbd className="text-[10px] bg-background border border-border/80 rounded px-1.5 py-0.5 font-sans leading-none">
278
278
  ⌘K
@@ -293,7 +293,7 @@ export default function App() {
293
293
  />
294
294
  }
295
295
  nativeButton={false}
296
- className="text-muted-foreground hover:text-foreground hidden sm:inline-flex"
296
+ className="text-muted-foreground hover:text-foreground hidden sm:inline-flex"
297
297
  >
298
298
  {/* GitHub SVG mark */}
299
299
  <svg
@@ -322,7 +322,7 @@ export default function App() {
322
322
  >
323
323
  <SunIcon className="size-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
324
324
  <MoonIcon className="absolute size-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
325
- <span className="sr-only">Toggle theme</span>
325
+ <span className="sr-only">Đổi giao diện</span>
326
326
  </Button>
327
327
  </div>
328
328
  </div>
@@ -362,7 +362,7 @@ export default function App() {
362
362
  )}
363
363
  >
364
364
  <LayersIcon className="size-3.5" />
365
- Overview
365
+ Tổng quan
366
366
  </button>
367
367
  <div className="h-px bg-border/50 my-2 mx-1"></div>
368
368
  <button
@@ -12,16 +12,18 @@ export function BlockViewer({
12
12
  title,
13
13
  description,
14
14
  codeString,
15
+ className,
15
16
  }: {
16
17
  blockId: string;
17
18
  title: string;
18
19
  description?: string;
19
20
  codeString: string;
21
+ className?: string;
20
22
  }) {
21
23
  const [width, setWidth] = useState<"100%" | "768px" | "375px">("100%");
22
24
 
23
25
  return (
24
- <div className="mb-12">
26
+ <div className={cn("flex flex-col gap-6", className)}>
25
27
  <SectionHeader title={title} description={description} />
26
28
 
27
29
  <Tabs defaultValue="preview" className="w-full relative group">
@@ -37,7 +39,10 @@ export function BlockViewer({
37
39
  size="sm"
38
40
  iconOnly
39
41
  onClick={() => setWidth("100%")}
40
- className={cn("size-7", width === "100%" ? "bg-muted text-foreground" : "text-muted-foreground")}
42
+ className={cn("size-7", {
43
+ "bg-muted text-foreground": width === "100%",
44
+ "text-muted-foreground": width !== "100%",
45
+ })}
41
46
  aria-label="Desktop view"
42
47
  >
43
48
  <MonitorIcon className="size-3.5" />
@@ -47,7 +52,10 @@ export function BlockViewer({
47
52
  size="sm"
48
53
  iconOnly
49
54
  onClick={() => setWidth("768px")}
50
- className={cn("size-7 hidden sm:flex", width === "768px" ? "bg-muted text-foreground" : "text-muted-foreground")}
55
+ className={cn("size-7 hidden sm:flex", {
56
+ "bg-muted text-foreground": width === "768px",
57
+ "text-muted-foreground": width !== "768px",
58
+ })}
51
59
  aria-label="Tablet view"
52
60
  >
53
61
  <TabletIcon className="size-3.5" />
@@ -57,7 +65,10 @@ export function BlockViewer({
57
65
  size="sm"
58
66
  iconOnly
59
67
  onClick={() => setWidth("375px")}
60
- className={cn("size-7", width === "375px" ? "bg-muted text-foreground" : "text-muted-foreground")}
68
+ className={cn("size-7", {
69
+ "bg-muted text-foreground": width === "375px",
70
+ "text-muted-foreground": width !== "375px",
71
+ })}
61
72
  aria-label="Mobile view"
62
73
  >
63
74
  <SmartphoneIcon className="size-3.5" />
@@ -0,0 +1,20 @@
1
+ import React from "react";
2
+ import { cn } from "../../lib/utils";
3
+
4
+ interface ContainerProps extends React.ComponentProps<"div"> {
5
+ as?: React.ElementType;
6
+ }
7
+
8
+ export const Container = React.forwardRef<HTMLElement, ContainerProps>(
9
+ ({ className, as: Component = "div", ...props }, ref) => {
10
+ return (
11
+ <Component
12
+ ref={ref}
13
+ className={cn("container max-w-6xl mx-auto px-4", className)}
14
+ {...props}
15
+ />
16
+ );
17
+ }
18
+ );
19
+
20
+ Container.displayName = "Container";
@@ -2,15 +2,12 @@ import { cn } from "../../lib/utils";
2
2
 
3
3
  interface GladvnLogoProps {
4
4
  className?: string;
5
- /** Size of the icon SVG */
6
- iconSize?: string;
7
5
  /** Show icon only (default) or icon + wordmark */
8
6
  variant?: "icon" | "wordmark";
9
7
  }
10
8
 
11
9
  export function GladvnLogo({
12
10
  className,
13
- iconSize = "size-7",
14
11
  variant = "icon",
15
12
  }: GladvnLogoProps) {
16
13
  const icon = (
@@ -18,10 +15,11 @@ export function GladvnLogo({
18
15
  viewBox="0 0 32 32"
19
16
  fill="none"
20
17
  xmlns="http://www.w3.org/2000/svg"
21
- className={cn(iconSize, variant === "icon" && className)}
18
+ className={cn("size-7", {
19
+ [`${className}`]: variant === "icon" && !!className
20
+ })}
22
21
  aria-hidden="true"
23
22
  >
24
- {/* Macro layer — outer rounded square, primary fill */}
25
23
  <rect
26
24
  x="1"
27
25
  y="1"
@@ -30,7 +28,6 @@ export function GladvnLogo({
30
28
  rx="7"
31
29
  className="fill-primary"
32
30
  />
33
- {/* Micro layer — inner square, offset top-right, semi-transparent white */}
34
31
  <rect
35
32
  x="11"
36
33
  y="5"
@@ -40,7 +37,6 @@ export function GladvnLogo({
40
37
  fill="white"
41
38
  fillOpacity="0.25"
42
39
  />
43
- {/* Micro inner highlight — brighter corner */}
44
40
  <rect
45
41
  x="15"
46
42
  y="5"
@@ -50,9 +46,7 @@ export function GladvnLogo({
50
46
  fill="white"
51
47
  fillOpacity="0.2"
52
48
  />
53
- {/* Token dot — bottom left, CSS token representation */}
54
49
  <circle cx="8" cy="24" r="3" fill="white" fillOpacity="0.7" />
55
- {/* Macro bottom bar — connects Macro to token */}
56
50
  <rect
57
51
  x="6"
58
52
  y="17"
@@ -1,8 +1,8 @@
1
1
  import { useEffect, useRef, useState } from "react";
2
+ import { cn } from "../../lib/utils";
2
3
 
3
4
  import { type Highlighter, createHighlighter } from "shiki";
4
5
 
5
- // Singleton highlighter — loaded once, reused across all CodeHighlighter instances
6
6
  let highlighterPromise: Promise<Highlighter> | null = null;
7
7
 
8
8
  function getHighlighter() {
@@ -18,9 +18,11 @@ function getHighlighter() {
18
18
  export function CodeHighlighter({
19
19
  code,
20
20
  language = "tsx",
21
+ className,
21
22
  }: {
22
23
  code: string;
23
24
  language?: "tsx" | "ts" | "css";
25
+ className?: string;
24
26
  }) {
25
27
  const [html, setHtml] = useState<string | null>(null);
26
28
  const containerRef = useRef<HTMLDivElement>(null);
@@ -50,7 +52,7 @@ export function CodeHighlighter({
50
52
 
51
53
  if (!html) {
52
54
  return (
53
- <div className="overflow-x-auto text-[13px] leading-relaxed font-mono">
55
+ <div className={cn("overflow-x-auto text-[13px] leading-relaxed font-mono", className)}>
54
56
  <pre className="!bg-transparent !p-0 !m-0 whitespace-pre-wrap break-words">
55
57
  <code className="text-muted-foreground">Loading...</code>
56
58
  </pre>
@@ -72,7 +74,7 @@ export function CodeHighlighter({
72
74
  `}</style>
73
75
  <div
74
76
  ref={containerRef}
75
- className="code-highlighter overflow-x-auto text-[13px] leading-relaxed"
77
+ className={cn("code-highlighter overflow-x-auto text-[13px] leading-relaxed", className)}
76
78
  dangerouslySetInnerHTML={{ __html: html }}
77
79
  />
78
80
  </>