create-unmint 1.1.0 → 1.2.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/dist/index.js CHANGED
@@ -3,6 +3,8 @@
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
5
  import chalk4 from "chalk";
6
+ import figlet from "figlet";
7
+ import gradient from "gradient-string";
6
8
 
7
9
  // src/commands/init.ts
8
10
  import fs2 from "fs-extra";
@@ -487,13 +489,32 @@ async function applyUpdates(projectDir, changes) {
487
489
  }
488
490
 
489
491
  // src/index.ts
490
- var program = new Command();
491
- program.name("create-unmint").description("Create and manage Unmint documentation projects").version("1.0.0");
492
- program.argument("[project-name]", "Name of the project to create").option("-y, --yes", "Skip prompts and use defaults").option("--update", "Update an existing Unmint project").option("--dry-run", "Show what would be updated without making changes").action(async (projectName, options) => {
492
+ var cyanGradient = gradient([
493
+ "#065f5f",
494
+ // dark teal
495
+ "#0d7377",
496
+ // medium teal
497
+ "#14a3a8",
498
+ // teal
499
+ "#32c4c4",
500
+ // cyan
501
+ "#5ce1e6"
502
+ // light cyan
503
+ ]);
504
+ function printBanner() {
505
+ const banner = figlet.textSync("UNMINT", {
506
+ font: "ANSI Shadow",
507
+ horizontalLayout: "default"
508
+ });
493
509
  console.log();
494
- console.log(chalk4.cyan.bold(" Unmint"));
510
+ console.log(cyanGradient.multiline(banner));
495
511
  console.log(chalk4.dim(" Beautiful documentation, open source"));
496
512
  console.log();
513
+ }
514
+ var program = new Command();
515
+ program.name("create-unmint").description("Create and manage Unmint documentation projects").version("1.0.0");
516
+ program.argument("[project-name]", "Name of the project to create").option("-y, --yes", "Skip prompts and use defaults").option("--update", "Update an existing Unmint project").option("--dry-run", "Show what would be updated without making changes").action(async (projectName, options) => {
517
+ printBanner();
497
518
  if (options.update) {
498
519
  await update(options);
499
520
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-unmint",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Create a new Unmint documentation project",
5
5
  "type": "module",
6
6
  "bin": {
@@ -20,12 +20,16 @@
20
20
  "chalk": "^5.3.0",
21
21
  "commander": "^12.1.0",
22
22
  "execa": "^9.5.2",
23
+ "figlet": "^1.9.4",
23
24
  "fs-extra": "^11.2.0",
25
+ "gradient-string": "^3.0.0",
24
26
  "inquirer": "^12.3.0",
25
27
  "ora": "^8.1.1"
26
28
  },
27
29
  "devDependencies": {
30
+ "@types/figlet": "^1.7.0",
28
31
  "@types/fs-extra": "^11.0.4",
32
+ "@types/gradient-string": "^1.1.6",
29
33
  "@types/node": "^22.10.7",
30
34
  "tsup": "^8.3.6",
31
35
  "typescript": "^5.7.3"
@@ -33,7 +33,7 @@ export function DocsHeader({ tree }: DocsHeaderProps) {
33
33
  aria-label="Open menu"
34
34
  aria-expanded={isMobileMenuOpen}
35
35
  >
36
- <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
36
+ <svg aria-hidden="true" className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
37
37
  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
38
38
  </svg>
39
39
  </button>
@@ -68,7 +68,7 @@ export function DocsHeader({ tree }: DocsHeaderProps) {
68
68
  className="p-2 text-muted-foreground hover:text-foreground transition-colors min-w-[44px] min-h-[44px] flex items-center justify-center"
69
69
  aria-label="GitHub"
70
70
  >
71
- <svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
71
+ <svg aria-hidden="true" className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
72
72
  <path fillRule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clipRule="evenodd" />
73
73
  </svg>
74
74
  </a>
@@ -25,7 +25,7 @@ export function DocsSidebar({ tree }: DocsSidebarProps) {
25
25
  className="flex items-center gap-3 py-1 text-sm text-[var(--accent)] font-medium hover:opacity-80 transition-opacity"
26
26
  >
27
27
  <span className="flex items-center justify-center w-7 h-7 rounded-md bg-[var(--accent-muted)]">
28
- <svg className="w-4 h-4 text-[var(--accent)]" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
28
+ <svg aria-hidden="true" className="w-4 h-4 text-[var(--accent)]" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
29
29
  <path strokeLinecap="round" strokeLinejoin="round" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
30
30
  </svg>
31
31
  </span>
@@ -41,7 +41,7 @@ export function DocsSidebar({ tree }: DocsSidebarProps) {
41
41
  className="flex items-center gap-3 py-1 text-sm text-muted-foreground hover:text-foreground transition-colors"
42
42
  >
43
43
  <span className="flex items-center justify-center w-7 h-7 rounded-md bg-gray-100 dark:bg-gray-800">
44
- <svg className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
44
+ <svg aria-hidden="true" className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
45
45
  <path fillRule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clipRule="evenodd" />
46
46
  </svg>
47
47
  </span>
@@ -56,7 +56,7 @@ export function DocsSidebar({ tree }: DocsSidebarProps) {
56
56
  className="flex items-center gap-3 py-1 text-sm text-muted-foreground hover:text-foreground transition-colors"
57
57
  >
58
58
  <span className="flex items-center justify-center w-7 h-7 rounded-md bg-gray-100 dark:bg-gray-800">
59
- <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
59
+ <svg aria-hidden="true" className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
60
60
  <path strokeLinecap="round" strokeLinejoin="round" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
61
61
  </svg>
62
62
  </span>
@@ -28,10 +28,12 @@ export function Accordion({ title, children, defaultOpen = false }: AccordionPro
28
28
  <div>
29
29
  <button
30
30
  onClick={() => setIsOpen(!isOpen)}
31
- className="flex w-full items-center justify-between px-4 py-4 text-left font-medium text-foreground hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors"
31
+ aria-expanded={isOpen}
32
+ className="flex w-full items-center justify-between px-4 py-4 text-left font-medium text-foreground hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--accent)] focus-visible:ring-inset"
32
33
  >
33
34
  <span>{title}</span>
34
35
  <svg
36
+ aria-hidden="true"
35
37
  className={cn(
36
38
  'w-5 h-5 text-muted-foreground transition-transform duration-200',
37
39
  isOpen && 'rotate-180'
@@ -1,11 +1,12 @@
1
1
  'use client'
2
2
 
3
- import { useState, createContext, useContext } from 'react'
3
+ import { useState, createContext, useContext, Children, isValidElement, useId } from 'react'
4
4
  import { cn } from '@/lib/utils'
5
5
 
6
6
  interface TabsContextValue {
7
7
  activeTab: string
8
8
  setActiveTab: (tab: string) => void
9
+ tabsId: string
9
10
  }
10
11
 
11
12
  const TabsContext = createContext<TabsContextValue | null>(null)
@@ -17,10 +18,87 @@ interface TabsProps {
17
18
 
18
19
  export function Tabs({ children, defaultValue }: TabsProps) {
19
20
  const [activeTab, setActiveTab] = useState(defaultValue || '')
21
+ const tabsId = useId()
22
+
23
+ // Extract tab titles and content from children
24
+ const tabs: { title: string; content: React.ReactNode }[] = []
25
+ Children.forEach(children, (child) => {
26
+ if (isValidElement<TabProps>(child) && child.props.title) {
27
+ tabs.push({
28
+ title: child.props.title,
29
+ content: child.props.children,
30
+ })
31
+ }
32
+ })
33
+
34
+ // Set default active tab if not set
35
+ const currentActiveTab = activeTab || (tabs[0]?.title ?? '')
20
36
 
21
37
  return (
22
- <TabsContext.Provider value={{ activeTab, setActiveTab }}>
23
- <div className="my-6">{children}</div>
38
+ <TabsContext.Provider value={{ activeTab: currentActiveTab, setActiveTab, tabsId }}>
39
+ <div className="my-6">
40
+ {/* Tab list */}
41
+ <div role="tablist" aria-label="Tabs" className="flex border-b border-border">
42
+ {tabs.map((tab, index) => {
43
+ const isActive = currentActiveTab === tab.title
44
+ const tabId = `${tabsId}-tab-${index}`
45
+ const panelId = `${tabsId}-panel-${index}`
46
+
47
+ return (
48
+ <button
49
+ key={tab.title}
50
+ role="tab"
51
+ id={tabId}
52
+ aria-selected={isActive}
53
+ aria-controls={panelId}
54
+ tabIndex={isActive ? 0 : -1}
55
+ onClick={() => setActiveTab(tab.title)}
56
+ onKeyDown={(e) => {
57
+ if (e.key === 'ArrowRight') {
58
+ e.preventDefault()
59
+ const nextIndex = (index + 1) % tabs.length
60
+ setActiveTab(tabs[nextIndex].title)
61
+ } else if (e.key === 'ArrowLeft') {
62
+ e.preventDefault()
63
+ const prevIndex = (index - 1 + tabs.length) % tabs.length
64
+ setActiveTab(tabs[prevIndex].title)
65
+ }
66
+ }}
67
+ className={cn(
68
+ 'px-4 py-2 text-sm font-medium border-b-2 -mb-px transition-colors',
69
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--accent)] focus-visible:ring-offset-2',
70
+ isActive
71
+ ? 'border-[var(--accent)] text-[var(--accent)]'
72
+ : 'border-transparent text-muted-foreground hover:text-foreground'
73
+ )}
74
+ >
75
+ {tab.title}
76
+ </button>
77
+ )
78
+ })}
79
+ </div>
80
+
81
+ {/* Tab panels */}
82
+ {tabs.map((tab, index) => {
83
+ const isActive = currentActiveTab === tab.title
84
+ const tabId = `${tabsId}-tab-${index}`
85
+ const panelId = `${tabsId}-panel-${index}`
86
+
87
+ return (
88
+ <div
89
+ key={tab.title}
90
+ role="tabpanel"
91
+ id={panelId}
92
+ aria-labelledby={tabId}
93
+ hidden={!isActive}
94
+ tabIndex={0}
95
+ className={cn('pt-4 [&>pre]:mt-0', !isActive && 'hidden')}
96
+ >
97
+ {tab.content}
98
+ </div>
99
+ )
100
+ })}
101
+ </div>
24
102
  </TabsContext.Provider>
25
103
  )
26
104
  }
@@ -30,31 +108,9 @@ interface TabProps {
30
108
  children: React.ReactNode
31
109
  }
32
110
 
111
+ // Tab is now just a data container, rendering is handled by Tabs
33
112
  export function Tab({ title, children }: TabProps) {
34
- const context = useContext(TabsContext)
35
- if (!context) return null
36
-
37
- const { activeTab, setActiveTab } = context
38
- const isActive = activeTab === title || (!activeTab && title)
39
-
40
- return (
41
- <>
42
- <button
43
- onClick={() => setActiveTab(title)}
44
- className={cn(
45
- 'px-4 py-2 text-sm font-medium border-b-2 -mb-px transition-colors',
46
- isActive
47
- ? 'border-[var(--accent)] text-[var(--accent)]'
48
- : 'border-transparent text-muted-foreground hover:text-foreground'
49
- )}
50
- >
51
- {title}
52
- </button>
53
- {isActive && (
54
- <div className="pt-4 [&>pre]:mt-0">
55
- {children}
56
- </div>
57
- )}
58
- </>
59
- )
113
+ // This component is used for data extraction only
114
+ // Actual rendering happens in Tabs component
115
+ return null
60
116
  }
@@ -135,7 +135,7 @@ export function MobileSidebar({ tree, isOpen, onClose }: MobileSidebarProps) {
135
135
  className="p-2 -mr-2 text-muted-foreground hover:text-foreground hover:bg-muted rounded-md transition-colors min-w-[44px] min-h-[44px] flex items-center justify-center"
136
136
  aria-label="Close menu"
137
137
  >
138
- <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
138
+ <svg aria-hidden="true" className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
139
139
  <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
140
140
  </svg>
141
141
  </button>
@@ -153,7 +153,7 @@ export function MobileSidebar({ tree, isOpen, onClose }: MobileSidebarProps) {
153
153
  className="flex items-center gap-3 py-2 px-2 text-sm text-[var(--accent)] font-medium hover:bg-[var(--accent-muted)] rounded-md transition-colors min-h-[44px]"
154
154
  >
155
155
  <span className="flex items-center justify-center w-7 h-7 rounded-md bg-[var(--accent-muted)]">
156
- <svg className="w-4 h-4 text-[var(--accent)]" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
156
+ <svg aria-hidden="true" className="w-4 h-4 text-[var(--accent)]" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
157
157
  <path strokeLinecap="round" strokeLinejoin="round" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
158
158
  </svg>
159
159
  </span>
@@ -169,7 +169,7 @@ export function MobileSidebar({ tree, isOpen, onClose }: MobileSidebarProps) {
169
169
  className="flex items-center gap-3 py-2 px-2 text-sm text-muted-foreground hover:text-foreground hover:bg-muted rounded-md transition-colors min-h-[44px]"
170
170
  >
171
171
  <span className="flex items-center justify-center w-7 h-7 rounded-md bg-muted">
172
- <svg className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
172
+ <svg aria-hidden="true" className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
173
173
  <path fillRule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" clipRule="evenodd" />
174
174
  </svg>
175
175
  </span>
@@ -184,7 +184,7 @@ export function MobileSidebar({ tree, isOpen, onClose }: MobileSidebarProps) {
184
184
  className="flex items-center gap-3 py-2 px-2 text-sm text-muted-foreground hover:text-foreground hover:bg-muted rounded-md transition-colors min-h-[44px]"
185
185
  >
186
186
  <span className="flex items-center justify-center w-7 h-7 rounded-md bg-muted">
187
- <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
187
+ <svg aria-hidden="true" className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
188
188
  <path strokeLinecap="round" strokeLinejoin="round" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
189
189
  </svg>
190
190
  </span>
@@ -243,10 +243,12 @@ function MobileSidebarNode({ node, pathname, onNavigate }: MobileSidebarNodeProp
243
243
  <div>
244
244
  <button
245
245
  onClick={() => setIsExpanded(!isExpanded)}
246
- className="flex items-center justify-between w-full py-2 px-2 text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-muted rounded-md transition-colors min-h-[44px]"
246
+ aria-expanded={isExpanded}
247
+ className="flex items-center justify-between w-full py-2 px-2 text-sm font-medium text-muted-foreground hover:text-foreground hover:bg-muted rounded-md transition-colors min-h-[44px] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--accent)] focus-visible:ring-inset"
247
248
  >
248
249
  <span>{node.name}</span>
249
250
  <svg
251
+ aria-hidden="true"
250
252
  className={cn('w-4 h-4 transition-transform', isExpanded && 'rotate-90')}
251
253
  fill="none"
252
254
  stroke="currentColor"
@@ -31,6 +31,7 @@ export function SearchTrigger() {
31
31
  <button
32
32
  type="button"
33
33
  onClick={() => setOpen(true)}
34
+ aria-haspopup="dialog"
34
35
  className={cn(
35
36
  'flex items-center gap-3 px-4 py-2.5 rounded-lg w-full max-w-md',
36
37
  'bg-muted/50 border border-border/50',
@@ -198,7 +199,7 @@ function SearchDialog({ onClose }: SearchDialogProps) {
198
199
  <p>Searching...</p>
199
200
  </div>
200
201
  ) : results.length > 0 ? (
201
- <ul ref={resultsRef} className="py-2">
202
+ <ul ref={resultsRef} role="listbox" aria-label="Search results" className="py-2">
202
203
  {results.map((result, index) => {
203
204
  // Build breadcrumb path
204
205
  const breadcrumbPath = result.breadcrumbs && result.breadcrumbs.length > 0
@@ -211,8 +212,11 @@ function SearchDialog({ onClose }: SearchDialogProps) {
211
212
  type="button"
212
213
  onClick={() => handleSelect(result.url)}
213
214
  onMouseEnter={() => setSelectedIndex(index)}
215
+ role="option"
216
+ aria-selected={selectedIndex === index}
214
217
  className={cn(
215
- 'w-full px-4 py-3 text-left transition-colors focus:outline-none',
218
+ 'w-full px-4 py-3 text-left transition-colors',
219
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--accent)]',
216
220
  selectedIndex === index
217
221
  ? 'bg-gray-100 dark:bg-gray-800'
218
222
  : 'hover:bg-gray-50 dark:hover:bg-gray-800/50'
@@ -265,6 +269,7 @@ function SearchDialog({ onClose }: SearchDialogProps) {
265
269
  function SearchIcon({ className }: { className?: string }) {
266
270
  return (
267
271
  <svg
272
+ aria-hidden="true"
268
273
  className={className}
269
274
  fill="none"
270
275
  viewBox="0 0 24 24"
@@ -12,21 +12,21 @@ export function ThemeToggle() {
12
12
  }, [])
13
13
 
14
14
  if (!mounted) {
15
- return <div className="w-9 h-9" />
15
+ return <div className="w-11 h-11" />
16
16
  }
17
17
 
18
18
  return (
19
19
  <button
20
20
  onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
21
- className="flex items-center justify-center w-9 h-9 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
21
+ className="flex items-center justify-center w-11 h-11 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
22
22
  aria-label="Toggle theme"
23
23
  >
24
24
  {theme === 'dark' ? (
25
- <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
25
+ <svg aria-hidden="true" className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
26
26
  <path strokeLinecap="round" strokeLinejoin="round" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
27
27
  </svg>
28
28
  ) : (
29
- <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
29
+ <svg aria-hidden="true" className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
30
30
  <path strokeLinecap="round" strokeLinejoin="round" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
31
31
  </svg>
32
32
  )}