@industry-theme/backlogmd-kanban-panel 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.
@@ -0,0 +1,939 @@
1
+ import { jsxs, jsx } from "react/jsx-runtime";
2
+ import React2, { forwardRef, createElement, createContext, useState, useEffect, useContext, useMemo, useCallback } from "react";
3
+ /**
4
+ * @license lucide-react v0.552.0 - ISC
5
+ *
6
+ * This source code is licensed under the ISC license.
7
+ * See the LICENSE file in the root directory of this source tree.
8
+ */
9
+ const toKebabCase = (string) => string.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
10
+ const toCamelCase = (string) => string.replace(
11
+ /^([A-Z])|[\s-_]+(\w)/g,
12
+ (match, p1, p2) => p2 ? p2.toUpperCase() : p1.toLowerCase()
13
+ );
14
+ const toPascalCase = (string) => {
15
+ const camelCase = toCamelCase(string);
16
+ return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
17
+ };
18
+ const mergeClasses = (...classes) => classes.filter((className, index, array) => {
19
+ return Boolean(className) && className.trim() !== "" && array.indexOf(className) === index;
20
+ }).join(" ").trim();
21
+ const hasA11yProp = (props) => {
22
+ for (const prop in props) {
23
+ if (prop.startsWith("aria-") || prop === "role" || prop === "title") {
24
+ return true;
25
+ }
26
+ }
27
+ };
28
+ /**
29
+ * @license lucide-react v0.552.0 - ISC
30
+ *
31
+ * This source code is licensed under the ISC license.
32
+ * See the LICENSE file in the root directory of this source tree.
33
+ */
34
+ var defaultAttributes = {
35
+ xmlns: "http://www.w3.org/2000/svg",
36
+ width: 24,
37
+ height: 24,
38
+ viewBox: "0 0 24 24",
39
+ fill: "none",
40
+ stroke: "currentColor",
41
+ strokeWidth: 2,
42
+ strokeLinecap: "round",
43
+ strokeLinejoin: "round"
44
+ };
45
+ /**
46
+ * @license lucide-react v0.552.0 - ISC
47
+ *
48
+ * This source code is licensed under the ISC license.
49
+ * See the LICENSE file in the root directory of this source tree.
50
+ */
51
+ const Icon = forwardRef(
52
+ ({
53
+ color = "currentColor",
54
+ size = 24,
55
+ strokeWidth = 2,
56
+ absoluteStrokeWidth,
57
+ className = "",
58
+ children,
59
+ iconNode,
60
+ ...rest
61
+ }, ref) => createElement(
62
+ "svg",
63
+ {
64
+ ref,
65
+ ...defaultAttributes,
66
+ width: size,
67
+ height: size,
68
+ stroke: color,
69
+ strokeWidth: absoluteStrokeWidth ? Number(strokeWidth) * 24 / Number(size) : strokeWidth,
70
+ className: mergeClasses("lucide", className),
71
+ ...!children && !hasA11yProp(rest) && { "aria-hidden": "true" },
72
+ ...rest
73
+ },
74
+ [
75
+ ...iconNode.map(([tag, attrs]) => createElement(tag, attrs)),
76
+ ...Array.isArray(children) ? children : [children]
77
+ ]
78
+ )
79
+ );
80
+ /**
81
+ * @license lucide-react v0.552.0 - ISC
82
+ *
83
+ * This source code is licensed under the ISC license.
84
+ * See the LICENSE file in the root directory of this source tree.
85
+ */
86
+ const createLucideIcon = (iconName, iconNode) => {
87
+ const Component = forwardRef(
88
+ ({ className, ...props }, ref) => createElement(Icon, {
89
+ ref,
90
+ iconNode,
91
+ className: mergeClasses(
92
+ `lucide-${toKebabCase(toPascalCase(iconName))}`,
93
+ `lucide-${iconName}`,
94
+ className
95
+ ),
96
+ ...props
97
+ })
98
+ );
99
+ Component.displayName = toPascalCase(iconName);
100
+ return Component;
101
+ };
102
+ /**
103
+ * @license lucide-react v0.552.0 - ISC
104
+ *
105
+ * This source code is licensed under the ISC license.
106
+ * See the LICENSE file in the root directory of this source tree.
107
+ */
108
+ const __iconNode$2 = [
109
+ ["circle", { cx: "12", cy: "12", r: "10", key: "1mglay" }],
110
+ ["line", { x1: "12", x2: "12", y1: "8", y2: "12", key: "1pkeuh" }],
111
+ ["line", { x1: "12", x2: "12.01", y1: "16", y2: "16", key: "4dfq90" }]
112
+ ];
113
+ const CircleAlert = createLucideIcon("circle-alert", __iconNode$2);
114
+ /**
115
+ * @license lucide-react v0.552.0 - ISC
116
+ *
117
+ * This source code is licensed under the ISC license.
118
+ * See the LICENSE file in the root directory of this source tree.
119
+ */
120
+ const __iconNode$1 = [
121
+ ["path", { d: "M5 3v14", key: "9nsxs2" }],
122
+ ["path", { d: "M12 3v8", key: "1h2ygw" }],
123
+ ["path", { d: "M19 3v18", key: "1sk56x" }]
124
+ ];
125
+ const Kanban = createLucideIcon("kanban", __iconNode$1);
126
+ /**
127
+ * @license lucide-react v0.552.0 - ISC
128
+ *
129
+ * This source code is licensed under the ISC license.
130
+ * See the LICENSE file in the root directory of this source tree.
131
+ */
132
+ const __iconNode = [
133
+ ["path", { d: "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8", key: "v9h5vc" }],
134
+ ["path", { d: "M21 3v5h-5", key: "1q7to0" }],
135
+ ["path", { d: "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16", key: "3uifl3" }],
136
+ ["path", { d: "M8 16H3v5", key: "1cv678" }]
137
+ ];
138
+ const RefreshCw = createLucideIcon("refresh-cw", __iconNode);
139
+ var terminalTheme = {
140
+ space: [0, 4, 8, 16, 32, 64, 128, 256, 512],
141
+ fonts: {
142
+ body: '"SF Mono", "Monaco", "Inconsolata", "Fira Code", monospace',
143
+ heading: '"SF Mono", "Monaco", "Inconsolata", "Fira Code", monospace',
144
+ monospace: '"SF Mono", "Monaco", "Inconsolata", "Fira Code", monospace'
145
+ },
146
+ fontSizes: [12, 14, 16, 18, 20, 24, 32, 48, 64, 96],
147
+ fontScale: 1,
148
+ fontWeights: {
149
+ body: 400,
150
+ heading: 500,
151
+ bold: 600,
152
+ light: 300,
153
+ medium: 500,
154
+ semibold: 600
155
+ },
156
+ lineHeights: {
157
+ body: 1.6,
158
+ heading: 1.3,
159
+ tight: 1.4,
160
+ relaxed: 1.8
161
+ },
162
+ breakpoints: ["640px", "768px", "1024px", "1280px"],
163
+ sizes: [16, 32, 64, 128, 256, 512, 768, 1024, 1536],
164
+ radii: [0, 2, 4, 6, 8, 12, 16, 24],
165
+ shadows: [
166
+ "none",
167
+ "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
168
+ "0 2px 4px 0 rgba(0, 0, 0, 0.06)",
169
+ "0 4px 6px 0 rgba(0, 0, 0, 0.07)",
170
+ "0 8px 12px 0 rgba(0, 0, 0, 0.08)",
171
+ "0 16px 24px 0 rgba(0, 0, 0, 0.10)"
172
+ ],
173
+ zIndices: [0, 1, 10, 20, 30, 40, 50],
174
+ colors: {
175
+ text: "#e4e4e4",
176
+ background: "rgba(10, 10, 10, 0.85)",
177
+ primary: "#66b3ff",
178
+ secondary: "#80c4ff",
179
+ accent: "#66ff99",
180
+ highlight: "rgba(102, 179, 255, 0.15)",
181
+ muted: "rgba(26, 26, 26, 0.8)",
182
+ success: "#66ff99",
183
+ warning: "#ffcc66",
184
+ error: "#ff6666",
185
+ info: "#66b3ff",
186
+ border: "rgba(255, 255, 255, 0.1)",
187
+ backgroundSecondary: "rgba(15, 15, 15, 0.9)",
188
+ backgroundTertiary: "rgba(20, 20, 20, 0.9)",
189
+ backgroundLight: "rgba(255, 255, 255, 0.05)",
190
+ backgroundHover: "rgba(102, 179, 255, 0.08)",
191
+ surface: "rgba(15, 15, 15, 0.95)",
192
+ textSecondary: "rgba(255, 255, 255, 0.7)",
193
+ textTertiary: "rgba(255, 255, 255, 0.5)",
194
+ textMuted: "rgba(255, 255, 255, 0.4)",
195
+ highlightBg: "rgba(255, 235, 59, 0.25)",
196
+ highlightBorder: "rgba(255, 235, 59, 0.5)"
197
+ },
198
+ modes: {
199
+ light: {
200
+ text: "#1a1a1a",
201
+ background: "rgba(255, 255, 255, 0.9)",
202
+ primary: "#0066cc",
203
+ secondary: "#0052a3",
204
+ accent: "#00cc88",
205
+ highlight: "rgba(0, 102, 204, 0.08)",
206
+ muted: "rgba(245, 245, 245, 0.8)",
207
+ success: "#00cc88",
208
+ warning: "#ffaa00",
209
+ error: "#ff3333",
210
+ info: "#0066cc",
211
+ border: "rgba(0, 0, 0, 0.1)",
212
+ backgroundSecondary: "rgba(250, 250, 250, 0.9)",
213
+ backgroundTertiary: "rgba(245, 245, 245, 0.9)",
214
+ backgroundLight: "rgba(0, 0, 0, 0.02)",
215
+ backgroundHover: "rgba(0, 102, 204, 0.04)",
216
+ surface: "rgba(255, 255, 255, 0.95)",
217
+ textSecondary: "rgba(0, 0, 0, 0.6)",
218
+ textTertiary: "rgba(0, 0, 0, 0.4)",
219
+ textMuted: "rgba(0, 0, 0, 0.3)",
220
+ highlightBg: "rgba(255, 235, 59, 0.3)",
221
+ highlightBorder: "rgba(255, 235, 59, 0.6)"
222
+ }
223
+ },
224
+ buttons: {
225
+ primary: {
226
+ color: "white",
227
+ bg: "primary",
228
+ borderWidth: 0,
229
+ "&:hover": {
230
+ bg: "secondary"
231
+ }
232
+ },
233
+ secondary: {
234
+ color: "primary",
235
+ bg: "transparent",
236
+ borderWidth: 1,
237
+ borderStyle: "solid",
238
+ borderColor: "primary",
239
+ "&:hover": {
240
+ bg: "highlight"
241
+ }
242
+ },
243
+ ghost: {
244
+ color: "text",
245
+ bg: "transparent",
246
+ "&:hover": {
247
+ bg: "backgroundHover"
248
+ }
249
+ }
250
+ },
251
+ text: {
252
+ heading: {
253
+ fontFamily: "heading",
254
+ fontWeight: "heading",
255
+ lineHeight: "heading"
256
+ },
257
+ body: {
258
+ fontFamily: "body",
259
+ fontWeight: "body",
260
+ lineHeight: "body"
261
+ },
262
+ caption: {
263
+ fontSize: 1,
264
+ color: "textSecondary"
265
+ }
266
+ },
267
+ cards: {
268
+ primary: {
269
+ bg: "surface",
270
+ border: "1px solid",
271
+ borderColor: "border",
272
+ borderRadius: 1
273
+ },
274
+ secondary: {
275
+ bg: "backgroundSecondary",
276
+ border: "1px solid",
277
+ borderColor: "border",
278
+ borderRadius: 1
279
+ }
280
+ }
281
+ };
282
+ function getMode(theme2, mode) {
283
+ if (!mode || !theme2.modes || !theme2.modes[mode]) {
284
+ return theme2.colors;
285
+ }
286
+ return {
287
+ ...theme2.colors,
288
+ ...theme2.modes[mode]
289
+ };
290
+ }
291
+ var ThemeContext;
292
+ var getThemeContext = () => {
293
+ if (typeof window !== "undefined") {
294
+ const globalWindow = window;
295
+ if (!globalWindow.__principlemd_theme_context__) {
296
+ globalWindow.__principlemd_theme_context__ = createContext(void 0);
297
+ }
298
+ return globalWindow.__principlemd_theme_context__;
299
+ } else {
300
+ if (!ThemeContext) {
301
+ ThemeContext = createContext(void 0);
302
+ }
303
+ return ThemeContext;
304
+ }
305
+ };
306
+ var ThemeContextSingleton = getThemeContext();
307
+ var useTheme = () => {
308
+ const context = useContext(ThemeContextSingleton);
309
+ if (!context) {
310
+ throw new Error("useTheme must be used within a ThemeProvider");
311
+ }
312
+ return context;
313
+ };
314
+ var ThemeProvider = ({
315
+ children,
316
+ theme: customTheme = theme,
317
+ initialMode
318
+ }) => {
319
+ const [mode, setMode] = useState(initialMode);
320
+ const activeTheme = React2.useMemo(() => {
321
+ if (!mode || !customTheme.modes || !customTheme.modes[mode]) {
322
+ return customTheme;
323
+ }
324
+ return {
325
+ ...customTheme,
326
+ colors: getMode(customTheme, mode)
327
+ };
328
+ }, [customTheme, mode]);
329
+ useEffect(() => {
330
+ if (!initialMode) {
331
+ const savedMode = localStorage.getItem("principlemd-theme-mode");
332
+ if (savedMode) {
333
+ setMode(savedMode);
334
+ }
335
+ }
336
+ }, [initialMode]);
337
+ useEffect(() => {
338
+ if (mode) {
339
+ localStorage.setItem("principlemd-theme-mode", mode);
340
+ } else {
341
+ localStorage.removeItem("principlemd-theme-mode");
342
+ }
343
+ }, [mode]);
344
+ const value = {
345
+ theme: activeTheme,
346
+ mode,
347
+ setMode
348
+ };
349
+ return /* @__PURE__ */ React2.createElement(ThemeContextSingleton.Provider, {
350
+ value
351
+ }, children);
352
+ };
353
+ var theme = terminalTheme;
354
+ function generateMockTasks() {
355
+ return [
356
+ {
357
+ id: "task-001",
358
+ title: "Implement user authentication",
359
+ status: "To Do",
360
+ assignee: ["alice@example.com"],
361
+ createdDate: "2025-11-01T10:00:00Z",
362
+ updatedDate: "2025-11-15T14:30:00Z",
363
+ labels: ["feature", "security"],
364
+ dependencies: [],
365
+ description: "Add OAuth2 authentication flow with support for multiple providers (Google, GitHub). Include session management and token refresh logic.",
366
+ priority: "high",
367
+ ordinal: 1,
368
+ filePath: "backlog/task-001.md",
369
+ source: "local"
370
+ },
371
+ {
372
+ id: "task-002",
373
+ title: "Design database schema",
374
+ status: "To Do",
375
+ assignee: ["bob@example.com"],
376
+ createdDate: "2025-11-02T09:15:00Z",
377
+ labels: ["database", "architecture"],
378
+ dependencies: [],
379
+ description: "Create normalized database schema for user data, tasks, and relationships. Include migration scripts.",
380
+ priority: "high",
381
+ ordinal: 2,
382
+ filePath: "backlog/task-002.md",
383
+ source: "local"
384
+ },
385
+ {
386
+ id: "task-003",
387
+ title: "Build REST API endpoints",
388
+ status: "In Progress",
389
+ assignee: ["charlie@example.com"],
390
+ createdDate: "2025-11-03T11:20:00Z",
391
+ updatedDate: "2025-11-16T09:00:00Z",
392
+ labels: ["backend", "api"],
393
+ dependencies: ["task-002"],
394
+ description: "Implement RESTful API endpoints for CRUD operations. Include validation, error handling, and rate limiting.",
395
+ implementationPlan: `
396
+ ## Implementation Steps
397
+ 1. Set up Express/Fastify server
398
+ 2. Define API routes and handlers
399
+ 3. Add request validation middleware
400
+ 4. Implement error handling
401
+ 5. Add rate limiting
402
+ 6. Write API documentation
403
+ `.trim(),
404
+ priority: "high",
405
+ ordinal: 1,
406
+ filePath: "backlog/task-003.md",
407
+ source: "local"
408
+ },
409
+ {
410
+ id: "task-004",
411
+ title: "Create UI component library",
412
+ status: "In Progress",
413
+ assignee: ["diana@example.com", "eve@example.com"],
414
+ createdDate: "2025-11-04T13:45:00Z",
415
+ updatedDate: "2025-11-17T16:20:00Z",
416
+ labels: ["frontend", "ui"],
417
+ dependencies: [],
418
+ description: "Build reusable React components with TypeScript. Include buttons, forms, modals, and data tables.",
419
+ priority: "medium",
420
+ ordinal: 2,
421
+ acceptanceCriteriaItems: [
422
+ { index: 0, text: "All components are fully typed", checked: true },
423
+ { index: 1, text: "Components support theme customization", checked: true },
424
+ { index: 2, text: "Storybook stories for each component", checked: false },
425
+ { index: 3, text: "Accessibility audit passes", checked: false }
426
+ ],
427
+ filePath: "backlog/task-004.md",
428
+ source: "local"
429
+ },
430
+ {
431
+ id: "task-005",
432
+ title: "Set up CI/CD pipeline",
433
+ status: "Done",
434
+ assignee: ["frank@example.com"],
435
+ createdDate: "2025-11-05T08:30:00Z",
436
+ updatedDate: "2025-11-10T17:00:00Z",
437
+ labels: ["devops", "automation"],
438
+ dependencies: [],
439
+ description: "Configure GitHub Actions for automated testing, building, and deployment.",
440
+ priority: "medium",
441
+ ordinal: 1,
442
+ filePath: "backlog/task-005.md",
443
+ source: "completed"
444
+ },
445
+ {
446
+ id: "task-006",
447
+ title: "Write unit tests",
448
+ status: "Done",
449
+ assignee: ["grace@example.com"],
450
+ createdDate: "2025-11-06T10:00:00Z",
451
+ updatedDate: "2025-11-12T14:30:00Z",
452
+ labels: ["testing", "quality"],
453
+ dependencies: ["task-003"],
454
+ description: "Add comprehensive unit tests for all API endpoints and utility functions. Aim for 80%+ coverage.",
455
+ priority: "medium",
456
+ ordinal: 2,
457
+ filePath: "backlog/task-006.md",
458
+ source: "completed"
459
+ },
460
+ {
461
+ id: "task-007",
462
+ title: "Optimize database queries",
463
+ status: "To Do",
464
+ assignee: ["henry@example.com"],
465
+ createdDate: "2025-11-07T15:20:00Z",
466
+ labels: ["performance", "database"],
467
+ dependencies: ["task-002", "task-003"],
468
+ description: "Profile and optimize slow database queries. Add indexes where needed.",
469
+ priority: "low",
470
+ ordinal: 3,
471
+ filePath: "backlog/task-007.md",
472
+ source: "local"
473
+ },
474
+ {
475
+ id: "task-008",
476
+ title: "Implement real-time notifications",
477
+ status: "To Do",
478
+ assignee: [],
479
+ createdDate: "2025-11-08T09:45:00Z",
480
+ labels: ["feature", "realtime"],
481
+ dependencies: ["task-003"],
482
+ description: "Add WebSocket support for real-time notifications when tasks are updated or assigned.",
483
+ priority: "low",
484
+ ordinal: 4,
485
+ filePath: "backlog/task-008.md",
486
+ source: "local"
487
+ }
488
+ ];
489
+ }
490
+ function getDefaultStatuses() {
491
+ return ["To Do", "In Progress", "Done"];
492
+ }
493
+ function useKanbanData() {
494
+ const [tasks, setTasks] = useState(() => generateMockTasks());
495
+ const [statuses] = useState(() => getDefaultStatuses());
496
+ const [isLoading, setIsLoading] = useState(false);
497
+ const [error, setError] = useState(null);
498
+ const tasksByStatus = useMemo(() => {
499
+ const grouped = /* @__PURE__ */ new Map();
500
+ for (const status of statuses) {
501
+ grouped.set(status, []);
502
+ }
503
+ for (const task of tasks) {
504
+ const statusKey = task.status ?? "";
505
+ const list = grouped.get(statusKey);
506
+ if (list) {
507
+ list.push(task);
508
+ }
509
+ }
510
+ for (const [status, taskList] of grouped.entries()) {
511
+ taskList.sort((a, b) => {
512
+ if (a.ordinal !== void 0 && b.ordinal !== void 0) {
513
+ return a.ordinal - b.ordinal;
514
+ }
515
+ const priorityOrder = { high: 3, medium: 2, low: 1 };
516
+ const aPriority = priorityOrder[a.priority] || 0;
517
+ const bPriority = priorityOrder[b.priority] || 0;
518
+ if (aPriority !== bPriority) {
519
+ return bPriority - aPriority;
520
+ }
521
+ return new Date(b.createdDate).getTime() - new Date(a.createdDate).getTime();
522
+ });
523
+ grouped.set(status, taskList);
524
+ }
525
+ return grouped;
526
+ }, [tasks, statuses]);
527
+ const refreshData = useCallback(async () => {
528
+ setIsLoading(true);
529
+ setError(null);
530
+ try {
531
+ await new Promise((resolve) => setTimeout(resolve, 500));
532
+ const newTasks = generateMockTasks();
533
+ setTasks(newTasks);
534
+ } catch (err) {
535
+ setError(err instanceof Error ? err.message : "Failed to refresh data");
536
+ } finally {
537
+ setIsLoading(false);
538
+ }
539
+ }, []);
540
+ const updateTaskStatus = useCallback(async (taskId, newStatus) => {
541
+ setError(null);
542
+ setTasks(
543
+ (prevTasks) => prevTasks.map(
544
+ (task) => task.id === taskId ? { ...task, status: newStatus, updatedDate: (/* @__PURE__ */ new Date()).toISOString() } : task
545
+ )
546
+ );
547
+ try {
548
+ await new Promise((resolve) => setTimeout(resolve, 300));
549
+ } catch (err) {
550
+ setError(err instanceof Error ? err.message : "Failed to update task");
551
+ setTasks(generateMockTasks());
552
+ }
553
+ }, []);
554
+ return {
555
+ tasks,
556
+ statuses,
557
+ isLoading,
558
+ error,
559
+ tasksByStatus,
560
+ refreshData,
561
+ updateTaskStatus
562
+ };
563
+ }
564
+ const KanbanColumn = ({
565
+ status,
566
+ tasks,
567
+ onTaskClick
568
+ }) => {
569
+ const { theme: theme2 } = useTheme();
570
+ const getPriorityColor = (priority) => {
571
+ switch (priority) {
572
+ case "high":
573
+ return theme2.colors.error;
574
+ case "medium":
575
+ return theme2.colors.warning;
576
+ case "low":
577
+ return theme2.colors.info;
578
+ default:
579
+ return theme2.colors.border;
580
+ }
581
+ };
582
+ return /* @__PURE__ */ jsxs(
583
+ "div",
584
+ {
585
+ style: {
586
+ flex: "0 0 320px",
587
+ display: "flex",
588
+ flexDirection: "column",
589
+ gap: "12px",
590
+ background: theme2.colors.backgroundSecondary,
591
+ borderRadius: theme2.radii[2],
592
+ padding: "16px",
593
+ border: `1px solid ${theme2.colors.border}`
594
+ },
595
+ children: [
596
+ /* @__PURE__ */ jsxs(
597
+ "div",
598
+ {
599
+ style: {
600
+ display: "flex",
601
+ alignItems: "center",
602
+ justifyContent: "space-between"
603
+ },
604
+ children: [
605
+ /* @__PURE__ */ jsx(
606
+ "h3",
607
+ {
608
+ style: {
609
+ margin: 0,
610
+ fontSize: theme2.fontSizes[3],
611
+ color: theme2.colors.text,
612
+ fontWeight: theme2.fontWeights.semibold
613
+ },
614
+ children: status
615
+ }
616
+ ),
617
+ /* @__PURE__ */ jsx(
618
+ "span",
619
+ {
620
+ style: {
621
+ fontSize: theme2.fontSizes[1],
622
+ color: theme2.colors.textSecondary,
623
+ background: theme2.colors.background,
624
+ padding: "2px 8px",
625
+ borderRadius: theme2.radii[1]
626
+ },
627
+ children: tasks.length
628
+ }
629
+ )
630
+ ]
631
+ }
632
+ ),
633
+ /* @__PURE__ */ jsx(
634
+ "div",
635
+ {
636
+ style: {
637
+ flex: 1,
638
+ display: "flex",
639
+ flexDirection: "column",
640
+ gap: "8px",
641
+ overflowY: "auto"
642
+ },
643
+ children: tasks.map((task) => /* @__PURE__ */ jsxs(
644
+ "div",
645
+ {
646
+ onClick: () => onTaskClick == null ? void 0 : onTaskClick(task),
647
+ style: {
648
+ background: theme2.colors.surface,
649
+ borderRadius: theme2.radii[2],
650
+ padding: "12px",
651
+ border: `1px solid ${theme2.colors.border}`,
652
+ borderLeft: `4px solid ${getPriorityColor(task.priority)}`,
653
+ cursor: onTaskClick ? "pointer" : "default",
654
+ transition: "all 0.2s ease"
655
+ },
656
+ onMouseEnter: (e) => {
657
+ if (onTaskClick) {
658
+ e.currentTarget.style.transform = "translateY(-2px)";
659
+ e.currentTarget.style.boxShadow = `0 4px 8px ${theme2.colors.border}`;
660
+ }
661
+ },
662
+ onMouseLeave: (e) => {
663
+ if (onTaskClick) {
664
+ e.currentTarget.style.transform = "translateY(0)";
665
+ e.currentTarget.style.boxShadow = "none";
666
+ }
667
+ },
668
+ children: [
669
+ /* @__PURE__ */ jsx(
670
+ "h4",
671
+ {
672
+ style: {
673
+ margin: "0 0 8px 0",
674
+ fontSize: theme2.fontSizes[2],
675
+ color: theme2.colors.text,
676
+ fontWeight: theme2.fontWeights.medium
677
+ },
678
+ children: task.title
679
+ }
680
+ ),
681
+ task.description && /* @__PURE__ */ jsx(
682
+ "p",
683
+ {
684
+ style: {
685
+ margin: "0 0 8px 0",
686
+ fontSize: theme2.fontSizes[1],
687
+ color: theme2.colors.textSecondary,
688
+ overflow: "hidden",
689
+ textOverflow: "ellipsis",
690
+ display: "-webkit-box",
691
+ WebkitLineClamp: 2,
692
+ WebkitBoxOrient: "vertical",
693
+ lineHeight: "1.4"
694
+ },
695
+ children: task.description
696
+ }
697
+ ),
698
+ task.labels && task.labels.length > 0 && /* @__PURE__ */ jsx(
699
+ "div",
700
+ {
701
+ style: {
702
+ display: "flex",
703
+ gap: "4px",
704
+ flexWrap: "wrap",
705
+ marginBottom: "8px"
706
+ },
707
+ children: task.labels.map((label) => /* @__PURE__ */ jsx(
708
+ "span",
709
+ {
710
+ style: {
711
+ fontSize: theme2.fontSizes[0],
712
+ color: theme2.colors.primary,
713
+ background: `${theme2.colors.primary}20`,
714
+ padding: "2px 8px",
715
+ borderRadius: theme2.radii[1],
716
+ fontWeight: theme2.fontWeights.medium
717
+ },
718
+ children: label
719
+ },
720
+ label
721
+ ))
722
+ }
723
+ ),
724
+ /* @__PURE__ */ jsxs(
725
+ "div",
726
+ {
727
+ style: {
728
+ display: "flex",
729
+ alignItems: "center",
730
+ justifyContent: "space-between",
731
+ fontSize: theme2.fontSizes[0],
732
+ color: theme2.colors.textMuted
733
+ },
734
+ children: [
735
+ /* @__PURE__ */ jsx(
736
+ "span",
737
+ {
738
+ style: {
739
+ fontFamily: theme2.fonts.monospace
740
+ },
741
+ children: task.id
742
+ }
743
+ ),
744
+ task.assignee && task.assignee.length > 0 && /* @__PURE__ */ jsxs(
745
+ "span",
746
+ {
747
+ style: {
748
+ color: theme2.colors.textSecondary
749
+ },
750
+ children: [
751
+ task.assignee.length,
752
+ " assignee",
753
+ task.assignee.length !== 1 ? "s" : ""
754
+ ]
755
+ }
756
+ )
757
+ ]
758
+ }
759
+ )
760
+ ]
761
+ },
762
+ task.id
763
+ ))
764
+ }
765
+ )
766
+ ]
767
+ }
768
+ );
769
+ };
770
+ const KanbanPanelContent = ({
771
+ context
772
+ }) => {
773
+ const { theme: theme2 } = useTheme();
774
+ const [_selectedTask, setSelectedTask] = useState(null);
775
+ const {
776
+ statuses,
777
+ tasksByStatus,
778
+ isLoading,
779
+ error,
780
+ refreshData
781
+ } = useKanbanData();
782
+ const handleTaskClick = (task) => {
783
+ setSelectedTask(task);
784
+ };
785
+ const handleRefresh = async () => {
786
+ await refreshData();
787
+ };
788
+ return /* @__PURE__ */ jsxs(
789
+ "div",
790
+ {
791
+ style: {
792
+ padding: "20px",
793
+ fontFamily: theme2.fonts.body,
794
+ height: "100%",
795
+ display: "flex",
796
+ flexDirection: "column",
797
+ gap: "16px",
798
+ backgroundColor: theme2.colors.background,
799
+ color: theme2.colors.text
800
+ },
801
+ children: [
802
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "12px" }, children: [
803
+ /* @__PURE__ */ jsx(Kanban, { size: 24, color: theme2.colors.primary }),
804
+ /* @__PURE__ */ jsx(
805
+ "h2",
806
+ {
807
+ style: {
808
+ margin: 0,
809
+ fontSize: theme2.fontSizes[4],
810
+ color: theme2.colors.text
811
+ },
812
+ children: "Kanban Board"
813
+ }
814
+ ),
815
+ context.currentScope.repository && /* @__PURE__ */ jsx(
816
+ "span",
817
+ {
818
+ style: {
819
+ marginLeft: "auto",
820
+ fontSize: theme2.fontSizes[1],
821
+ color: theme2.colors.textSecondary,
822
+ fontFamily: theme2.fonts.monospace
823
+ },
824
+ children: context.currentScope.repository.name
825
+ }
826
+ ),
827
+ /* @__PURE__ */ jsxs(
828
+ "button",
829
+ {
830
+ onClick: handleRefresh,
831
+ disabled: isLoading,
832
+ style: {
833
+ padding: "8px 12px",
834
+ display: "flex",
835
+ alignItems: "center",
836
+ gap: "6px",
837
+ border: `1px solid ${theme2.colors.border}`,
838
+ borderRadius: theme2.radii[1],
839
+ background: theme2.colors.surface,
840
+ color: theme2.colors.text,
841
+ cursor: isLoading ? "not-allowed" : "pointer",
842
+ opacity: isLoading ? 0.6 : 1
843
+ },
844
+ children: [
845
+ /* @__PURE__ */ jsx(RefreshCw, { size: 16, style: { animation: isLoading ? "spin 1s linear infinite" : "none" } }),
846
+ "Refresh"
847
+ ]
848
+ }
849
+ )
850
+ ] }),
851
+ error && /* @__PURE__ */ jsxs(
852
+ "div",
853
+ {
854
+ style: {
855
+ padding: "12px",
856
+ background: `${theme2.colors.error}20`,
857
+ border: `1px solid ${theme2.colors.error}`,
858
+ borderRadius: theme2.radii[2],
859
+ display: "flex",
860
+ alignItems: "center",
861
+ gap: "8px",
862
+ color: theme2.colors.error
863
+ },
864
+ children: [
865
+ /* @__PURE__ */ jsx(CircleAlert, { size: 16 }),
866
+ /* @__PURE__ */ jsx("span", { children: error })
867
+ ]
868
+ }
869
+ ),
870
+ /* @__PURE__ */ jsx(
871
+ "div",
872
+ {
873
+ style: {
874
+ flex: 1,
875
+ display: "flex",
876
+ gap: "16px",
877
+ overflowX: "auto",
878
+ paddingBottom: "8px"
879
+ },
880
+ children: statuses.map((status) => {
881
+ const columnTasks = tasksByStatus.get(status) || [];
882
+ return /* @__PURE__ */ jsx(
883
+ KanbanColumn,
884
+ {
885
+ status,
886
+ tasks: columnTasks,
887
+ onTaskClick: handleTaskClick
888
+ },
889
+ status
890
+ );
891
+ })
892
+ }
893
+ )
894
+ ]
895
+ }
896
+ );
897
+ };
898
+ const KanbanPanel = (props) => {
899
+ return /* @__PURE__ */ jsx(ThemeProvider, { children: /* @__PURE__ */ jsx(KanbanPanelContent, { ...props }) });
900
+ };
901
+ const panels = [
902
+ {
903
+ metadata: {
904
+ id: "principal-ade.kanban-panel",
905
+ name: "Kanban Board",
906
+ icon: "📋",
907
+ version: "0.1.0",
908
+ author: "Principal ADE",
909
+ description: "Kanban board for visualizing Backlog.md tasks",
910
+ slices: ["fileTree"]
911
+ // Data slices this panel depends on
912
+ },
913
+ component: KanbanPanel,
914
+ // Optional: Called when this specific panel is mounted
915
+ onMount: async (context) => {
916
+ var _a;
917
+ console.log(
918
+ "Kanban Panel mounted",
919
+ (_a = context.currentScope.repository) == null ? void 0 : _a.path
920
+ );
921
+ },
922
+ // Optional: Called when this specific panel is unmounted
923
+ onUnmount: async (_context) => {
924
+ console.log("Kanban Panel unmounting");
925
+ }
926
+ }
927
+ ];
928
+ const onPackageLoad = async () => {
929
+ console.log("Panel package loaded - Kanban Panel Extension");
930
+ };
931
+ const onPackageUnload = async () => {
932
+ console.log("Panel package unloading - Kanban Panel Extension");
933
+ };
934
+ export {
935
+ onPackageLoad,
936
+ onPackageUnload,
937
+ panels
938
+ };
939
+ //# sourceMappingURL=panels.bundle.js.map