kanban-lite 1.0.4

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 (99) hide show
  1. package/.editorconfig +9 -0
  2. package/.github/workflows/ci.yml +59 -0
  3. package/.github/workflows/release.yml +75 -0
  4. package/.prettierignore +6 -0
  5. package/.prettierrc.yaml +4 -0
  6. package/.vscode/extensions.json +3 -0
  7. package/.vscode/launch.json +17 -0
  8. package/.vscode/settings.json +21 -0
  9. package/.vscode/tasks.json +22 -0
  10. package/.vscodeignore +11 -0
  11. package/CHANGELOG.md +184 -0
  12. package/CLAUDE.md +58 -0
  13. package/CONTRIBUTING.md +114 -0
  14. package/LICENSE +22 -0
  15. package/README.md +482 -0
  16. package/SKILL.md +237 -0
  17. package/dist/cli.js +8716 -0
  18. package/dist/extension.js +8463 -0
  19. package/dist/mcp-server.js +1327 -0
  20. package/dist/standalone-webview/icons-Dx9MGYqN.js +180 -0
  21. package/dist/standalone-webview/icons-Dx9MGYqN.js.map +1 -0
  22. package/dist/standalone-webview/index.js +85 -0
  23. package/dist/standalone-webview/index.js.map +1 -0
  24. package/dist/standalone-webview/react-vendor-DkYdDBET.js +25 -0
  25. package/dist/standalone-webview/react-vendor-DkYdDBET.js.map +1 -0
  26. package/dist/standalone-webview/style.css +1 -0
  27. package/dist/standalone.js +7513 -0
  28. package/dist/webview/icons-Dx9MGYqN.js +180 -0
  29. package/dist/webview/icons-Dx9MGYqN.js.map +1 -0
  30. package/dist/webview/index.js +85 -0
  31. package/dist/webview/index.js.map +1 -0
  32. package/dist/webview/react-vendor-DkYdDBET.js +25 -0
  33. package/dist/webview/react-vendor-DkYdDBET.js.map +1 -0
  34. package/dist/webview/style.css +1 -0
  35. package/docs/images/board-overview.png +0 -0
  36. package/docs/images/editor-view.png +0 -0
  37. package/docs/plans/2026-02-20-kanban-json-config-design.md +74 -0
  38. package/docs/plans/2026-02-20-kanban-json-config.md +690 -0
  39. package/eslint.config.mjs +31 -0
  40. package/package.json +161 -0
  41. package/postcss.config.js +6 -0
  42. package/resources/icon-light.png +0 -0
  43. package/resources/icon-light.svg +105 -0
  44. package/resources/icon.png +0 -0
  45. package/resources/icon.svg +105 -0
  46. package/resources/kanban-dark.svg +21 -0
  47. package/resources/kanban-light.svg +21 -0
  48. package/resources/kanban.svg +21 -0
  49. package/src/cli/index.ts +846 -0
  50. package/src/extension/FeatureHeaderProvider.ts +370 -0
  51. package/src/extension/KanbanPanel.ts +973 -0
  52. package/src/extension/SidebarViewProvider.ts +507 -0
  53. package/src/extension/featureFileUtils.ts +82 -0
  54. package/src/extension/index.ts +234 -0
  55. package/src/mcp-server/index.ts +632 -0
  56. package/src/sdk/KanbanSDK.ts +349 -0
  57. package/src/sdk/__tests__/KanbanSDK.test.ts +468 -0
  58. package/src/sdk/__tests__/parser.test.ts +170 -0
  59. package/src/sdk/fileUtils.ts +76 -0
  60. package/src/sdk/index.ts +6 -0
  61. package/src/sdk/parser.ts +70 -0
  62. package/src/sdk/types.ts +15 -0
  63. package/src/shared/config.ts +113 -0
  64. package/src/shared/editorTypes.ts +14 -0
  65. package/src/shared/types.ts +120 -0
  66. package/src/standalone/__tests__/server.integration.test.ts +1916 -0
  67. package/src/standalone/__tests__/webhooks.test.ts +357 -0
  68. package/src/standalone/fileUtils.ts +70 -0
  69. package/src/standalone/index.ts +71 -0
  70. package/src/standalone/server.ts +1046 -0
  71. package/src/standalone/webhooks.ts +135 -0
  72. package/src/webview/App.tsx +469 -0
  73. package/src/webview/assets/main.css +329 -0
  74. package/src/webview/assets/standalone-theme.css +130 -0
  75. package/src/webview/components/ColumnDialog.tsx +119 -0
  76. package/src/webview/components/CreateFeatureDialog.tsx +524 -0
  77. package/src/webview/components/DatePicker.tsx +185 -0
  78. package/src/webview/components/FeatureCard.tsx +186 -0
  79. package/src/webview/components/FeatureEditor.tsx +623 -0
  80. package/src/webview/components/KanbanBoard.tsx +144 -0
  81. package/src/webview/components/KanbanColumn.tsx +159 -0
  82. package/src/webview/components/MarkdownEditor.tsx +291 -0
  83. package/src/webview/components/PrioritySelect.tsx +39 -0
  84. package/src/webview/components/QuickAddInput.tsx +72 -0
  85. package/src/webview/components/SettingsPanel.tsx +284 -0
  86. package/src/webview/components/Toolbar.tsx +175 -0
  87. package/src/webview/components/UndoToast.tsx +70 -0
  88. package/src/webview/index.html +12 -0
  89. package/src/webview/lib/utils.ts +6 -0
  90. package/src/webview/main.tsx +11 -0
  91. package/src/webview/standalone-main.tsx +13 -0
  92. package/src/webview/standalone-shim.ts +132 -0
  93. package/src/webview/standalone.html +12 -0
  94. package/src/webview/store/index.ts +241 -0
  95. package/tailwind.config.js +53 -0
  96. package/tsconfig.json +22 -0
  97. package/vite.config.ts +36 -0
  98. package/vite.standalone.config.ts +62 -0
  99. package/vitest.config.ts +15 -0
