create-claudecraft 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +194 -0
- package/bin/cli.js +2 -0
- package/dist/constants.d.ts +71 -0
- package/dist/constants.js +128 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +229 -0
- package/dist/ink-prompts.d.ts +12 -0
- package/dist/ink-prompts.js +363 -0
- package/dist/prompts.d.ts +16 -0
- package/dist/prompts.js +434 -0
- package/dist/scaffold.d.ts +19 -0
- package/dist/scaffold.js +303 -0
- package/dist/ui.d.ts +27 -0
- package/dist/ui.js +254 -0
- package/package.json +74 -0
- package/templates/app/App.tsx +21 -0
- package/templates/base/CLAUDE.md +332 -0
- package/templates/base/eslint.config.js +28 -0
- package/templates/base/index.html +17 -0
- package/templates/base/package.json +43 -0
- package/templates/base/postcss.config.js +6 -0
- package/templates/base/tailwind.config.js +81 -0
- package/templates/base/tsconfig.json +25 -0
- package/templates/base/vite.config.ts +16 -0
- package/templates/commands/brainstorm.md +6 -0
- package/templates/commands/build.md +41 -0
- package/templates/commands/execute-plan.md +6 -0
- package/templates/commands/lint.md +41 -0
- package/templates/commands/ralph.md +113 -0
- package/templates/commands/typecheck.md +44 -0
- package/templates/commands/write-plan.md +6 -0
- package/templates/components/ErrorBoundary.tsx +49 -0
- package/templates/components/ui/Button.tsx +60 -0
- package/templates/components/ui/CodeBlock.tsx +46 -0
- package/templates/components/ui/CopyCommand.tsx +38 -0
- package/templates/components/ui/FilePreview.tsx +46 -0
- package/templates/components/ui/SkipLink.tsx +7 -0
- package/templates/components/ui/ThemeSelector.tsx +41 -0
- package/templates/components/ui/UICarousel.tsx +309 -0
- package/templates/context/ThemeContext.tsx +61 -0
- package/templates/homepage/HomePage.tsx +534 -0
- package/templates/homepage/NotFoundPage.tsx +17 -0
- package/templates/hooks/README.md +82 -0
- package/templates/hooks/check-branch.js +27 -0
- package/templates/hooks/typecheck-after-edit.js +51 -0
- package/templates/index.css +67 -0
- package/templates/lib/utils.ts +9 -0
- package/templates/main.tsx +16 -0
- package/templates/settings/MCP_SETUP.md +76 -0
- package/templates/settings/settings.json +16 -0
- package/templates/settings/settings.local.json +37 -0
- package/templates/skills/design/a11y-audit/SKILL.md +173 -0
- package/templates/skills/design/design-polish/SKILL.md +75 -0
- package/templates/skills/design/figma-to-code/SKILL.md +157 -0
- package/templates/skills/design/json-ld/SKILL.md +125 -0
- package/templates/skills/design/microcopy/SKILL.md +197 -0
- package/templates/skills/design/og-image/SKILL.md +157 -0
- package/templates/skills/design/ralph-wiggum-loops/SKILL.md +299 -0
- package/templates/skills/design/react-best-practices/SKILL.md +106 -0
- package/templates/skills/design/react-best-practices/references/react-performance-guidelines.md +143 -0
- package/templates/skills/design/seo-review/SKILL.md +96 -0
- package/templates/skills/design/sitemap-generator/SKILL.md +66 -0
- package/templates/skills/design/testing-patterns/SKILL.md +276 -0
- package/templates/skills/design/ui-skills/SKILL.md +85 -0
- package/templates/skills/design/visual-iteration/SKILL.md +88 -0
- package/templates/skills/workflow/brainstorming/SKILL.md +54 -0
- package/templates/skills/workflow/dispatching-parallel-agents/SKILL.md +180 -0
- package/templates/skills/workflow/executing-plans/SKILL.md +76 -0
- package/templates/skills/workflow/finishing-a-development-branch/SKILL.md +200 -0
- package/templates/skills/workflow/receiving-code-review/SKILL.md +213 -0
- package/templates/skills/workflow/requesting-code-review/SKILL.md +105 -0
- package/templates/skills/workflow/requesting-code-review/code-reviewer.md +146 -0
- package/templates/skills/workflow/subagent-driven-development/SKILL.md +240 -0
- package/templates/skills/workflow/subagent-driven-development/code-quality-reviewer-prompt.md +20 -0
- package/templates/skills/workflow/subagent-driven-development/implementer-prompt.md +78 -0
- package/templates/skills/workflow/subagent-driven-development/spec-reviewer-prompt.md +61 -0
- package/templates/skills/workflow/systematic-debugging/CREATION-LOG.md +119 -0
- package/templates/skills/workflow/systematic-debugging/SKILL.md +296 -0
- package/templates/skills/workflow/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/templates/skills/workflow/systematic-debugging/condition-based-waiting.md +115 -0
- package/templates/skills/workflow/systematic-debugging/defense-in-depth.md +122 -0
- package/templates/skills/workflow/systematic-debugging/find-polluter.sh +63 -0
- package/templates/skills/workflow/systematic-debugging/root-cause-tracing.md +169 -0
- package/templates/skills/workflow/systematic-debugging/test-academic.md +14 -0
- package/templates/skills/workflow/systematic-debugging/test-pressure-1.md +58 -0
- package/templates/skills/workflow/systematic-debugging/test-pressure-2.md +68 -0
- package/templates/skills/workflow/systematic-debugging/test-pressure-3.md +69 -0
- package/templates/skills/workflow/test-driven-development/SKILL.md +371 -0
- package/templates/skills/workflow/test-driven-development/testing-anti-patterns.md +299 -0
- package/templates/skills/workflow/using-git-worktrees/SKILL.md +217 -0
- package/templates/skills/workflow/using-superpowers/SKILL.md +87 -0
- package/templates/skills/workflow/verification-before-completion/SKILL.md +139 -0
- package/templates/skills/workflow/writing-plans/SKILL.md +116 -0
- package/templates/skills/workflow/writing-skills/SKILL.md +655 -0
- package/templates/skills/workflow/writing-skills/anthropic-best-practices.md +1150 -0
- package/templates/skills/workflow/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
- package/templates/skills/workflow/writing-skills/graphviz-conventions.dot +172 -0
- package/templates/skills/workflow/writing-skills/persuasion-principles.md +187 -0
- package/templates/skills/workflow/writing-skills/render-graphs.js +168 -0
- package/templates/skills/workflow/writing-skills/testing-skills-with-subagents.md +384 -0
- package/templates/types/index.ts +17 -0
- package/templates/vite-env.d.ts +1 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react'
|
|
2
|
+
import { useTheme, type Theme } from '@/context/ThemeContext'
|
|
3
|
+
|
|
4
|
+
const slides = [
|
|
5
|
+
{
|
|
6
|
+
id: 'chat',
|
|
7
|
+
content: (
|
|
8
|
+
<div className="space-y-3">
|
|
9
|
+
<div className="chat chat-start">
|
|
10
|
+
<div className="chat-bubble chat-bubble-primary text-sm">Can you make this pop more?</div>
|
|
11
|
+
</div>
|
|
12
|
+
<div className="chat chat-end">
|
|
13
|
+
<div className="chat-bubble chat-bubble-secondary text-sm">I've added 47 gradients</div>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
),
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: 'card',
|
|
20
|
+
content: (
|
|
21
|
+
<div className="card bg-base-100 shadow-lg">
|
|
22
|
+
<div className="card-body p-4">
|
|
23
|
+
<div className="flex items-center gap-3">
|
|
24
|
+
<div className="avatar placeholder">
|
|
25
|
+
<div className="bg-primary text-primary-content rounded-full w-10">
|
|
26
|
+
<span className="text-sm">AI</span>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
<div className="flex-1">
|
|
30
|
+
<h3 className="font-medium text-sm">Claude</h3>
|
|
31
|
+
<p className="text-xs text-base-content/50">Probably typing...</p>
|
|
32
|
+
</div>
|
|
33
|
+
<span className="badge badge-success badge-sm gap-1">
|
|
34
|
+
<span className="w-1.5 h-1.5 bg-current rounded-full animate-pulse" aria-hidden="true"></span>
|
|
35
|
+
Online
|
|
36
|
+
</span>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
),
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: 'notification',
|
|
44
|
+
content: (
|
|
45
|
+
<div className="space-y-3">
|
|
46
|
+
<div className="alert alert-success py-2 px-3">
|
|
47
|
+
<svg xmlns="http://www.w3.org/2000/svg" className="stroke-current shrink-0 h-5 w-5" fill="none" viewBox="0 0 24 24" aria-hidden="true"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
|
48
|
+
<span className="text-sm">Deployed. Nobody noticed.</span>
|
|
49
|
+
</div>
|
|
50
|
+
<div className="alert alert-warning py-2 px-3">
|
|
51
|
+
<svg xmlns="http://www.w3.org/2000/svg" className="stroke-current shrink-0 h-5 w-5" fill="none" viewBox="0 0 24 24" aria-hidden="true"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>
|
|
52
|
+
<span className="text-sm">Client is typing...</span>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
),
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'modal',
|
|
59
|
+
content: (
|
|
60
|
+
<div className="bg-base-100 rounded-lg shadow-xl p-4">
|
|
61
|
+
<h3 className="font-bold text-sm mb-2">Delete everything?</h3>
|
|
62
|
+
<p className="text-xs text-base-content/70 mb-4">This will remove all your work. Forever. No take-backs.</p>
|
|
63
|
+
<div className="flex gap-2 justify-end">
|
|
64
|
+
<button className="btn btn-ghost btn-sm">Keep it</button>
|
|
65
|
+
<button className="btn btn-error btn-sm">Burn it down</button>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
),
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: 'stats',
|
|
72
|
+
content: (
|
|
73
|
+
<div className="stats stats-vertical bg-base-200 shadow w-full">
|
|
74
|
+
<div className="stat py-3 px-4">
|
|
75
|
+
<div className="stat-title text-xs">Meetings survived</div>
|
|
76
|
+
<div className="stat-value text-2xl text-primary">47</div>
|
|
77
|
+
</div>
|
|
78
|
+
<div className="stat py-3 px-4">
|
|
79
|
+
<div className="stat-title text-xs">Coffee consumed</div>
|
|
80
|
+
<div className="stat-value text-2xl text-secondary">∞</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
),
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: 'progress',
|
|
87
|
+
content: (
|
|
88
|
+
<div className="space-y-4">
|
|
89
|
+
<div>
|
|
90
|
+
<div className="flex justify-between text-xs mb-1">
|
|
91
|
+
<span className="text-base-content/70">Scope creep</span>
|
|
92
|
+
<span className="text-base-content/50">89%</span>
|
|
93
|
+
</div>
|
|
94
|
+
<progress className="progress progress-error w-full" value="89" max="100"></progress>
|
|
95
|
+
</div>
|
|
96
|
+
<div>
|
|
97
|
+
<div className="flex justify-between text-xs mb-1">
|
|
98
|
+
<span className="text-base-content/70">Sanity remaining</span>
|
|
99
|
+
<span className="text-base-content/50">12%</span>
|
|
100
|
+
</div>
|
|
101
|
+
<progress className="progress progress-success w-full" value="12" max="100"></progress>
|
|
102
|
+
</div>
|
|
103
|
+
<div>
|
|
104
|
+
<div className="flex justify-between text-xs mb-1">
|
|
105
|
+
<span className="text-base-content/70">Budget</span>
|
|
106
|
+
<span className="text-base-content/50">3%</span>
|
|
107
|
+
</div>
|
|
108
|
+
<progress className="progress progress-warning w-full" value="3" max="100"></progress>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
),
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: 'tabs',
|
|
115
|
+
content: (
|
|
116
|
+
<div>
|
|
117
|
+
<div role="tablist" className="tabs tabs-boxed bg-base-200 mb-3" aria-label="Project phases">
|
|
118
|
+
<button role="tab" className="tab tab-active text-xs" aria-selected="true" id="tab-design" aria-controls="tabpanel-design">Design</button>
|
|
119
|
+
<button role="tab" className="tab text-xs" aria-selected="false" id="tab-code" aria-controls="tabpanel-code">Code</button>
|
|
120
|
+
<button role="tab" className="tab text-xs" aria-selected="false" id="tab-regret" aria-controls="tabpanel-regret">Regret</button>
|
|
121
|
+
</div>
|
|
122
|
+
<div role="tabpanel" id="tabpanel-design" aria-labelledby="tab-design" className="bg-base-200 rounded-lg p-3">
|
|
123
|
+
<p className="text-sm text-base-content/70">Current tab: Design</p>
|
|
124
|
+
<p className="text-xs text-base-content/50 mt-1">Where ideas go to become tickets.</p>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
),
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: 'form',
|
|
131
|
+
content: (
|
|
132
|
+
<div className="space-y-3">
|
|
133
|
+
<input
|
|
134
|
+
type="text"
|
|
135
|
+
placeholder="Your best idea"
|
|
136
|
+
className="input input-bordered input-sm w-full"
|
|
137
|
+
aria-label="Idea input"
|
|
138
|
+
readOnly
|
|
139
|
+
/>
|
|
140
|
+
<div className="flex gap-2">
|
|
141
|
+
<button className="btn btn-primary btn-sm flex-1">Ship it</button>
|
|
142
|
+
<button className="btn btn-ghost btn-sm flex-1">Overthink it</button>
|
|
143
|
+
</div>
|
|
144
|
+
<label className="flex items-center gap-2 cursor-pointer">
|
|
145
|
+
<input type="checkbox" className="checkbox checkbox-xs checkbox-primary" defaultChecked readOnly />
|
|
146
|
+
<span className="text-xs text-base-content/70">I accept that pixels are my life now</span>
|
|
147
|
+
</label>
|
|
148
|
+
</div>
|
|
149
|
+
),
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
id: 'timeline',
|
|
153
|
+
content: (
|
|
154
|
+
<ul className="timeline timeline-vertical timeline-compact">
|
|
155
|
+
<li>
|
|
156
|
+
<div className="timeline-start text-xs text-base-content/50">Mon</div>
|
|
157
|
+
<div className="timeline-middle">
|
|
158
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="w-4 h-4 text-success"><path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clipRule="evenodd" /></svg>
|
|
159
|
+
</div>
|
|
160
|
+
<div className="timeline-end text-sm">Started project</div>
|
|
161
|
+
<hr className="bg-success"/>
|
|
162
|
+
</li>
|
|
163
|
+
<li>
|
|
164
|
+
<hr className="bg-success"/>
|
|
165
|
+
<div className="timeline-start text-xs text-base-content/50">Tue</div>
|
|
166
|
+
<div className="timeline-middle">
|
|
167
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="w-4 h-4 text-success"><path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clipRule="evenodd" /></svg>
|
|
168
|
+
</div>
|
|
169
|
+
<div className="timeline-end text-sm">Added scope</div>
|
|
170
|
+
<hr/>
|
|
171
|
+
</li>
|
|
172
|
+
<li>
|
|
173
|
+
<hr/>
|
|
174
|
+
<div className="timeline-start text-xs text-base-content/50">Fri</div>
|
|
175
|
+
<div className="timeline-middle">
|
|
176
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" className="w-4 h-4 text-base-content/30"><path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z" clipRule="evenodd" /></svg>
|
|
177
|
+
</div>
|
|
178
|
+
<div className="timeline-end text-sm text-base-content/50">Ship?</div>
|
|
179
|
+
</li>
|
|
180
|
+
</ul>
|
|
181
|
+
),
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
id: 'rating',
|
|
185
|
+
content: (
|
|
186
|
+
<div className="text-center space-y-4">
|
|
187
|
+
<p className="text-sm text-base-content/70">Rate your work-life balance</p>
|
|
188
|
+
<div className="rating rating-lg gap-1">
|
|
189
|
+
<input type="radio" name="rating" className="mask mask-star-2 bg-primary" aria-label="1 star" />
|
|
190
|
+
<input type="radio" name="rating" className="mask mask-star-2 bg-primary" aria-label="2 stars" />
|
|
191
|
+
<input type="radio" name="rating" className="mask mask-star-2 bg-primary" defaultChecked aria-label="3 stars" />
|
|
192
|
+
<input type="radio" name="rating" className="mask mask-star-2 bg-base-300" aria-label="4 stars" />
|
|
193
|
+
<input type="radio" name="rating" className="mask mask-star-2 bg-base-300" aria-label="5 stars" />
|
|
194
|
+
</div>
|
|
195
|
+
<p className="text-xs text-base-content/50">Could be worse. Could be better.</p>
|
|
196
|
+
</div>
|
|
197
|
+
),
|
|
198
|
+
},
|
|
199
|
+
]
|
|
200
|
+
|
|
201
|
+
export function UICarousel() {
|
|
202
|
+
const [current, setCurrent] = useState(0)
|
|
203
|
+
const [isHovered, setIsHovered] = useState(false)
|
|
204
|
+
const { theme, setTheme, themes } = useTheme()
|
|
205
|
+
|
|
206
|
+
useEffect(() => {
|
|
207
|
+
if (isHovered) return
|
|
208
|
+
const timer = setInterval(() => {
|
|
209
|
+
setCurrent((prev) => (prev + 1) % slides.length)
|
|
210
|
+
}, 3500)
|
|
211
|
+
return () => clearInterval(timer)
|
|
212
|
+
}, [isHovered])
|
|
213
|
+
|
|
214
|
+
return (
|
|
215
|
+
<div
|
|
216
|
+
className="relative"
|
|
217
|
+
onMouseEnter={() => setIsHovered(true)}
|
|
218
|
+
onMouseLeave={() => setIsHovered(false)}
|
|
219
|
+
>
|
|
220
|
+
{/* Theme selector */}
|
|
221
|
+
<div className="flex items-center justify-between mb-4">
|
|
222
|
+
<span className="text-sm text-base-content/50">Try a theme:</span>
|
|
223
|
+
<select
|
|
224
|
+
value={theme}
|
|
225
|
+
onChange={(e) => setTheme(e.target.value as Theme)}
|
|
226
|
+
className="select select-bordered select-sm w-40"
|
|
227
|
+
aria-label="Select theme"
|
|
228
|
+
>
|
|
229
|
+
{themes.map((t) => (
|
|
230
|
+
<option key={t} value={t}>
|
|
231
|
+
{t}
|
|
232
|
+
</option>
|
|
233
|
+
))}
|
|
234
|
+
</select>
|
|
235
|
+
</div>
|
|
236
|
+
|
|
237
|
+
{/* Carousel container */}
|
|
238
|
+
<div className="bg-base-200 rounded-lg p-6 min-h-[220px] flex items-center justify-center overflow-hidden relative">
|
|
239
|
+
{/* Prev button */}
|
|
240
|
+
<button
|
|
241
|
+
onClick={() => setCurrent((prev) => (prev - 1 + slides.length) % slides.length)}
|
|
242
|
+
className="absolute left-2 top-1/2 -translate-y-1/2 btn btn-circle btn-sm btn-ghost"
|
|
243
|
+
aria-label="Previous slide"
|
|
244
|
+
>
|
|
245
|
+
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
|
246
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
|
247
|
+
</svg>
|
|
248
|
+
</button>
|
|
249
|
+
|
|
250
|
+
{/* Content */}
|
|
251
|
+
<div
|
|
252
|
+
className="w-full px-8 transition-opacity duration-300"
|
|
253
|
+
key={slides[current].id}
|
|
254
|
+
>
|
|
255
|
+
{slides[current].content}
|
|
256
|
+
</div>
|
|
257
|
+
|
|
258
|
+
{/* Next button */}
|
|
259
|
+
<button
|
|
260
|
+
onClick={() => setCurrent((prev) => (prev + 1) % slides.length)}
|
|
261
|
+
className="absolute right-2 top-1/2 -translate-y-1/2 btn btn-circle btn-sm btn-ghost"
|
|
262
|
+
aria-label="Next slide"
|
|
263
|
+
>
|
|
264
|
+
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
|
265
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
|
266
|
+
</svg>
|
|
267
|
+
</button>
|
|
268
|
+
</div>
|
|
269
|
+
|
|
270
|
+
{/* Navigation */}
|
|
271
|
+
<div className="flex items-center justify-between mt-4">
|
|
272
|
+
{/* Counter */}
|
|
273
|
+
<span className="text-xs text-base-content/40 tabular-nums">
|
|
274
|
+
{current + 1} / {slides.length}
|
|
275
|
+
</span>
|
|
276
|
+
|
|
277
|
+
{/* Dots - with adequate touch targets */}
|
|
278
|
+
<div className="flex gap-1" role="tablist" aria-label="Carousel slides">
|
|
279
|
+
{slides.map((slide, index) => (
|
|
280
|
+
<button
|
|
281
|
+
key={slide.id}
|
|
282
|
+
onClick={() => setCurrent(index)}
|
|
283
|
+
role="tab"
|
|
284
|
+
aria-selected={index === current}
|
|
285
|
+
className={`p-2 -m-1.5 transition-all ${
|
|
286
|
+
index === current ? '' : ''
|
|
287
|
+
}`}
|
|
288
|
+
aria-label={`Go to slide ${index + 1}`}
|
|
289
|
+
>
|
|
290
|
+
<span
|
|
291
|
+
className={`block h-2 rounded-full transition-all ${
|
|
292
|
+
index === current
|
|
293
|
+
? 'bg-primary w-6'
|
|
294
|
+
: 'bg-base-300 hover:bg-base-content/30 w-2'
|
|
295
|
+
}`}
|
|
296
|
+
aria-hidden="true"
|
|
297
|
+
/>
|
|
298
|
+
</button>
|
|
299
|
+
))}
|
|
300
|
+
</div>
|
|
301
|
+
|
|
302
|
+
{/* Pause indicator */}
|
|
303
|
+
<span className="text-xs text-base-content/40 w-12 text-right">
|
|
304
|
+
{isHovered ? 'paused' : ''}
|
|
305
|
+
</span>
|
|
306
|
+
</div>
|
|
307
|
+
</div>
|
|
308
|
+
)
|
|
309
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { createContext, useContext, useState, useEffect, ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
export const THEMES = [
|
|
4
|
+
// Light themes
|
|
5
|
+
'light', 'cupcake', 'bumblebee', 'emerald', 'corporate', 'garden',
|
|
6
|
+
'lofi', 'pastel', 'fantasy', 'wireframe', 'cmyk', 'autumn',
|
|
7
|
+
'acid', 'lemonade', 'winter', 'nord',
|
|
8
|
+
// Dark themes
|
|
9
|
+
'dark', 'synthwave', 'retro', 'cyberpunk', 'valentine', 'halloween',
|
|
10
|
+
'aqua', 'forest', 'black', 'luxury', 'dracula', 'business',
|
|
11
|
+
'night', 'coffee', 'dim', 'sunset',
|
|
12
|
+
// Custom
|
|
13
|
+
'custom',
|
|
14
|
+
] as const
|
|
15
|
+
|
|
16
|
+
export type Theme = typeof THEMES[number]
|
|
17
|
+
|
|
18
|
+
interface ThemeContextType {
|
|
19
|
+
theme: Theme
|
|
20
|
+
setTheme: (theme: Theme) => void
|
|
21
|
+
themes: readonly Theme[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const ThemeContext = createContext<ThemeContextType | undefined>(undefined)
|
|
25
|
+
|
|
26
|
+
const STORAGE_KEY = 'app-theme'
|
|
27
|
+
|
|
28
|
+
export function ThemeProvider({ children }: { children: ReactNode }) {
|
|
29
|
+
const [theme, setThemeState] = useState<Theme>(() => {
|
|
30
|
+
if (typeof window !== 'undefined') {
|
|
31
|
+
const stored = localStorage.getItem(STORAGE_KEY) as Theme | null
|
|
32
|
+
if (stored && THEMES.includes(stored)) {
|
|
33
|
+
return stored
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return 'halloween'
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
document.documentElement.setAttribute('data-theme', theme)
|
|
41
|
+
localStorage.setItem(STORAGE_KEY, theme)
|
|
42
|
+
}, [theme])
|
|
43
|
+
|
|
44
|
+
const setTheme = (newTheme: Theme) => {
|
|
45
|
+
setThemeState(newTheme)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<ThemeContext.Provider value={{ theme, setTheme, themes: THEMES }}>
|
|
50
|
+
{children}
|
|
51
|
+
</ThemeContext.Provider>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function useTheme() {
|
|
56
|
+
const context = useContext(ThemeContext)
|
|
57
|
+
if (!context) {
|
|
58
|
+
throw new Error('useTheme must be used within a ThemeProvider')
|
|
59
|
+
}
|
|
60
|
+
return context
|
|
61
|
+
}
|