@assistkick/create 1.6.0 → 1.8.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/package.json +2 -2
- package/templates/assistkick-product-system/.env.example +1 -0
- package/templates/assistkick-product-system/local.db +0 -0
- package/templates/assistkick-product-system/package.json +4 -2
- package/templates/assistkick-product-system/packages/backend/package.json +2 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/agents.ts +165 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/files.test.ts +358 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/files.ts +356 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/git.ts +96 -1
- package/templates/assistkick-product-system/packages/backend/src/routes/graph.ts +1 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/kanban.ts +43 -4
- package/templates/assistkick-product-system/packages/backend/src/routes/pipeline.ts +200 -84
- package/templates/assistkick-product-system/packages/backend/src/routes/projects.ts +6 -3
- package/templates/assistkick-product-system/packages/backend/src/routes/terminal.ts +53 -17
- package/templates/assistkick-product-system/packages/backend/src/routes/video.ts +218 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/workflow_groups.ts +119 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/workflows.ts +154 -0
- package/templates/assistkick-product-system/packages/backend/src/server.ts +81 -9
- package/templates/assistkick-product-system/packages/backend/src/services/agent_service.test.ts +489 -0
- package/templates/assistkick-product-system/packages/backend/src/services/agent_service.ts +416 -0
- package/templates/assistkick-product-system/packages/backend/src/services/bundle_service.test.ts +189 -0
- package/templates/assistkick-product-system/packages/backend/src/services/bundle_service.ts +182 -0
- package/templates/assistkick-product-system/packages/backend/src/services/init.ts +28 -78
- package/templates/assistkick-product-system/packages/backend/src/services/project_service.test.ts +16 -0
- package/templates/assistkick-product-system/packages/backend/src/services/project_service.ts +73 -2
- package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.test.ts +4 -4
- package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.ts +87 -11
- package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.test.ts +210 -69
- package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.ts +210 -215
- package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.test.ts +162 -0
- package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.ts +148 -0
- package/templates/assistkick-product-system/packages/backend/src/services/terminal_ws_handler.ts +11 -5
- package/templates/assistkick-product-system/packages/backend/src/services/tts_service.test.ts +64 -0
- package/templates/assistkick-product-system/packages/backend/src/services/tts_service.ts +134 -0
- package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.test.ts +256 -0
- package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.ts +258 -0
- package/templates/assistkick-product-system/packages/backend/src/services/workflow_group_service.ts +106 -0
- package/templates/assistkick-product-system/packages/backend/src/services/workflow_service.test.ts +275 -0
- package/templates/assistkick-product-system/packages/backend/src/services/workflow_service.ts +222 -0
- package/templates/assistkick-product-system/packages/frontend/index.html +3 -0
- package/templates/assistkick-product-system/packages/frontend/package-lock.json +800 -11
- package/templates/assistkick-product-system/packages/frontend/package.json +11 -1
- package/templates/assistkick-product-system/packages/frontend/src/App.tsx +24 -7
- package/templates/assistkick-product-system/packages/frontend/src/api/client.ts +456 -16
- package/templates/assistkick-product-system/packages/frontend/src/api/client_files.test.ts +172 -0
- package/templates/assistkick-product-system/packages/frontend/src/api/client_video.test.ts +238 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/AgentsView.tsx +307 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/CoherenceView.tsx +82 -66
- package/templates/assistkick-product-system/packages/frontend/src/components/CompositionPlaceholder.tsx +97 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/DesignSystemView.tsx +383 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/EditorTabBar.tsx +57 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/FileTree.tsx +313 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/FileTreeContextMenu.tsx +61 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/FileTreeInlineInput.tsx +73 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/FilesView.tsx +404 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/GitRepoModal.tsx +193 -64
- package/templates/assistkick-product-system/packages/frontend/src/components/GraphLegend.tsx +71 -73
- package/templates/assistkick-product-system/packages/frontend/src/components/GraphSettings.tsx +8 -8
- package/templates/assistkick-product-system/packages/frontend/src/components/GraphView.tsx +1 -1
- package/templates/assistkick-product-system/packages/frontend/src/components/InviteUserDialog.tsx +15 -11
- package/templates/assistkick-product-system/packages/frontend/src/components/KanbanView.tsx +226 -291
- package/templates/assistkick-product-system/packages/frontend/src/components/LoginPage.tsx +14 -14
- package/templates/assistkick-product-system/packages/frontend/src/components/ProjectSelector.tsx +54 -33
- package/templates/assistkick-product-system/packages/frontend/src/components/QaIssueSheet.tsx +40 -66
- package/templates/assistkick-product-system/packages/frontend/src/components/SidePanel.tsx +55 -115
- package/templates/assistkick-product-system/packages/frontend/src/components/TerminalView.tsx +121 -52
- package/templates/assistkick-product-system/packages/frontend/src/components/Toolbar.tsx +155 -77
- package/templates/assistkick-product-system/packages/frontend/src/components/UsersView.tsx +52 -52
- package/templates/assistkick-product-system/packages/frontend/src/components/VideoGallery.tsx +313 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/VideographyView.tsx +250 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/WorkflowsView.tsx +474 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/AccentBorderList.tsx +53 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/Button.tsx +87 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/ButtonGroup.tsx +29 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/ButtonShowcase.tsx +221 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/CardGlass.tsx +141 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/CompletionRing.tsx +30 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/ContentCard.tsx +34 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/IconButton.tsx +74 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCard.tsx +270 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCardShowcase.tsx +37 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/Kbd.tsx +11 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/KindBadge.tsx +21 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/NavBarSidekick.tsx +207 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/SidePanelShowcase.tsx +370 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/SideSheet.tsx +64 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/StatusDot.tsx +18 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/CheckCardPositionNode.tsx +36 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/CheckCycleCountNode.tsx +60 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/EndNode.tsx +42 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/GroupNode.tsx +189 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/NodePalette.tsx +123 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/RunAgentNode.tsx +51 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/SetCardMetadataNode.tsx +53 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/StartNode.tsx +18 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/TransitionCardNode.tsx +59 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowCanvas.tsx +335 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowMonitorModal.tsx +634 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/autoLayout.ts +103 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/edgeColors.ts +35 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/monitor_nodes.tsx +208 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.test.ts +119 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.ts +107 -0
- package/templates/assistkick-product-system/packages/frontend/src/constants/graph.ts +13 -11
- package/templates/assistkick-product-system/packages/frontend/src/hooks/useAutoSave.ts +75 -0
- package/templates/assistkick-product-system/packages/frontend/src/hooks/useGraph.ts +6 -21
- package/templates/assistkick-product-system/packages/frontend/src/hooks/useProjects.ts +15 -80
- package/templates/assistkick-product-system/packages/frontend/src/hooks/useToast.tsx +16 -3
- package/templates/assistkick-product-system/packages/frontend/src/pages/accept_invitation_page.tsx +30 -27
- package/templates/assistkick-product-system/packages/frontend/src/pages/forgot_password_page.tsx +18 -15
- package/templates/assistkick-product-system/packages/frontend/src/pages/register_page.tsx +21 -18
- package/templates/assistkick-product-system/packages/frontend/src/pages/reset_password_page.tsx +28 -25
- package/templates/assistkick-product-system/packages/frontend/src/routes/AgentsRoute.tsx +6 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/CoherenceRoute.tsx +19 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/DashboardLayout.tsx +54 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/DesignSystemRoute.tsx +6 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/FilesRoute.tsx +13 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/GraphRoute.tsx +93 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/KanbanRoute.tsx +30 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/TerminalRoute.tsx +9 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/UsersRoute.tsx +6 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/VideographyRoute.tsx +13 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/WorkflowsRoute.tsx +6 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useGitModalStore.ts +14 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useGraphStore.ts +36 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useGraphUIStore.ts +25 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useProjectStore.ts +90 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useQaSheetStore.ts +27 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useSidePanelStore.ts +76 -0
- package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +336 -3632
- package/templates/assistkick-product-system/packages/frontend/src/utils/auto_save_service.test.ts +167 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/auto_save_service.ts +101 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/composition_matcher.test.ts +42 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/composition_matcher.ts +17 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/file_utils.test.ts +145 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/file_utils.ts +42 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/task_status.test.ts +4 -10
- package/templates/assistkick-product-system/packages/frontend/src/utils/task_status.ts +19 -1
- package/templates/assistkick-product-system/packages/frontend/vite.config.ts +7 -1
- package/templates/assistkick-product-system/packages/shared/db/local.db +0 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0004_tidy_matthew_murdock.sql +9 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0005_mysterious_falcon.sql +692 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0006_next_venom.sql +9 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0007_deep_barracuda.sql +39 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0008_puzzling_hannibal_king.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0009_amused_beast.sql +8 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0010_spotty_moira_mactaggert.sql +9 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0011_goofy_snowbird.sql +3 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0011_supreme_doctor_octopus.sql +3 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0013_reflective_prowler.sql +15 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0004_snapshot.json +921 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0005_snapshot.json +1042 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0006_snapshot.json +1101 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0007_snapshot.json +1336 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0008_snapshot.json +1275 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0009_snapshot.json +1327 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0010_snapshot.json +1393 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0011_snapshot.json +1436 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0013_snapshot.json +1538 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +70 -0
- package/templates/assistkick-product-system/packages/shared/db/schema.ts +113 -0
- package/templates/assistkick-product-system/packages/shared/lib/claude-service.ts +32 -7
- package/templates/assistkick-product-system/packages/shared/lib/constants.ts +9 -0
- package/templates/assistkick-product-system/packages/shared/lib/git_workflow.ts +12 -4
- package/templates/assistkick-product-system/packages/shared/lib/graph.ts +16 -5
- package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.test.ts +1753 -0
- package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.ts +1281 -0
- package/templates/assistkick-product-system/packages/shared/lib/workflow_orchestrator.ts +211 -0
- package/templates/assistkick-product-system/packages/shared/tools/add_node.test.ts +43 -0
- package/templates/assistkick-product-system/packages/shared/tools/add_node.ts +13 -2
- package/templates/assistkick-product-system/packages/shared/tools/get_kanban.ts +1 -1
- package/templates/assistkick-product-system/packages/shared/tools/migrate_epics.test.ts +226 -0
- package/templates/assistkick-product-system/packages/shared/tools/migrate_epics.ts +251 -0
- package/templates/assistkick-product-system/packages/shared/tools/update_node.ts +2 -2
- package/templates/assistkick-product-system/packages/shared/utils/hello_workflow.test.ts +10 -0
- package/templates/assistkick-product-system/packages/shared/utils/hello_workflow.ts +6 -0
- package/templates/assistkick-product-system/packages/video/Root.tsx +85 -0
- package/templates/assistkick-product-system/packages/video/components/email_scene.tsx +231 -0
- package/templates/assistkick-product-system/packages/video/components/outro_scene.tsx +153 -0
- package/templates/assistkick-product-system/packages/video/components/part_divider.tsx +90 -0
- package/templates/assistkick-product-system/packages/video/components/scene.tsx +226 -0
- package/templates/assistkick-product-system/packages/video/components/theme.ts +22 -0
- package/templates/assistkick-product-system/packages/video/components/title_scene.tsx +169 -0
- package/templates/assistkick-product-system/packages/video/components/video_split_layout.tsx +84 -0
- package/templates/assistkick-product-system/packages/video/compositions/.gitkeep +0 -0
- package/templates/assistkick-product-system/packages/video/index.ts +4 -0
- package/templates/assistkick-product-system/packages/video/package.json +28 -0
- package/templates/assistkick-product-system/packages/video/remotion.config.ts +11 -0
- package/templates/assistkick-product-system/packages/video/scripts/process_script.test.ts +326 -0
- package/templates/assistkick-product-system/packages/video/scripts/process_script.ts +630 -0
- package/templates/assistkick-product-system/packages/video/style.css +1 -0
- package/templates/assistkick-product-system/packages/video/tsconfig.json +18 -0
- package/templates/assistkick-product-system/tests/graph_legend.test.ts +2 -1
- package/templates/assistkick-product-system/tests/video_render_service.test.ts +179 -0
- package/templates/assistkick-product-system/tests/web_terminal.test.ts +219 -455
- package/templates/assistkick-product-system/tests/workflow_integration.test.ts +341 -0
- package/templates/skills/assistkick-bootstrap/SKILL.md +3 -3
- package/templates/skills/assistkick-code-reviewer/SKILL.md +2 -2
- package/templates/skills/assistkick-debugger/SKILL.md +2 -2
- package/templates/skills/assistkick-developer/SKILL.md +6 -3
- package/templates/skills/assistkick-developer/references/react_development_guidelines.md +225 -0
- package/templates/skills/assistkick-interview/SKILL.md +2 -2
- package/templates/skills/product-system/graph.json +1890 -0
- package/templates/skills/product-system/kanban.json +304 -0
- package/templates/skills/product-system/nodes/comp_001.md +56 -0
- package/templates/skills/product-system/nodes/comp_002.md +57 -0
- package/templates/skills/product-system/nodes/data_001.md +51 -0
- package/templates/skills/product-system/nodes/data_002.md +40 -0
- package/templates/skills/product-system/nodes/data_004.md +38 -0
- package/templates/skills/product-system/nodes/dec_001.md +34 -0
- package/templates/skills/product-system/nodes/dec_016.md +32 -0
- package/templates/skills/product-system/nodes/feat_008.md +30 -0
- package/templates/skills/video-composition-agent/SKILL.md +232 -0
- package/templates/skills/video-script-writer/SKILL.md +136 -0
package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCard.tsx
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Play, AlertTriangle, Lock, CircleDot, Copy, Check, ChevronDown,
|
|
4
|
+
} from 'lucide-react';
|
|
5
|
+
import { KindBadge } from './KindBadge';
|
|
6
|
+
|
|
7
|
+
/* ── Types ── */
|
|
8
|
+
|
|
9
|
+
export interface KanbanCardProps {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
/** 0–100 */
|
|
13
|
+
pct: number;
|
|
14
|
+
kind?: string;
|
|
15
|
+
rejectionCount: number;
|
|
16
|
+
blocked: boolean;
|
|
17
|
+
/** Workflow execution status string */
|
|
18
|
+
pipeline: string;
|
|
19
|
+
/** Status badge label (e.g. "Running", "Completed") */
|
|
20
|
+
pipelineLabel?: string;
|
|
21
|
+
/** Number of issues / notes */
|
|
22
|
+
issueCount: number;
|
|
23
|
+
/** Issue button label override */
|
|
24
|
+
issueLabel?: string;
|
|
25
|
+
/** Show play button */
|
|
26
|
+
showPlay?: boolean;
|
|
27
|
+
/** Show dropdown arrow next to play button for workflow selection */
|
|
28
|
+
showPlayDropdown?: boolean;
|
|
29
|
+
/** Show stop button (for epic cards when orchestrator is running) */
|
|
30
|
+
showStop?: boolean;
|
|
31
|
+
/** Show resume button */
|
|
32
|
+
showResume?: boolean;
|
|
33
|
+
/** Whether copy was just triggered */
|
|
34
|
+
copied?: boolean;
|
|
35
|
+
/** Is this the currently-playing card in Play All */
|
|
36
|
+
playAllActive?: boolean;
|
|
37
|
+
/** Name of the currently executing agent (when on a RunAgent node) */
|
|
38
|
+
agentName?: string | null;
|
|
39
|
+
/** Error message from a failed workflow execution */
|
|
40
|
+
errorMessage?: string | null;
|
|
41
|
+
/** Whether this card represents an epic node */
|
|
42
|
+
isEpic?: boolean;
|
|
43
|
+
|
|
44
|
+
/* Callbacks */
|
|
45
|
+
onClick?: () => void;
|
|
46
|
+
onCopy?: () => void;
|
|
47
|
+
onPlay?: () => void;
|
|
48
|
+
onPlayDropdown?: () => void;
|
|
49
|
+
onStop?: () => void;
|
|
50
|
+
onResume?: () => void;
|
|
51
|
+
onUnblock?: () => void;
|
|
52
|
+
onIssuesClick?: () => void;
|
|
53
|
+
onMonitorClick?: () => void;
|
|
54
|
+
|
|
55
|
+
/* Drag */
|
|
56
|
+
draggable?: boolean;
|
|
57
|
+
onDragStart?: (e: React.DragEvent) => void;
|
|
58
|
+
onDragEnd?: (e: React.DragEvent) => void;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* ── Status style helpers ── */
|
|
62
|
+
|
|
63
|
+
function statusCls(status: string): string {
|
|
64
|
+
if (status === 'running') return 'bg-accent/10 text-accent animate-pulse';
|
|
65
|
+
switch (status) {
|
|
66
|
+
case 'completed': return 'bg-emerald-500/10 text-emerald-400';
|
|
67
|
+
case 'failed': return 'bg-error/10 text-error';
|
|
68
|
+
default: return 'bg-white/[0.06] text-content-muted';
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function statusIcon(status: string) {
|
|
73
|
+
if (status === 'running') return <CircleDot size={12} strokeWidth={2.5} />;
|
|
74
|
+
if (status === 'failed') return <AlertTriangle size={12} strokeWidth={2.5} />;
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* ── Component ── */
|
|
79
|
+
|
|
80
|
+
export const KanbanCard = ({
|
|
81
|
+
id, name, pct, kind, rejectionCount, blocked, pipeline, pipelineLabel: pipLabel,
|
|
82
|
+
issueCount, issueLabel, showPlay, showPlayDropdown, showStop, showResume, copied,
|
|
83
|
+
playAllActive, agentName, errorMessage,
|
|
84
|
+
isEpic,
|
|
85
|
+
onClick, onCopy, onPlay, onPlayDropdown, onStop, onResume, onUnblock, onIssuesClick, onMonitorClick,
|
|
86
|
+
draggable, onDragStart, onDragEnd,
|
|
87
|
+
}: KanbanCardProps) => {
|
|
88
|
+
const hasPipeline = pipeline !== 'idle' && !!pipLabel;
|
|
89
|
+
const hasStatusRow = rejectionCount > 0 || hasPipeline;
|
|
90
|
+
|
|
91
|
+
const stripColor = blocked
|
|
92
|
+
? 'from-error/60 to-error/20'
|
|
93
|
+
: rejectionCount >= 3
|
|
94
|
+
? 'from-error/40 to-amber-500/20'
|
|
95
|
+
: pipeline === 'running'
|
|
96
|
+
? 'from-accent/60 to-accent/20'
|
|
97
|
+
: pipeline === 'completed'
|
|
98
|
+
? 'from-emerald-400/60 to-emerald-400/20'
|
|
99
|
+
: playAllActive
|
|
100
|
+
? 'from-accent/60 to-accent/20'
|
|
101
|
+
: 'from-edge to-transparent';
|
|
102
|
+
|
|
103
|
+
const handleCardClick = (e: React.MouseEvent) => {
|
|
104
|
+
if ((e.target as HTMLElement).closest('button')) return;
|
|
105
|
+
onClick?.();
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<div
|
|
110
|
+
className={[
|
|
111
|
+
'group relative shrink-0 overflow-hidden rounded-2xl border shadow-lg shadow-black/10 backdrop-blur-sm transition-all duration-200 hover:border-content/15 hover:shadow-xl hover:shadow-black/15 cursor-pointer',
|
|
112
|
+
isEpic
|
|
113
|
+
? 'bg-gradient-to-br from-accent/[0.06] to-accent/[0.02] border-accent/15'
|
|
114
|
+
: 'bg-surface border-edge',
|
|
115
|
+
].join(' ')}
|
|
116
|
+
draggable={draggable}
|
|
117
|
+
onDragStart={onDragStart}
|
|
118
|
+
onDragEnd={onDragEnd}
|
|
119
|
+
onClick={handleCardClick}
|
|
120
|
+
data-feature-id={id}
|
|
121
|
+
>
|
|
122
|
+
{/* Left accent strip */}
|
|
123
|
+
<div className={`absolute left-0 top-0 h-full w-1 bg-gradient-to-b ${stripColor}`} />
|
|
124
|
+
|
|
125
|
+
<div className="p-4 pl-5">
|
|
126
|
+
{/* Header */}
|
|
127
|
+
<div className="flex items-center gap-2">
|
|
128
|
+
<span className="font-mono text-[12px] text-content-secondary">{id}</span>
|
|
129
|
+
<KindBadge kind={kind || 'new'} />
|
|
130
|
+
<button
|
|
131
|
+
className="flex h-6 w-6 items-center justify-center rounded-md bg-white/[0.08] text-content-secondary transition-colors hover:bg-white/15 hover:text-content cursor-pointer"
|
|
132
|
+
title="Copy feature ID and name"
|
|
133
|
+
onClick={(e) => { e.stopPropagation(); onCopy?.(); }}
|
|
134
|
+
>
|
|
135
|
+
{copied ? <Check size={12} strokeWidth={2.5} className="text-emerald-400" /> : <Copy size={12} strokeWidth={2} />}
|
|
136
|
+
</button>
|
|
137
|
+
<div className="flex-1" />
|
|
138
|
+
{blocked ? (
|
|
139
|
+
<span className="flex items-center gap-1.5 rounded-full bg-error/15 px-2.5 py-1 text-[11px] font-bold text-error backdrop-blur">
|
|
140
|
+
<Lock size={11} strokeWidth={2.5} /> Blocked
|
|
141
|
+
</span>
|
|
142
|
+
) : showStop ? (
|
|
143
|
+
<button
|
|
144
|
+
className="flex h-7 w-7 items-center justify-center rounded-full border border-error text-error text-[12px] hover:bg-error hover:text-surface transition-colors cursor-pointer"
|
|
145
|
+
title="Stop processing epic"
|
|
146
|
+
onClick={(e) => { e.stopPropagation(); onStop?.(); }}
|
|
147
|
+
>
|
|
148
|
+
{'\u25A0'}
|
|
149
|
+
</button>
|
|
150
|
+
) : showPlay ? (
|
|
151
|
+
<div className="flex items-center">
|
|
152
|
+
<button
|
|
153
|
+
className="flex h-7 w-7 items-center justify-center rounded-full bg-accent/10 text-accent backdrop-blur transition-all hover:bg-accent hover:text-surface hover:shadow-[0_0_12px_-2px_var(--accent)] cursor-pointer"
|
|
154
|
+
title="Start default workflow"
|
|
155
|
+
onClick={(e) => { e.stopPropagation(); onPlay?.(); }}
|
|
156
|
+
>
|
|
157
|
+
<Play size={12} strokeWidth={2.5} fill="currentColor" />
|
|
158
|
+
</button>
|
|
159
|
+
{showPlayDropdown && (
|
|
160
|
+
<button
|
|
161
|
+
className="flex h-7 w-5 items-center justify-center rounded-r-full text-accent/60 hover:text-accent transition-colors cursor-pointer"
|
|
162
|
+
title="Choose workflow"
|
|
163
|
+
onClick={(e) => { e.stopPropagation(); onPlayDropdown?.(); }}
|
|
164
|
+
>
|
|
165
|
+
<ChevronDown size={12} strokeWidth={2.5} />
|
|
166
|
+
</button>
|
|
167
|
+
)}
|
|
168
|
+
</div>
|
|
169
|
+
) : showResume ? (
|
|
170
|
+
<button
|
|
171
|
+
className="flex h-7 w-7 items-center justify-center rounded-full bg-accent/10 text-accent backdrop-blur transition-all hover:bg-accent hover:text-surface hover:shadow-[0_0_12px_-2px_var(--accent)] cursor-pointer"
|
|
172
|
+
title="Resume workflow from last completed step"
|
|
173
|
+
onClick={(e) => { e.stopPropagation(); onResume?.(); }}
|
|
174
|
+
>
|
|
175
|
+
<Play size={12} strokeWidth={2.5} fill="currentColor" />
|
|
176
|
+
</button>
|
|
177
|
+
) : null}
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
{/* Name */}
|
|
181
|
+
<div className="mt-2.5 text-[14px] font-semibold leading-snug text-content">{name}</div>
|
|
182
|
+
|
|
183
|
+
{/* Agent tag — shows when a RunAgent node is executing */}
|
|
184
|
+
{agentName && (
|
|
185
|
+
<div className="mt-2 inline-flex items-center gap-1.5 rounded-full bg-accent/10 px-2.5 py-1 text-[10px] font-semibold text-accent animate-pulse backdrop-blur">
|
|
186
|
+
<CircleDot size={10} strokeWidth={2.5} />
|
|
187
|
+
{agentName}
|
|
188
|
+
</div>
|
|
189
|
+
)}
|
|
190
|
+
|
|
191
|
+
{/* Error tag — shows when workflow failed, clickable to open monitor */}
|
|
192
|
+
{errorMessage && pipeline === 'failed' && (
|
|
193
|
+
<button
|
|
194
|
+
className="mt-2 inline-flex items-center gap-1.5 rounded-full bg-error/10 px-2.5 py-1 text-[10px] font-semibold text-error backdrop-blur cursor-pointer transition-opacity hover:opacity-80"
|
|
195
|
+
title={errorMessage}
|
|
196
|
+
onClick={(e) => { e.stopPropagation(); onMonitorClick?.(); }}
|
|
197
|
+
>
|
|
198
|
+
<AlertTriangle size={10} strokeWidth={2.5} />
|
|
199
|
+
Error
|
|
200
|
+
</button>
|
|
201
|
+
)}
|
|
202
|
+
|
|
203
|
+
{/* Spec disc */}
|
|
204
|
+
<div className="mt-3 flex items-center gap-2">
|
|
205
|
+
<svg width="22" height="22" viewBox="0 0 22 22" className="shrink-0 -rotate-90">
|
|
206
|
+
<circle cx="11" cy="11" r="9" fill="none" stroke="currentColor" strokeWidth="2.5" className="text-white/5" />
|
|
207
|
+
<circle
|
|
208
|
+
cx="11" cy="11" r="9" fill="none" strokeWidth="2.5"
|
|
209
|
+
stroke="url(#specGrad)" strokeLinecap="round"
|
|
210
|
+
strokeDasharray={`${(pct / 100) * 2 * Math.PI * 9} ${2 * Math.PI * 9}`}
|
|
211
|
+
/>
|
|
212
|
+
<defs>
|
|
213
|
+
<linearGradient id="specGrad" x1="0" y1="0" x2="1" y2="1">
|
|
214
|
+
<stop offset="0%" stopColor="var(--completeness-fill)" />
|
|
215
|
+
<stop offset="100%" stopColor="var(--accent)" stopOpacity="0.6" />
|
|
216
|
+
</linearGradient>
|
|
217
|
+
</defs>
|
|
218
|
+
</svg>
|
|
219
|
+
<span className="font-mono text-[11px] text-content-secondary">{pct}%</span>
|
|
220
|
+
</div>
|
|
221
|
+
|
|
222
|
+
{/* Status pills */}
|
|
223
|
+
{hasStatusRow && (
|
|
224
|
+
<div className="mt-3" onClick={(e) => e.stopPropagation()}>
|
|
225
|
+
<div className="flex gap-1">
|
|
226
|
+
{rejectionCount > 0 && (
|
|
227
|
+
<span className="flex-1 rounded-full bg-amber-400/15 py-1 text-center text-[10px] font-semibold text-amber-400 backdrop-blur">
|
|
228
|
+
{rejectionCount}x rejected
|
|
229
|
+
</span>
|
|
230
|
+
)}
|
|
231
|
+
{hasPipeline && (
|
|
232
|
+
<button
|
|
233
|
+
className={`flex-1 inline-flex items-center justify-center gap-1.5 rounded-full py-1 text-[10px] font-semibold backdrop-blur cursor-pointer transition-opacity hover:opacity-80 ${statusCls(pipeline)}`}
|
|
234
|
+
onClick={(e) => { e.stopPropagation(); onMonitorClick?.(); }}
|
|
235
|
+
title="Open workflow monitor"
|
|
236
|
+
>
|
|
237
|
+
{statusIcon(pipeline)}
|
|
238
|
+
{pipLabel}
|
|
239
|
+
</button>
|
|
240
|
+
)}
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
)}
|
|
244
|
+
|
|
245
|
+
{/* Unblock */}
|
|
246
|
+
{blocked && (
|
|
247
|
+
<button
|
|
248
|
+
className="mt-3 w-full rounded-full border border-error/30 py-1.5 text-center text-[11px] font-mono text-error backdrop-blur transition-all hover:bg-error/10 cursor-pointer"
|
|
249
|
+
onClick={(e) => { e.stopPropagation(); onUnblock?.(); }}
|
|
250
|
+
>
|
|
251
|
+
Unblock
|
|
252
|
+
</button>
|
|
253
|
+
)}
|
|
254
|
+
|
|
255
|
+
{/* Issues */}
|
|
256
|
+
<button
|
|
257
|
+
className={[
|
|
258
|
+
'mt-3 w-full rounded-full border py-1.5 text-center text-[11px] font-mono backdrop-blur transition-all cursor-pointer',
|
|
259
|
+
issueCount > 0
|
|
260
|
+
? 'border-white/10 text-content-secondary hover:border-accent/30 hover:text-accent'
|
|
261
|
+
: 'border-white/5 text-content-muted/50 hover:border-white/10 hover:text-content-muted',
|
|
262
|
+
].join(' ')}
|
|
263
|
+
onClick={(e) => { e.stopPropagation(); onIssuesClick?.(); }}
|
|
264
|
+
>
|
|
265
|
+
{issueLabel}
|
|
266
|
+
</button>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
);
|
|
270
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { CardGlass } from './CardGlass';
|
|
2
|
+
import type { DemoCard } from './CardGlass';
|
|
3
|
+
|
|
4
|
+
/* ── Demo data ── */
|
|
5
|
+
|
|
6
|
+
const DEMO_CARDS: DemoCard[] = [
|
|
7
|
+
{
|
|
8
|
+
id: 'feat_0f2d6616', name: 'TailwindCSS Integration', pct: 100, kind: 'new',
|
|
9
|
+
rejections: 1, pipeline: 'interrupted', blocked: false, issues: 1,
|
|
10
|
+
stage: 'in_review',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
id: 'feat_a3c71b02', name: 'Auth Flow Redesign', pct: 72, kind: 'improvement',
|
|
14
|
+
rejections: 0, pipeline: 'active', blocked: false, issues: 0,
|
|
15
|
+
stage: 'in_progress',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: 'feat_9e45f1d8', name: 'WebSocket Events', pct: 45, kind: 'new',
|
|
19
|
+
rejections: 3, pipeline: 'failed', blocked: false, issues: 3,
|
|
20
|
+
stage: 'qa',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: 'feat_b7d20cc4', name: 'Rate Limiter Middleware', pct: 88, kind: 'bugfix',
|
|
24
|
+
rejections: 0, pipeline: 'idle', blocked: true, issues: 0,
|
|
25
|
+
stage: 'in_progress',
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
/* ── Component ── */
|
|
30
|
+
|
|
31
|
+
export const KanbanCardShowcase = () => {
|
|
32
|
+
return (
|
|
33
|
+
<div className="grid grid-cols-2 gap-3 lg:grid-cols-4">
|
|
34
|
+
{DEMO_CARDS.map(c => <CardGlass key={c.id} card={c} />)}
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const Kbd = ({ children }: Props) => (
|
|
8
|
+
<kbd className="inline-block px-1.5 py-px text-[11px] font-inherit bg-surface-raised border border-edge rounded text-content">
|
|
9
|
+
{children}
|
|
10
|
+
</kbd>
|
|
11
|
+
);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/* ── Types ── */
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
kind: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/* ── Component ── */
|
|
8
|
+
|
|
9
|
+
export const KindBadge = ({ kind }: Props) => {
|
|
10
|
+
if (!kind || kind === 'new') return null;
|
|
11
|
+
|
|
12
|
+
const cls = kind === 'improvement'
|
|
13
|
+
? 'text-blue-400 bg-blue-400/15'
|
|
14
|
+
: 'text-amber-400 bg-amber-400/15';
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<span className={`rounded px-2 py-0.5 text-[11px] font-semibold uppercase tracking-wide ${cls}`}>
|
|
18
|
+
{kind}
|
|
19
|
+
</span>
|
|
20
|
+
);
|
|
21
|
+
};
|
package/templates/assistkick-product-system/packages/frontend/src/components/ds/NavBarSidekick.tsx
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Link, useNavigate } from 'react-router-dom';
|
|
3
|
+
import { Search } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
export interface NavItem {
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
icon?: React.ReactNode;
|
|
9
|
+
description?: string;
|
|
10
|
+
href?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface NavBarSidekickProps {
|
|
14
|
+
/** Navigation items shown in the segmented control */
|
|
15
|
+
items: NavItem[];
|
|
16
|
+
/** Currently active item id — controlled externally */
|
|
17
|
+
activeId?: string;
|
|
18
|
+
/** Called when a nav item is selected */
|
|
19
|
+
onNavigate?: (id: string) => void;
|
|
20
|
+
/** Left-side brand / project slot */
|
|
21
|
+
brand?: React.ReactNode;
|
|
22
|
+
/** Content between the segmented control and the right-side actions */
|
|
23
|
+
center?: React.ReactNode;
|
|
24
|
+
/** Right-side actions rendered before the search trigger */
|
|
25
|
+
actions?: React.ReactNode;
|
|
26
|
+
/** Far-right trailing element (avatar, etc.) */
|
|
27
|
+
trailing?: React.ReactNode;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const NavBarSidekick = ({
|
|
31
|
+
items, activeId, onNavigate, brand, center, actions, trailing,
|
|
32
|
+
}: NavBarSidekickProps) => {
|
|
33
|
+
const navigate = useNavigate();
|
|
34
|
+
|
|
35
|
+
// Fallback to internal state when no external activeId is provided (design system demo mode)
|
|
36
|
+
const [internalActive, setInternalActive] = useState(items[0]?.id ?? '');
|
|
37
|
+
const active = activeId ?? internalActive;
|
|
38
|
+
|
|
39
|
+
const [cmdOpen, setCmdOpen] = useState(false);
|
|
40
|
+
const [cmdQuery, setCmdQuery] = useState('');
|
|
41
|
+
|
|
42
|
+
const filteredItems = cmdQuery
|
|
43
|
+
? items.filter((i) =>
|
|
44
|
+
i.label.toLowerCase().includes(cmdQuery.toLowerCase()) ||
|
|
45
|
+
(i.description?.toLowerCase().includes(cmdQuery.toLowerCase()) ?? false))
|
|
46
|
+
: items;
|
|
47
|
+
|
|
48
|
+
const handleSelect = (id: string) => {
|
|
49
|
+
if (onNavigate) {
|
|
50
|
+
onNavigate(id);
|
|
51
|
+
} else {
|
|
52
|
+
setInternalActive(id);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<nav className="bg-surface-alt">
|
|
58
|
+
{/* Top bar */}
|
|
59
|
+
<div className="flex items-center gap-3 px-5 py-2">
|
|
60
|
+
{brand && <div className="mr-1 shrink-0">{brand}</div>}
|
|
61
|
+
|
|
62
|
+
{/* Segmented control */}
|
|
63
|
+
<div className="flex items-center rounded-xl bg-surface p-1 border border-edge">
|
|
64
|
+
{items.map((item) => {
|
|
65
|
+
const isActive = active === item.id;
|
|
66
|
+
const classes = [
|
|
67
|
+
'flex items-center gap-1.5 rounded-lg px-3.5 py-1.5',
|
|
68
|
+
'text-[12px] font-semibold uppercase tracking-wider',
|
|
69
|
+
'transition-all duration-150 cursor-pointer outline-none no-underline',
|
|
70
|
+
isActive
|
|
71
|
+
? 'bg-accent text-surface shadow-sm'
|
|
72
|
+
: 'text-content-muted hover:text-content hover:bg-surface-raised',
|
|
73
|
+
].join(' ');
|
|
74
|
+
const style = isActive ? { boxShadow: '0 2px 8px -2px var(--accent)' } : {};
|
|
75
|
+
const children = (
|
|
76
|
+
<>
|
|
77
|
+
{item.icon && <span className="flex items-center">{item.icon}</span>}
|
|
78
|
+
{item.label}
|
|
79
|
+
</>
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
return item.href ? (
|
|
83
|
+
<Link
|
|
84
|
+
key={item.id}
|
|
85
|
+
to={item.href}
|
|
86
|
+
className={classes}
|
|
87
|
+
style={style}
|
|
88
|
+
>
|
|
89
|
+
{children}
|
|
90
|
+
</Link>
|
|
91
|
+
) : (
|
|
92
|
+
<button
|
|
93
|
+
key={item.id}
|
|
94
|
+
onClick={() => handleSelect(item.id)}
|
|
95
|
+
className={classes}
|
|
96
|
+
style={style}
|
|
97
|
+
>
|
|
98
|
+
{children}
|
|
99
|
+
</button>
|
|
100
|
+
);
|
|
101
|
+
})}
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<div className="flex-1">
|
|
105
|
+
{center}
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
{/* Actions slot */}
|
|
109
|
+
{actions}
|
|
110
|
+
|
|
111
|
+
{/* Command palette trigger */}
|
|
112
|
+
<button
|
|
113
|
+
data-cmd-trigger
|
|
114
|
+
onClick={() => { setCmdOpen(!cmdOpen); setCmdQuery(''); }}
|
|
115
|
+
className={[
|
|
116
|
+
'flex items-center gap-2 rounded-lg border px-3 py-1.5',
|
|
117
|
+
'text-[12px] transition-all duration-150 cursor-pointer outline-none',
|
|
118
|
+
cmdOpen
|
|
119
|
+
? 'border-accent/40 bg-surface text-content'
|
|
120
|
+
: 'border-edge text-content-muted hover:border-content/20 hover:text-content-secondary',
|
|
121
|
+
].join(' ')}
|
|
122
|
+
>
|
|
123
|
+
<Search size={13} strokeWidth={2} />
|
|
124
|
+
<span className="hidden sm:inline">Search</span>
|
|
125
|
+
<kbd className="hidden sm:inline-flex items-center rounded border border-edge bg-surface-raised px-1.5 py-0.5 text-[10px] font-mono text-content-muted">
|
|
126
|
+
/
|
|
127
|
+
</kbd>
|
|
128
|
+
</button>
|
|
129
|
+
|
|
130
|
+
{trailing && <div className="ml-1 shrink-0">{trailing}</div>}
|
|
131
|
+
</div>
|
|
132
|
+
|
|
133
|
+
{/* Command palette dropdown */}
|
|
134
|
+
{cmdOpen && (
|
|
135
|
+
<div className="border-t border-edge bg-surface px-5 py-3">
|
|
136
|
+
<input
|
|
137
|
+
autoFocus
|
|
138
|
+
value={cmdQuery}
|
|
139
|
+
onChange={(e) => setCmdQuery(e.target.value)}
|
|
140
|
+
placeholder="Type to search pages..."
|
|
141
|
+
className="w-full bg-transparent text-[13px] text-content outline-none placeholder:text-content-muted"
|
|
142
|
+
onKeyDown={(e) => {
|
|
143
|
+
if (e.key === 'Escape') { setCmdOpen(false); setCmdQuery(''); }
|
|
144
|
+
if (e.key === 'Enter' && filteredItems.length > 0) {
|
|
145
|
+
const first = filteredItems[0];
|
|
146
|
+
if (first.href) {
|
|
147
|
+
navigate(first.href);
|
|
148
|
+
} else {
|
|
149
|
+
handleSelect(first.id);
|
|
150
|
+
}
|
|
151
|
+
setCmdOpen(false);
|
|
152
|
+
setCmdQuery('');
|
|
153
|
+
}
|
|
154
|
+
}}
|
|
155
|
+
/>
|
|
156
|
+
{cmdQuery && (
|
|
157
|
+
<div className="mt-2 flex flex-col gap-1">
|
|
158
|
+
{filteredItems.length === 0 ? (
|
|
159
|
+
<div className="py-2 text-[12px] text-content-muted">No results</div>
|
|
160
|
+
) : (
|
|
161
|
+
filteredItems.map((item) => {
|
|
162
|
+
const cmdClasses = [
|
|
163
|
+
'flex items-center gap-3 rounded-lg px-3 py-2 text-left no-underline',
|
|
164
|
+
'text-[13px] transition-colors duration-100 cursor-pointer outline-none',
|
|
165
|
+
active === item.id
|
|
166
|
+
? 'bg-accent/10 text-accent'
|
|
167
|
+
: 'text-content-secondary hover:bg-surface-raised hover:text-content',
|
|
168
|
+
].join(' ');
|
|
169
|
+
const cmdChildren = (
|
|
170
|
+
<>
|
|
171
|
+
{item.icon && <span className="flex items-center">{item.icon}</span>}
|
|
172
|
+
<div>
|
|
173
|
+
<div className="font-medium">{item.label}</div>
|
|
174
|
+
{item.description && (
|
|
175
|
+
<div className="text-[11px] text-content-muted">{item.description}</div>
|
|
176
|
+
)}
|
|
177
|
+
</div>
|
|
178
|
+
</>
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
return item.href ? (
|
|
182
|
+
<Link
|
|
183
|
+
key={item.id}
|
|
184
|
+
to={item.href}
|
|
185
|
+
onClick={() => { setCmdOpen(false); setCmdQuery(''); }}
|
|
186
|
+
className={cmdClasses}
|
|
187
|
+
>
|
|
188
|
+
{cmdChildren}
|
|
189
|
+
</Link>
|
|
190
|
+
) : (
|
|
191
|
+
<button
|
|
192
|
+
key={item.id}
|
|
193
|
+
onClick={() => { handleSelect(item.id); setCmdOpen(false); setCmdQuery(''); }}
|
|
194
|
+
className={cmdClasses}
|
|
195
|
+
>
|
|
196
|
+
{cmdChildren}
|
|
197
|
+
</button>
|
|
198
|
+
);
|
|
199
|
+
})
|
|
200
|
+
)}
|
|
201
|
+
</div>
|
|
202
|
+
)}
|
|
203
|
+
</div>
|
|
204
|
+
)}
|
|
205
|
+
</nav>
|
|
206
|
+
);
|
|
207
|
+
};
|