md-redline 0.1.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.
Files changed (207) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +169 -0
  3. package/bin/md-redline +255 -0
  4. package/bin/test-windows.ps1 +70 -0
  5. package/dist/assets/_baseFor-Ck08IaSF.js +1 -0
  6. package/dist/assets/arc-DI2g9LXK.js +1 -0
  7. package/dist/assets/architecture-YZFGNWBL-BDgMfc-b.js +1 -0
  8. package/dist/assets/architectureDiagram-Q4EWVU46-Dg1hcUEa.js +36 -0
  9. package/dist/assets/array-DOVTz2Mq.js +1 -0
  10. package/dist/assets/blockDiagram-DXYQGD6D-BAXkTCAk.js +132 -0
  11. package/dist/assets/c4Diagram-AHTNJAMY-BIkgwQSx.js +10 -0
  12. package/dist/assets/channel-DPCihw7y.js +1 -0
  13. package/dist/assets/chunk-2KRD3SAO-Dc_tBGsw.js +1 -0
  14. package/dist/assets/chunk-336JU56O-Dhi-ID9Y.js +2 -0
  15. package/dist/assets/chunk-426QAEUC-DnFdrNMW.js +1 -0
  16. package/dist/assets/chunk-4BX2VUAB-Z63FkGov.js +1 -0
  17. package/dist/assets/chunk-4TB4RGXK-BAiBlfyy.js +206 -0
  18. package/dist/assets/chunk-55IACEB6-BXDWXbxy.js +1 -0
  19. package/dist/assets/chunk-5FUZZQ4R-C72e1c_O.js +62 -0
  20. package/dist/assets/chunk-5PVQY5BW-BBHW_uCu.js +2 -0
  21. package/dist/assets/chunk-67CJDMHE-3Cf_D9m6.js +1 -0
  22. package/dist/assets/chunk-7N4EOEYR-DAXUXJ2c.js +1 -0
  23. package/dist/assets/chunk-AA7GKIK3-Dr7fOryc.js +1 -0
  24. package/dist/assets/chunk-BSJP7CBP-BmsSs1Nt.js +1 -0
  25. package/dist/assets/chunk-CIAEETIT-QDzV-X_Y.js +1 -0
  26. package/dist/assets/chunk-EDXVE4YY-C25WFHxY.js +1 -0
  27. package/dist/assets/chunk-ENJZ2VHE-_OzxcZOU.js +10 -0
  28. package/dist/assets/chunk-FMBD7UC4-CjsTKY4u.js +15 -0
  29. package/dist/assets/chunk-FOC6F5B3-g-xaH5nc.js +1 -0
  30. package/dist/assets/chunk-ICPOFSXX-iKiUSjDK.js +121 -0
  31. package/dist/assets/chunk-K5T4RW27-CKR-lPBN.js +94 -0
  32. package/dist/assets/chunk-KGLVRYIC-DRccT-B_.js +1 -0
  33. package/dist/assets/chunk-LIHQZDEY-DTbMwMXj.js +1 -0
  34. package/dist/assets/chunk-ORNJ4GCN-DlerdcWX.js +1 -0
  35. package/dist/assets/chunk-OYMX7WX6-Dekv1on2.js +231 -0
  36. package/dist/assets/chunk-QZHKN3VN-BHu0RdKl.js +1 -0
  37. package/dist/assets/chunk-U2HBQHQK-BvtlVHAg.js +70 -0
  38. package/dist/assets/chunk-X2U36JSP-BI_g8mub.js +1 -0
  39. package/dist/assets/chunk-XPW4576I-B39JkmSE.js +32 -0
  40. package/dist/assets/chunk-YZCP3GAM-BfPcXRm2.js +1 -0
  41. package/dist/assets/chunk-ZZ45TVLE-Bg4q68wZ.js +1 -0
  42. package/dist/assets/classDiagram-6PBFFD2Q-p73p727_.js +1 -0
  43. package/dist/assets/classDiagram-v2-HSJHXN6E-C4Ftpivp.js +1 -0
  44. package/dist/assets/clone-CI9aUwHe.js +1 -0
  45. package/dist/assets/cose-bilkent-S5V4N54A-7BpAeDh5.js +1 -0
  46. package/dist/assets/cytoscape.esm-DoTFyJaN.js +321 -0
  47. package/dist/assets/dagre-CilMRazv.js +1 -0
  48. package/dist/assets/dagre-KV5264BT-DDMqpjkB.js +4 -0
  49. package/dist/assets/defaultLocale-Ck2Xxk-C.js +1 -0
  50. package/dist/assets/diagram-5BDNPKRD-BFeyfnCx.js +10 -0
  51. package/dist/assets/diagram-G4DWMVQ6-DoqT-PtF.js +24 -0
  52. package/dist/assets/diagram-MMDJMWI5-BPV6KADk.js +43 -0
  53. package/dist/assets/diagram-TYMM5635-okvcTBtl.js +24 -0
  54. package/dist/assets/dist-C_eddq6m.js +1 -0
  55. package/dist/assets/erDiagram-SMLLAGMA-Dl-Ixy8n.js +85 -0
  56. package/dist/assets/flatten-B8XIuT0x.js +1 -0
  57. package/dist/assets/flowDiagram-DWJPFMVM-CsqWAx5r.js +162 -0
  58. package/dist/assets/ganttDiagram-T4ZO3ILL-mIt6zVeF.js +292 -0
  59. package/dist/assets/gitGraph-7Q5UKJZL-COXHGMvj.js +1 -0
  60. package/dist/assets/gitGraphDiagram-UUTBAWPF-syVqZJX_.js +106 -0
  61. package/dist/assets/graphlib-Bpd0q3yO.js +1 -0
  62. package/dist/assets/index-BoggyWS0.css +2 -0
  63. package/dist/assets/index-aLvjHQW4.js +104 -0
  64. package/dist/assets/info-OMHHGYJF-B-0wfxwL.js +1 -0
  65. package/dist/assets/infoDiagram-42DDH7IO-C0_uqsVa.js +2 -0
  66. package/dist/assets/init-Bft5Ffpj.js +1 -0
  67. package/dist/assets/isEmpty-BrFi5AqV.js +1 -0
  68. package/dist/assets/ishikawaDiagram-UXIWVN3A-CTjFbDBV.js +70 -0
  69. package/dist/assets/journeyDiagram-VCZTEJTY-BDBcej1q.js +139 -0
  70. package/dist/assets/kanban-definition-6JOO6SKY-Ylgzakw7.js +89 -0
  71. package/dist/assets/katex-Uj9wLT16.js +265 -0
  72. package/dist/assets/line-CRxEwpOv.js +1 -0
  73. package/dist/assets/linear-PDPfFByd.js +1 -0
  74. package/dist/assets/mermaid-parser.core-CY-XNOOy.js +4 -0
  75. package/dist/assets/mermaid.core-BPlTADIX.js +11 -0
  76. package/dist/assets/mindmap-definition-QFDTVHPH-TefzJnBM.js +96 -0
  77. package/dist/assets/ordinal-DIg8h6NI.js +1 -0
  78. package/dist/assets/packet-4T2RLAQJ-BW1T_A-C.js +1 -0
  79. package/dist/assets/path-DfRbCp9y.js +1 -0
  80. package/dist/assets/pie-ZZUOXDRM-DkKU-SFu.js +1 -0
  81. package/dist/assets/pieDiagram-DEJITSTG-BCXuaeEy.js +30 -0
  82. package/dist/assets/quadrantDiagram-34T5L4WZ-VSBAicWL.js +7 -0
  83. package/dist/assets/radar-PYXPWWZC-CYvTacKJ.js +1 -0
  84. package/dist/assets/reduce-CV2X8n1a.js +1 -0
  85. package/dist/assets/requirementDiagram-MS252O5E-4NeL9Z6J.js +84 -0
  86. package/dist/assets/rough.esm-Bbn_-PMU.js +1 -0
  87. package/dist/assets/sankeyDiagram-XADWPNL6-DMBSDnrH.js +10 -0
  88. package/dist/assets/sequenceDiagram-FGHM5R23-DVpzcZUi.js +157 -0
  89. package/dist/assets/src-PKe5NtkK.js +1 -0
  90. package/dist/assets/stateDiagram-FHFEXIEX-BkHTlCjL.js +1 -0
  91. package/dist/assets/stateDiagram-v2-QKLJ7IA2-nMeWu9fP.js +1 -0
  92. package/dist/assets/timeline-definition-GMOUNBTQ-CyLt92nf.js +120 -0
  93. package/dist/assets/treeView-SZITEDCU-BUgcJ4eR.js +1 -0
  94. package/dist/assets/treemap-W4RFUUIX-BIWGQ4Pw.js +1 -0
  95. package/dist/assets/vennDiagram-DHZGUBPP-BCK0xB_m.js +34 -0
  96. package/dist/assets/wardley-RL74JXVD-DMZZRlby.js +1 -0
  97. package/dist/assets/wardleyDiagram-NUSXRM2D-BisBgfsF.js +20 -0
  98. package/dist/assets/xychartDiagram-5P7HB3ND-D_REDciv.js +7 -0
  99. package/dist/favicon.svg +15 -0
  100. package/dist/index.html +14 -0
  101. package/dist/screenshot.png +0 -0
  102. package/index.html +13 -0
  103. package/package.json +105 -0
  104. package/public/favicon.svg +15 -0
  105. package/public/screenshot.png +0 -0
  106. package/server/index.test.ts +814 -0
  107. package/server/index.ts +736 -0
  108. package/server/preferences.test.ts +126 -0
  109. package/server/preferences.ts +76 -0
  110. package/src/App.tsx +1620 -0
  111. package/src/components/ActionButton.tsx +41 -0
  112. package/src/components/CommandPalette.tsx +191 -0
  113. package/src/components/CommentCard.tsx +556 -0
  114. package/src/components/CommentForm.tsx +285 -0
  115. package/src/components/CommentSidebar.tsx +428 -0
  116. package/src/components/ConfirmDialog.tsx +64 -0
  117. package/src/components/ContextMenu.tsx +220 -0
  118. package/src/components/DragHandles.tsx +48 -0
  119. package/src/components/FileExplorer.tsx +251 -0
  120. package/src/components/FileOpener.tsx +304 -0
  121. package/src/components/IconButton.tsx +32 -0
  122. package/src/components/KeyboardShortcutsPanel.tsx +136 -0
  123. package/src/components/MarkdownViewer.tsx +682 -0
  124. package/src/components/RawView.tsx +798 -0
  125. package/src/components/SearchBar.tsx +129 -0
  126. package/src/components/Separator.tsx +7 -0
  127. package/src/components/SettingsPanel.tsx +813 -0
  128. package/src/components/SplitIconButton.tsx +133 -0
  129. package/src/components/TabBar.tsx +594 -0
  130. package/src/components/TableOfContents.tsx +70 -0
  131. package/src/components/ThemeSelector.tsx +159 -0
  132. package/src/components/Toast.tsx +99 -0
  133. package/src/components/Toolbar.tsx +161 -0
  134. package/src/components/iconButtonVariants.ts +19 -0
  135. package/src/components/rawView.test.ts +291 -0
  136. package/src/contexts/SettingsContext.tsx +120 -0
  137. package/src/hooks/useAuthor.test.ts +58 -0
  138. package/src/hooks/useAuthor.ts +69 -0
  139. package/src/hooks/useAutoResize.ts +20 -0
  140. package/src/hooks/useCommentCardTriggers.ts +20 -0
  141. package/src/hooks/useComments.test.ts +773 -0
  142. package/src/hooks/useComments.ts +332 -0
  143. package/src/hooks/useContextMenu.ts +48 -0
  144. package/src/hooks/useContextMenuItems.ts +392 -0
  145. package/src/hooks/useDiffSnapshot.test.ts +130 -0
  146. package/src/hooks/useDiffSnapshot.ts +67 -0
  147. package/src/hooks/useDragHandles.ts +417 -0
  148. package/src/hooks/useFileWatcher.ts +45 -0
  149. package/src/hooks/useHeadingTracking.ts +84 -0
  150. package/src/hooks/useMermaidRenderer.ts +75 -0
  151. package/src/hooks/useModalState.ts +22 -0
  152. package/src/hooks/usePageVisible.test.ts +69 -0
  153. package/src/hooks/usePageVisible.ts +19 -0
  154. package/src/hooks/usePaneLayout.test.ts +108 -0
  155. package/src/hooks/usePaneLayout.ts +102 -0
  156. package/src/hooks/useRecentFiles.test.ts +103 -0
  157. package/src/hooks/useRecentFiles.ts +99 -0
  158. package/src/hooks/useResizablePanel.test.ts +84 -0
  159. package/src/hooks/useResizablePanel.ts +118 -0
  160. package/src/hooks/useSearch.test.ts +72 -0
  161. package/src/hooks/useSearch.ts +53 -0
  162. package/src/hooks/useSelection.ts +48 -0
  163. package/src/hooks/useSessionPersistence.test.ts +59 -0
  164. package/src/hooks/useSessionPersistence.ts +43 -0
  165. package/src/hooks/useTabs.test.ts +127 -0
  166. package/src/hooks/useTabs.ts +561 -0
  167. package/src/hooks/useThemePersistence.ts +41 -0
  168. package/src/hooks/useToast.ts +27 -0
  169. package/src/index.css +1047 -0
  170. package/src/lib/agent-prompts.test.ts +34 -0
  171. package/src/lib/agent-prompts.ts +68 -0
  172. package/src/lib/comment-editor-state.ts +6 -0
  173. package/src/lib/comment-parser.test.ts +1959 -0
  174. package/src/lib/comment-parser.ts +1021 -0
  175. package/src/lib/diff.test.ts +164 -0
  176. package/src/lib/diff.ts +139 -0
  177. package/src/lib/heading-slugs.test.ts +85 -0
  178. package/src/lib/heading-slugs.ts +44 -0
  179. package/src/lib/http.test.ts +43 -0
  180. package/src/lib/http.ts +29 -0
  181. package/src/lib/mermaid-highlights.test.ts +517 -0
  182. package/src/lib/mermaid-highlights.ts +936 -0
  183. package/src/lib/mermaid-renderer.test.ts +114 -0
  184. package/src/lib/mermaid-renderer.ts +89 -0
  185. package/src/lib/path-utils.test.ts +17 -0
  186. package/src/lib/path-utils.ts +7 -0
  187. package/src/lib/platform.test.ts +58 -0
  188. package/src/lib/platform.ts +14 -0
  189. package/src/lib/preferences-client.test.ts +177 -0
  190. package/src/lib/preferences-client.ts +94 -0
  191. package/src/lib/selection-resolver.test.ts +118 -0
  192. package/src/lib/selection-resolver.ts +37 -0
  193. package/src/lib/settings.test.ts +152 -0
  194. package/src/lib/settings.ts +78 -0
  195. package/src/lib/shortcut-label.tsx +18 -0
  196. package/src/lib/themes.ts +21 -0
  197. package/src/lib/visible-text.test.ts +86 -0
  198. package/src/lib/visible-text.ts +77 -0
  199. package/src/main.tsx +22 -0
  200. package/src/markdown/pipeline.test.ts +82 -0
  201. package/src/markdown/pipeline.ts +33 -0
  202. package/src/types.test.ts +43 -0
  203. package/src/types.ts +46 -0
  204. package/tsconfig.app.json +28 -0
  205. package/tsconfig.json +7 -0
  206. package/tsconfig.node.json +26 -0
  207. package/vite.config.ts +50 -0
