@assistkick/create 1.7.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/create.js +0 -0
- package/package.json +9 -7
- 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 +61 -6
- 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 +158 -0
- package/templates/assistkick-product-system/packages/backend/src/server.ts +60 -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 +43 -77
- 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 +245 -0
- package/templates/assistkick-product-system/packages/frontend/package-lock.json +3455 -0
- package/templates/assistkick-product-system/packages/frontend/package.json +6 -0
- package/templates/assistkick-product-system/packages/frontend/src/App.tsx +8 -0
- package/templates/assistkick-product-system/packages/frontend/src/api/client.ts +458 -18
- 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 +20 -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 +187 -56
- 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/IterationCommentModal.tsx +80 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/KanbanView.tsx +263 -167
- 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 +32 -49
- package/templates/assistkick-product-system/packages/frontend/src/components/SidePanel.tsx +43 -48
- package/templates/assistkick-product-system/packages/frontend/src/components/TerminalView.tsx +121 -52
- package/templates/assistkick-product-system/packages/frontend/src/components/Toolbar.tsx +20 -14
- 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 +103 -87
- package/templates/assistkick-product-system/packages/frontend/src/components/ds/KanbanCardShowcase.tsx +9 -188
- 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 +81 -37
- 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/GenerateTTSNode.tsx +52 -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/RebuildBundleNode.tsx +20 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/RenderVideoNode.tsx +72 -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 +341 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowMonitorModal.tsx +643 -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 +246 -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 +136 -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/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 +1 -1
- package/templates/assistkick-product-system/packages/frontend/src/routes/DashboardLayout.tsx +2 -2
- package/templates/assistkick-product-system/packages/frontend/src/routes/FilesRoute.tsx +13 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/GraphRoute.tsx +2 -2
- 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/useProjectStore.ts +6 -3
- package/templates/assistkick-product-system/packages/frontend/src/stores/useSidePanelStore.ts +4 -4
- package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +275 -3535
- 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 +5 -0
- 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/0014_nifty_punisher.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/0014_snapshot.json +1545 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +77 -0
- package/templates/assistkick-product-system/packages/shared/db/schema.ts +114 -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 +5 -0
- package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.test.ts +1999 -0
- package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.ts +1437 -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 +181 -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-developer/SKILL.md +3 -0
- package/templates/skills/assistkick-developer/references/react_development_guidelines.md +225 -0
- 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/WorkflowsView.tsx
ADDED
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
|
2
|
+
import { apiClient } from '../api/client';
|
|
3
|
+
import { useProjectStore } from '../stores/useProjectStore';
|
|
4
|
+
import { useToast } from '../hooks/useToast';
|
|
5
|
+
import { Button } from './ds/Button';
|
|
6
|
+
import { Plus, Trash2, Save, ChevronDown, Star, HelpCircle } from 'lucide-react';
|
|
7
|
+
import { WorkflowCanvas } from './workflow/WorkflowCanvas';
|
|
8
|
+
import { NodePalette } from './workflow/NodePalette';
|
|
9
|
+
import { SideSheet } from './ds/SideSheet';
|
|
10
|
+
import { Kbd } from './ds/Kbd';
|
|
11
|
+
import type { Node, Edge } from '@xyflow/react';
|
|
12
|
+
|
|
13
|
+
interface WorkflowListItem {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
description: string | null;
|
|
17
|
+
projectId: string | null;
|
|
18
|
+
isDefault: number;
|
|
19
|
+
createdAt: string;
|
|
20
|
+
updatedAt: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface WorkflowFull extends WorkflowListItem {
|
|
24
|
+
graphData: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const INITIAL_START_NODE: Node = {
|
|
28
|
+
id: 'start_1',
|
|
29
|
+
type: 'start',
|
|
30
|
+
position: { x: 250, y: 50 },
|
|
31
|
+
data: {},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export function WorkflowsView() {
|
|
35
|
+
const projects = useProjectStore((s) => s.projects);
|
|
36
|
+
const selectedProjectId = useProjectStore((s) => s.selectedProjectId);
|
|
37
|
+
const { showToast } = useToast();
|
|
38
|
+
|
|
39
|
+
const [scope, setScope] = useState<'global' | 'project'>('global');
|
|
40
|
+
const [projectId, setProjectId] = useState(selectedProjectId || '');
|
|
41
|
+
const [workflows, setWorkflows] = useState<WorkflowListItem[]>([]);
|
|
42
|
+
const [selectedWorkflow, setSelectedWorkflow] = useState<WorkflowFull | null>(null);
|
|
43
|
+
const [editName, setEditName] = useState('');
|
|
44
|
+
const [editDescription, setEditDescription] = useState('');
|
|
45
|
+
const [loading, setLoading] = useState(true);
|
|
46
|
+
const [saving, setSaving] = useState(false);
|
|
47
|
+
const [error, setError] = useState('');
|
|
48
|
+
|
|
49
|
+
// Track current canvas state for saving
|
|
50
|
+
const currentNodesRef = useRef<Node[]>([]);
|
|
51
|
+
const currentEdgesRef = useRef<Edge[]>([]);
|
|
52
|
+
|
|
53
|
+
const [howToOpen, setHowToOpen] = useState(false);
|
|
54
|
+
|
|
55
|
+
// Key to force remount of WorkflowCanvas when switching workflows
|
|
56
|
+
const [canvasKey, setCanvasKey] = useState(0);
|
|
57
|
+
// Key to force re-fetch of palette groups when a new group is saved
|
|
58
|
+
const [paletteKey, setPaletteKey] = useState(0);
|
|
59
|
+
|
|
60
|
+
const fetchWorkflows = useCallback(async () => {
|
|
61
|
+
setLoading(true);
|
|
62
|
+
setError('');
|
|
63
|
+
try {
|
|
64
|
+
const pid = scope === 'project' ? projectId : undefined;
|
|
65
|
+
const resp = await apiClient.fetchWorkflows(pid);
|
|
66
|
+
setWorkflows(resp.workflows);
|
|
67
|
+
} catch (err: any) {
|
|
68
|
+
setError(err.message || 'Failed to load workflows');
|
|
69
|
+
} finally {
|
|
70
|
+
setLoading(false);
|
|
71
|
+
}
|
|
72
|
+
}, [scope, projectId]);
|
|
73
|
+
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
fetchWorkflows();
|
|
76
|
+
}, [fetchWorkflows]);
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (selectedProjectId) setProjectId(selectedProjectId);
|
|
80
|
+
}, [selectedProjectId]);
|
|
81
|
+
|
|
82
|
+
const selectWorkflow = useCallback(async (wf: WorkflowListItem) => {
|
|
83
|
+
setError('');
|
|
84
|
+
try {
|
|
85
|
+
const resp = await apiClient.fetchWorkflow(wf.id);
|
|
86
|
+
const full: WorkflowFull = resp.workflow;
|
|
87
|
+
setSelectedWorkflow(full);
|
|
88
|
+
setEditName(full.name);
|
|
89
|
+
setEditDescription(full.description || '');
|
|
90
|
+
setCanvasKey((k) => k + 1);
|
|
91
|
+
} catch (err: any) {
|
|
92
|
+
setError(err.message || 'Failed to load workflow');
|
|
93
|
+
}
|
|
94
|
+
}, []);
|
|
95
|
+
|
|
96
|
+
const getGraphData = useCallback((): { nodes: Node[]; edges: Edge[] } => {
|
|
97
|
+
if (selectedWorkflow) {
|
|
98
|
+
try {
|
|
99
|
+
return JSON.parse(selectedWorkflow.graphData);
|
|
100
|
+
} catch {
|
|
101
|
+
return { nodes: [INITIAL_START_NODE], edges: [] };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return { nodes: [INITIAL_START_NODE], edges: [] };
|
|
105
|
+
}, [selectedWorkflow]);
|
|
106
|
+
|
|
107
|
+
// Debounced auto-save for graph changes (positions, new nodes, edges, etc.)
|
|
108
|
+
const autoSaveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
109
|
+
const selectedWorkflowRef = useRef(selectedWorkflow);
|
|
110
|
+
selectedWorkflowRef.current = selectedWorkflow;
|
|
111
|
+
|
|
112
|
+
// Clean up auto-save timer on unmount
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
return () => {
|
|
115
|
+
if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current);
|
|
116
|
+
};
|
|
117
|
+
}, []);
|
|
118
|
+
|
|
119
|
+
const autoSaveGraph = useCallback(async (nodes: Node[], edges: Edge[]) => {
|
|
120
|
+
const wf = selectedWorkflowRef.current;
|
|
121
|
+
if (!wf) return;
|
|
122
|
+
try {
|
|
123
|
+
const graphData = JSON.stringify({ nodes, edges });
|
|
124
|
+
await apiClient.updateWorkflow(wf.id, { graphData });
|
|
125
|
+
} catch {
|
|
126
|
+
// Silent fail for auto-save — user can still manually save
|
|
127
|
+
}
|
|
128
|
+
}, []);
|
|
129
|
+
|
|
130
|
+
const handleCanvasChange = useCallback((nodes: Node[], edges: Edge[]) => {
|
|
131
|
+
currentNodesRef.current = nodes;
|
|
132
|
+
currentEdgesRef.current = edges;
|
|
133
|
+
|
|
134
|
+
// Debounce auto-save: 800ms after last change
|
|
135
|
+
if (autoSaveTimerRef.current) clearTimeout(autoSaveTimerRef.current);
|
|
136
|
+
autoSaveTimerRef.current = setTimeout(() => {
|
|
137
|
+
autoSaveGraph(nodes, edges);
|
|
138
|
+
}, 800);
|
|
139
|
+
}, [autoSaveGraph]);
|
|
140
|
+
|
|
141
|
+
const handleCreate = useCallback(async () => {
|
|
142
|
+
try {
|
|
143
|
+
const graphData = JSON.stringify({
|
|
144
|
+
nodes: [INITIAL_START_NODE],
|
|
145
|
+
edges: [],
|
|
146
|
+
});
|
|
147
|
+
const data: any = {
|
|
148
|
+
name: 'New Workflow',
|
|
149
|
+
graphData,
|
|
150
|
+
};
|
|
151
|
+
if (scope === 'project' && projectId) data.projectId = projectId;
|
|
152
|
+
const resp = await apiClient.createWorkflow(data);
|
|
153
|
+
await fetchWorkflows();
|
|
154
|
+
await selectWorkflow(resp.workflow);
|
|
155
|
+
showToast('Workflow created', 'success');
|
|
156
|
+
} catch (err: any) {
|
|
157
|
+
setError(err.message);
|
|
158
|
+
}
|
|
159
|
+
}, [scope, projectId, fetchWorkflows, selectWorkflow, showToast]);
|
|
160
|
+
|
|
161
|
+
const handleSave = useCallback(async () => {
|
|
162
|
+
if (!selectedWorkflow) return;
|
|
163
|
+
setSaving(true);
|
|
164
|
+
setError('');
|
|
165
|
+
try {
|
|
166
|
+
const graphData = JSON.stringify({
|
|
167
|
+
nodes: currentNodesRef.current,
|
|
168
|
+
edges: currentEdgesRef.current,
|
|
169
|
+
});
|
|
170
|
+
const resp = await apiClient.updateWorkflow(selectedWorkflow.id, {
|
|
171
|
+
name: editName.trim(),
|
|
172
|
+
description: editDescription.trim() || undefined,
|
|
173
|
+
graphData,
|
|
174
|
+
});
|
|
175
|
+
setSelectedWorkflow(resp.workflow);
|
|
176
|
+
await fetchWorkflows();
|
|
177
|
+
showToast('Workflow saved', 'success');
|
|
178
|
+
} catch (err: any) {
|
|
179
|
+
setError(err.message);
|
|
180
|
+
} finally {
|
|
181
|
+
setSaving(false);
|
|
182
|
+
}
|
|
183
|
+
}, [selectedWorkflow, editName, editDescription, fetchWorkflows, showToast]);
|
|
184
|
+
|
|
185
|
+
const handleDelete = useCallback(async () => {
|
|
186
|
+
if (!selectedWorkflow) return;
|
|
187
|
+
setError('');
|
|
188
|
+
try {
|
|
189
|
+
await apiClient.deleteWorkflow(selectedWorkflow.id);
|
|
190
|
+
setSelectedWorkflow(null);
|
|
191
|
+
await fetchWorkflows();
|
|
192
|
+
showToast('Workflow deleted', 'success');
|
|
193
|
+
} catch (err: any) {
|
|
194
|
+
setError(err.message);
|
|
195
|
+
}
|
|
196
|
+
}, [selectedWorkflow, fetchWorkflows, showToast]);
|
|
197
|
+
|
|
198
|
+
const handleSetDefault = useCallback(async (wfId: string) => {
|
|
199
|
+
setError('');
|
|
200
|
+
try {
|
|
201
|
+
await apiClient.setDefaultWorkflow(wfId);
|
|
202
|
+
await fetchWorkflows();
|
|
203
|
+
if (selectedWorkflow?.id === wfId) {
|
|
204
|
+
const resp = await apiClient.fetchWorkflow(wfId);
|
|
205
|
+
setSelectedWorkflow(resp.workflow);
|
|
206
|
+
}
|
|
207
|
+
showToast('Default workflow updated', 'success');
|
|
208
|
+
} catch (err: any) {
|
|
209
|
+
setError(err.message);
|
|
210
|
+
}
|
|
211
|
+
}, [selectedWorkflow, fetchWorkflows, showToast]);
|
|
212
|
+
|
|
213
|
+
const handleSaveAsGroup = useCallback(async (
|
|
214
|
+
name: string,
|
|
215
|
+
groupNodes: Node[],
|
|
216
|
+
groupEdges: Edge[],
|
|
217
|
+
inputHandles: string[],
|
|
218
|
+
outputHandles: string[],
|
|
219
|
+
) => {
|
|
220
|
+
try {
|
|
221
|
+
const graphData = JSON.stringify({ nodes: groupNodes, edges: groupEdges, inputHandles, outputHandles });
|
|
222
|
+
const pid = scope === 'project' && projectId ? projectId : undefined;
|
|
223
|
+
await apiClient.createWorkflowGroup({ name, projectId: pid, graphData });
|
|
224
|
+
showToast(`Group "${name}" saved`, 'success');
|
|
225
|
+
// Refresh the palette to show the new group
|
|
226
|
+
setPaletteKey((k) => k + 1);
|
|
227
|
+
} catch (err: any) {
|
|
228
|
+
setError(err.message || 'Failed to save group');
|
|
229
|
+
}
|
|
230
|
+
}, [scope, projectId, showToast]);
|
|
231
|
+
|
|
232
|
+
const graphData = selectedWorkflow ? getGraphData() : null;
|
|
233
|
+
const effectiveProjectId = scope === 'project' ? projectId : undefined;
|
|
234
|
+
|
|
235
|
+
return (
|
|
236
|
+
<div className="flex h-full">
|
|
237
|
+
{/* Left Panel — Workflow List */}
|
|
238
|
+
<div className="w-72 border-r border-edge flex flex-col shrink-0">
|
|
239
|
+
{/* Scope Toggle */}
|
|
240
|
+
<div className="p-3 border-b border-edge space-y-2">
|
|
241
|
+
<div className="flex gap-1 p-0.5 rounded-lg bg-surface-raised">
|
|
242
|
+
<button
|
|
243
|
+
onClick={() => setScope('global')}
|
|
244
|
+
className={`flex-1 px-3 py-1.5 rounded-md text-[12px] font-medium transition-all ${
|
|
245
|
+
scope === 'global'
|
|
246
|
+
? 'bg-accent text-surface shadow-sm'
|
|
247
|
+
: 'text-content-muted hover:text-content'
|
|
248
|
+
}`}
|
|
249
|
+
>
|
|
250
|
+
Global
|
|
251
|
+
</button>
|
|
252
|
+
<button
|
|
253
|
+
onClick={() => setScope('project')}
|
|
254
|
+
className={`flex-1 px-3 py-1.5 rounded-md text-[12px] font-medium transition-all ${
|
|
255
|
+
scope === 'project'
|
|
256
|
+
? 'bg-accent text-surface shadow-sm'
|
|
257
|
+
: 'text-content-muted hover:text-content'
|
|
258
|
+
}`}
|
|
259
|
+
>
|
|
260
|
+
Project
|
|
261
|
+
</button>
|
|
262
|
+
</div>
|
|
263
|
+
|
|
264
|
+
{scope === 'project' && (
|
|
265
|
+
<div className="relative">
|
|
266
|
+
<select
|
|
267
|
+
value={projectId}
|
|
268
|
+
onChange={(e) => setProjectId(e.target.value)}
|
|
269
|
+
className="w-full px-3 py-1.5 rounded-md border border-edge bg-surface text-[12px] text-content appearance-none pr-7"
|
|
270
|
+
>
|
|
271
|
+
{projects.map(p => (
|
|
272
|
+
<option key={p.id} value={p.id}>{p.name}</option>
|
|
273
|
+
))}
|
|
274
|
+
</select>
|
|
275
|
+
<ChevronDown size={12} className="absolute right-2 top-1/2 -translate-y-1/2 text-content-muted pointer-events-none" />
|
|
276
|
+
</div>
|
|
277
|
+
)}
|
|
278
|
+
</div>
|
|
279
|
+
|
|
280
|
+
{/* Workflow List */}
|
|
281
|
+
<div className="flex-1 overflow-y-auto">
|
|
282
|
+
{loading ? (
|
|
283
|
+
<div className="p-4 text-center text-[12px] text-content-muted">Loading...</div>
|
|
284
|
+
) : workflows.length === 0 ? (
|
|
285
|
+
<div className="p-4 text-center text-[12px] text-content-muted">No workflows found</div>
|
|
286
|
+
) : (
|
|
287
|
+
workflows.map(wf => {
|
|
288
|
+
const isSelected = selectedWorkflow?.id === wf.id;
|
|
289
|
+
return (
|
|
290
|
+
<button
|
|
291
|
+
key={wf.id}
|
|
292
|
+
onClick={() => selectWorkflow(wf)}
|
|
293
|
+
className={`w-full text-left px-3 py-2.5 border-b border-edge transition-all ${
|
|
294
|
+
isSelected
|
|
295
|
+
? 'bg-accent/10 border-l-2 border-l-accent'
|
|
296
|
+
: 'hover:bg-surface-raised border-l-2 border-l-transparent'
|
|
297
|
+
}`}
|
|
298
|
+
>
|
|
299
|
+
<div className="flex items-center gap-2">
|
|
300
|
+
<span className="text-[13px] font-medium text-content truncate">{wf.name}</span>
|
|
301
|
+
{wf.isDefault === 1 && (
|
|
302
|
+
<span className="shrink-0 px-1.5 py-0.5 rounded text-[9px] font-semibold uppercase bg-accent/10 text-accent">Default</span>
|
|
303
|
+
)}
|
|
304
|
+
</div>
|
|
305
|
+
{wf.description && (
|
|
306
|
+
<div className="text-[11px] text-content-muted mt-0.5 truncate">{wf.description}</div>
|
|
307
|
+
)}
|
|
308
|
+
<div className="flex items-center gap-2 mt-1">
|
|
309
|
+
{wf.projectId ? (
|
|
310
|
+
<span className="text-[9px] text-content-muted">Project</span>
|
|
311
|
+
) : (
|
|
312
|
+
<span className="text-[9px] text-content-muted">Global</span>
|
|
313
|
+
)}
|
|
314
|
+
{wf.isDefault !== 1 && (
|
|
315
|
+
<button
|
|
316
|
+
onClick={(e) => { e.stopPropagation(); handleSetDefault(wf.id); }}
|
|
317
|
+
className="text-[9px] text-content-muted hover:text-accent transition-colors"
|
|
318
|
+
title="Set as default"
|
|
319
|
+
>
|
|
320
|
+
<Star size={10} />
|
|
321
|
+
</button>
|
|
322
|
+
)}
|
|
323
|
+
</div>
|
|
324
|
+
</button>
|
|
325
|
+
);
|
|
326
|
+
})
|
|
327
|
+
)}
|
|
328
|
+
</div>
|
|
329
|
+
|
|
330
|
+
{/* Create Workflow Button */}
|
|
331
|
+
<div className="p-3 border-t border-edge">
|
|
332
|
+
<Button variant="primary" size="sm" icon={<Plus size={12} />} onClick={handleCreate} className="w-full justify-center">
|
|
333
|
+
Create Workflow
|
|
334
|
+
</Button>
|
|
335
|
+
</div>
|
|
336
|
+
</div>
|
|
337
|
+
|
|
338
|
+
{/* Right Panel — Workflow Editor */}
|
|
339
|
+
<div className="flex-1 flex flex-col overflow-hidden">
|
|
340
|
+
{error && (
|
|
341
|
+
<div className="mx-4 mt-3 px-3 py-2 rounded-md bg-error/10 border border-error/20 text-error text-[12px] flex items-center justify-between">
|
|
342
|
+
<span>{error}</span>
|
|
343
|
+
<button onClick={() => setError('')} className="text-error hover:text-error/80">×</button>
|
|
344
|
+
</div>
|
|
345
|
+
)}
|
|
346
|
+
|
|
347
|
+
{!selectedWorkflow ? (
|
|
348
|
+
<div className="flex-1 flex items-center justify-center text-content-muted text-[13px]">
|
|
349
|
+
Select a workflow to edit or create a new one
|
|
350
|
+
</div>
|
|
351
|
+
) : (
|
|
352
|
+
<>
|
|
353
|
+
{/* Header */}
|
|
354
|
+
<div className="p-3 border-b border-edge flex items-center justify-between gap-3">
|
|
355
|
+
<div className="flex-1 flex items-center gap-2">
|
|
356
|
+
<input
|
|
357
|
+
value={editName}
|
|
358
|
+
onChange={e => setEditName(e.target.value)}
|
|
359
|
+
className="flex-1 px-3 py-1.5 rounded-md border border-edge bg-surface text-[14px] font-medium text-content"
|
|
360
|
+
placeholder="Workflow name"
|
|
361
|
+
/>
|
|
362
|
+
<input
|
|
363
|
+
value={editDescription}
|
|
364
|
+
onChange={e => setEditDescription(e.target.value)}
|
|
365
|
+
className="flex-1 px-3 py-1.5 rounded-md border border-edge bg-surface text-[12px] text-content"
|
|
366
|
+
placeholder="Description (optional)"
|
|
367
|
+
/>
|
|
368
|
+
</div>
|
|
369
|
+
<div className="flex items-center gap-2">
|
|
370
|
+
{selectedWorkflow.isDefault !== 1 && (
|
|
371
|
+
<Button variant="ghost" size="sm" icon={<Star size={12} />} onClick={() => handleSetDefault(selectedWorkflow.id)}>
|
|
372
|
+
Set Default
|
|
373
|
+
</Button>
|
|
374
|
+
)}
|
|
375
|
+
{selectedWorkflow.isDefault !== 1 && (
|
|
376
|
+
<Button variant="danger" size="sm" icon={<Trash2 size={12} />} onClick={handleDelete}>
|
|
377
|
+
Delete
|
|
378
|
+
</Button>
|
|
379
|
+
)}
|
|
380
|
+
<Button variant="primary" size="sm" icon={<Save size={12} />} onClick={handleSave} disabled={saving}>
|
|
381
|
+
{saving ? 'Saving...' : 'Save'}
|
|
382
|
+
</Button>
|
|
383
|
+
</div>
|
|
384
|
+
</div>
|
|
385
|
+
|
|
386
|
+
{/* Canvas + Palette */}
|
|
387
|
+
<div className="flex-1 flex overflow-hidden">
|
|
388
|
+
{/* Node Palette Sidebar */}
|
|
389
|
+
<div className="w-52 border-r border-edge p-3 overflow-y-auto shrink-0">
|
|
390
|
+
<button
|
|
391
|
+
onClick={() => setHowToOpen(true)}
|
|
392
|
+
className="flex items-center gap-1.5 w-full px-2.5 py-2 mb-3 rounded-md border border-accent/30 bg-accent/5 hover:bg-accent/10 text-[12px] text-accent font-medium transition-colors"
|
|
393
|
+
>
|
|
394
|
+
<HelpCircle size={14} />
|
|
395
|
+
How To Use
|
|
396
|
+
</button>
|
|
397
|
+
<NodePalette key={paletteKey} projectId={effectiveProjectId} />
|
|
398
|
+
</div>
|
|
399
|
+
|
|
400
|
+
{/* Canvas */}
|
|
401
|
+
{graphData && (
|
|
402
|
+
<WorkflowCanvas
|
|
403
|
+
key={canvasKey}
|
|
404
|
+
initialNodes={graphData.nodes}
|
|
405
|
+
initialEdges={graphData.edges}
|
|
406
|
+
onChange={handleCanvasChange}
|
|
407
|
+
onSaveAsGroup={handleSaveAsGroup}
|
|
408
|
+
/>
|
|
409
|
+
)}
|
|
410
|
+
</div>
|
|
411
|
+
</>
|
|
412
|
+
)}
|
|
413
|
+
</div>
|
|
414
|
+
|
|
415
|
+
<SideSheet isOpen={howToOpen} onClose={() => setHowToOpen(false)} title="Workflow Editor — How To">
|
|
416
|
+
<div className="space-y-4">
|
|
417
|
+
<section>
|
|
418
|
+
<h3 className="text-[13px] font-semibold text-content mb-1">Adding Nodes</h3>
|
|
419
|
+
<p className="text-[12px] text-content-secondary">Drag any node from the <strong className="text-content">Node Palette</strong> on the left and drop it onto the canvas.</p>
|
|
420
|
+
</section>
|
|
421
|
+
|
|
422
|
+
<section>
|
|
423
|
+
<h3 className="text-[13px] font-semibold text-content mb-1">Connecting Nodes</h3>
|
|
424
|
+
<p className="text-[12px] text-content-secondary">Hover over a node to see its connection handles (small dots on the edges). Click and drag from one handle to another to create a connection.</p>
|
|
425
|
+
</section>
|
|
426
|
+
|
|
427
|
+
<section>
|
|
428
|
+
<h3 className="text-[13px] font-semibold text-content mb-1">Removing a Connection</h3>
|
|
429
|
+
<p className="text-[12px] text-content-secondary">Click on a connection line to select it, then press <Kbd>Backspace</Kbd> or <Kbd>Delete</Kbd> to remove it.</p>
|
|
430
|
+
</section>
|
|
431
|
+
|
|
432
|
+
<section>
|
|
433
|
+
<h3 className="text-[13px] font-semibold text-content mb-1">Deleting Nodes</h3>
|
|
434
|
+
<p className="text-[12px] text-content-secondary">Click on a node to select it, then press <Kbd>Backspace</Kbd> or <Kbd>Delete</Kbd>.</p>
|
|
435
|
+
</section>
|
|
436
|
+
|
|
437
|
+
<section>
|
|
438
|
+
<h3 className="text-[13px] font-semibold text-content mb-1">Multi-Select</h3>
|
|
439
|
+
<p className="text-[12px] text-content-secondary">Hold <Kbd>Shift</Kbd> and click multiple nodes to select them together. You can also click and drag on the canvas background to create a selection box.</p>
|
|
440
|
+
</section>
|
|
441
|
+
|
|
442
|
+
<section>
|
|
443
|
+
<h3 className="text-[13px] font-semibold text-content mb-1">Saving as Reusable Group</h3>
|
|
444
|
+
<p className="text-[12px] text-content-secondary">Select 2 or more nodes, then <strong className="text-content">right-click</strong> on one of them and choose “Save as Reusable Group”. The group will appear in the palette under <strong className="text-content">Groups</strong> for reuse.</p>
|
|
445
|
+
</section>
|
|
446
|
+
|
|
447
|
+
<section>
|
|
448
|
+
<h3 className="text-[13px] font-semibold text-content mb-1">Auto Layout</h3>
|
|
449
|
+
<p className="text-[12px] text-content-secondary">Click the <strong className="text-content">Auto Layout</strong> button in the top-right corner of the canvas to automatically arrange nodes in a tree layout.</p>
|
|
450
|
+
</section>
|
|
451
|
+
|
|
452
|
+
<section>
|
|
453
|
+
<h3 className="text-[13px] font-semibold text-content mb-1">Moving Nodes</h3>
|
|
454
|
+
<p className="text-[12px] text-content-secondary">Click and drag any node to reposition it. The canvas uses a 16px snap grid for alignment.</p>
|
|
455
|
+
</section>
|
|
456
|
+
|
|
457
|
+
<section>
|
|
458
|
+
<h3 className="text-[13px] font-semibold text-content mb-1">Zooming & Panning</h3>
|
|
459
|
+
<ul className="pl-4 mt-1 space-y-0.5 text-[12px] text-content-secondary list-disc">
|
|
460
|
+
<li>Scroll to zoom in/out</li>
|
|
461
|
+
<li>Click and drag on the canvas background to pan</li>
|
|
462
|
+
<li>Use the controls in the bottom-left corner for zoom and fit-to-view</li>
|
|
463
|
+
</ul>
|
|
464
|
+
</section>
|
|
465
|
+
|
|
466
|
+
<section>
|
|
467
|
+
<h3 className="text-[13px] font-semibold text-content mb-1">Auto-Save</h3>
|
|
468
|
+
<p className="text-[12px] text-content-secondary">All changes to the canvas (node positions, connections, new nodes) are auto-saved after 800ms of inactivity. Use the <strong className="text-content">Save</strong> button in the header to also save name and description changes.</p>
|
|
469
|
+
</section>
|
|
470
|
+
</div>
|
|
471
|
+
</SideSheet>
|
|
472
|
+
</div>
|
|
473
|
+
);
|
|
474
|
+
}
|
package/templates/assistkick-product-system/packages/frontend/src/components/ds/AccentBorderList.tsx
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/* ── Border color presets ── */
|
|
4
|
+
|
|
5
|
+
const BORDER_COLORS = {
|
|
6
|
+
accent: 'border-accent/30',
|
|
7
|
+
secondary: 'border-accent-secondary/40',
|
|
8
|
+
muted: 'border-content-muted/30',
|
|
9
|
+
error: 'border-error/30',
|
|
10
|
+
} as const;
|
|
11
|
+
|
|
12
|
+
const LABEL_COLORS = {
|
|
13
|
+
accent: 'text-accent',
|
|
14
|
+
secondary: 'text-accent-secondary',
|
|
15
|
+
muted: 'text-content-muted',
|
|
16
|
+
error: 'text-error',
|
|
17
|
+
} as const;
|
|
18
|
+
|
|
19
|
+
export type AccentBorderColor = keyof typeof BORDER_COLORS;
|
|
20
|
+
|
|
21
|
+
/* ── List container ── */
|
|
22
|
+
|
|
23
|
+
export interface AccentBorderListProps {
|
|
24
|
+
children: React.ReactNode;
|
|
25
|
+
gap?: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function AccentBorderList({ children, gap = 4 }: AccentBorderListProps) {
|
|
29
|
+
return <div className={`space-y-${gap}`}>{children}</div>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* ── Individual item ── */
|
|
33
|
+
|
|
34
|
+
export interface AccentBorderItemProps {
|
|
35
|
+
/** Left border color preset */
|
|
36
|
+
color?: AccentBorderColor;
|
|
37
|
+
/** Optional number/label shown before the title */
|
|
38
|
+
index?: string | number;
|
|
39
|
+
children: React.ReactNode;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function AccentBorderItem({ color = 'accent', index, children }: AccentBorderItemProps) {
|
|
43
|
+
return (
|
|
44
|
+
<div className={`border-l-2 ${BORDER_COLORS[color]} pl-3`}>
|
|
45
|
+
{index != null && (
|
|
46
|
+
<span className={`text-[10px] font-mono font-bold ${LABEL_COLORS[color]} mr-1.5`}>
|
|
47
|
+
{typeof index === 'number' ? `${index}.` : index}
|
|
48
|
+
</span>
|
|
49
|
+
)}
|
|
50
|
+
{children}
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/* ── Types ── */
|
|
4
|
+
|
|
5
|
+
type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger';
|
|
6
|
+
type ButtonSize = 'sm' | 'md' | 'lg';
|
|
7
|
+
|
|
8
|
+
interface Props extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
9
|
+
variant?: ButtonVariant;
|
|
10
|
+
size?: ButtonSize;
|
|
11
|
+
icon?: React.ReactNode;
|
|
12
|
+
iconPosition?: 'left' | 'right';
|
|
13
|
+
children: React.ReactNode;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/* ── Style maps ── */
|
|
17
|
+
|
|
18
|
+
const variantClasses: Record<ButtonVariant, string> = {
|
|
19
|
+
primary: [
|
|
20
|
+
'bg-accent text-surface font-semibold',
|
|
21
|
+
'hover:brightness-110',
|
|
22
|
+
].join(' '),
|
|
23
|
+
secondary: [
|
|
24
|
+
'border border-accent bg-transparent text-accent font-semibold',
|
|
25
|
+
'hover:bg-accent hover:text-surface',
|
|
26
|
+
].join(' '),
|
|
27
|
+
ghost: [
|
|
28
|
+
'border border-edge bg-transparent text-content-secondary',
|
|
29
|
+
'hover:border-content/20 hover:text-content',
|
|
30
|
+
].join(' '),
|
|
31
|
+
danger: [
|
|
32
|
+
'border border-dashed border-error bg-transparent text-error font-semibold',
|
|
33
|
+
'hover:bg-error hover:text-white',
|
|
34
|
+
].join(' '),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const sizeClasses: Record<ButtonSize, string> = {
|
|
38
|
+
sm: 'gap-1.5 rounded-md px-3 py-1 text-[11px]',
|
|
39
|
+
md: 'gap-2 rounded-lg px-4 py-2 text-[13px]',
|
|
40
|
+
lg: 'gap-2 rounded-xl px-6 py-3 text-[15px]',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const glowShadow: Record<ButtonSize, string> = {
|
|
44
|
+
sm: '0 1px 4px -1px var(--accent)',
|
|
45
|
+
md: '0 2px 8px -2px var(--accent)',
|
|
46
|
+
lg: '0 3px 12px -3px var(--accent)',
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/* ── Component ── */
|
|
50
|
+
|
|
51
|
+
export const Button = ({
|
|
52
|
+
variant = 'primary',
|
|
53
|
+
size = 'md',
|
|
54
|
+
icon,
|
|
55
|
+
iconPosition = 'left',
|
|
56
|
+
disabled,
|
|
57
|
+
className = '',
|
|
58
|
+
style,
|
|
59
|
+
children,
|
|
60
|
+
...rest
|
|
61
|
+
}: Props) => {
|
|
62
|
+
const base = 'inline-flex items-center outline-none cursor-pointer transition-all duration-150';
|
|
63
|
+
const disabledClass = disabled ? 'opacity-50 cursor-not-allowed' : '';
|
|
64
|
+
|
|
65
|
+
const shadow = variant === 'primary' && !disabled
|
|
66
|
+
? { boxShadow: glowShadow[size], ...style }
|
|
67
|
+
: style;
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<button
|
|
71
|
+
disabled={disabled}
|
|
72
|
+
className={[
|
|
73
|
+
base,
|
|
74
|
+
variantClasses[variant],
|
|
75
|
+
sizeClasses[size],
|
|
76
|
+
disabledClass,
|
|
77
|
+
className,
|
|
78
|
+
].join(' ')}
|
|
79
|
+
style={shadow}
|
|
80
|
+
{...rest}
|
|
81
|
+
>
|
|
82
|
+
{icon && iconPosition === 'left' && icon}
|
|
83
|
+
{children}
|
|
84
|
+
{icon && iconPosition === 'right' && icon}
|
|
85
|
+
</button>
|
|
86
|
+
);
|
|
87
|
+
};
|
package/templates/assistkick-product-system/packages/frontend/src/components/ds/ButtonGroup.tsx
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/* ── Types ── */
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/* ── Component ── */
|
|
11
|
+
|
|
12
|
+
export const ButtonGroup = ({ children, className = '' }: Props) => {
|
|
13
|
+
return (
|
|
14
|
+
<div
|
|
15
|
+
role="group"
|
|
16
|
+
className={[
|
|
17
|
+
'inline-flex overflow-hidden rounded-lg border border-edge',
|
|
18
|
+
'[&>button]:outline-none [&>button]:cursor-pointer',
|
|
19
|
+
'[&>button]:px-3 [&>button]:py-2 [&>button]:text-[12px]',
|
|
20
|
+
'[&>button]:text-content-secondary [&>button]:transition-colors [&>button]:duration-150',
|
|
21
|
+
'[&>button:hover]:bg-surface-raised [&>button:hover]:text-content',
|
|
22
|
+
'[&>button:not(:last-child)]:border-r [&>button:not(:last-child)]:border-edge',
|
|
23
|
+
className,
|
|
24
|
+
].join(' ')}
|
|
25
|
+
>
|
|
26
|
+
{children}
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
};
|