@atercates/claude-deck 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (293) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +123 -0
  3. package/app/api/claude/hidden/route.ts +66 -0
  4. package/app/api/claude/projects/[name]/sessions/route.ts +71 -0
  5. package/app/api/claude/projects/route.ts +44 -0
  6. package/app/api/code-search/available/route.ts +12 -0
  7. package/app/api/code-search/route.ts +47 -0
  8. package/app/api/dev-servers/[id]/logs/route.ts +23 -0
  9. package/app/api/dev-servers/[id]/restart/route.ts +20 -0
  10. package/app/api/dev-servers/[id]/route.ts +51 -0
  11. package/app/api/dev-servers/[id]/stop/route.ts +20 -0
  12. package/app/api/dev-servers/detect/route.ts +39 -0
  13. package/app/api/dev-servers/route.ts +48 -0
  14. package/app/api/exec/route.ts +60 -0
  15. package/app/api/files/content/route.ts +76 -0
  16. package/app/api/files/route.ts +37 -0
  17. package/app/api/files/upload-temp/route.ts +41 -0
  18. package/app/api/git/check/route.ts +54 -0
  19. package/app/api/git/clone/route.ts +99 -0
  20. package/app/api/git/commit/route.ts +75 -0
  21. package/app/api/git/discard/route.ts +38 -0
  22. package/app/api/git/file-content/route.ts +64 -0
  23. package/app/api/git/history/[hash]/diff/route.ts +38 -0
  24. package/app/api/git/history/[hash]/route.ts +34 -0
  25. package/app/api/git/history/route.ts +27 -0
  26. package/app/api/git/multi-status/route.ts +46 -0
  27. package/app/api/git/pr/route.ts +164 -0
  28. package/app/api/git/push/route.ts +64 -0
  29. package/app/api/git/stage/route.ts +40 -0
  30. package/app/api/git/status/route.ts +51 -0
  31. package/app/api/git/unstage/route.ts +46 -0
  32. package/app/api/groups/[...path]/route.ts +136 -0
  33. package/app/api/groups/route.ts +93 -0
  34. package/app/api/orchestrate/spawn/route.ts +45 -0
  35. package/app/api/orchestrate/workers/[id]/route.ts +89 -0
  36. package/app/api/orchestrate/workers/route.ts +31 -0
  37. package/app/api/projects/[id]/detect/route.ts +27 -0
  38. package/app/api/projects/[id]/dev-servers/[dsId]/route.ts +66 -0
  39. package/app/api/projects/[id]/dev-servers/route.ts +51 -0
  40. package/app/api/projects/[id]/repositories/[repoId]/route.ts +67 -0
  41. package/app/api/projects/[id]/repositories/route.ts +74 -0
  42. package/app/api/projects/[id]/route.ts +108 -0
  43. package/app/api/projects/detect/route.ts +33 -0
  44. package/app/api/projects/route.ts +59 -0
  45. package/app/api/sessions/[id]/claude-session/route.ts +42 -0
  46. package/app/api/sessions/[id]/fork/route.ts +74 -0
  47. package/app/api/sessions/[id]/mcp-config/route.ts +34 -0
  48. package/app/api/sessions/[id]/messages/route.ts +60 -0
  49. package/app/api/sessions/[id]/pr/route.ts +188 -0
  50. package/app/api/sessions/[id]/preview/route.ts +42 -0
  51. package/app/api/sessions/[id]/route.ts +229 -0
  52. package/app/api/sessions/[id]/send-keys/route.ts +119 -0
  53. package/app/api/sessions/[id]/summarize/route.ts +331 -0
  54. package/app/api/sessions/init-script/route.ts +84 -0
  55. package/app/api/sessions/route.ts +209 -0
  56. package/app/api/sessions/status/route.ts +237 -0
  57. package/app/api/system/route.ts +9 -0
  58. package/app/api/tmux/kill-all/route.ts +57 -0
  59. package/app/api/tmux/rename/route.ts +30 -0
  60. package/app/globals.css +174 -0
  61. package/app/icon.svg +11 -0
  62. package/app/layout.tsx +122 -0
  63. package/app/page.tsx +629 -0
  64. package/components/ChatMessage.tsx +65 -0
  65. package/components/ChatView.tsx +276 -0
  66. package/components/ClaudeProjects/ClaudeProjectCard.tsx +195 -0
  67. package/components/ClaudeProjects/ClaudeProjectsSection.tsx +89 -0
  68. package/components/ClaudeProjects/ClaudeSessionCard.tsx +100 -0
  69. package/components/ClaudeProjects/index.ts +1 -0
  70. package/components/CodeSearch/CodeSearchResults.tsx +177 -0
  71. package/components/ConductorPanel.tsx +256 -0
  72. package/components/DevServers/DevServerCard.tsx +311 -0
  73. package/components/DevServers/DevServersSection.tsx +91 -0
  74. package/components/DevServers/ServerLogsModal.tsx +151 -0
  75. package/components/DevServers/StartServerDialog.tsx +359 -0
  76. package/components/DevServers/index.ts +4 -0
  77. package/components/DiffViewer/DiffModal.tsx +151 -0
  78. package/components/DiffViewer/UnifiedDiff.tsx +185 -0
  79. package/components/DiffViewer/index.tsx +2 -0
  80. package/components/DirectoryPicker.tsx +355 -0
  81. package/components/FileExplorer/FileEditor.tsx +276 -0
  82. package/components/FileExplorer/FileTabs.tsx +118 -0
  83. package/components/FileExplorer/FileTree.tsx +214 -0
  84. package/components/FileExplorer/HtmlRenderer.tsx +16 -0
  85. package/components/FileExplorer/MarkdownRenderer.tsx +18 -0
  86. package/components/FileExplorer/index.tsx +520 -0
  87. package/components/FilePicker.tsx +339 -0
  88. package/components/FolderPicker.tsx +201 -0
  89. package/components/GitDrawer/FileEditDialog.tsx +400 -0
  90. package/components/GitDrawer/index.tsx +464 -0
  91. package/components/GitPanel/CommitForm.tsx +205 -0
  92. package/components/GitPanel/CommitHistory.tsx +174 -0
  93. package/components/GitPanel/CommitItem.tsx +196 -0
  94. package/components/GitPanel/FileChanges.tsx +414 -0
  95. package/components/GitPanel/GitPanelTabs.tsx +39 -0
  96. package/components/GitPanel/index.tsx +817 -0
  97. package/components/MessageInput.tsx +82 -0
  98. package/components/NewClaudeSessionDialog.tsx +166 -0
  99. package/components/NewSessionDialog/AdvancedSettings.tsx +78 -0
  100. package/components/NewSessionDialog/AgentSelector.tsx +37 -0
  101. package/components/NewSessionDialog/CreatingOverlay.tsx +94 -0
  102. package/components/NewSessionDialog/NewSessionDialog.types.ts +136 -0
  103. package/components/NewSessionDialog/ProjectSelector.tsx +146 -0
  104. package/components/NewSessionDialog/WorkingDirectoryInput.tsx +55 -0
  105. package/components/NewSessionDialog/WorktreeSection.tsx +92 -0
  106. package/components/NewSessionDialog/hooks/useNewSessionForm.ts +370 -0
  107. package/components/NewSessionDialog/index.tsx +106 -0
  108. package/components/NotificationSettings.tsx +127 -0
  109. package/components/PRCreationModal.tsx +272 -0
  110. package/components/Pane/DesktopTabBar.tsx +353 -0
  111. package/components/Pane/MobileTabBar.tsx +210 -0
  112. package/components/Pane/OpenInVSCode.tsx +69 -0
  113. package/components/Pane/PaneSkeletons.tsx +57 -0
  114. package/components/Pane/index.tsx +558 -0
  115. package/components/PaneLayout.tsx +60 -0
  116. package/components/Projects/DevServersSection.tsx +140 -0
  117. package/components/Projects/DirectoryField.tsx +92 -0
  118. package/components/Projects/NewProjectDialog.tsx +188 -0
  119. package/components/Projects/NewProjectDialog.types.ts +46 -0
  120. package/components/Projects/ProjectCard.tsx +276 -0
  121. package/components/Projects/ProjectSettingsDialog.tsx +811 -0
  122. package/components/Projects/hooks/useNewProjectForm.ts +249 -0
  123. package/components/Projects/index.ts +3 -0
  124. package/components/Providers.tsx +49 -0
  125. package/components/QuickSwitcher.tsx +306 -0
  126. package/components/SessionList/KillAllConfirm.tsx +46 -0
  127. package/components/SessionList/SelectionToolbar.tsx +164 -0
  128. package/components/SessionList/SessionList.types.ts +37 -0
  129. package/components/SessionList/SessionListHeader.tsx +71 -0
  130. package/components/SessionList/hooks/useSessionListMutations.ts +269 -0
  131. package/components/SessionList/index.tsx +189 -0
  132. package/components/ShellDrawer/index.tsx +106 -0
  133. package/components/SidebarFooter.tsx +55 -0
  134. package/components/Terminal/KeybarToggleButton.tsx +45 -0
  135. package/components/Terminal/ScrollToBottomButton.tsx +32 -0
  136. package/components/Terminal/SearchBar.tsx +71 -0
  137. package/components/Terminal/TerminalToolbar.tsx +551 -0
  138. package/components/Terminal/VirtualKeyboard.tsx +711 -0
  139. package/components/Terminal/constants.ts +20 -0
  140. package/components/Terminal/hooks/index.ts +5 -0
  141. package/components/Terminal/hooks/resize-handlers.ts +140 -0
  142. package/components/Terminal/hooks/terminal-init.ts +151 -0
  143. package/components/Terminal/hooks/touch-scroll.ts +155 -0
  144. package/components/Terminal/hooks/useTerminalConnection.ts +282 -0
  145. package/components/Terminal/hooks/useTerminalConnection.types.ts +39 -0
  146. package/components/Terminal/hooks/useTerminalSearch.ts +103 -0
  147. package/components/Terminal/hooks/websocket-connection.ts +274 -0
  148. package/components/Terminal/index.tsx +320 -0
  149. package/components/ThemeToggle.tsx +168 -0
  150. package/components/TmuxSessions.tsx +132 -0
  151. package/components/ToolCallDisplay.tsx +71 -0
  152. package/components/WorkerCard.tsx +245 -0
  153. package/components/a/ABadge.tsx +115 -0
  154. package/components/a/AButton.tsx +163 -0
  155. package/components/a/ADialog.tsx +93 -0
  156. package/components/a/ADropdownMenu.tsx +279 -0
  157. package/components/a/AIconButton.tsx +190 -0
  158. package/components/a/ASheet.tsx +150 -0
  159. package/components/a/ATooltip.tsx +77 -0
  160. package/components/a/index.ts +64 -0
  161. package/components/mobile/SwipeSidebar.tsx +122 -0
  162. package/components/ui/badge.tsx +41 -0
  163. package/components/ui/button.tsx +60 -0
  164. package/components/ui/context-menu.tsx +197 -0
  165. package/components/ui/dialog.tsx +143 -0
  166. package/components/ui/dropdown-menu.tsx +257 -0
  167. package/components/ui/input.tsx +21 -0
  168. package/components/ui/scroll-area.tsx +52 -0
  169. package/components/ui/select.tsx +159 -0
  170. package/components/ui/skeleton.tsx +111 -0
  171. package/components/ui/switch.tsx +31 -0
  172. package/components/ui/textarea.tsx +21 -0
  173. package/components/ui/tooltip.tsx +32 -0
  174. package/components/views/DesktopView.tsx +244 -0
  175. package/components/views/MobileView.tsx +110 -0
  176. package/components/views/types.ts +75 -0
  177. package/contexts/PaneContext.tsx +336 -0
  178. package/data/claude/index.ts +9 -0
  179. package/data/claude/keys.ts +6 -0
  180. package/data/claude/queries.ts +120 -0
  181. package/data/claude/useClaudeUpdates.ts +37 -0
  182. package/data/code-search/index.ts +2 -0
  183. package/data/code-search/keys.ts +7 -0
  184. package/data/code-search/queries.ts +61 -0
  185. package/data/dev-servers/index.ts +8 -0
  186. package/data/dev-servers/keys.ts +4 -0
  187. package/data/dev-servers/queries.ts +104 -0
  188. package/data/files/index.ts +3 -0
  189. package/data/files/keys.ts +4 -0
  190. package/data/files/queries.ts +25 -0
  191. package/data/git/keys.ts +15 -0
  192. package/data/git/queries.ts +395 -0
  193. package/data/groups/index.ts +1 -0
  194. package/data/groups/mutations.ts +95 -0
  195. package/data/projects/index.ts +10 -0
  196. package/data/projects/keys.ts +4 -0
  197. package/data/projects/queries.ts +193 -0
  198. package/data/repositories/index.ts +7 -0
  199. package/data/repositories/keys.ts +5 -0
  200. package/data/repositories/queries.ts +122 -0
  201. package/data/sessions/index.ts +12 -0
  202. package/data/sessions/keys.ts +8 -0
  203. package/data/sessions/queries.ts +218 -0
  204. package/data/statuses/index.ts +1 -0
  205. package/data/statuses/queries.ts +69 -0
  206. package/hooks/useCopyToClipboard.ts +48 -0
  207. package/hooks/useDevServersManager.ts +73 -0
  208. package/hooks/useDirectoryBrowser.ts +90 -0
  209. package/hooks/useDrawerAnimation.ts +27 -0
  210. package/hooks/useFileDrop.ts +87 -0
  211. package/hooks/useFileEditor.ts +184 -0
  212. package/hooks/useGroups.ts +37 -0
  213. package/hooks/useHomePath.ts +34 -0
  214. package/hooks/useKeyRepeat.ts +55 -0
  215. package/hooks/useKeybarVisibility.ts +42 -0
  216. package/hooks/useNotifications.ts +257 -0
  217. package/hooks/useProjects.ts +53 -0
  218. package/hooks/useSessionStatuses.ts +30 -0
  219. package/hooks/useSessions.ts +86 -0
  220. package/hooks/useSpeechRecognition.ts +124 -0
  221. package/hooks/useViewport.ts +32 -0
  222. package/hooks/useViewportHeight.ts +50 -0
  223. package/lib/async-operations.ts +35 -0
  224. package/lib/banner.ts +81 -0
  225. package/lib/claude/jsonl-cache.ts +86 -0
  226. package/lib/claude/jsonl-reader.ts +271 -0
  227. package/lib/claude/process-manager.ts +278 -0
  228. package/lib/claude/stream-parser.ts +173 -0
  229. package/lib/claude/types.ts +154 -0
  230. package/lib/claude/watcher.ts +71 -0
  231. package/lib/client/session-registry.ts +111 -0
  232. package/lib/code-search.ts +121 -0
  233. package/lib/db/index.ts +48 -0
  234. package/lib/db/migrations.ts +45 -0
  235. package/lib/db/queries.ts +460 -0
  236. package/lib/db/schema.ts +114 -0
  237. package/lib/db/types.ts +92 -0
  238. package/lib/db.ts +2 -0
  239. package/lib/dev-servers.ts +509 -0
  240. package/lib/diff-parser.ts +221 -0
  241. package/lib/env-setup.ts +285 -0
  242. package/lib/file-upload.ts +34 -0
  243. package/lib/file-utils.ts +50 -0
  244. package/lib/files.ts +207 -0
  245. package/lib/git-history.ts +294 -0
  246. package/lib/git-status.ts +391 -0
  247. package/lib/git.ts +257 -0
  248. package/lib/mcp-config.ts +81 -0
  249. package/lib/multi-repo-git.ts +179 -0
  250. package/lib/notifications.ts +219 -0
  251. package/lib/orchestration.ts +448 -0
  252. package/lib/panes.ts +232 -0
  253. package/lib/ports.ts +97 -0
  254. package/lib/pr-generation.ts +307 -0
  255. package/lib/pr.ts +234 -0
  256. package/lib/projects.ts +578 -0
  257. package/lib/providers/registry.ts +70 -0
  258. package/lib/providers.ts +121 -0
  259. package/lib/query-client.ts +14 -0
  260. package/lib/rangeSelectionUtils.ts +65 -0
  261. package/lib/status-detector.ts +375 -0
  262. package/lib/terminal-themes.ts +265 -0
  263. package/lib/theme-config.ts +327 -0
  264. package/lib/utils.ts +6 -0
  265. package/lib/worktrees.ts +262 -0
  266. package/mcp/orchestration-server.ts +438 -0
  267. package/package.json +139 -0
  268. package/postcss.config.mjs +7 -0
  269. package/public/icon.svg +10 -0
  270. package/public/icons/icon-128x128.png +0 -0
  271. package/public/icons/icon-144x144.png +0 -0
  272. package/public/icons/icon-152x152.png +0 -0
  273. package/public/icons/icon-192x192.png +0 -0
  274. package/public/icons/icon-384x384.png +0 -0
  275. package/public/icons/icon-512x512.png +0 -0
  276. package/public/icons/icon-72x72.png +0 -0
  277. package/public/icons/icon-96x96.png +0 -0
  278. package/public/manifest.json +61 -0
  279. package/public/sw.js +64 -0
  280. package/scripts/agent-os +91 -0
  281. package/scripts/install.sh +48 -0
  282. package/scripts/lib/ai-clis.sh +132 -0
  283. package/scripts/lib/commands.sh +487 -0
  284. package/scripts/lib/common.sh +89 -0
  285. package/scripts/lib/prerequisites.sh +462 -0
  286. package/scripts/setup.sh +134 -0
  287. package/server.ts +155 -0
  288. package/stores/fileOpen.ts +26 -0
  289. package/stores/index.ts +1 -0
  290. package/stores/initialPrompt.ts +24 -0
  291. package/stores/sessionSelection.ts +48 -0
  292. package/styles/themes.css +603 -0
  293. package/tsconfig.json +33 -0
