nextworks 0.2.0-alpha.11 → 0.2.0-alpha.13
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/README.md +283 -282
- package/dist/cli_manifests/blocks_manifest.json +198 -175
- package/dist/kits/blocks/.nextworks/docs/BLOCKS_QUICKSTART.md +101 -100
- package/dist/kits/blocks/.nextworks/docs/BLOCKS_README.md +105 -104
- package/dist/kits/blocks/.nextworks/docs/THEME_GUIDE.md +1 -1
- package/dist/kits/blocks/app/templates/aiworkflow/PresetThemeVars.tsx +58 -0
- package/dist/kits/blocks/app/templates/aiworkflow/README.md +46 -0
- package/dist/kits/blocks/app/templates/aiworkflow/components/CTA.tsx +44 -0
- package/dist/kits/blocks/app/templates/aiworkflow/components/Contact.tsx +105 -0
- package/dist/kits/blocks/app/templates/aiworkflow/components/FAQ.tsx +63 -0
- package/dist/kits/blocks/app/templates/aiworkflow/components/Features.tsx +65 -0
- package/dist/kits/blocks/app/templates/aiworkflow/components/Footer.tsx +109 -0
- package/dist/kits/blocks/app/templates/aiworkflow/components/Hero.tsx +636 -0
- package/dist/kits/blocks/app/templates/aiworkflow/components/Navbar.tsx +90 -0
- package/dist/kits/blocks/app/templates/aiworkflow/components/Pricing.tsx +86 -0
- package/dist/kits/blocks/app/templates/aiworkflow/components/ProcessTimeline.tsx +103 -0
- package/dist/kits/blocks/app/templates/aiworkflow/components/Testimonials.tsx +56 -0
- package/dist/kits/blocks/app/templates/aiworkflow/components/TrustBadges.tsx +59 -0
- package/dist/kits/blocks/app/templates/aiworkflow/page.tsx +43 -0
- package/dist/kits/blocks/app/templates/digitalagency/PresetThemeVars.tsx +80 -80
- package/dist/kits/blocks/app/templates/digitalagency/README.md +42 -42
- package/dist/kits/blocks/app/templates/digitalagency/components/Pricing.tsx +114 -114
- package/dist/kits/blocks/app/templates/digitalagency/components/Process.tsx +59 -59
- package/dist/kits/blocks/app/templates/digitalagency/components/Services.tsx +55 -55
- package/dist/kits/blocks/app/templates/digitalagency/components/Team.tsx +28 -28
- package/dist/kits/blocks/app/templates/digitalagency/components/Testimonials.tsx +65 -65
- package/dist/kits/blocks/app/templates/digitalagency/page.tsx +38 -38
- package/dist/kits/blocks/app/templates/gallery/PresetThemeVars.tsx +84 -84
- package/dist/kits/blocks/app/templates/productlaunch/PresetThemeVars.tsx +75 -75
- package/dist/kits/blocks/app/templates/productlaunch/README.md +62 -62
- package/dist/kits/blocks/app/templates/productlaunch/components/About.tsx +84 -84
- package/dist/kits/blocks/app/templates/productlaunch/components/CTA.tsx +50 -50
- package/dist/kits/blocks/app/templates/productlaunch/components/Contact.tsx +231 -231
- package/dist/kits/blocks/app/templates/productlaunch/components/FAQ.tsx +86 -86
- package/dist/kits/blocks/app/templates/productlaunch/components/Features.tsx +83 -83
- package/dist/kits/blocks/app/templates/productlaunch/components/Footer.tsx +132 -132
- package/dist/kits/blocks/app/templates/productlaunch/components/Hero.tsx +88 -88
- package/dist/kits/blocks/app/templates/productlaunch/components/Navbar.tsx +116 -116
- package/dist/kits/blocks/app/templates/productlaunch/components/Pricing.tsx +106 -106
- package/dist/kits/blocks/app/templates/productlaunch/components/ProcessTimeline.tsx +110 -110
- package/dist/kits/blocks/app/templates/productlaunch/components/ServicesGrid.tsx +68 -68
- package/dist/kits/blocks/app/templates/productlaunch/components/Team.tsx +104 -104
- package/dist/kits/blocks/app/templates/productlaunch/components/Testimonials.tsx +90 -90
- package/dist/kits/blocks/app/templates/productlaunch/components/TrustBadges.tsx +76 -76
- package/dist/kits/blocks/app/templates/productlaunch/page.tsx +43 -43
- package/dist/kits/blocks/app/templates/saasdashboard/PresetThemeVars.tsx +80 -80
- package/dist/kits/blocks/app/templates/saasdashboard/README.md +44 -44
- package/dist/kits/blocks/app/templates/saasdashboard/components/Contact.tsx +129 -129
- package/dist/kits/blocks/app/templates/saasdashboard/components/Dashboard.tsx +293 -293
- package/dist/kits/blocks/app/templates/saasdashboard/components/FAQ.tsx +55 -55
- package/dist/kits/blocks/app/templates/saasdashboard/components/Features.tsx +90 -90
- package/dist/kits/blocks/app/templates/saasdashboard/components/Footer.tsx +77 -77
- package/dist/kits/blocks/app/templates/saasdashboard/components/Hero.tsx +104 -104
- package/dist/kits/blocks/app/templates/saasdashboard/components/Hero_mask.tsx +126 -126
- package/dist/kits/blocks/app/templates/saasdashboard/components/Navbar.tsx +117 -117
- package/dist/kits/blocks/app/templates/saasdashboard/components/Pricing.tsx +90 -90
- package/dist/kits/blocks/app/templates/saasdashboard/components/SmoothScroll.tsx +96 -96
- package/dist/kits/blocks/app/templates/saasdashboard/components/Testimonials.tsx +72 -72
- package/dist/kits/blocks/app/templates/saasdashboard/components/TrustBadges.tsx +53 -53
- package/dist/kits/blocks/app/templates/saasdashboard/page.tsx +39 -39
- package/dist/kits/blocks/components/enhanced-theme-provider.tsx +195 -195
- package/dist/kits/blocks/components/providers/BlocksAppProviders.tsx +27 -27
- package/dist/kits/blocks/components/sections/About.tsx +291 -291
- package/dist/kits/blocks/components/sections/CTA.tsx +257 -257
- package/dist/kits/blocks/components/sections/Contact.tsx +267 -267
- package/dist/kits/blocks/components/sections/FAQ.tsx +214 -214
- package/dist/kits/blocks/components/sections/Features.tsx +268 -268
- package/dist/kits/blocks/components/sections/Footer.tsx +302 -302
- package/dist/kits/blocks/components/sections/HeroMotion.tsx +308 -308
- package/dist/kits/blocks/components/sections/HeroOverlay.tsx +358 -358
- package/dist/kits/blocks/components/sections/HeroProductDemo.tsx +236 -0
- package/dist/kits/blocks/components/sections/HeroSplit.tsx +352 -352
- package/dist/kits/blocks/components/sections/Navbar.tsx +350 -350
- package/dist/kits/blocks/components/sections/PortfolioSimple.tsx +549 -549
- package/dist/kits/blocks/components/sections/Pricing.tsx +264 -264
- package/dist/kits/blocks/components/sections/ProcessTimeline.tsx +325 -325
- package/dist/kits/blocks/components/sections/ServicesGrid.tsx +210 -210
- package/dist/kits/blocks/components/sections/Team.tsx +309 -309
- package/dist/kits/blocks/components/sections/Testimonials.tsx +158 -158
- package/dist/kits/blocks/components/sections/TrustBadges.tsx +162 -162
- package/dist/kits/blocks/components/sections/product-demo/ApprovalInboxPanel.tsx +125 -0
- package/dist/kits/blocks/components/sections/product-demo/DemoStage.tsx +397 -0
- package/dist/kits/blocks/components/sections/product-demo/DemoWindow.tsx +128 -0
- package/dist/kits/blocks/components/sections/product-demo/KnowledgePanel.tsx +127 -0
- package/dist/kits/blocks/components/sections/product-demo/RunConsolePanel.tsx +150 -0
- package/dist/kits/blocks/components/sections/product-demo/WorkflowStudioPanel.tsx +191 -0
- package/dist/kits/blocks/components/sections/product-demo/types.ts +193 -0
- package/dist/kits/blocks/components/theme-provider.tsx +1 -1
- package/dist/kits/blocks/components/ui/alert-dialog.tsx +134 -134
- package/dist/kits/blocks/components/ui/brand-node.tsx +121 -121
- package/dist/kits/blocks/components/ui/button.tsx +122 -122
- package/dist/kits/blocks/components/ui/card.tsx +95 -95
- package/dist/kits/blocks/components/ui/checkbox.tsx +30 -30
- package/dist/kits/blocks/components/ui/cta-button.tsx +125 -125
- package/dist/kits/blocks/components/ui/dropdown-menu.tsx +201 -201
- package/dist/kits/blocks/components/ui/feature-card.tsx +91 -91
- package/dist/kits/blocks/components/ui/input.tsx +27 -27
- package/dist/kits/blocks/components/ui/label.tsx +29 -29
- package/dist/kits/blocks/components/ui/pricing-card.tsx +120 -120
- package/dist/kits/blocks/components/ui/select.tsx +25 -25
- package/dist/kits/blocks/components/ui/skeleton.tsx +13 -13
- package/dist/kits/blocks/components/ui/table.tsx +98 -98
- package/dist/kits/blocks/components/ui/testimonial-card.tsx +108 -108
- package/dist/kits/blocks/components/ui/textarea.tsx +26 -26
- package/dist/kits/blocks/components/ui/theme-selector.tsx +243 -243
- package/dist/kits/blocks/components/ui/theme-toggle.tsx +74 -74
- package/dist/kits/blocks/components/ui/toaster.tsx +7 -7
- package/dist/kits/blocks/lib/themes.ts +400 -400
- package/dist/kits/blocks/lib/utils.ts +6 -6
- package/dist/kits/blocks/package-deps.json +3 -3
- package/package.json +1 -1
|
@@ -1,243 +1,243 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
import { Check, Palette, Wrench } from "lucide-react";
|
|
5
|
-
import { useTheme } from "next-themes";
|
|
6
|
-
import { useThemeVariant } from "../enhanced-theme-provider";
|
|
7
|
-
import {
|
|
8
|
-
themes,
|
|
9
|
-
darkThemes,
|
|
10
|
-
type ThemeVariant,
|
|
11
|
-
type ThemeConfig,
|
|
12
|
-
} from "@/lib/themes";
|
|
13
|
-
|
|
14
|
-
import { Button } from "@/components/ui/button";
|
|
15
|
-
import {
|
|
16
|
-
DropdownMenu,
|
|
17
|
-
DropdownMenuContent,
|
|
18
|
-
DropdownMenuItem,
|
|
19
|
-
DropdownMenuLabel,
|
|
20
|
-
DropdownMenuSeparator,
|
|
21
|
-
DropdownMenuTrigger,
|
|
22
|
-
} from "@/components/ui/dropdown-menu";
|
|
23
|
-
|
|
24
|
-
type ThemeSelectorProps = React.ComponentPropsWithoutRef<typeof Button> & {
|
|
25
|
-
ariaLabel?: string;
|
|
26
|
-
label?: string; // optional override for the visible text ("Theme")
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export function ThemeSelector({
|
|
30
|
-
ariaLabel = "Demo: Color theme",
|
|
31
|
-
label = "Theme",
|
|
32
|
-
className,
|
|
33
|
-
...buttonProps
|
|
34
|
-
}: ThemeSelectorProps) {
|
|
35
|
-
const { theme, setTheme } = useTheme();
|
|
36
|
-
const { themeVariant, setThemeVariant, setCustomBrandColors } =
|
|
37
|
-
useThemeVariant();
|
|
38
|
-
|
|
39
|
-
const isDark = theme === "dark";
|
|
40
|
-
|
|
41
|
-
const [showCustom, setShowCustom] = React.useState(false);
|
|
42
|
-
const [custom, setCustom] = React.useState<Partial<ThemeConfig["colors"]>>(
|
|
43
|
-
{},
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
function openCustomDialog() {
|
|
47
|
-
setCustom({});
|
|
48
|
-
setShowCustom(true);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function applyCustom() {
|
|
52
|
-
setCustomBrandColors(custom);
|
|
53
|
-
document.cookie = `theme-variant=custom; Path=/; Max-Age=31536000; SameSite=Lax`;
|
|
54
|
-
document.cookie = `theme-custom=${encodeURIComponent(JSON.stringify(custom))}; Path=/; Max-Age=31536000; SameSite=Lax`;
|
|
55
|
-
setShowCustom(false);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return (
|
|
59
|
-
<DropdownMenu>
|
|
60
|
-
<DropdownMenuTrigger asChild>
|
|
61
|
-
<Button
|
|
62
|
-
variant="outline"
|
|
63
|
-
size="sm"
|
|
64
|
-
className={`gap-2 ${className ?? ""}`}
|
|
65
|
-
aria-label={ariaLabel}
|
|
66
|
-
{...buttonProps}
|
|
67
|
-
>
|
|
68
|
-
<Palette className="h-4 w-4" />
|
|
69
|
-
{label}
|
|
70
|
-
</Button>
|
|
71
|
-
</DropdownMenuTrigger>
|
|
72
|
-
<DropdownMenuContent align="end" className="w-64">
|
|
73
|
-
<DropdownMenuLabel className="space-y-1">
|
|
74
|
-
<div className="flex items-center justify-between">
|
|
75
|
-
<span>Color theme variants</span>
|
|
76
|
-
<span
|
|
77
|
-
aria-hidden="true"
|
|
78
|
-
className="border-border bg-muted/50 text-muted-foreground rounded-md border px-1.5 py-0.5 text-[10px] font-medium tracking-wide uppercase"
|
|
79
|
-
>
|
|
80
|
-
Gallery
|
|
81
|
-
</span>
|
|
82
|
-
</div>
|
|
83
|
-
<p className="text-muted-foreground text-xs">
|
|
84
|
-
Affects this preview only ( button is not part of the shared Navbar.
|
|
85
|
-
)
|
|
86
|
-
</p>
|
|
87
|
-
</DropdownMenuLabel>
|
|
88
|
-
|
|
89
|
-
<DropdownMenuSeparator />
|
|
90
|
-
{Object.entries(themes).map(([key, themeConfig]) => (
|
|
91
|
-
<DropdownMenuItem
|
|
92
|
-
key={key}
|
|
93
|
-
onClick={() => {
|
|
94
|
-
setThemeVariant(key as ThemeVariant);
|
|
95
|
-
document.cookie = `theme-variant=${key}; Path=/; Max-Age=31536000; SameSite=Lax`;
|
|
96
|
-
}}
|
|
97
|
-
className="flex items-center justify-between"
|
|
98
|
-
>
|
|
99
|
-
<div className="flex items-center gap-2">
|
|
100
|
-
<div
|
|
101
|
-
className="h-4 w-4 rounded-full border"
|
|
102
|
-
style={{
|
|
103
|
-
backgroundColor: isDark
|
|
104
|
-
? darkThemes[key as ThemeVariant].colors.primary
|
|
105
|
-
: themeConfig.colors.primary,
|
|
106
|
-
}}
|
|
107
|
-
/>
|
|
108
|
-
{themeConfig.name}
|
|
109
|
-
</div>
|
|
110
|
-
{themeVariant === key && <Check className="h-4 w-4" />}
|
|
111
|
-
</DropdownMenuItem>
|
|
112
|
-
))}
|
|
113
|
-
<DropdownMenuSeparator />
|
|
114
|
-
<DropdownMenuLabel>Custom</DropdownMenuLabel>
|
|
115
|
-
<DropdownMenuItem
|
|
116
|
-
onClick={openCustomDialog}
|
|
117
|
-
className="flex items-center gap-2"
|
|
118
|
-
>
|
|
119
|
-
<Wrench className="h-4 w-4" />
|
|
120
|
-
Customize brand colors…
|
|
121
|
-
</DropdownMenuItem>
|
|
122
|
-
|
|
123
|
-
<DropdownMenuSeparator />
|
|
124
|
-
<DropdownMenuLabel>Appearance</DropdownMenuLabel>
|
|
125
|
-
<DropdownMenuItem onClick={() => setTheme("light")}>
|
|
126
|
-
Light
|
|
127
|
-
</DropdownMenuItem>
|
|
128
|
-
<DropdownMenuItem onClick={() => setTheme("dark")}>
|
|
129
|
-
Dark
|
|
130
|
-
</DropdownMenuItem>
|
|
131
|
-
<DropdownMenuItem onClick={() => setTheme("system")}>
|
|
132
|
-
System
|
|
133
|
-
</DropdownMenuItem>
|
|
134
|
-
</DropdownMenuContent>
|
|
135
|
-
{showCustom && (
|
|
136
|
-
<div className="fixed inset-0 z-[60] flex items-center justify-center bg-black/40 p-4">
|
|
137
|
-
<div
|
|
138
|
-
role="dialog"
|
|
139
|
-
aria-modal="true"
|
|
140
|
-
aria-labelledby="customBrandColorsTitle"
|
|
141
|
-
className="bg-popover text-popover-foreground w-full max-w-md rounded-md border p-4 shadow-lg"
|
|
142
|
-
>
|
|
143
|
-
<div
|
|
144
|
-
id="customBrandColorsTitle"
|
|
145
|
-
className="mb-3 text-sm font-semibold"
|
|
146
|
-
>
|
|
147
|
-
Custom brand colors
|
|
148
|
-
</div>
|
|
149
|
-
<div className="space-y-3">
|
|
150
|
-
<div className="flex items-center gap-3">
|
|
151
|
-
<label className="w-28 text-sm">primary</label>
|
|
152
|
-
<input
|
|
153
|
-
type="text"
|
|
154
|
-
placeholder="oklch(...) or #hex"
|
|
155
|
-
className="bg-background flex-1 rounded border p-2 text-sm"
|
|
156
|
-
value={custom.primary ?? ""}
|
|
157
|
-
onChange={(e) =>
|
|
158
|
-
setCustom((c) => ({ ...c, primary: e.target.value }))
|
|
159
|
-
}
|
|
160
|
-
/>
|
|
161
|
-
<input
|
|
162
|
-
type="color"
|
|
163
|
-
className="h-8 w-10 cursor-pointer"
|
|
164
|
-
value={
|
|
165
|
-
typeof custom.primary === "string" &&
|
|
166
|
-
custom.primary.startsWith("#")
|
|
167
|
-
? (custom.primary as string)
|
|
168
|
-
: "#000000"
|
|
169
|
-
}
|
|
170
|
-
onChange={(e) =>
|
|
171
|
-
setCustom((c) => ({ ...c, primary: e.target.value }))
|
|
172
|
-
}
|
|
173
|
-
/>
|
|
174
|
-
</div>
|
|
175
|
-
<div className="flex items-center gap-3">
|
|
176
|
-
<label className="w-28 text-sm">accent</label>
|
|
177
|
-
<input
|
|
178
|
-
type="text"
|
|
179
|
-
placeholder="oklch(...) or #hex"
|
|
180
|
-
className="bg-background flex-1 rounded border p-2 text-sm"
|
|
181
|
-
value={custom.accent ?? ""}
|
|
182
|
-
onChange={(e) =>
|
|
183
|
-
setCustom((c) => ({ ...c, accent: e.target.value }))
|
|
184
|
-
}
|
|
185
|
-
/>
|
|
186
|
-
<input
|
|
187
|
-
type="color"
|
|
188
|
-
className="h-8 w-10 cursor-pointer"
|
|
189
|
-
value={
|
|
190
|
-
typeof custom.accent === "string" &&
|
|
191
|
-
custom.accent.startsWith("#")
|
|
192
|
-
? (custom.accent as string)
|
|
193
|
-
: "#000000"
|
|
194
|
-
}
|
|
195
|
-
onChange={(e) =>
|
|
196
|
-
setCustom((c) => ({ ...c, accent: e.target.value }))
|
|
197
|
-
}
|
|
198
|
-
/>
|
|
199
|
-
</div>
|
|
200
|
-
<div className="flex items-center gap-3">
|
|
201
|
-
<label className="w-28 text-sm">ring</label>
|
|
202
|
-
<input
|
|
203
|
-
type="text"
|
|
204
|
-
placeholder="oklch(...) or #hex"
|
|
205
|
-
className="bg-background flex-1 rounded border p-2 text-sm"
|
|
206
|
-
value={custom.ring ?? ""}
|
|
207
|
-
onChange={(e) =>
|
|
208
|
-
setCustom((c) => ({ ...c, ring: e.target.value }))
|
|
209
|
-
}
|
|
210
|
-
/>
|
|
211
|
-
<input
|
|
212
|
-
type="color"
|
|
213
|
-
className="h-8 w-10 cursor-pointer"
|
|
214
|
-
value={
|
|
215
|
-
typeof custom.ring === "string" &&
|
|
216
|
-
custom.ring.startsWith("#")
|
|
217
|
-
? (custom.ring as string)
|
|
218
|
-
: "#000000"
|
|
219
|
-
}
|
|
220
|
-
onChange={(e) =>
|
|
221
|
-
setCustom((c) => ({ ...c, ring: e.target.value }))
|
|
222
|
-
}
|
|
223
|
-
/>
|
|
224
|
-
</div>
|
|
225
|
-
</div>
|
|
226
|
-
<div className="mt-4 flex justify-end gap-2">
|
|
227
|
-
<Button
|
|
228
|
-
variant="outline"
|
|
229
|
-
size="sm"
|
|
230
|
-
onClick={() => setShowCustom(false)}
|
|
231
|
-
>
|
|
232
|
-
Cancel
|
|
233
|
-
</Button>
|
|
234
|
-
<Button size="sm" onClick={applyCustom}>
|
|
235
|
-
Apply
|
|
236
|
-
</Button>
|
|
237
|
-
</div>
|
|
238
|
-
</div>
|
|
239
|
-
</div>
|
|
240
|
-
)}
|
|
241
|
-
</DropdownMenu>
|
|
242
|
-
);
|
|
243
|
-
}
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Check, Palette, Wrench } from "lucide-react";
|
|
5
|
+
import { useTheme } from "next-themes";
|
|
6
|
+
import { useThemeVariant } from "../enhanced-theme-provider";
|
|
7
|
+
import {
|
|
8
|
+
themes,
|
|
9
|
+
darkThemes,
|
|
10
|
+
type ThemeVariant,
|
|
11
|
+
type ThemeConfig,
|
|
12
|
+
} from "@/lib/themes";
|
|
13
|
+
|
|
14
|
+
import { Button } from "@/components/ui/button";
|
|
15
|
+
import {
|
|
16
|
+
DropdownMenu,
|
|
17
|
+
DropdownMenuContent,
|
|
18
|
+
DropdownMenuItem,
|
|
19
|
+
DropdownMenuLabel,
|
|
20
|
+
DropdownMenuSeparator,
|
|
21
|
+
DropdownMenuTrigger,
|
|
22
|
+
} from "@/components/ui/dropdown-menu";
|
|
23
|
+
|
|
24
|
+
type ThemeSelectorProps = React.ComponentPropsWithoutRef<typeof Button> & {
|
|
25
|
+
ariaLabel?: string;
|
|
26
|
+
label?: string; // optional override for the visible text ("Theme")
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function ThemeSelector({
|
|
30
|
+
ariaLabel = "Demo: Color theme",
|
|
31
|
+
label = "Theme",
|
|
32
|
+
className,
|
|
33
|
+
...buttonProps
|
|
34
|
+
}: ThemeSelectorProps) {
|
|
35
|
+
const { theme, setTheme } = useTheme();
|
|
36
|
+
const { themeVariant, setThemeVariant, setCustomBrandColors } =
|
|
37
|
+
useThemeVariant();
|
|
38
|
+
|
|
39
|
+
const isDark = theme === "dark";
|
|
40
|
+
|
|
41
|
+
const [showCustom, setShowCustom] = React.useState(false);
|
|
42
|
+
const [custom, setCustom] = React.useState<Partial<ThemeConfig["colors"]>>(
|
|
43
|
+
{},
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
function openCustomDialog() {
|
|
47
|
+
setCustom({});
|
|
48
|
+
setShowCustom(true);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function applyCustom() {
|
|
52
|
+
setCustomBrandColors(custom);
|
|
53
|
+
document.cookie = `theme-variant=custom; Path=/; Max-Age=31536000; SameSite=Lax`;
|
|
54
|
+
document.cookie = `theme-custom=${encodeURIComponent(JSON.stringify(custom))}; Path=/; Max-Age=31536000; SameSite=Lax`;
|
|
55
|
+
setShowCustom(false);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<DropdownMenu>
|
|
60
|
+
<DropdownMenuTrigger asChild>
|
|
61
|
+
<Button
|
|
62
|
+
variant="outline"
|
|
63
|
+
size="sm"
|
|
64
|
+
className={`gap-2 ${className ?? ""}`}
|
|
65
|
+
aria-label={ariaLabel}
|
|
66
|
+
{...buttonProps}
|
|
67
|
+
>
|
|
68
|
+
<Palette className="h-4 w-4" />
|
|
69
|
+
{label}
|
|
70
|
+
</Button>
|
|
71
|
+
</DropdownMenuTrigger>
|
|
72
|
+
<DropdownMenuContent align="end" className="w-64">
|
|
73
|
+
<DropdownMenuLabel className="space-y-1">
|
|
74
|
+
<div className="flex items-center justify-between">
|
|
75
|
+
<span>Color theme variants</span>
|
|
76
|
+
<span
|
|
77
|
+
aria-hidden="true"
|
|
78
|
+
className="border-border bg-muted/50 text-muted-foreground rounded-md border px-1.5 py-0.5 text-[10px] font-medium tracking-wide uppercase"
|
|
79
|
+
>
|
|
80
|
+
Gallery
|
|
81
|
+
</span>
|
|
82
|
+
</div>
|
|
83
|
+
<p className="text-muted-foreground text-xs">
|
|
84
|
+
Affects this preview only ( button is not part of the shared Navbar.
|
|
85
|
+
)
|
|
86
|
+
</p>
|
|
87
|
+
</DropdownMenuLabel>
|
|
88
|
+
|
|
89
|
+
<DropdownMenuSeparator />
|
|
90
|
+
{Object.entries(themes).map(([key, themeConfig]) => (
|
|
91
|
+
<DropdownMenuItem
|
|
92
|
+
key={key}
|
|
93
|
+
onClick={() => {
|
|
94
|
+
setThemeVariant(key as ThemeVariant);
|
|
95
|
+
document.cookie = `theme-variant=${key}; Path=/; Max-Age=31536000; SameSite=Lax`;
|
|
96
|
+
}}
|
|
97
|
+
className="flex items-center justify-between"
|
|
98
|
+
>
|
|
99
|
+
<div className="flex items-center gap-2">
|
|
100
|
+
<div
|
|
101
|
+
className="h-4 w-4 rounded-full border"
|
|
102
|
+
style={{
|
|
103
|
+
backgroundColor: isDark
|
|
104
|
+
? darkThemes[key as ThemeVariant].colors.primary
|
|
105
|
+
: themeConfig.colors.primary,
|
|
106
|
+
}}
|
|
107
|
+
/>
|
|
108
|
+
{themeConfig.name}
|
|
109
|
+
</div>
|
|
110
|
+
{themeVariant === key && <Check className="h-4 w-4" />}
|
|
111
|
+
</DropdownMenuItem>
|
|
112
|
+
))}
|
|
113
|
+
<DropdownMenuSeparator />
|
|
114
|
+
<DropdownMenuLabel>Custom</DropdownMenuLabel>
|
|
115
|
+
<DropdownMenuItem
|
|
116
|
+
onClick={openCustomDialog}
|
|
117
|
+
className="flex items-center gap-2"
|
|
118
|
+
>
|
|
119
|
+
<Wrench className="h-4 w-4" />
|
|
120
|
+
Customize brand colors…
|
|
121
|
+
</DropdownMenuItem>
|
|
122
|
+
|
|
123
|
+
<DropdownMenuSeparator />
|
|
124
|
+
<DropdownMenuLabel>Appearance</DropdownMenuLabel>
|
|
125
|
+
<DropdownMenuItem onClick={() => setTheme("light")}>
|
|
126
|
+
Light
|
|
127
|
+
</DropdownMenuItem>
|
|
128
|
+
<DropdownMenuItem onClick={() => setTheme("dark")}>
|
|
129
|
+
Dark
|
|
130
|
+
</DropdownMenuItem>
|
|
131
|
+
<DropdownMenuItem onClick={() => setTheme("system")}>
|
|
132
|
+
System
|
|
133
|
+
</DropdownMenuItem>
|
|
134
|
+
</DropdownMenuContent>
|
|
135
|
+
{showCustom && (
|
|
136
|
+
<div className="fixed inset-0 z-[60] flex items-center justify-center bg-black/40 p-4">
|
|
137
|
+
<div
|
|
138
|
+
role="dialog"
|
|
139
|
+
aria-modal="true"
|
|
140
|
+
aria-labelledby="customBrandColorsTitle"
|
|
141
|
+
className="bg-popover text-popover-foreground w-full max-w-md rounded-md border p-4 shadow-lg"
|
|
142
|
+
>
|
|
143
|
+
<div
|
|
144
|
+
id="customBrandColorsTitle"
|
|
145
|
+
className="mb-3 text-sm font-semibold"
|
|
146
|
+
>
|
|
147
|
+
Custom brand colors
|
|
148
|
+
</div>
|
|
149
|
+
<div className="space-y-3">
|
|
150
|
+
<div className="flex items-center gap-3">
|
|
151
|
+
<label className="w-28 text-sm">primary</label>
|
|
152
|
+
<input
|
|
153
|
+
type="text"
|
|
154
|
+
placeholder="oklch(...) or #hex"
|
|
155
|
+
className="bg-background flex-1 rounded border p-2 text-sm"
|
|
156
|
+
value={custom.primary ?? ""}
|
|
157
|
+
onChange={(e) =>
|
|
158
|
+
setCustom((c) => ({ ...c, primary: e.target.value }))
|
|
159
|
+
}
|
|
160
|
+
/>
|
|
161
|
+
<input
|
|
162
|
+
type="color"
|
|
163
|
+
className="h-8 w-10 cursor-pointer"
|
|
164
|
+
value={
|
|
165
|
+
typeof custom.primary === "string" &&
|
|
166
|
+
custom.primary.startsWith("#")
|
|
167
|
+
? (custom.primary as string)
|
|
168
|
+
: "#000000"
|
|
169
|
+
}
|
|
170
|
+
onChange={(e) =>
|
|
171
|
+
setCustom((c) => ({ ...c, primary: e.target.value }))
|
|
172
|
+
}
|
|
173
|
+
/>
|
|
174
|
+
</div>
|
|
175
|
+
<div className="flex items-center gap-3">
|
|
176
|
+
<label className="w-28 text-sm">accent</label>
|
|
177
|
+
<input
|
|
178
|
+
type="text"
|
|
179
|
+
placeholder="oklch(...) or #hex"
|
|
180
|
+
className="bg-background flex-1 rounded border p-2 text-sm"
|
|
181
|
+
value={custom.accent ?? ""}
|
|
182
|
+
onChange={(e) =>
|
|
183
|
+
setCustom((c) => ({ ...c, accent: e.target.value }))
|
|
184
|
+
}
|
|
185
|
+
/>
|
|
186
|
+
<input
|
|
187
|
+
type="color"
|
|
188
|
+
className="h-8 w-10 cursor-pointer"
|
|
189
|
+
value={
|
|
190
|
+
typeof custom.accent === "string" &&
|
|
191
|
+
custom.accent.startsWith("#")
|
|
192
|
+
? (custom.accent as string)
|
|
193
|
+
: "#000000"
|
|
194
|
+
}
|
|
195
|
+
onChange={(e) =>
|
|
196
|
+
setCustom((c) => ({ ...c, accent: e.target.value }))
|
|
197
|
+
}
|
|
198
|
+
/>
|
|
199
|
+
</div>
|
|
200
|
+
<div className="flex items-center gap-3">
|
|
201
|
+
<label className="w-28 text-sm">ring</label>
|
|
202
|
+
<input
|
|
203
|
+
type="text"
|
|
204
|
+
placeholder="oklch(...) or #hex"
|
|
205
|
+
className="bg-background flex-1 rounded border p-2 text-sm"
|
|
206
|
+
value={custom.ring ?? ""}
|
|
207
|
+
onChange={(e) =>
|
|
208
|
+
setCustom((c) => ({ ...c, ring: e.target.value }))
|
|
209
|
+
}
|
|
210
|
+
/>
|
|
211
|
+
<input
|
|
212
|
+
type="color"
|
|
213
|
+
className="h-8 w-10 cursor-pointer"
|
|
214
|
+
value={
|
|
215
|
+
typeof custom.ring === "string" &&
|
|
216
|
+
custom.ring.startsWith("#")
|
|
217
|
+
? (custom.ring as string)
|
|
218
|
+
: "#000000"
|
|
219
|
+
}
|
|
220
|
+
onChange={(e) =>
|
|
221
|
+
setCustom((c) => ({ ...c, ring: e.target.value }))
|
|
222
|
+
}
|
|
223
|
+
/>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
<div className="mt-4 flex justify-end gap-2">
|
|
227
|
+
<Button
|
|
228
|
+
variant="outline"
|
|
229
|
+
size="sm"
|
|
230
|
+
onClick={() => setShowCustom(false)}
|
|
231
|
+
>
|
|
232
|
+
Cancel
|
|
233
|
+
</Button>
|
|
234
|
+
<Button size="sm" onClick={applyCustom}>
|
|
235
|
+
Apply
|
|
236
|
+
</Button>
|
|
237
|
+
</div>
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
)}
|
|
241
|
+
</DropdownMenu>
|
|
242
|
+
);
|
|
243
|
+
}
|
|
@@ -1,74 +1,74 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
import { Moon, Sun } from "lucide-react";
|
|
5
|
-
import { useTheme } from "next-themes";
|
|
6
|
-
import { cn } from "@/lib/utils";
|
|
7
|
-
|
|
8
|
-
import { Button } from "@/components/ui/button";
|
|
9
|
-
|
|
10
|
-
type ButtonProps = React.ComponentProps<typeof Button>;
|
|
11
|
-
|
|
12
|
-
export type ThemeToggleProps = {
|
|
13
|
-
/** Forwarded to internal Button. You can set unstyled, className, variant, size, etc. */
|
|
14
|
-
buttonProps?: Partial<ButtonProps>;
|
|
15
|
-
/** Optional aria-label override */
|
|
16
|
-
ariaLabel?: string;
|
|
17
|
-
/** Optional class overrides for icons */
|
|
18
|
-
moonClassName?: string;
|
|
19
|
-
sunClassName?: string;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export function ThemeToggle({
|
|
23
|
-
buttonProps,
|
|
24
|
-
ariaLabel = "Toggle theme",
|
|
25
|
-
moonClassName,
|
|
26
|
-
sunClassName,
|
|
27
|
-
}: ThemeToggleProps) {
|
|
28
|
-
const { theme, setTheme } = useTheme();
|
|
29
|
-
|
|
30
|
-
const mergedButtonProps: ButtonProps = {
|
|
31
|
-
variant: "outline",
|
|
32
|
-
size: "icon",
|
|
33
|
-
...(buttonProps as ButtonProps),
|
|
34
|
-
className: cn(
|
|
35
|
-
"relative",
|
|
36
|
-
// Prefer preset variables if provided on Navbar
|
|
37
|
-
"bg-[var(--navbar-toggle-bg)] text-[var(--navbar-accent)] hover:bg-[var(--navbar-hover-bg)] focus-visible:ring-[var(--navbar-ring)]",
|
|
38
|
-
// Ensure border uses preset variable; provide width for unstyled cases
|
|
39
|
-
"border border-[var(--navbar-border)]",
|
|
40
|
-
buttonProps?.className,
|
|
41
|
-
),
|
|
42
|
-
// Inline style ensures our accent wins over token classes even under dark: variants
|
|
43
|
-
style: {
|
|
44
|
-
...(buttonProps?.style as React.CSSProperties),
|
|
45
|
-
color: "var(--navbar-accent)",
|
|
46
|
-
backgroundColor: "var(--navbar-toggle-bg)",
|
|
47
|
-
borderColor: "var(--navbar-border)",
|
|
48
|
-
// Tell Tailwind ring utilities which ring color to use
|
|
49
|
-
"--tw-ring-color": "var(--navbar-ring)",
|
|
50
|
-
},
|
|
51
|
-
} as ButtonProps;
|
|
52
|
-
|
|
53
|
-
return (
|
|
54
|
-
<Button
|
|
55
|
-
{...mergedButtonProps}
|
|
56
|
-
onClick={() => setTheme(theme === "light" ? "dark" : "light")}
|
|
57
|
-
aria-label={ariaLabel}
|
|
58
|
-
>
|
|
59
|
-
<Moon
|
|
60
|
-
className={cn(
|
|
61
|
-
"h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90",
|
|
62
|
-
moonClassName,
|
|
63
|
-
)}
|
|
64
|
-
/>
|
|
65
|
-
<Sun
|
|
66
|
-
className={cn(
|
|
67
|
-
"absolute h-[1.2rem] w-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0",
|
|
68
|
-
sunClassName,
|
|
69
|
-
)}
|
|
70
|
-
/>
|
|
71
|
-
<span className="sr-only">{ariaLabel}</span>
|
|
72
|
-
</Button>
|
|
73
|
-
);
|
|
74
|
-
}
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Moon, Sun } from "lucide-react";
|
|
5
|
+
import { useTheme } from "next-themes";
|
|
6
|
+
import { cn } from "@/lib/utils";
|
|
7
|
+
|
|
8
|
+
import { Button } from "@/components/ui/button";
|
|
9
|
+
|
|
10
|
+
type ButtonProps = React.ComponentProps<typeof Button>;
|
|
11
|
+
|
|
12
|
+
export type ThemeToggleProps = {
|
|
13
|
+
/** Forwarded to internal Button. You can set unstyled, className, variant, size, etc. */
|
|
14
|
+
buttonProps?: Partial<ButtonProps>;
|
|
15
|
+
/** Optional aria-label override */
|
|
16
|
+
ariaLabel?: string;
|
|
17
|
+
/** Optional class overrides for icons */
|
|
18
|
+
moonClassName?: string;
|
|
19
|
+
sunClassName?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function ThemeToggle({
|
|
23
|
+
buttonProps,
|
|
24
|
+
ariaLabel = "Toggle theme",
|
|
25
|
+
moonClassName,
|
|
26
|
+
sunClassName,
|
|
27
|
+
}: ThemeToggleProps) {
|
|
28
|
+
const { theme, setTheme } = useTheme();
|
|
29
|
+
|
|
30
|
+
const mergedButtonProps: ButtonProps = {
|
|
31
|
+
variant: "outline",
|
|
32
|
+
size: "icon",
|
|
33
|
+
...(buttonProps as ButtonProps),
|
|
34
|
+
className: cn(
|
|
35
|
+
"relative",
|
|
36
|
+
// Prefer preset variables if provided on Navbar
|
|
37
|
+
"bg-[var(--navbar-toggle-bg)] text-[var(--navbar-accent)] hover:bg-[var(--navbar-hover-bg)] focus-visible:ring-[var(--navbar-ring)]",
|
|
38
|
+
// Ensure border uses preset variable; provide width for unstyled cases
|
|
39
|
+
"border border-[var(--navbar-border)]",
|
|
40
|
+
buttonProps?.className,
|
|
41
|
+
),
|
|
42
|
+
// Inline style ensures our accent wins over token classes even under dark: variants
|
|
43
|
+
style: {
|
|
44
|
+
...(buttonProps?.style as React.CSSProperties),
|
|
45
|
+
color: "var(--navbar-accent)",
|
|
46
|
+
backgroundColor: "var(--navbar-toggle-bg)",
|
|
47
|
+
borderColor: "var(--navbar-border)",
|
|
48
|
+
// Tell Tailwind ring utilities which ring color to use
|
|
49
|
+
"--tw-ring-color": "var(--navbar-ring)",
|
|
50
|
+
},
|
|
51
|
+
} as ButtonProps;
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<Button
|
|
55
|
+
{...mergedButtonProps}
|
|
56
|
+
onClick={() => setTheme(theme === "light" ? "dark" : "light")}
|
|
57
|
+
aria-label={ariaLabel}
|
|
58
|
+
>
|
|
59
|
+
<Moon
|
|
60
|
+
className={cn(
|
|
61
|
+
"h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90",
|
|
62
|
+
moonClassName,
|
|
63
|
+
)}
|
|
64
|
+
/>
|
|
65
|
+
<Sun
|
|
66
|
+
className={cn(
|
|
67
|
+
"absolute h-[1.2rem] w-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0",
|
|
68
|
+
sunClassName,
|
|
69
|
+
)}
|
|
70
|
+
/>
|
|
71
|
+
<span className="sr-only">{ariaLabel}</span>
|
|
72
|
+
</Button>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { Toaster } from "sonner";
|
|
4
|
-
|
|
5
|
-
export default function AppToaster() {
|
|
6
|
-
return <Toaster position="top-center" richColors closeButton />;
|
|
7
|
-
}
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Toaster } from "sonner";
|
|
4
|
+
|
|
5
|
+
export default function AppToaster() {
|
|
6
|
+
return <Toaster position="top-center" richColors closeButton />;
|
|
7
|
+
}
|