@@ -0,0 +1,329 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ * {
6
+ box-sizing: border-box;
7
+ }
8
+
9
+ html,
10
+ body {
11
+ margin: 0;
12
+ padding: 0;
13
+ height: 100%;
14
+ width: 100%;
15
+ overflow: hidden;
16
+ font-family: var(--vscode-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif);
17
+ font-size: var(--vscode-font-size, 13px);
18
+ color: var(--vscode-foreground);
19
+ background-color: var(--vscode-editor-background);
20
+ -webkit-font-smoothing: antialiased;
21
+ -moz-osx-font-smoothing: grayscale;
22
+ }
23
+
24
+ #root {
25
+ height: 100%;
26
+ width: 100%;
27
+ }
28
+
29
+ /* Custom scrollbar that matches VSCode */
30
+ ::-webkit-scrollbar {
31
+ width: 10px;
32
+ height: 10px;
33
+ }
34
+
35
+ ::-webkit-scrollbar-track {
36
+ background: transparent;
37
+ }
38
+
39
+ ::-webkit-scrollbar-thumb {
40
+ background: var(--vscode-scrollbarSlider-background);
41
+ border-radius: 5px;
42
+ }
43
+
44
+ ::-webkit-scrollbar-thumb:hover {
45
+ background: var(--vscode-scrollbarSlider-hoverBackground);
46
+ }
47
+
48
+ ::-webkit-scrollbar-thumb:active {
49
+ background: var(--vscode-scrollbarSlider-activeBackground);
50
+ }
51
+
52
+ /* Hover background matching VSCode theme */
53
+ .vscode-hover-bg:hover {
54
+ background: var(--vscode-list-hoverBackground);
55
+ }
56
+
57
+ /* Focus visible for accessibility */
58
+ button:focus-visible,
59
+ input:focus-visible {
60
+ outline: 1px solid var(--vscode-focusBorder);
61
+ outline-offset: -1px;
62
+ }
63
+
64
+ /* Tiptap Editor Styles */
65
+ .ProseMirror {
66
+ outline: none;
67
+ }
68
+
69
+ .ProseMirror p.is-editor-empty:first-child::before {
70
+ content: attr(data-placeholder);
71
+ float: left;
72
+ color: var(--vscode-input-placeholderForeground);
73
+ pointer-events: none;
74
+ height: 0;
75
+ }
76
+
77
+ /* Prose (markdown) styling for editor */
78
+ .prose {
79
+ line-height: 1.625;
80
+ }
81
+
82
+ .prose h1 {
83
+ font-size: 1.875rem;
84
+ font-weight: 700;
85
+ margin-top: 0;
86
+ margin-bottom: 0.875rem;
87
+ line-height: 1.2;
88
+ }
89
+
90
+ .prose h2 {
91
+ font-size: 1.5rem;
92
+ font-weight: 600;
93
+ margin-top: 1.5rem;
94
+ margin-bottom: 0.75rem;
95
+ line-height: 1.3;
96
+ }
97
+
98
+ .prose h3 {
99
+ font-size: 1.25rem;
100
+ font-weight: 600;
101
+ margin-top: 1.25rem;
102
+ margin-bottom: 0.5rem;
103
+ line-height: 1.4;
104
+ }
105
+
106
+ .prose h4 {
107
+ font-size: 1.125rem;
108
+ font-weight: 600;
109
+ margin-top: 1rem;
110
+ margin-bottom: 0.5rem;
111
+ }
112
+
113
+ .prose p {
114
+ margin-top: 0;
115
+ margin-bottom: 0.75rem;
116
+ }
117
+
118
+ .prose ul,
119
+ .prose ol {
120
+ margin-top: 0.5rem;
121
+ margin-bottom: 0.75rem;
122
+ padding-left: 1.5rem;
123
+ }
124
+
125
+ .prose ul {
126
+ list-style-type: disc;
127
+ }
128
+
129
+ .prose ol {
130
+ list-style-type: decimal;
131
+ }
132
+
133
+ .prose li {
134
+ margin-top: 0.25rem;
135
+ margin-bottom: 0.25rem;
136
+ }
137
+
138
+ .prose li > p {
139
+ margin-top: 0;
140
+ margin-bottom: 0;
141
+ }
142
+
143
+ .prose li > ul,
144
+ .prose li > ol {
145
+ margin-top: 0.25rem;
146
+ margin-bottom: 0.25rem;
147
+ }
148
+
149
+ .prose blockquote {
150
+ border-left: 3px solid var(--vscode-textBlockQuote-border, #4b5563);
151
+ padding-left: 1rem;
152
+ margin-left: 0;
153
+ margin-right: 0;
154
+ margin-top: 0.75rem;
155
+ margin-bottom: 0.75rem;
156
+ font-style: italic;
157
+ color: var(--vscode-textBlockQuote-foreground, inherit);
158
+ }
159
+
160
+ .prose code {
161
+ font-family: var(--vscode-editor-font-family, 'SF Mono', Monaco, Menlo, Consolas, monospace);
162
+ font-size: 0.875em;
163
+ background-color: var(--vscode-textCodeBlock-background, rgba(127, 127, 127, 0.15));
164
+ padding: 0.125rem 0.375rem;
165
+ border-radius: 0.25rem;
166
+ }
167
+
168
+ .prose pre {
169
+ font-family: var(--vscode-editor-font-family, 'SF Mono', Monaco, Menlo, Consolas, monospace);
170
+ font-size: 0.875em;
171
+ background-color: var(--vscode-textCodeBlock-background, rgba(127, 127, 127, 0.1));
172
+ padding: 0.75rem 1rem;
173
+ border-radius: 0.375rem;
174
+ overflow-x: auto;
175
+ margin-top: 0.75rem;
176
+ margin-bottom: 0.75rem;
177
+ }
178
+
179
+ .prose pre code {
180
+ background-color: transparent;
181
+ padding: 0;
182
+ border-radius: 0;
183
+ font-size: inherit;
184
+ }
185
+
186
+ .prose hr {
187
+ border: none;
188
+ border-top: 1px solid var(--vscode-panel-border, #4b5563);
189
+ margin-top: 1.5rem;
190
+ margin-bottom: 1.5rem;
191
+ }
192
+
193
+ .prose a {
194
+ color: var(--vscode-textLink-foreground, #3b82f6);
195
+ text-decoration: underline;
196
+ }
197
+
198
+ .prose a:hover {
199
+ color: var(--vscode-textLink-activeForeground, #60a5fa);
200
+ }
201
+
202
+ .prose strong {
203
+ font-weight: 600;
204
+ }
205
+
206
+ .prose em {
207
+ font-style: italic;
208
+ }
209
+
210
+ .prose img {
211
+ max-width: 100%;
212
+ height: auto;
213
+ border-radius: 0.375rem;
214
+ margin-top: 0.75rem;
215
+ margin-bottom: 0.75rem;
216
+ }
217
+
218
+ /* Task list styling */
219
+ .prose ul[data-type="taskList"] {
220
+ list-style: none;
221
+ padding-left: 0;
222
+ }
223
+
224
+ .prose ul[data-type="taskList"] li {
225
+ display: flex;
226
+ align-items: flex-start;
227
+ gap: 0.5rem;
228
+ }
229
+
230
+ .prose ul[data-type="taskList"] li > label {
231
+ flex-shrink: 0;
232
+ margin-top: 0.125rem;
233
+ }
234
+
235
+ .prose ul[data-type="taskList"] li > div {
236
+ flex: 1;
237
+ }
238
+
239
+ /* Table styling */
240
+ .prose table {
241
+ width: 100%;
242
+ border-collapse: collapse;
243
+ margin-top: 0.75rem;
244
+ margin-bottom: 0.75rem;
245
+ }
246
+
247
+ .prose th,
248
+ .prose td {
249
+ border: 1px solid var(--vscode-panel-border, #4b5563);
250
+ padding: 0.5rem 0.75rem;
251
+ text-align: left;
252
+ }
253
+
254
+ .prose th {
255
+ font-weight: 600;
256
+ background-color: var(--vscode-editor-inactiveSelectionBackground, rgba(127, 127, 127, 0.1));
257
+ }
258
+
259
+ /* Selection styling */
260
+ .ProseMirror ::selection {
261
+ background-color: var(--vscode-editor-selectionBackground, rgba(59, 130, 246, 0.3));
262
+ }
263
+
264
+ /* Strikethrough */
265
+ .prose s,
266
+ .prose del {
267
+ text-decoration: line-through;
268
+ opacity: 0.7;
269
+ }
270
+
271
+ /* Markdown Editor textarea */
272
+ .markdown-editor-textarea {
273
+ width: 100%;
274
+ height: 100%;
275
+ min-height: 200px;
276
+ padding: 1rem;
277
+ margin: 0;
278
+ border: none;
279
+ outline: none;
280
+ resize: none;
281
+ background: transparent;
282
+ color: var(--vscode-foreground);
283
+ font-family: var(--vscode-editor-font-family, 'SF Mono', Monaco, Menlo, Consolas, monospace);
284
+ font-size: var(--vscode-editor-font-size, 13px);
285
+ line-height: 1.6;
286
+ tab-size: 2;
287
+ }
288
+
289
+ .markdown-editor-textarea::placeholder {
290
+ color: var(--vscode-input-placeholderForeground);
291
+ }
292
+
293
+ /* Card inline markdown description */
294
+ .card-inline-markdown code {
295
+ font-family: var(--vscode-editor-font-family, 'SF Mono', Monaco, Menlo, Consolas, monospace);
296
+ font-size: 0.85em;
297
+ padding: 0.1em 0.3em;
298
+ border-radius: 0.2em;
299
+ background-color: rgba(127, 127, 127, 0.15);
300
+ }
301
+
302
+ .card-inline-markdown p {
303
+ margin: 0;
304
+ }
305
+
306
+ .card-inline-markdown ul,
307
+ .card-inline-markdown ol {
308
+ margin: 0.25em 0;
309
+ padding-left: 1.25em;
310
+ }
311
+
312
+ .card-inline-markdown li {
313
+ margin: 0;
314
+ }
315
+
316
+ .card-inline-markdown li p {
317
+ margin: 0;
318
+ }
319
+
320
+ .card-inline-markdown h1,
321
+ .card-inline-markdown h2,
322
+ .card-inline-markdown h3,
323
+ .card-inline-markdown h4,
324
+ .card-inline-markdown h5,
325
+ .card-inline-markdown h6 {
326
+ font-size: inherit;
327
+ font-weight: 600;
328
+ margin: 0;
329
+ }
@@ -0,0 +1,130 @@
1
+ /* Standalone theme: provides fallback values for --vscode-* CSS variables */
2
+
3
+ :root {
4
+ /* Core */
5
+ --vscode-foreground: #333333;
6
+ --vscode-descriptionForeground: #717171;
7
+ --vscode-errorForeground: #e51400;
8
+ --vscode-focusBorder: #0078d4;
9
+ --vscode-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
10
+ --vscode-font-size: 13px;
11
+
12
+ /* Editor */
13
+ --vscode-editor-background: #ffffff;
14
+ --vscode-editor-foreground: #333333;
15
+ --vscode-editor-font-family: 'SF Mono', Monaco, Menlo, Consolas, 'Courier New', monospace;
16
+ --vscode-editor-selectionBackground: rgba(59, 130, 246, 0.2);
17
+ --vscode-editor-inactiveSelectionBackground: rgba(127, 127, 127, 0.1);
18
+
19
+ /* Panel / Borders */
20
+ --vscode-panel-border: #e0e0e0;
21
+ --vscode-contrastBorder: transparent;
22
+ --vscode-widget-border: #e0e0e0;
23
+
24
+ /* Sidebar */
25
+ --vscode-sideBar-background: #f3f3f3;
26
+
27
+ /* Lists */
28
+ --vscode-list-hoverBackground: rgba(0, 0, 0, 0.04);
29
+ --vscode-list-activeSelectionBackground: rgba(0, 120, 212, 0.1);
30
+
31
+ /* Buttons */
32
+ --vscode-button-background: #0078d4;
33
+ --vscode-button-foreground: #ffffff;
34
+ --vscode-button-hoverBackground: #106ebe;
35
+
36
+ /* Badges */
37
+ --vscode-badge-background: #e0e0e0;
38
+ --vscode-badge-foreground: #333333;
39
+
40
+ /* Dropdowns */
41
+ --vscode-dropdown-background: #ffffff;
42
+ --vscode-dropdown-foreground: #333333;
43
+ --vscode-dropdown-border: #e0e0e0;
44
+
45
+ /* Input */
46
+ --vscode-input-placeholderForeground: #a0a0a0;
47
+
48
+ /* Scrollbar */
49
+ --vscode-scrollbarSlider-background: rgba(0, 0, 0, 0.1);
50
+ --vscode-scrollbarSlider-hoverBackground: rgba(0, 0, 0, 0.2);
51
+ --vscode-scrollbarSlider-activeBackground: rgba(0, 0, 0, 0.3);
52
+
53
+ /* Links */
54
+ --vscode-textLink-foreground: #0078d4;
55
+ --vscode-textLink-activeForeground: #005a9e;
56
+
57
+ /* Code blocks */
58
+ --vscode-textCodeBlock-background: rgba(0, 0, 0, 0.04);
59
+ --vscode-textBlockQuote-border: #d0d0d0;
60
+ --vscode-textBlockQuote-foreground: inherit;
61
+
62
+ /* Notifications */
63
+ --vscode-notifications-background: #ffffff;
64
+ --vscode-notifications-foreground: #333333;
65
+ --vscode-notifications-border: #e0e0e0;
66
+
67
+ /* Progress */
68
+ --vscode-progressBar-background: #0078d4;
69
+
70
+ /* Keybindings */
71
+ --vscode-keybindingLabel-background: #f0f0f0;
72
+ --vscode-keybindingLabel-foreground: #333333;
73
+ --vscode-keybindingLabel-border: #d0d0d0;
74
+ }
75
+
76
+ /* Dark mode */
77
+ @media (prefers-color-scheme: dark) {
78
+ :root {
79
+ --vscode-foreground: #cccccc;
80
+ --vscode-descriptionForeground: #9e9e9e;
81
+ --vscode-errorForeground: #f48771;
82
+ --vscode-focusBorder: #007fd4;
83
+
84
+ --vscode-editor-background: #1e1e1e;
85
+ --vscode-editor-foreground: #cccccc;
86
+ --vscode-editor-selectionBackground: rgba(59, 130, 246, 0.3);
87
+ --vscode-editor-inactiveSelectionBackground: rgba(127, 127, 127, 0.15);
88
+
89
+ --vscode-panel-border: #3c3c3c;
90
+ --vscode-widget-border: #3c3c3c;
91
+
92
+ --vscode-sideBar-background: #252526;
93
+
94
+ --vscode-list-hoverBackground: rgba(255, 255, 255, 0.04);
95
+ --vscode-list-activeSelectionBackground: rgba(0, 120, 212, 0.2);
96
+
97
+ --vscode-button-background: #0e639c;
98
+ --vscode-button-foreground: #ffffff;
99
+ --vscode-button-hoverBackground: #1177bb;
100
+
101
+ --vscode-badge-background: #4d4d4d;
102
+ --vscode-badge-foreground: #cccccc;
103
+
104
+ --vscode-dropdown-background: #3c3c3c;
105
+ --vscode-dropdown-foreground: #cccccc;
106
+ --vscode-dropdown-border: #3c3c3c;
107
+
108
+ --vscode-input-placeholderForeground: #6e6e6e;
109
+
110
+ --vscode-scrollbarSlider-background: rgba(255, 255, 255, 0.1);
111
+ --vscode-scrollbarSlider-hoverBackground: rgba(255, 255, 255, 0.2);
112
+ --vscode-scrollbarSlider-activeBackground: rgba(255, 255, 255, 0.3);
113
+
114
+ --vscode-textLink-foreground: #3794ff;
115
+ --vscode-textLink-activeForeground: #3794ff;
116
+
117
+ --vscode-textCodeBlock-background: rgba(255, 255, 255, 0.06);
118
+ --vscode-textBlockQuote-border: #4b5563;
119
+
120
+ --vscode-notifications-background: #252526;
121
+ --vscode-notifications-foreground: #cccccc;
122
+ --vscode-notifications-border: #3c3c3c;
123
+
124
+ --vscode-progressBar-background: #0e70c0;
125
+
126
+ --vscode-keybindingLabel-background: #3c3c3c;
127
+ --vscode-keybindingLabel-foreground: #cccccc;
128
+ --vscode-keybindingLabel-border: #4d4d4d;
129
+ }
130
+ }
@@ -0,0 +1,119 @@
1
+ import { useState, useEffect, useRef } from 'react'
2
+ import { X } from 'lucide-react'
3
+
4
+ const PRESET_COLORS = [
5
+ '#6b7280', '#3b82f6', '#f59e0b', '#8b5cf6', '#22c55e',
6
+ '#ef4444', '#ec4899', '#14b8a6', '#f97316', '#06b6d4'
7
+ ]
8
+
9
+ interface ColumnDialogProps {
10
+ isOpen: boolean
11
+ onClose: () => void
12
+ onSave: (data: { name: string; color: string }) => void
13
+ initial?: { name: string; color: string }
14
+ title: string
15
+ }
16
+
17
+ export function ColumnDialog({ isOpen, onClose, onSave, initial, title }: ColumnDialogProps) {
18
+ const [name, setName] = useState(initial?.name ?? '')
19
+ const [color, setColor] = useState(initial?.color ?? '#3b82f6')
20
+ const inputRef = useRef<HTMLInputElement>(null)
21
+
22
+ useEffect(() => {
23
+ if (isOpen) {
24
+ setName(initial?.name ?? '')
25
+ setColor(initial?.color ?? '#3b82f6')
26
+ setTimeout(() => inputRef.current?.focus(), 50)
27
+ }
28
+ }, [isOpen, initial])
29
+
30
+ useEffect(() => {
31
+ if (!isOpen) return
32
+ const handleKeyDown = (e: KeyboardEvent) => {
33
+ if (e.key === 'Escape') onClose()
34
+ if (e.key === 'Enter' && (e.ctrlKey || e.metaKey) && name.trim()) {
35
+ onSave({ name: name.trim(), color })
36
+ }
37
+ }
38
+ window.addEventListener('keydown', handleKeyDown)
39
+ return () => window.removeEventListener('keydown', handleKeyDown)
40
+ }, [isOpen, onClose, onSave, name, color])
41
+
42
+ if (!isOpen) return null
43
+
44
+ return (
45
+ <div className="fixed inset-0 z-50 flex items-center justify-center">
46
+ <div className="absolute inset-0 bg-black/50" onClick={onClose} />
47
+ <div className="relative bg-white dark:bg-zinc-800 rounded-lg shadow-xl w-full max-w-sm mx-4 border border-zinc-200 dark:border-zinc-600">
48
+ {/* Header */}
49
+ <div className="flex items-center justify-between px-4 py-3 border-b border-zinc-200 dark:border-zinc-700">
50
+ <h2 className="text-sm font-semibold text-zinc-900 dark:text-zinc-100">{title}</h2>
51
+ <button
52
+ type="button"
53
+ onClick={onClose}
54
+ className="p-1 rounded hover:bg-zinc-100 dark:hover:bg-zinc-700 transition-colors"
55
+ >
56
+ <X size={16} className="text-zinc-500" />
57
+ </button>
58
+ </div>
59
+
60
+ {/* Body */}
61
+ <div className="px-4 py-4 space-y-4">
62
+ <div>
63
+ <label className="block text-xs font-medium text-zinc-600 dark:text-zinc-400 mb-1">Name</label>
64
+ <input
65
+ ref={inputRef}
66
+ type="text"
67
+ value={name}
68
+ onChange={(e) => setName(e.target.value)}
69
+ placeholder="List name..."
70
+ className="w-full px-3 py-1.5 text-sm bg-white dark:bg-zinc-700 border border-zinc-200 dark:border-zinc-600 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 text-zinc-900 dark:text-zinc-100 placeholder-zinc-400"
71
+ />
72
+ </div>
73
+
74
+ <div>
75
+ <label className="block text-xs font-medium text-zinc-600 dark:text-zinc-400 mb-1">Color</label>
76
+ <div className="flex items-center gap-2 flex-wrap">
77
+ {PRESET_COLORS.map((c) => (
78
+ <button
79
+ key={c}
80
+ type="button"
81
+ onClick={() => setColor(c)}
82
+ className={`w-6 h-6 rounded-full border-2 transition-all ${color === c ? 'border-zinc-900 dark:border-white scale-110' : 'border-transparent hover:scale-110'}`}
83
+ style={{ backgroundColor: c }}
84
+ title={c}
85
+ />
86
+ ))}
87
+ <input
88
+ type="color"
89
+ value={color}
90
+ onChange={(e) => setColor(e.target.value)}
91
+ className="w-6 h-6 rounded cursor-pointer border-0 p-0 bg-transparent"
92
+ title="Custom color"
93
+ />
94
+ </div>
95
+ </div>
96
+ </div>
97
+
98
+ {/* Footer */}
99
+ <div className="flex items-center justify-end gap-2 px-4 py-3 border-t border-zinc-200 dark:border-zinc-700">
100
+ <button
101
+ type="button"
102
+ onClick={onClose}
103
+ className="px-3 py-1.5 text-sm rounded-md text-zinc-600 dark:text-zinc-400 hover:bg-zinc-100 dark:hover:bg-zinc-700 transition-colors"
104
+ >
105
+ Cancel
106
+ </button>
107
+ <button
108
+ type="button"
109
+ onClick={() => { if (name.trim()) onSave({ name: name.trim(), color }) }}
110
+ disabled={!name.trim()}
111
+ className="px-3 py-1.5 text-sm rounded-md bg-blue-600 text-white hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
112
+ >
113
+ Save
114
+ </button>
115
+ </div>
116
+ </div>
117
+ </div>
118
+ )
119
+ }