@@ -0,0 +1,279 @@
1
+ /**
2
+ * ADropdownMenu - Declarative dropdown menu component
3
+ *
4
+ * @example
5
+ * ```tsx
6
+ * <ADropdownMenu
7
+ * trigger={<Button><MoreHorizontal /></Button>}
8
+ * items={[
9
+ * menuItem('Edit', onEdit, { icon: Pencil }),
10
+ * toggleItem('Full Width', isFullWidth, onToggle),
11
+ * separator(),
12
+ * menuItem('Delete', onDelete, { icon: Trash2, variant: 'destructive' }),
13
+ * ]}
14
+ * />
15
+ * ```
16
+ */
17
+
18
+ "use client";
19
+
20
+ import type { ReactNode } from "react";
21
+ import type { LucideIcon } from "lucide-react";
22
+ import {
23
+ DropdownMenu,
24
+ DropdownMenuContent,
25
+ DropdownMenuItem,
26
+ DropdownMenuSeparator,
27
+ DropdownMenuSub,
28
+ DropdownMenuSubContent,
29
+ DropdownMenuSubTrigger,
30
+ DropdownMenuTrigger,
31
+ DropdownMenuPortal,
32
+ } from "@/components/ui/dropdown-menu";
33
+ import {
34
+ Tooltip,
35
+ TooltipContent,
36
+ TooltipTrigger,
37
+ } from "@/components/ui/tooltip";
38
+ import { Button } from "@/components/ui/button";
39
+ import { Switch } from "@/components/ui/switch";
40
+ import { cn } from "@/lib/utils";
41
+
42
+ // Types
43
+
44
+ interface BaseItem {
45
+ key?: string;
46
+ visible?: boolean;
47
+ }
48
+
49
+ export interface MenuItemConfig extends BaseItem {
50
+ type: "item";
51
+ label: string;
52
+ onClick: () => void;
53
+ icon?: LucideIcon;
54
+ disabled?: boolean;
55
+ variant?: "default" | "destructive";
56
+ }
57
+
58
+ export interface ToggleItemConfig extends BaseItem {
59
+ type: "toggle";
60
+ label: string;
61
+ checked: boolean;
62
+ onChange: () => void;
63
+ icon?: LucideIcon;
64
+ disabled?: boolean;
65
+ }
66
+
67
+ export interface SubmenuItemConfig extends BaseItem {
68
+ type: "submenu";
69
+ label: string;
70
+ icon?: LucideIcon;
71
+ items: DropdownItemConfig[];
72
+ disabled?: boolean;
73
+ }
74
+
75
+ export interface SeparatorItemConfig extends BaseItem {
76
+ type: "separator";
77
+ }
78
+
79
+ export interface CustomItemConfig extends BaseItem {
80
+ type: "custom";
81
+ render: () => ReactNode;
82
+ }
83
+
84
+ export type DropdownItemConfig =
85
+ | MenuItemConfig
86
+ | ToggleItemConfig
87
+ | SubmenuItemConfig
88
+ | SeparatorItemConfig
89
+ | CustomItemConfig;
90
+
91
+ type FalsyItem = false | null | undefined | "" | 0;
92
+
93
+ export interface ADropdownMenuProps {
94
+ trigger?: ReactNode;
95
+ icon?: LucideIcon;
96
+ items: (DropdownItemConfig | FalsyItem)[];
97
+ align?: "start" | "center" | "end";
98
+ minWidth?: string;
99
+ className?: string;
100
+ tooltip?: string;
101
+ }
102
+
103
+ function filterVisibleItems(
104
+ items: (DropdownItemConfig | FalsyItem)[]
105
+ ): DropdownItemConfig[] {
106
+ return items.filter(
107
+ (item): item is DropdownItemConfig => !!item && item.visible !== false
108
+ );
109
+ }
110
+
111
+ // Renderers
112
+
113
+ function ToggleItem({ item }: { item: ToggleItemConfig }) {
114
+ const Icon = item.icon;
115
+ return (
116
+ <div className="flex min-h-[32px] items-center justify-between px-2 py-1.5">
117
+ <div className="flex items-center gap-2">
118
+ {Icon && <Icon className="text-muted-foreground h-4 w-4" />}
119
+ <span className="text-sm">{item.label}</span>
120
+ </div>
121
+ <Switch
122
+ checked={item.checked}
123
+ onCheckedChange={item.onChange}
124
+ className="scale-75"
125
+ disabled={item.disabled}
126
+ />
127
+ </div>
128
+ );
129
+ }
130
+
131
+ function MenuItem({ item }: { item: MenuItemConfig }) {
132
+ const Icon = item.icon;
133
+ return (
134
+ <DropdownMenuItem
135
+ onClick={item.onClick}
136
+ disabled={item.disabled}
137
+ className={cn(
138
+ item.variant === "destructive" &&
139
+ "text-destructive focus:text-destructive"
140
+ )}
141
+ >
142
+ {Icon && <Icon className="mr-2 h-4 w-4" />}
143
+ {item.label}
144
+ </DropdownMenuItem>
145
+ );
146
+ }
147
+
148
+ function SubmenuItem({ item }: { item: SubmenuItemConfig }) {
149
+ const Icon = item.icon;
150
+ const visibleItems = filterVisibleItems(item.items);
151
+
152
+ if (visibleItems.length === 0) return null;
153
+
154
+ return (
155
+ <DropdownMenuSub>
156
+ <DropdownMenuSubTrigger disabled={item.disabled}>
157
+ {Icon && <Icon className="mr-2 h-4 w-4" />}
158
+ {item.label}
159
+ </DropdownMenuSubTrigger>
160
+ <DropdownMenuPortal>
161
+ <DropdownMenuSubContent>
162
+ {visibleItems.map((subItem, index) => (
163
+ <DropdownItemRenderer
164
+ key={subItem.key ?? `sub-${index}`}
165
+ item={subItem}
166
+ />
167
+ ))}
168
+ </DropdownMenuSubContent>
169
+ </DropdownMenuPortal>
170
+ </DropdownMenuSub>
171
+ );
172
+ }
173
+
174
+ function DropdownItemRenderer({ item }: { item: DropdownItemConfig }) {
175
+ switch (item.type) {
176
+ case "item":
177
+ return <MenuItem item={item} />;
178
+ case "toggle":
179
+ return <ToggleItem item={item} />;
180
+ case "submenu":
181
+ return <SubmenuItem item={item} />;
182
+ case "separator":
183
+ return <DropdownMenuSeparator />;
184
+ case "custom":
185
+ return <>{item.render()}</>;
186
+ default:
187
+ return null;
188
+ }
189
+ }
190
+
191
+ // Main Component
192
+
193
+ export function ADropdownMenu({
194
+ trigger,
195
+ icon: Icon,
196
+ items,
197
+ align = "end",
198
+ minWidth = "180px",
199
+ className,
200
+ tooltip,
201
+ }: ADropdownMenuProps) {
202
+ const visibleItems = filterVisibleItems(items);
203
+
204
+ const resolvedTrigger =
205
+ trigger ??
206
+ (Icon ? (
207
+ <Button variant="ghost" size="icon-sm">
208
+ <Icon className="h-4 w-4" />
209
+ </Button>
210
+ ) : null);
211
+
212
+ const triggerElement = tooltip ? (
213
+ <Tooltip>
214
+ <TooltipTrigger asChild>
215
+ <DropdownMenuTrigger asChild>{resolvedTrigger}</DropdownMenuTrigger>
216
+ </TooltipTrigger>
217
+ <TooltipContent>{tooltip}</TooltipContent>
218
+ </Tooltip>
219
+ ) : (
220
+ <DropdownMenuTrigger asChild>{resolvedTrigger}</DropdownMenuTrigger>
221
+ );
222
+
223
+ return (
224
+ <DropdownMenu>
225
+ {triggerElement}
226
+ <DropdownMenuContent
227
+ align={align}
228
+ className={className}
229
+ style={{ minWidth }}
230
+ >
231
+ {visibleItems.map((item, index) => (
232
+ <DropdownItemRenderer key={item.key ?? index} item={item} />
233
+ ))}
234
+ </DropdownMenuContent>
235
+ </DropdownMenu>
236
+ );
237
+ }
238
+
239
+ // Factory Functions
240
+
241
+ export function menuItem(
242
+ label: string,
243
+ onClick: () => void,
244
+ options?: Partial<Omit<MenuItemConfig, "type" | "label" | "onClick">>
245
+ ): MenuItemConfig {
246
+ return { type: "item", label, onClick, ...options };
247
+ }
248
+
249
+ export function toggleItem(
250
+ label: string,
251
+ checked: boolean,
252
+ onChange: () => void,
253
+ options?: Partial<
254
+ Omit<ToggleItemConfig, "type" | "label" | "checked" | "onChange">
255
+ >
256
+ ): ToggleItemConfig {
257
+ return { type: "toggle", label, checked, onChange, ...options };
258
+ }
259
+
260
+ export function submenuItem(
261
+ label: string,
262
+ items: DropdownItemConfig[],
263
+ options?: Partial<Omit<SubmenuItemConfig, "type" | "label" | "items">>
264
+ ): SubmenuItemConfig {
265
+ return { type: "submenu", label, items, ...options };
266
+ }
267
+
268
+ export function separator(
269
+ options?: Partial<Omit<SeparatorItemConfig, "type">>
270
+ ): SeparatorItemConfig {
271
+ return { type: "separator", ...options };
272
+ }
273
+
274
+ export function customItem(
275
+ render: () => ReactNode,
276
+ options?: Partial<Omit<CustomItemConfig, "type" | "render">>
277
+ ): CustomItemConfig {
278
+ return { type: "custom", render, ...options };
279
+ }
@@ -0,0 +1,190 @@
1
+ /**
2
+ * AIconButton - Icon button component for the design system
3
+ *
4
+ * A consistent icon button with optional tooltip, badge, and dot indicator support.
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * // Simple icon button
9
+ * <AIconButton icon={Settings} onClick={handleClick} />
10
+ *
11
+ * // With tooltip
12
+ * <AIconButton icon={MessageSquare} tooltip="Comments" onClick={handleClick} />
13
+ *
14
+ * // With badge (for notifications)
15
+ * <AIconButton icon={Bell} tooltip="Notifications" badge={5} onClick={handleClick} />
16
+ *
17
+ * // With dot indicator (for active state)
18
+ * <AIconButton icon={Share2} tooltip="Share" dot dotColor="blue" onClick={handleClick} />
19
+ *
20
+ * // Active/selected state
21
+ * <AIconButton icon={MessageSquare} active onClick={handleClick} />
22
+ * ```
23
+ */
24
+
25
+ "use client";
26
+
27
+ import { forwardRef } from "react";
28
+ import type { LucideIcon } from "lucide-react";
29
+ import { Button } from "@/components/ui/button";
30
+ import { Badge } from "@/components/ui/badge";
31
+ import { ATooltip } from "@/components/a/ATooltip";
32
+ import { cn } from "@/lib/utils";
33
+
34
+ export type AIconButtonSize = "sm" | "md" | "lg";
35
+ export type AIconButtonHighlight =
36
+ | "blue"
37
+ | "green"
38
+ | "red"
39
+ | "orange"
40
+ | "purple";
41
+
42
+ export interface AIconButtonProps extends Omit<
43
+ React.ButtonHTMLAttributes<HTMLButtonElement>,
44
+ "children"
45
+ > {
46
+ /** Lucide icon component */
47
+ icon: LucideIcon;
48
+ /** Optional tooltip text */
49
+ tooltip?: string;
50
+ /** Optional keyboard shortcut hint for tooltip */
51
+ shortcut?: string;
52
+ /** Tooltip position */
53
+ tooltipSide?: "top" | "right" | "bottom" | "left";
54
+ /** Optional badge count (shows red notification badge) */
55
+ badge?: number;
56
+ /** Show a small dot indicator */
57
+ dot?: boolean;
58
+ /** Dot color (default: "purple") */
59
+ dotColor?: AIconButtonHighlight;
60
+ /** Whether the button is in active/selected state */
61
+ active?: boolean;
62
+ /** Highlight color for the icon */
63
+ highlight?: AIconButtonHighlight;
64
+ /** Button size variant */
65
+ size?: AIconButtonSize;
66
+ /** Button variant */
67
+ variant?: "ghost" | "default" | "muted";
68
+ /** Additional className for the icon */
69
+ iconClassName?: string;
70
+ /** Additional className */
71
+ className?: string;
72
+ /** Accessible label (required if no tooltip) */
73
+ "aria-label"?: string;
74
+ }
75
+
76
+ const SIZE_CLASSES: Record<AIconButtonSize, { button: string; icon: string }> =
77
+ {
78
+ sm: { button: "h-7 w-7", icon: "h-3.5 w-3.5" },
79
+ md: { button: "h-8 w-8", icon: "h-4 w-4" },
80
+ lg: { button: "h-9 w-9", icon: "h-5 w-5" },
81
+ };
82
+
83
+ const VARIANT_CLASSES = {
84
+ ghost: "bg-transparent hover:bg-muted/60",
85
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
86
+ muted: "bg-muted/40 hover:bg-muted/60",
87
+ };
88
+
89
+ const HIGHLIGHT_CLASSES: Record<AIconButtonHighlight, string> = {
90
+ blue: "text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300",
91
+ green:
92
+ "text-green-600 hover:text-green-700 dark:text-green-400 dark:hover:text-green-300",
93
+ red: "text-red-600 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300",
94
+ orange:
95
+ "text-orange-600 hover:text-orange-700 dark:text-orange-400 dark:hover:text-orange-300",
96
+ purple:
97
+ "text-purple-600 hover:text-purple-700 dark:text-purple-400 dark:hover:text-purple-300",
98
+ };
99
+
100
+ const DOT_COLORS: Record<AIconButtonHighlight, string> = {
101
+ blue: "bg-blue-500 dark:bg-blue-400",
102
+ green: "bg-green-500 dark:bg-green-400",
103
+ red: "bg-red-500 dark:bg-red-400",
104
+ orange: "bg-orange-500 dark:bg-orange-400",
105
+ purple: "bg-purple-500 dark:bg-purple-400",
106
+ };
107
+
108
+ export const AIconButton = forwardRef<HTMLButtonElement, AIconButtonProps>(
109
+ (
110
+ {
111
+ icon: Icon,
112
+ tooltip,
113
+ shortcut,
114
+ tooltipSide = "bottom",
115
+ badge,
116
+ dot = false,
117
+ dotColor = "purple",
118
+ active = false,
119
+ highlight,
120
+ size = "md",
121
+ variant = "ghost",
122
+ iconClassName,
123
+ className,
124
+ disabled,
125
+ "aria-label": ariaLabel,
126
+ ...props
127
+ },
128
+ ref
129
+ ) => {
130
+ const sizeClasses = SIZE_CLASSES[size];
131
+
132
+ const button = (
133
+ <Button
134
+ ref={ref}
135
+ variant="ghost"
136
+ size="sm"
137
+ disabled={disabled}
138
+ aria-label={ariaLabel || tooltip}
139
+ className={cn(
140
+ "relative p-0",
141
+ sizeClasses.button,
142
+ active ? VARIANT_CLASSES.default : VARIANT_CLASSES[variant],
143
+ highlight && !active && HIGHLIGHT_CLASSES[highlight],
144
+ disabled && "cursor-not-allowed opacity-50",
145
+ className
146
+ )}
147
+ {...props}
148
+ >
149
+ <Icon className={cn(sizeClasses.icon, iconClassName)} />
150
+
151
+ {/* Badge with count */}
152
+ {badge !== undefined && badge > 0 && (
153
+ <Badge
154
+ variant="destructive"
155
+ className="absolute -top-1 -right-1 h-4 min-w-4 px-1 text-[10px] font-medium"
156
+ >
157
+ {badge > 99 ? "99+" : badge}
158
+ </Badge>
159
+ )}
160
+
161
+ {/* Dot indicator */}
162
+ {dot && !badge && (
163
+ <div
164
+ className={cn(
165
+ "absolute -top-0.5 -right-0.5 h-2 w-2 rounded-full",
166
+ DOT_COLORS[dotColor]
167
+ )}
168
+ />
169
+ )}
170
+ </Button>
171
+ );
172
+
173
+ if (tooltip) {
174
+ return (
175
+ <ATooltip
176
+ content={tooltip}
177
+ shortcut={shortcut}
178
+ side={tooltipSide}
179
+ disabled={disabled}
180
+ >
181
+ {button}
182
+ </ATooltip>
183
+ );
184
+ }
185
+
186
+ return button;
187
+ }
188
+ );
189
+
190
+ AIconButton.displayName = "AIconButton";
@@ -0,0 +1,150 @@
1
+ /**
2
+ * ASheet - Design system Sheet component
3
+ *
4
+ * A slide-out panel that follows ClaudeDeck design guidelines:
5
+ * - No strong borders, uses subtle shadows instead
6
+ * - Consistent styling across the app
7
+ * - Wraps Radix Sheet primitive
8
+ */
9
+
10
+ "use client";
11
+
12
+ import * as React from "react";
13
+ import * as SheetPrimitive from "@radix-ui/react-dialog";
14
+ import { cva, type VariantProps } from "class-variance-authority";
15
+ import { X } from "lucide-react";
16
+ import { cn } from "@/lib/utils";
17
+
18
+ const ASheet = SheetPrimitive.Root;
19
+
20
+ const ASheetTrigger = SheetPrimitive.Trigger;
21
+
22
+ const ASheetClose = SheetPrimitive.Close;
23
+
24
+ const ASheetPortal = SheetPrimitive.Portal;
25
+
26
+ const ASheetOverlay = React.forwardRef<
27
+ React.ElementRef<typeof SheetPrimitive.Overlay>,
28
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
29
+ >(({ className, ...props }, ref) => (
30
+ <SheetPrimitive.Overlay
31
+ className={cn(
32
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/60",
33
+ className
34
+ )}
35
+ {...props}
36
+ ref={ref}
37
+ />
38
+ ));
39
+ ASheetOverlay.displayName = "ASheetOverlay";
40
+
41
+ const aSheetVariants = cva(
42
+ "fixed z-50 flex flex-col bg-background shadow-xl transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
43
+ {
44
+ variants: {
45
+ side: {
46
+ top: "inset-x-0 top-0 data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
47
+ bottom:
48
+ "inset-x-0 bottom-0 data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
49
+ left: "inset-y-0 left-0 h-full w-3/4 data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
50
+ right:
51
+ "inset-y-0 right-0 h-full w-3/4 data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
52
+ },
53
+ },
54
+ defaultVariants: {
55
+ side: "right",
56
+ },
57
+ }
58
+ );
59
+
60
+ interface ASheetContentProps
61
+ extends
62
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
63
+ VariantProps<typeof aSheetVariants> {
64
+ /** Hide the default close button */
65
+ hideCloseButton?: boolean;
66
+ }
67
+
68
+ const ASheetContent = React.forwardRef<
69
+ React.ElementRef<typeof SheetPrimitive.Content>,
70
+ ASheetContentProps
71
+ >(
72
+ (
73
+ { side = "right", className, children, hideCloseButton = false, ...props },
74
+ ref
75
+ ) => (
76
+ <ASheetPortal>
77
+ <ASheetOverlay />
78
+ <SheetPrimitive.Content
79
+ ref={ref}
80
+ className={cn(aSheetVariants({ side }), className)}
81
+ {...props}
82
+ >
83
+ {children}
84
+ {!hideCloseButton && (
85
+ <SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:pointer-events-none">
86
+ <X className="h-4 w-4" />
87
+ <span className="sr-only">Close</span>
88
+ </SheetPrimitive.Close>
89
+ )}
90
+ </SheetPrimitive.Content>
91
+ </ASheetPortal>
92
+ )
93
+ );
94
+ ASheetContent.displayName = "ASheetContent";
95
+
96
+ const ASheetHeader = ({
97
+ className,
98
+ ...props
99
+ }: React.HTMLAttributes<HTMLDivElement>) => (
100
+ <div className={cn("flex flex-col space-y-1.5 p-4", className)} {...props} />
101
+ );
102
+ ASheetHeader.displayName = "ASheetHeader";
103
+
104
+ const ASheetFooter = ({
105
+ className,
106
+ ...props
107
+ }: React.HTMLAttributes<HTMLDivElement>) => (
108
+ <div
109
+ className={cn("mt-auto flex flex-col gap-2 p-4", className)}
110
+ {...props}
111
+ />
112
+ );
113
+ ASheetFooter.displayName = "ASheetFooter";
114
+
115
+ const ASheetTitle = React.forwardRef<
116
+ React.ElementRef<typeof SheetPrimitive.Title>,
117
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
118
+ >(({ className, ...props }, ref) => (
119
+ <SheetPrimitive.Title
120
+ ref={ref}
121
+ className={cn("text-foreground text-base font-semibold", className)}
122
+ {...props}
123
+ />
124
+ ));
125
+ ASheetTitle.displayName = "ASheetTitle";
126
+
127
+ const ASheetDescription = React.forwardRef<
128
+ React.ElementRef<typeof SheetPrimitive.Description>,
129
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
130
+ >(({ className, ...props }, ref) => (
131
+ <SheetPrimitive.Description
132
+ ref={ref}
133
+ className={cn("text-muted-foreground text-sm", className)}
134
+ {...props}
135
+ />
136
+ ));
137
+ ASheetDescription.displayName = "ASheetDescription";
138
+
139
+ export {
140
+ ASheet,
141
+ ASheetPortal,
142
+ ASheetOverlay,
143
+ ASheetTrigger,
144
+ ASheetClose,
145
+ ASheetContent,
146
+ ASheetHeader,
147
+ ASheetFooter,
148
+ ASheetTitle,
149
+ ASheetDescription,
150
+ };
@@ -0,0 +1,77 @@
1
+ /**
2
+ * ATooltip - Simple tooltip wrapper for the design system
3
+ *
4
+ * Wraps Radix tooltip primitives into a single, easy-to-use component.
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * <ATooltip content="Edit document">
9
+ * <Button>Edit</Button>
10
+ * </ATooltip>
11
+ *
12
+ * // With shortcut hint
13
+ * <ATooltip content="Save" shortcut="Cmd+S">
14
+ * <Button>Save</Button>
15
+ * </ATooltip>
16
+ * ```
17
+ */
18
+
19
+ "use client";
20
+
21
+ import { type ReactNode } from "react";
22
+ import {
23
+ Tooltip,
24
+ TooltipContent,
25
+ TooltipTrigger,
26
+ } from "@/components/ui/tooltip";
27
+ import { cn } from "@/lib/utils";
28
+
29
+ export interface ATooltipProps {
30
+ /** The element that triggers the tooltip */
31
+ children: ReactNode;
32
+ /** Tooltip content - can be a string or ReactNode */
33
+ content: ReactNode;
34
+ /** Optional keyboard shortcut hint (e.g., "Cmd+S") */
35
+ shortcut?: string;
36
+ /** Side of the trigger to render the tooltip (default: "top") */
37
+ side?: "top" | "right" | "bottom" | "left";
38
+ /** Alignment of the tooltip relative to trigger (default: "center") */
39
+ align?: "start" | "center" | "end";
40
+ /** Delay in ms before showing tooltip (default: 300) */
41
+ delayDuration?: number;
42
+ /** Whether the tooltip is disabled */
43
+ disabled?: boolean;
44
+ /** Additional className for the tooltip content */
45
+ className?: string;
46
+ }
47
+
48
+ export function ATooltip({
49
+ children,
50
+ content,
51
+ shortcut,
52
+ side = "top",
53
+ align = "center",
54
+ delayDuration = 300,
55
+ disabled = false,
56
+ className,
57
+ }: ATooltipProps) {
58
+ if (disabled || !content) {
59
+ return <>{children}</>;
60
+ }
61
+
62
+ return (
63
+ <Tooltip delayDuration={delayDuration}>
64
+ <TooltipTrigger asChild>{children}</TooltipTrigger>
65
+ <TooltipContent side={side} align={align} className={cn(className)}>
66
+ <div className="flex items-center gap-2">
67
+ <span>{content}</span>
68
+ {shortcut && (
69
+ <kbd className="bg-muted text-muted-foreground rounded px-1.5 py-0.5 text-[10px] font-medium">
70
+ {shortcut}
71
+ </kbd>
72
+ )}
73
+ </div>
74
+ </TooltipContent>
75
+ </Tooltip>
76
+ );
77
+ }