@@ -0,0 +1,813 @@
1
+ import { useState, useRef, useEffect, useCallback } from 'react';
2
+ import { useThemePersistence } from '../hooks/useThemePersistence';
3
+ import { useSettings } from '../contexts/SettingsContext';
4
+ import type { CommentTemplate } from '../lib/settings';
5
+ import { DEFAULT_TEMPLATES } from '../lib/settings';
6
+ import { LIGHT_THEMES, DARK_THEMES } from '../lib/themes';
7
+
8
+ type Section = 'templates' | 'general' | 'theme';
9
+
10
+ interface Props {
11
+ open: boolean;
12
+ onClose: () => void;
13
+ author: string;
14
+ onAuthorChange: (name: string) => void;
15
+ }
16
+
17
+ export function SettingsPanel({ open, onClose, author, onAuthorChange }: Props) {
18
+ const {
19
+ settings,
20
+ updateTemplates,
21
+ updateCommentMaxLength,
22
+ updateShowTemplatesByDefault,
23
+ updateEnableResolve,
24
+ updateQuickComment,
25
+ resetTemplates,
26
+ } = useSettings();
27
+ const { theme, setTheme } = useThemePersistence();
28
+ const [activeSection, setActiveSection] = useState<Section>('general');
29
+ const panelRef = useRef<HTMLDivElement>(null);
30
+
31
+ // Local draft state for templates editing
32
+ const [draftTemplates, setDraftTemplates] = useState<CommentTemplate[]>(settings.templates);
33
+ const [editingIndex, setEditingIndex] = useState<number | null>(null);
34
+ const [editLabel, setEditLabel] = useState('');
35
+ const [editText, setEditText] = useState('');
36
+ const [addingNew, setAddingNew] = useState(false);
37
+ const [newLabel, setNewLabel] = useState('');
38
+ const [newText, setNewText] = useState('');
39
+ const [dragIndex, setDragIndex] = useState<number | null>(null);
40
+ const [dragOverIndex, setDragOverIndex] = useState<number | null>(null);
41
+
42
+ // Local draft state for general settings
43
+ const [draftAuthor, setDraftAuthor] = useState(author);
44
+ const [draftMaxLength, setDraftMaxLength] = useState(String(settings.commentMaxLength));
45
+
46
+ const authorInputRef = useRef<HTMLInputElement>(null);
47
+ const newLabelRef = useRef<HTMLInputElement>(null);
48
+
49
+ // Sync drafts when panel opens (not during editing to avoid discarding in-progress changes)
50
+ const prevOpenRef = useRef(false);
51
+ useEffect(() => {
52
+ if (open && !prevOpenRef.current) {
53
+ setDraftTemplates(settings.templates);
54
+ setDraftAuthor(author);
55
+ setDraftMaxLength(String(settings.commentMaxLength));
56
+ setEditingIndex(null);
57
+ setAddingNew(false);
58
+ }
59
+ prevOpenRef.current = open;
60
+ }, [open, settings.templates, author, settings.commentMaxLength]);
61
+
62
+ // Focus new template label input when adding
63
+ useEffect(() => {
64
+ if (addingNew && newLabelRef.current) {
65
+ newLabelRef.current.focus();
66
+ }
67
+ }, [addingNew]);
68
+
69
+ // Close on Escape
70
+ useEffect(() => {
71
+ if (!open) return;
72
+ const handler = (e: KeyboardEvent) => {
73
+ if (e.key === 'Escape') {
74
+ e.preventDefault();
75
+ onClose();
76
+ }
77
+ };
78
+ document.addEventListener('keydown', handler);
79
+ return () => document.removeEventListener('keydown', handler);
80
+ }, [open, onClose]);
81
+
82
+ // --- Template operations ---
83
+ const handleDeleteTemplate = useCallback(
84
+ (index: number) => {
85
+ const next = draftTemplates.filter((_, i) => i !== index);
86
+ setDraftTemplates(next);
87
+ updateTemplates(next);
88
+ if (editingIndex === index) setEditingIndex(null);
89
+ },
90
+ [draftTemplates, editingIndex, updateTemplates],
91
+ );
92
+
93
+ const handleStartEdit = useCallback(
94
+ (index: number) => {
95
+ setEditingIndex(index);
96
+ setEditLabel(draftTemplates[index].label);
97
+ setEditText(draftTemplates[index].text);
98
+ setAddingNew(false);
99
+ },
100
+ [draftTemplates],
101
+ );
102
+
103
+ const handleSaveEdit = useCallback(() => {
104
+ if (editingIndex === null || !editLabel.trim() || !editText.trim()) return;
105
+ const next = [...draftTemplates];
106
+ next[editingIndex] = { label: editLabel.trim(), text: editText.trim() };
107
+ setDraftTemplates(next);
108
+ updateTemplates(next);
109
+ setEditingIndex(null);
110
+ }, [editingIndex, editLabel, editText, draftTemplates, updateTemplates]);
111
+
112
+ const handleAddTemplate = useCallback(() => {
113
+ if (!newLabel.trim() || !newText.trim()) return;
114
+ const next = [...draftTemplates, { label: newLabel.trim(), text: newText.trim() }];
115
+ setDraftTemplates(next);
116
+ updateTemplates(next);
117
+ setNewLabel('');
118
+ setNewText('');
119
+ setAddingNew(false);
120
+ }, [newLabel, newText, draftTemplates, updateTemplates]);
121
+
122
+ const handleResetTemplates = useCallback(() => {
123
+ resetTemplates();
124
+ setDraftTemplates(DEFAULT_TEMPLATES);
125
+ setEditingIndex(null);
126
+ setAddingNew(false);
127
+ }, [resetTemplates]);
128
+
129
+ const handleDragStart = useCallback((index: number) => {
130
+ setDragIndex(index);
131
+ }, []);
132
+
133
+ const handleDragOver = useCallback((e: React.DragEvent, index: number) => {
134
+ e.preventDefault();
135
+ setDragOverIndex(index);
136
+ }, []);
137
+
138
+ const handleDrop = useCallback(
139
+ (targetIndex: number) => {
140
+ if (dragIndex === null || dragIndex === targetIndex) {
141
+ setDragIndex(null);
142
+ setDragOverIndex(null);
143
+ return;
144
+ }
145
+ const next = [...draftTemplates];
146
+ const [moved] = next.splice(dragIndex, 1);
147
+ const adjustedTarget = targetIndex > dragIndex ? targetIndex - 1 : targetIndex;
148
+ next.splice(adjustedTarget, 0, moved);
149
+ setDraftTemplates(next);
150
+ updateTemplates(next);
151
+ if (editingIndex === dragIndex) setEditingIndex(adjustedTarget);
152
+ setDragIndex(null);
153
+ setDragOverIndex(null);
154
+ },
155
+ [dragIndex, draftTemplates, editingIndex, updateTemplates],
156
+ );
157
+
158
+ const handleDragEnd = useCallback(() => {
159
+ setDragIndex(null);
160
+ setDragOverIndex(null);
161
+ }, []);
162
+
163
+ // --- General settings ---
164
+ const handleAuthorBlur = useCallback(() => {
165
+ const trimmed = draftAuthor.trim() || 'User';
166
+ setDraftAuthor(trimmed);
167
+ onAuthorChange(trimmed);
168
+ }, [draftAuthor, onAuthorChange]);
169
+
170
+ const handleMaxLengthBlur = useCallback(() => {
171
+ const num = parseInt(draftMaxLength);
172
+ const valid = !isNaN(num) && num > 0 ? num : 1000;
173
+ setDraftMaxLength(String(valid));
174
+ updateCommentMaxLength(valid);
175
+ }, [draftMaxLength, updateCommentMaxLength]);
176
+
177
+ if (!open) return null;
178
+
179
+ const sections: { key: Section; label: string; icon: React.ReactNode }[] = [
180
+ {
181
+ key: 'general',
182
+ label: 'General',
183
+ icon: (
184
+ <svg
185
+ className="w-4 h-4"
186
+ fill="none"
187
+ viewBox="0 0 24 24"
188
+ stroke="currentColor"
189
+ strokeWidth={2}
190
+ >
191
+ <path
192
+ strokeLinecap="round"
193
+ strokeLinejoin="round"
194
+ d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 010 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 010-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28z"
195
+ />
196
+ <path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
197
+ </svg>
198
+ ),
199
+ },
200
+ {
201
+ key: 'templates',
202
+ label: 'Templates',
203
+ icon: (
204
+ <svg
205
+ className="w-4 h-4"
206
+ fill="none"
207
+ viewBox="0 0 24 24"
208
+ stroke="currentColor"
209
+ strokeWidth={2}
210
+ >
211
+ <path
212
+ strokeLinecap="round"
213
+ strokeLinejoin="round"
214
+ d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25H12"
215
+ />
216
+ </svg>
217
+ ),
218
+ },
219
+ {
220
+ key: 'theme',
221
+ label: 'Theme',
222
+ icon: (
223
+ <svg
224
+ className="w-4 h-4"
225
+ fill="none"
226
+ viewBox="0 0 24 24"
227
+ stroke="currentColor"
228
+ strokeWidth={2}
229
+ >
230
+ <path
231
+ strokeLinecap="round"
232
+ strokeLinejoin="round"
233
+ d="M4.098 19.902a3.75 3.75 0 005.304 0l6.401-6.402M6.75 21A3.75 3.75 0 013 17.25V4.125C3 3.504 3.504 3 4.125 3h5.25c.621 0 1.125.504 1.125 1.125v4.072M6.75 21a3.75 3.75 0 003.75-3.75V8.197M6.75 21h13.125c.621 0 1.125-.504 1.125-1.125v-5.25c0-.621-.504-1.125-1.125-1.125h-4.072M10.5 8.197l2.88-2.88c.438-.439 1.15-.439 1.59 0l3.712 3.713c.44.44.44 1.152 0 1.59l-2.879 2.88M6.75 17.25h.008v.008H6.75v-.008z"
234
+ />
235
+ </svg>
236
+ ),
237
+ },
238
+ ];
239
+
240
+ return (
241
+ <div className="fixed inset-0 z-[100] flex items-center justify-center" onClick={onClose}>
242
+ {/* Backdrop */}
243
+ <div className="absolute inset-0 bg-black/40" />
244
+
245
+ {/* Panel */}
246
+ <div
247
+ ref={panelRef}
248
+ className="relative w-full max-w-3xl max-h-[85vh] bg-surface-raised rounded-xl shadow-2xl border border-border overflow-hidden flex flex-col"
249
+ onClick={(e) => e.stopPropagation()}
250
+ >
251
+ {/* Header */}
252
+ <div className="flex items-center justify-between px-5 py-3 border-b border-border shrink-0">
253
+ <h2 className="text-sm font-semibold text-content">Settings</h2>
254
+ <button
255
+ onClick={onClose}
256
+ className="p-1 rounded text-content-muted hover:text-content-secondary hover:bg-tint transition-colors"
257
+ >
258
+ <svg
259
+ className="w-4 h-4"
260
+ viewBox="0 0 24 24"
261
+ fill="none"
262
+ stroke="currentColor"
263
+ strokeWidth={2}
264
+ >
265
+ <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
266
+ </svg>
267
+ </button>
268
+ </div>
269
+
270
+ <div className="flex h-[580px]">
271
+ {/* Sidebar navigation */}
272
+ <div className="w-40 border-r border-border bg-surface-secondary shrink-0 py-2">
273
+ {sections.map((s) => (
274
+ <button
275
+ key={s.key}
276
+ onClick={() => setActiveSection(s.key)}
277
+ className={`w-full text-left px-4 py-2 flex items-center gap-2.5 text-sm transition-colors ${
278
+ activeSection === s.key
279
+ ? 'bg-primary-bg text-primary-text font-medium'
280
+ : 'text-content-secondary hover:bg-tint hover:text-content'
281
+ }`}
282
+ >
283
+ {s.icon}
284
+ {s.label}
285
+ </button>
286
+ ))}
287
+ </div>
288
+
289
+ {/* Content area */}
290
+ <div className="flex-1 overflow-y-auto p-5">
291
+ {activeSection === 'templates' && (
292
+ <div>
293
+ {/* Show templates by default — standalone preference */}
294
+ <label className="flex items-center justify-between gap-4">
295
+ <div>
296
+ <h3 className="text-sm font-semibold text-content">
297
+ Show templates by default
298
+ </h3>
299
+ <p className="text-xs text-content-muted mt-0.5">
300
+ Automatically show the template picker when adding a new comment.
301
+ </p>
302
+ </div>
303
+ <button
304
+ role="switch"
305
+ aria-checked={settings.showTemplatesByDefault}
306
+ onClick={() => updateShowTemplatesByDefault(!settings.showTemplatesByDefault)}
307
+ className={`relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors ${
308
+ settings.showTemplatesByDefault ? 'bg-primary' : 'bg-border'
309
+ }`}
310
+ >
311
+ <span
312
+ className={`inline-block h-3.5 w-3.5 rounded-full bg-white shadow-sm transition-transform ${
313
+ settings.showTemplatesByDefault ? 'translate-x-[18px]' : 'translate-x-[3px]'
314
+ }`}
315
+ />
316
+ </button>
317
+ </label>
318
+
319
+ {/* Divider */}
320
+ <div className="border-t border-border-subtle my-4" />
321
+
322
+ {/* Template list header */}
323
+ <div className="flex items-center justify-between mb-1">
324
+ <h3 className="text-sm font-semibold text-content">Templates</h3>
325
+ <button
326
+ onClick={handleResetTemplates}
327
+ className="text-xs px-2.5 py-1 rounded-md border border-border-subtle text-content-secondary hover:bg-tint transition-colors"
328
+ >
329
+ Reset to defaults
330
+ </button>
331
+ </div>
332
+ <p className="text-xs text-content-muted mb-3">
333
+ Drag to reorder. The order here matches the template picker.
334
+ </p>
335
+
336
+ {/* Template list */}
337
+ <div className="space-y-1">
338
+ {draftTemplates.map((t, i) => (
339
+ <div
340
+ key={`${t.label}-${t.text}`}
341
+ draggable={editingIndex !== i}
342
+ onDragStart={() => handleDragStart(i)}
343
+ onDragOver={(e) => handleDragOver(e, i)}
344
+ onDrop={() => handleDrop(i)}
345
+ onDragEnd={handleDragEnd}
346
+ >
347
+ {editingIndex === i ? (
348
+ /* Editing inline */
349
+ <div className="rounded-lg border border-primary-border bg-primary-bg p-3 space-y-2">
350
+ <input
351
+ value={editLabel}
352
+ onChange={(e) => setEditLabel(e.target.value)}
353
+ placeholder="Label"
354
+ className="w-full text-sm px-2.5 py-1.5 rounded-md border border-border-subtle bg-surface text-content focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
355
+ />
356
+ <textarea
357
+ value={editText}
358
+ onChange={(e) => setEditText(e.target.value)}
359
+ placeholder="Template text"
360
+ rows={2}
361
+ className="w-full text-sm px-2.5 py-1.5 rounded-md border border-border-subtle bg-surface text-content resize-none focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
362
+ />
363
+ <div className="flex gap-2 justify-end">
364
+ <button
365
+ onClick={() => setEditingIndex(null)}
366
+ className="text-xs px-2.5 py-1 rounded-md text-content-secondary hover:bg-tint transition-colors"
367
+ >
368
+ Cancel
369
+ </button>
370
+ <button
371
+ onClick={handleSaveEdit}
372
+ disabled={!editLabel.trim() || !editText.trim()}
373
+ className="text-xs px-2.5 py-1 rounded-md bg-primary text-on-primary hover:bg-primary-hover transition-colors disabled:opacity-40"
374
+ >
375
+ Save
376
+ </button>
377
+ </div>
378
+ </div>
379
+ ) : (
380
+ /* Display row */
381
+ <div
382
+ className={`group flex items-center gap-2 rounded-lg px-3 py-2 transition-colors ${
383
+ dragOverIndex === i && dragIndex !== i
384
+ ? 'border-t-2 border-primary'
385
+ : 'border-t-2 border-transparent'
386
+ } ${dragIndex === i ? 'opacity-40' : 'hover:bg-tint'}`}
387
+ >
388
+ {/* Drag handle */}
389
+ <span className="shrink-0 cursor-grab active:cursor-grabbing text-content-faint hover:text-content-muted">
390
+ <svg
391
+ className="w-4 h-4"
392
+ fill="none"
393
+ viewBox="0 0 24 24"
394
+ stroke="currentColor"
395
+ strokeWidth={2}
396
+ >
397
+ <path
398
+ strokeLinecap="round"
399
+ strokeLinejoin="round"
400
+ d="M3.75 9h16.5m-16.5 6.75h16.5"
401
+ />
402
+ </svg>
403
+ </span>
404
+
405
+ {/* Label + text preview */}
406
+ <div className="flex-1 min-w-0">
407
+ <span className="text-sm text-content font-medium">{t.label}</span>
408
+ <p className="text-xs text-content-muted truncate">{t.text}</p>
409
+ </div>
410
+
411
+ {/* Actions */}
412
+ <div className="flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity shrink-0">
413
+ <button
414
+ onClick={() => handleStartEdit(i)}
415
+ className="p-1 rounded text-content-muted hover:text-primary-text hover:bg-tint-primary transition-colors"
416
+ title="Edit"
417
+ >
418
+ <svg
419
+ className="w-3.5 h-3.5"
420
+ fill="none"
421
+ viewBox="0 0 24 24"
422
+ stroke="currentColor"
423
+ strokeWidth={2}
424
+ >
425
+ <path
426
+ strokeLinecap="round"
427
+ strokeLinejoin="round"
428
+ d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L6.832 19.82a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.863 4.487z"
429
+ />
430
+ </svg>
431
+ </button>
432
+ <button
433
+ onClick={() => handleDeleteTemplate(i)}
434
+ className="p-1 rounded text-content-muted hover:text-danger-text hover:bg-tint-danger transition-colors"
435
+ title="Delete"
436
+ >
437
+ <svg
438
+ className="w-3.5 h-3.5"
439
+ fill="none"
440
+ viewBox="0 0 24 24"
441
+ stroke="currentColor"
442
+ strokeWidth={2}
443
+ >
444
+ <path
445
+ strokeLinecap="round"
446
+ strokeLinejoin="round"
447
+ d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
448
+ />
449
+ </svg>
450
+ </button>
451
+ </div>
452
+ </div>
453
+ )}
454
+ </div>
455
+ ))}
456
+ </div>
457
+
458
+ {/* Add new template */}
459
+ {addingNew ? (
460
+ <div className="mt-3 rounded-lg border border-primary-border bg-primary-bg p-3 space-y-2">
461
+ <input
462
+ ref={newLabelRef}
463
+ value={newLabel}
464
+ onChange={(e) => setNewLabel(e.target.value)}
465
+ placeholder="Label (e.g. Clarify)"
466
+ className="w-full text-sm px-2.5 py-1.5 rounded-md border border-border-subtle bg-surface text-content focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
467
+ onKeyDown={(e) => {
468
+ if (e.key === 'Escape') {
469
+ setAddingNew(false);
470
+ setNewLabel('');
471
+ setNewText('');
472
+ }
473
+ }}
474
+ />
475
+ <textarea
476
+ value={newText}
477
+ onChange={(e) => setNewText(e.target.value)}
478
+ placeholder="Template text (e.g. Please clarify this section.)"
479
+ rows={2}
480
+ className="w-full text-sm px-2.5 py-1.5 rounded-md border border-border-subtle bg-surface text-content resize-none focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
481
+ onKeyDown={(e) => {
482
+ if (e.key === 'Escape') {
483
+ setAddingNew(false);
484
+ setNewLabel('');
485
+ setNewText('');
486
+ }
487
+ if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
488
+ e.preventDefault();
489
+ handleAddTemplate();
490
+ }
491
+ }}
492
+ />
493
+ <div className="flex gap-2 justify-end">
494
+ <button
495
+ onClick={() => {
496
+ setAddingNew(false);
497
+ setNewLabel('');
498
+ setNewText('');
499
+ }}
500
+ className="text-xs px-2.5 py-1 rounded-md text-content-secondary hover:bg-tint transition-colors"
501
+ >
502
+ Cancel
503
+ </button>
504
+ <button
505
+ onClick={handleAddTemplate}
506
+ disabled={!newLabel.trim() || !newText.trim()}
507
+ className="text-xs px-2.5 py-1 rounded-md bg-primary text-on-primary hover:bg-primary-hover transition-colors disabled:opacity-40"
508
+ >
509
+ Add
510
+ </button>
511
+ </div>
512
+ </div>
513
+ ) : (
514
+ <button
515
+ onClick={() => {
516
+ setAddingNew(true);
517
+ setEditingIndex(null);
518
+ }}
519
+ className="mt-3 w-full text-left px-3 py-2 rounded-lg border border-dashed border-border-subtle text-sm text-content-muted hover:text-primary-text hover:border-primary-border hover:bg-tint-primary transition-colors flex items-center gap-2"
520
+ >
521
+ <svg
522
+ className="w-4 h-4"
523
+ fill="none"
524
+ viewBox="0 0 24 24"
525
+ stroke="currentColor"
526
+ strokeWidth={2}
527
+ >
528
+ <path
529
+ strokeLinecap="round"
530
+ strokeLinejoin="round"
531
+ d="M12 4.5v15m7.5-7.5h-15"
532
+ />
533
+ </svg>
534
+ Add template
535
+ </button>
536
+ )}
537
+ </div>
538
+ )}
539
+
540
+ {activeSection === 'general' && (
541
+ <div className="space-y-6">
542
+ {/* Author Name */}
543
+ <div>
544
+ <h3 className="text-sm font-semibold text-content mb-1">Author Name</h3>
545
+ <p className="text-xs text-content-muted mb-2">
546
+ Name attached to your comments and replies.
547
+ </p>
548
+ <input
549
+ ref={authorInputRef}
550
+ value={draftAuthor}
551
+ onChange={(e) => setDraftAuthor(e.target.value)}
552
+ onBlur={handleAuthorBlur}
553
+ onKeyDown={(e) => {
554
+ if (e.key === 'Enter') {
555
+ e.preventDefault();
556
+ handleAuthorBlur();
557
+ (e.target as HTMLInputElement).blur();
558
+ }
559
+ }}
560
+ className="w-60 text-sm px-3 py-1.5 rounded-md border border-border-subtle bg-surface text-content focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
561
+ placeholder="Your name"
562
+ />
563
+ </div>
564
+
565
+ {/* Enable Resolve */}
566
+ <div>
567
+ <label className="flex items-center justify-between gap-4">
568
+ <div>
569
+ <h3 className="text-sm font-semibold text-content">
570
+ Enable resolve workflow
571
+ </h3>
572
+ <p className="text-xs text-content-muted mt-0.5">
573
+ Adds resolve and reopen actions for reviewing with humans. Leave off when
574
+ working with AI agents.
575
+ </p>
576
+ </div>
577
+ <button
578
+ role="switch"
579
+ aria-checked={settings.enableResolve}
580
+ onClick={() => updateEnableResolve(!settings.enableResolve)}
581
+ className={`relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors ${
582
+ settings.enableResolve ? 'bg-primary' : 'bg-border'
583
+ }`}
584
+ >
585
+ <span
586
+ className={`inline-block h-3.5 w-3.5 rounded-full bg-white shadow-sm transition-transform ${
587
+ settings.enableResolve ? 'translate-x-[18px]' : 'translate-x-[3px]'
588
+ }`}
589
+ />
590
+ </button>
591
+ </label>
592
+ </div>
593
+
594
+ {/* Quick Comment */}
595
+ <div>
596
+ <label className="flex items-center justify-between gap-4">
597
+ <div>
598
+ <h3 className="text-sm font-semibold text-content">Quick comment</h3>
599
+ <p className="text-xs text-content-muted mt-0.5">
600
+ Open the comment form immediately when text is selected, skipping the
601
+ "Comment" button.
602
+ </p>
603
+ </div>
604
+ <button
605
+ role="switch"
606
+ aria-checked={settings.quickComment}
607
+ onClick={() => updateQuickComment(!settings.quickComment)}
608
+ className={`relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors ${
609
+ settings.quickComment ? 'bg-primary' : 'bg-border'
610
+ }`}
611
+ >
612
+ <span
613
+ className={`inline-block h-3.5 w-3.5 rounded-full bg-white shadow-sm transition-transform ${
614
+ settings.quickComment ? 'translate-x-[18px]' : 'translate-x-[3px]'
615
+ }`}
616
+ />
617
+ </button>
618
+ </label>
619
+ </div>
620
+
621
+ {/* Comment Max Length */}
622
+ <div>
623
+ <h3 className="text-sm font-semibold text-content mb-1">Comment Max Length</h3>
624
+ <p className="text-xs text-content-muted mb-2">
625
+ Maximum characters per comment. Long inline markers can confuse AI agents
626
+ parsing the file.
627
+ </p>
628
+ <input
629
+ type="number"
630
+ min="50"
631
+ max="10000"
632
+ value={draftMaxLength}
633
+ onChange={(e) => setDraftMaxLength(e.target.value)}
634
+ onBlur={handleMaxLengthBlur}
635
+ onKeyDown={(e) => {
636
+ if (e.key === 'Enter') {
637
+ e.preventDefault();
638
+ handleMaxLengthBlur();
639
+ (e.target as HTMLInputElement).blur();
640
+ }
641
+ }}
642
+ className="w-32 text-sm px-3 py-1.5 rounded-md border border-border-subtle bg-surface text-content focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
643
+ />
644
+ <span className="text-xs text-content-muted ml-2">characters</span>
645
+ </div>
646
+ </div>
647
+ )}
648
+
649
+ {activeSection === 'theme' && (
650
+ <div>
651
+ <h3 className="text-sm font-semibold text-content mb-1">Theme</h3>
652
+ <p className="text-xs text-content-muted mb-4">
653
+ Choose a color theme for the interface.
654
+ </p>
655
+
656
+ {/* System theme */}
657
+ <button
658
+ onClick={() => setTheme('system')}
659
+ className={`w-full text-left px-4 py-3 rounded-lg border-2 transition-colors mb-5 flex items-center gap-3 ${
660
+ theme === 'system'
661
+ ? 'border-primary bg-primary-bg'
662
+ : 'border-border hover:border-primary-border hover:bg-tint'
663
+ }`}
664
+ >
665
+ <svg
666
+ className={`w-5 h-5 ${theme === 'system' ? 'text-primary-text' : 'text-content-muted'}`}
667
+ fill="none"
668
+ viewBox="0 0 24 24"
669
+ stroke="currentColor"
670
+ strokeWidth={1.5}
671
+ >
672
+ <path
673
+ strokeLinecap="round"
674
+ strokeLinejoin="round"
675
+ d="M9 17.25v1.007a3 3 0 01-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0115 18.257V17.25m6-12V15a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 15V5.25A2.25 2.25 0 015.25 3h13.5A2.25 2.25 0 0121 5.25z"
676
+ />
677
+ </svg>
678
+ <div>
679
+ <span
680
+ className={`text-sm font-medium ${theme === 'system' ? 'text-primary-text' : 'text-content'}`}
681
+ >
682
+ System
683
+ </span>
684
+ <p className="text-xs text-content-muted">Follows your OS appearance setting</p>
685
+ </div>
686
+ {theme === 'system' && (
687
+ <svg
688
+ className="w-3.5 h-3.5 ml-auto text-primary-text"
689
+ fill="none"
690
+ viewBox="0 0 24 24"
691
+ stroke="currentColor"
692
+ strokeWidth={2}
693
+ >
694
+ <path
695
+ strokeLinecap="round"
696
+ strokeLinejoin="round"
697
+ d="M4.5 12.75l6 6 9-13.5"
698
+ />
699
+ </svg>
700
+ )}
701
+ </button>
702
+
703
+ {/* Light themes */}
704
+ <p className="text-xs font-medium text-content-muted uppercase tracking-wider mb-2">
705
+ Light
706
+ </p>
707
+ <div className="grid grid-cols-2 gap-3 mb-5">
708
+ {LIGHT_THEMES.map((t) => (
709
+ <button
710
+ key={t.key}
711
+ onClick={() => setTheme(t.key)}
712
+ className={`text-left px-4 py-3 rounded-lg border-2 transition-colors ${
713
+ theme === t.key
714
+ ? 'border-primary bg-primary-bg'
715
+ : 'border-border hover:border-primary-border hover:bg-tint'
716
+ }`}
717
+ >
718
+ <div className="flex items-center gap-2 mb-1.5">
719
+ <div className="flex gap-1">
720
+ {t.colors.map((c, i) => (
721
+ <div
722
+ key={i}
723
+ className="w-4 h-4 rounded-full border border-border-subtle"
724
+ style={{ backgroundColor: c }}
725
+ />
726
+ ))}
727
+ </div>
728
+ </div>
729
+ <span
730
+ className={`text-sm font-medium ${
731
+ theme === t.key ? 'text-primary-text' : 'text-content'
732
+ }`}
733
+ >
734
+ {t.label}
735
+ </span>
736
+ {theme === t.key && (
737
+ <svg
738
+ className="inline-block w-3.5 h-3.5 ml-1.5 text-primary-text"
739
+ fill="none"
740
+ viewBox="0 0 24 24"
741
+ stroke="currentColor"
742
+ strokeWidth={2}
743
+ >
744
+ <path
745
+ strokeLinecap="round"
746
+ strokeLinejoin="round"
747
+ d="M4.5 12.75l6 6 9-13.5"
748
+ />
749
+ </svg>
750
+ )}
751
+ </button>
752
+ ))}
753
+ </div>
754
+
755
+ {/* Dark themes */}
756
+ <p className="text-xs font-medium text-content-muted uppercase tracking-wider mb-2">
757
+ Dark
758
+ </p>
759
+ <div className="grid grid-cols-2 gap-3">
760
+ {DARK_THEMES.map((t) => (
761
+ <button
762
+ key={t.key}
763
+ onClick={() => setTheme(t.key)}
764
+ className={`text-left px-4 py-3 rounded-lg border-2 transition-colors ${
765
+ theme === t.key
766
+ ? 'border-primary bg-primary-bg'
767
+ : 'border-border hover:border-primary-border hover:bg-tint'
768
+ }`}
769
+ >
770
+ <div className="flex items-center gap-2 mb-1.5">
771
+ <div className="flex gap-1">
772
+ {t.colors.map((c, i) => (
773
+ <div
774
+ key={i}
775
+ className="w-4 h-4 rounded-full border border-border-subtle"
776
+ style={{ backgroundColor: c }}
777
+ />
778
+ ))}
779
+ </div>
780
+ </div>
781
+ <span
782
+ className={`text-sm font-medium ${
783
+ theme === t.key ? 'text-primary-text' : 'text-content'
784
+ }`}
785
+ >
786
+ {t.label}
787
+ </span>
788
+ {theme === t.key && (
789
+ <svg
790
+ className="inline-block w-3.5 h-3.5 ml-1.5 text-primary-text"
791
+ fill="none"
792
+ viewBox="0 0 24 24"
793
+ stroke="currentColor"
794
+ strokeWidth={2}
795
+ >
796
+ <path
797
+ strokeLinecap="round"
798
+ strokeLinejoin="round"
799
+ d="M4.5 12.75l6 6 9-13.5"
800
+ />
801
+ </svg>
802
+ )}
803
+ </button>
804
+ ))}
805
+ </div>
806
+ </div>
807
+ )}
808
+ </div>
809
+ </div>
810
+ </div>
811
+ </div>
812
+ );
813
+ }