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,113 @@
|
|
|
1
|
+
# Ralph Wiggum Loop Quick Start
|
|
2
|
+
|
|
3
|
+
Start an autonomous Ralph Wiggum loop for common design tasks.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
First, install the plugin if you haven't:
|
|
8
|
+
```bash
|
|
9
|
+
/plugin marketplace add anthropics/claude-code
|
|
10
|
+
/plugin install ralph-wiggum@claude-plugins-official
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Available Loop Templates
|
|
14
|
+
|
|
15
|
+
### 1. Component Builder
|
|
16
|
+
Build a new component with full polish:
|
|
17
|
+
```bash
|
|
18
|
+
/ralph-loop "Build a $ARGUMENTS component.
|
|
19
|
+
|
|
20
|
+
Requirements:
|
|
21
|
+
- Use DaisyUI + Tailwind CSS from our design system
|
|
22
|
+
- Follow patterns in src/components/ui/
|
|
23
|
+
- Include hover, focus, and active states
|
|
24
|
+
- Make responsive (mobile, tablet, desktop)
|
|
25
|
+
- Add TypeScript props interface
|
|
26
|
+
|
|
27
|
+
Each iteration:
|
|
28
|
+
1. Check component exists
|
|
29
|
+
2. Run: bun run typecheck
|
|
30
|
+
3. Run: bun run lint
|
|
31
|
+
4. If errors, fix them
|
|
32
|
+
5. When all pass, output: <promise>COMPLETE</promise>
|
|
33
|
+
|
|
34
|
+
Escape: After 12 iterations, document blockers and complete." --completion-promise "COMPLETE" --max-iterations 15
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. Polish Pass
|
|
38
|
+
Run a design polish pass on a file or directory:
|
|
39
|
+
```bash
|
|
40
|
+
/ralph-loop "Polish pass on $ARGUMENTS
|
|
41
|
+
|
|
42
|
+
Check and fix:
|
|
43
|
+
- Consistent spacing (4, 8, 16, 24, 32px)
|
|
44
|
+
- Typography hierarchy
|
|
45
|
+
- Hover states on interactive elements
|
|
46
|
+
- Focus states for accessibility
|
|
47
|
+
- Smooth transitions (150-300ms)
|
|
48
|
+
- No hardcoded colors
|
|
49
|
+
|
|
50
|
+
After each fix:
|
|
51
|
+
1. Run: bun run typecheck
|
|
52
|
+
2. Run: bun run lint
|
|
53
|
+
3. If errors, fix them
|
|
54
|
+
|
|
55
|
+
When all polished and checks pass, output: <promise>COMPLETE</promise>" --completion-promise "COMPLETE" --max-iterations 20
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 3. Responsive Fix
|
|
59
|
+
Make a component/page fully responsive:
|
|
60
|
+
```bash
|
|
61
|
+
/ralph-loop "Make $ARGUMENTS fully responsive.
|
|
62
|
+
|
|
63
|
+
Breakpoints:
|
|
64
|
+
- Mobile: 375px (touch targets 44px+)
|
|
65
|
+
- Tablet: 768px
|
|
66
|
+
- Desktop: 1280px+
|
|
67
|
+
|
|
68
|
+
Process:
|
|
69
|
+
1. Review current responsive behavior
|
|
70
|
+
2. Fix layout issues at each breakpoint
|
|
71
|
+
3. Run typecheck and lint
|
|
72
|
+
4. When fully responsive, output: <promise>COMPLETE</promise>" --completion-promise "COMPLETE" --max-iterations 15
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 4. Overnight Feature Build
|
|
76
|
+
Build a complete feature while you sleep:
|
|
77
|
+
```bash
|
|
78
|
+
/ralph-loop "Build $ARGUMENTS feature.
|
|
79
|
+
|
|
80
|
+
Phases:
|
|
81
|
+
1. Create component structure
|
|
82
|
+
2. Implement core functionality
|
|
83
|
+
3. Style with DaisyUI
|
|
84
|
+
4. Make responsive
|
|
85
|
+
5. Add TypeScript types
|
|
86
|
+
|
|
87
|
+
Each iteration:
|
|
88
|
+
- Work on current phase
|
|
89
|
+
- Run: bun run typecheck && bun run lint
|
|
90
|
+
- If errors, fix them
|
|
91
|
+
- If phase complete, move to next
|
|
92
|
+
|
|
93
|
+
When all phases done and checks pass, output: <promise>COMPLETE</promise>
|
|
94
|
+
|
|
95
|
+
Escape: After 25 iterations, document progress and complete." --completion-promise "COMPLETE" --max-iterations 30
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Usage
|
|
99
|
+
|
|
100
|
+
Pick a template above, replace `$ARGUMENTS` with your target, and run.
|
|
101
|
+
|
|
102
|
+
## Safety
|
|
103
|
+
|
|
104
|
+
- **Always** use `--max-iterations`
|
|
105
|
+
- Start small (5-10) to test, then increase
|
|
106
|
+
- Use `/cancel-ralph` to stop anytime
|
|
107
|
+
|
|
108
|
+
## Tips
|
|
109
|
+
|
|
110
|
+
- More specific prompts = better results
|
|
111
|
+
- Include verification steps (typecheck, lint)
|
|
112
|
+
- Add escape hatches for stuck loops
|
|
113
|
+
- Check `.claude/skills/ralph-wiggum-loops/SKILL.md` for full docs
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# /typecheck Command
|
|
2
|
+
|
|
3
|
+
Run TypeScript type checking and provide categorized error summary.
|
|
4
|
+
|
|
5
|
+
## Steps
|
|
6
|
+
|
|
7
|
+
1. Run `npx tsc --noEmit` to check types without emitting files
|
|
8
|
+
|
|
9
|
+
2. Parse errors and categorize by:
|
|
10
|
+
- **Critical:** Type mismatches, missing properties
|
|
11
|
+
- **Warning:** Implicit any, unused variables
|
|
12
|
+
- **Info:** Style issues, unnecessary type assertions
|
|
13
|
+
|
|
14
|
+
3. Format results as:
|
|
15
|
+
|
|
16
|
+
```markdown
|
|
17
|
+
## TypeScript Check Results
|
|
18
|
+
|
|
19
|
+
**Status:** ✅ No errors | ⚠️ X errors found
|
|
20
|
+
|
|
21
|
+
### Critical Errors
|
|
22
|
+
| File | Line | Code | Message |
|
|
23
|
+
|------|------|------|---------|
|
|
24
|
+
| src/App.tsx | 42 | TS2322 | Type 'string' is not assignable to type 'number' |
|
|
25
|
+
|
|
26
|
+
### Warnings
|
|
27
|
+
| File | Line | Code | Message |
|
|
28
|
+
|------|------|------|---------|
|
|
29
|
+
| ... | ... | ... | ... |
|
|
30
|
+
|
|
31
|
+
### Summary
|
|
32
|
+
- Critical: X
|
|
33
|
+
- Warnings: Y
|
|
34
|
+
- Total files checked: Z
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
4. For each critical error:
|
|
38
|
+
- Show the problematic code snippet
|
|
39
|
+
- Suggest the fix
|
|
40
|
+
- Offer to apply fix
|
|
41
|
+
|
|
42
|
+
5. If no errors:
|
|
43
|
+
- Report "All X files passed type checking"
|
|
44
|
+
- Note any strict mode opportunities
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Component, ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
children: ReactNode
|
|
5
|
+
fallback?: ReactNode
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface State {
|
|
9
|
+
hasError: boolean
|
|
10
|
+
error?: Error
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class ErrorBoundary extends Component<Props, State> {
|
|
14
|
+
constructor(props: Props) {
|
|
15
|
+
super(props)
|
|
16
|
+
this.state = { hasError: false }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static getDerivedStateFromError(error: Error): State {
|
|
20
|
+
return { hasError: true, error }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
render() {
|
|
24
|
+
if (this.state.hasError) {
|
|
25
|
+
if (this.props.fallback) {
|
|
26
|
+
return this.props.fallback
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className="min-h-dvh bg-base-100 text-base-content flex items-center justify-center px-6">
|
|
31
|
+
<div className="text-center max-w-md">
|
|
32
|
+
<h1 className="text-2xl font-bold mb-4 text-balance">Something went wrong</h1>
|
|
33
|
+
<p className="text-base-content/70 mb-8 text-pretty">
|
|
34
|
+
We couldn't load this page. Try refreshing or come back later.
|
|
35
|
+
</p>
|
|
36
|
+
<button
|
|
37
|
+
onClick={() => window.location.reload()}
|
|
38
|
+
className="btn btn-primary"
|
|
39
|
+
>
|
|
40
|
+
Refresh page
|
|
41
|
+
</button>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return this.props.children
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { ButtonHTMLAttributes, ReactNode } from 'react'
|
|
2
|
+
import { cn } from '@/lib/utils'
|
|
3
|
+
|
|
4
|
+
type ButtonVariant = 'primary' | 'secondary' | 'accent' | 'ghost' | 'link' | 'info' | 'success' | 'warning' | 'error'
|
|
5
|
+
type ButtonSize = 'xs' | 'sm' | 'md' | 'lg'
|
|
6
|
+
|
|
7
|
+
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
8
|
+
children: ReactNode
|
|
9
|
+
variant?: ButtonVariant
|
|
10
|
+
size?: ButtonSize
|
|
11
|
+
outline?: boolean
|
|
12
|
+
loading?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const variantClasses: Record<ButtonVariant, string> = {
|
|
16
|
+
primary: 'btn-primary',
|
|
17
|
+
secondary: 'btn-secondary',
|
|
18
|
+
accent: 'btn-accent',
|
|
19
|
+
ghost: 'btn-ghost',
|
|
20
|
+
link: 'btn-link',
|
|
21
|
+
info: 'btn-info',
|
|
22
|
+
success: 'btn-success',
|
|
23
|
+
warning: 'btn-warning',
|
|
24
|
+
error: 'btn-error',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const sizeClasses: Record<ButtonSize, string> = {
|
|
28
|
+
xs: 'btn-xs',
|
|
29
|
+
sm: 'btn-sm',
|
|
30
|
+
md: '',
|
|
31
|
+
lg: 'btn-lg',
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function Button({
|
|
35
|
+
children,
|
|
36
|
+
variant = 'primary',
|
|
37
|
+
size = 'md',
|
|
38
|
+
outline = false,
|
|
39
|
+
loading = false,
|
|
40
|
+
className,
|
|
41
|
+
disabled,
|
|
42
|
+
...props
|
|
43
|
+
}: ButtonProps) {
|
|
44
|
+
return (
|
|
45
|
+
<button
|
|
46
|
+
className={cn(
|
|
47
|
+
'btn',
|
|
48
|
+
variantClasses[variant],
|
|
49
|
+
sizeClasses[size],
|
|
50
|
+
outline && 'btn-outline',
|
|
51
|
+
className
|
|
52
|
+
)}
|
|
53
|
+
disabled={disabled || loading}
|
|
54
|
+
{...props}
|
|
55
|
+
>
|
|
56
|
+
{loading && <span className="loading loading-spinner" />}
|
|
57
|
+
{children}
|
|
58
|
+
</button>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { Check, Copy } from 'lucide-react'
|
|
3
|
+
|
|
4
|
+
interface CodeBlockProps {
|
|
5
|
+
children: string
|
|
6
|
+
language?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function CodeBlock({ children, language }: CodeBlockProps) {
|
|
10
|
+
const [copied, setCopied] = useState(false)
|
|
11
|
+
|
|
12
|
+
const handleCopy = async () => {
|
|
13
|
+
await navigator.clipboard.writeText(children)
|
|
14
|
+
setCopied(true)
|
|
15
|
+
setTimeout(() => setCopied(false), 2000)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="relative group">
|
|
20
|
+
<pre className="font-mono text-xs bg-base-200/80 border border-base-300 p-3 rounded-lg overflow-x-auto">
|
|
21
|
+
{language && (
|
|
22
|
+
<span className="absolute top-2 left-3 text-[10px] text-base-content/40 uppercase">
|
|
23
|
+
{language}
|
|
24
|
+
</span>
|
|
25
|
+
)}
|
|
26
|
+
<code className={language ? 'block pt-4' : ''}>{children}</code>
|
|
27
|
+
</pre>
|
|
28
|
+
<button
|
|
29
|
+
onClick={handleCopy}
|
|
30
|
+
className="absolute top-2 right-2 p-1.5 rounded-md bg-base-100/90 border border-base-300 opacity-0 group-hover:opacity-100 focus:opacity-100 hover:bg-base-100"
|
|
31
|
+
aria-label="Copy code"
|
|
32
|
+
>
|
|
33
|
+
<span aria-hidden="true">
|
|
34
|
+
{copied ? (
|
|
35
|
+
<Check className="size-3 text-success" />
|
|
36
|
+
) : (
|
|
37
|
+
<Copy className="size-3 text-base-content/50" />
|
|
38
|
+
)}
|
|
39
|
+
</span>
|
|
40
|
+
<span className="sr-only" aria-live="polite">
|
|
41
|
+
{copied ? 'Copied to clipboard' : ''}
|
|
42
|
+
</span>
|
|
43
|
+
</button>
|
|
44
|
+
</div>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useState } from 'react'
|
|
2
|
+
import { Check, Copy } from 'lucide-react'
|
|
3
|
+
|
|
4
|
+
interface CopyCommandProps {
|
|
5
|
+
children: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function CopyCommand({ children }: CopyCommandProps) {
|
|
9
|
+
const [copied, setCopied] = useState(false)
|
|
10
|
+
|
|
11
|
+
const handleCopy = async () => {
|
|
12
|
+
await navigator.clipboard.writeText(children)
|
|
13
|
+
setCopied(true)
|
|
14
|
+
setTimeout(() => setCopied(false), 2000)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<button
|
|
19
|
+
onClick={handleCopy}
|
|
20
|
+
className="group inline-flex items-center gap-2 text-left"
|
|
21
|
+
aria-label={`Copy command: ${children}`}
|
|
22
|
+
>
|
|
23
|
+
<code className="font-mono text-sm bg-base-200/60 px-2 py-1 rounded border border-base-300/50 group-hover:border-base-300 group-focus-visible:border-base-300">
|
|
24
|
+
{children}
|
|
25
|
+
</code>
|
|
26
|
+
<span className="opacity-0 group-hover:opacity-100 group-focus-visible:opacity-100" aria-hidden="true">
|
|
27
|
+
{copied ? (
|
|
28
|
+
<Check className="size-3.5 text-success" />
|
|
29
|
+
) : (
|
|
30
|
+
<Copy className="size-3.5 text-base-content/40" />
|
|
31
|
+
)}
|
|
32
|
+
</span>
|
|
33
|
+
<span className="sr-only" aria-live="polite">
|
|
34
|
+
{copied ? 'Copied to clipboard' : ''}
|
|
35
|
+
</span>
|
|
36
|
+
</button>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useState, useId } from 'react'
|
|
2
|
+
|
|
3
|
+
interface FilePreviewProps {
|
|
4
|
+
filename: string
|
|
5
|
+
description: string
|
|
6
|
+
content: string
|
|
7
|
+
language?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function FilePreview({ filename, description, content, language = 'markdown' }: FilePreviewProps) {
|
|
11
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
12
|
+
const panelId = useId()
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div className="border border-base-300 rounded-lg overflow-hidden">
|
|
16
|
+
<button
|
|
17
|
+
onClick={() => setIsOpen(!isOpen)}
|
|
18
|
+
className="w-full flex items-center justify-between p-4 hover:bg-base-200 transition-colors text-left"
|
|
19
|
+
aria-expanded={isOpen}
|
|
20
|
+
aria-controls={panelId}
|
|
21
|
+
>
|
|
22
|
+
<div>
|
|
23
|
+
<div className="font-mono text-sm font-medium">{filename}</div>
|
|
24
|
+
<div className="text-xs text-base-content/50 mt-1">{description}</div>
|
|
25
|
+
</div>
|
|
26
|
+
<svg
|
|
27
|
+
className={`w-5 h-5 text-base-content/50 transition-transform ${isOpen ? 'rotate-180' : ''}`}
|
|
28
|
+
fill="none"
|
|
29
|
+
viewBox="0 0 24 24"
|
|
30
|
+
stroke="currentColor"
|
|
31
|
+
aria-hidden="true"
|
|
32
|
+
>
|
|
33
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
|
34
|
+
</svg>
|
|
35
|
+
</button>
|
|
36
|
+
|
|
37
|
+
{isOpen && (
|
|
38
|
+
<div id={panelId} className="border-t border-base-300 bg-base-200 p-4 overflow-x-auto">
|
|
39
|
+
<pre className="text-xs font-mono text-base-content/80 whitespace-pre-wrap">
|
|
40
|
+
<code className={`language-${language}`}>{content}</code>
|
|
41
|
+
</pre>
|
|
42
|
+
</div>
|
|
43
|
+
)}
|
|
44
|
+
</div>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useTheme, Theme } from '@/context/ThemeContext'
|
|
2
|
+
import { Palette } from 'lucide-react'
|
|
3
|
+
|
|
4
|
+
export function ThemeSelector() {
|
|
5
|
+
const { theme, setTheme, themes } = useTheme()
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<div className="dropdown dropdown-end">
|
|
9
|
+
<div tabIndex={0} role="button" aria-label="Select theme" className="btn btn-ghost gap-2">
|
|
10
|
+
<Palette className="size-5" aria-hidden="true" />
|
|
11
|
+
<span className="hidden sm:inline">Theme</span>
|
|
12
|
+
<svg
|
|
13
|
+
className="size-2 fill-current opacity-60"
|
|
14
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
15
|
+
viewBox="0 0 2048 2048"
|
|
16
|
+
aria-hidden="true"
|
|
17
|
+
>
|
|
18
|
+
<path d="M1799 349l242 241-1017 1017L7 590l242-241 775 775 775-775z" />
|
|
19
|
+
</svg>
|
|
20
|
+
</div>
|
|
21
|
+
<ul
|
|
22
|
+
tabIndex={0}
|
|
23
|
+
className="dropdown-content bg-base-200 text-base-content rounded-box z-50 w-52 max-h-96 overflow-y-auto p-2 shadow-2xl"
|
|
24
|
+
>
|
|
25
|
+
{themes.map((t) => (
|
|
26
|
+
<li key={t}>
|
|
27
|
+
<input
|
|
28
|
+
type="radio"
|
|
29
|
+
name="theme-dropdown"
|
|
30
|
+
className="theme-controller btn btn-sm btn-block btn-ghost justify-start"
|
|
31
|
+
aria-label={t}
|
|
32
|
+
value={t}
|
|
33
|
+
checked={theme === t}
|
|
34
|
+
onChange={() => setTheme(t as Theme)}
|
|
35
|
+
/>
|
|
36
|
+
</li>
|
|
37
|
+
))}
|
|
38
|
+
</ul>
|
|
39
|
+
</div>
|
|
40
|
+
)
|
|
41
|
+
}
|