kcg-code 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (286) hide show
  1. package/README.md +228 -2
  2. package/bin/kcgcode.ts +13 -0
  3. package/bun-env.d.ts +19 -0
  4. package/kcg-code.config.example.json +3 -0
  5. package/package.json +83 -4
  6. package/public/logo.svg +1 -0
  7. package/public/manifest.json +18 -0
  8. package/public/sw.js +49 -0
  9. package/src/App.tsx +39 -0
  10. package/src/__tests__/manifest.test.ts +61 -0
  11. package/src/build-config.ts +9 -0
  12. package/src/cli/__tests__/dashboard.test.ts +62 -0
  13. package/src/cli/args.ts +123 -0
  14. package/src/cli/commands.ts +174 -0
  15. package/src/cli/dashboard.tsx +343 -0
  16. package/src/cli/index.ts +37 -0
  17. package/src/cli/theme.ts +100 -0
  18. package/src/components/auth/AuthGate.tsx +182 -0
  19. package/src/components/auth/AvatarEditor.tsx +236 -0
  20. package/src/components/auth/LockScreen.tsx +714 -0
  21. package/src/components/auth/ProfileAvatar.tsx +60 -0
  22. package/src/components/auth/WelcomeScreen.tsx +672 -0
  23. package/src/components/auth/__tests__/WelcomeScreen.test.tsx +42 -0
  24. package/src/components/auth/avatar-icons.tsx +114 -0
  25. package/src/components/layout/AppShell.tsx +142 -0
  26. package/src/components/layout/AppSidebar.tsx +419 -0
  27. package/src/components/layout/SearchDialog.tsx +189 -0
  28. package/src/components/layout/SidebarAccount.tsx +111 -0
  29. package/src/components/layout/SidebarSessionMenu.tsx +163 -0
  30. package/src/components/layout/ThemeToggle.tsx +67 -0
  31. package/src/components/projects/FolderBrowser.tsx +142 -0
  32. package/src/components/projects/HomeChat.tsx +250 -0
  33. package/src/components/projects/NewProjectDialog.tsx +249 -0
  34. package/src/components/projects/PanelSection.tsx +102 -0
  35. package/src/components/projects/ProjectInstructions.tsx +229 -0
  36. package/src/components/projects/ProjectRow.tsx +58 -0
  37. package/src/components/projects/ProjectSidePanel.tsx +448 -0
  38. package/src/components/sessions/AgentModeList.tsx +265 -0
  39. package/src/components/sessions/AgentPicker.tsx +123 -0
  40. package/src/components/sessions/AssistantTurn.tsx +145 -0
  41. package/src/components/sessions/AttachmentTile.tsx +161 -0
  42. package/src/components/sessions/CodeBlock.tsx +107 -0
  43. package/src/components/sessions/ComposerActionSheet.tsx +96 -0
  44. package/src/components/sessions/ConfirmDeleteProjectDialog.tsx +80 -0
  45. package/src/components/sessions/ConfirmSessionDeleteDialog.tsx +72 -0
  46. package/src/components/sessions/ContextPanel.tsx +561 -0
  47. package/src/components/sessions/DropPlate.tsx +165 -0
  48. package/src/components/sessions/FileDropOverlay.tsx +232 -0
  49. package/src/components/sessions/FileMentionDropdown.tsx +47 -0
  50. package/src/components/sessions/ImageLightbox.tsx +173 -0
  51. package/src/components/sessions/MarkdownContent.tsx +187 -0
  52. package/src/components/sessions/Mascot.tsx +47 -0
  53. package/src/components/sessions/MessageActions.tsx +60 -0
  54. package/src/components/sessions/MessageRail.tsx +121 -0
  55. package/src/components/sessions/ModelPicker.tsx +209 -0
  56. package/src/components/sessions/ModelSearchList.tsx +151 -0
  57. package/src/components/sessions/NewSessionDialog.tsx +189 -0
  58. package/src/components/sessions/PendingAttachments.tsx +127 -0
  59. package/src/components/sessions/PromptCard.tsx +1085 -0
  60. package/src/components/sessions/PromptPanel.tsx +112 -0
  61. package/src/components/sessions/SessionComposer.tsx +640 -0
  62. package/src/components/sessions/SessionHeader.tsx +175 -0
  63. package/src/components/sessions/SessionList.tsx +396 -0
  64. package/src/components/sessions/SessionRow.tsx +137 -0
  65. package/src/components/sessions/SessionTimeline.tsx +275 -0
  66. package/src/components/sessions/SessionTimelineSkeleton.tsx +83 -0
  67. package/src/components/sessions/SessionView.tsx +236 -0
  68. package/src/components/sessions/ThoughtProcessBlock.tsx +199 -0
  69. package/src/components/sessions/UserMessage.tsx +177 -0
  70. package/src/components/sessions/__tests__/ContextPanel.test.tsx +220 -0
  71. package/src/components/sessions/__tests__/MarkdownContent.test.tsx +89 -0
  72. package/src/components/sessions/__tests__/MessageRail.test.tsx +40 -0
  73. package/src/components/sessions/__tests__/PermissionCard.test.tsx +79 -0
  74. package/src/components/sessions/__tests__/PromptCard.test.ts +166 -0
  75. package/src/components/sessions/__tests__/QuestionCard.test.tsx +166 -0
  76. package/src/components/sessions/__tests__/SessionTimelineSkeleton.test.tsx +32 -0
  77. package/src/components/sessions/__tests__/SessionView.test.ts +386 -0
  78. package/src/components/settings/AboutGroup.tsx +170 -0
  79. package/src/components/settings/AccountGroup.tsx +122 -0
  80. package/src/components/settings/DevicesGroup.tsx +151 -0
  81. package/src/components/settings/LhrRemoteAccess.tsx +262 -0
  82. package/src/components/settings/PasskeysGroup.tsx +551 -0
  83. package/src/components/settings/RemoteAccessGroup.tsx +1226 -0
  84. package/src/components/settings/SecurityGroup.tsx +566 -0
  85. package/src/components/settings/WorkspaceGroup.tsx +370 -0
  86. package/src/components/settings/__tests__/AboutGroup.test.tsx +47 -0
  87. package/src/components/settings/__tests__/PasskeysGroup.test.tsx +109 -0
  88. package/src/components/settings/__tests__/WorkspaceGroup.test.tsx +49 -0
  89. package/src/components/settings/prefs.tsx +265 -0
  90. package/src/components/skills/InstallNotifications.tsx +325 -0
  91. package/src/components/skills/InstallSkillDialog.tsx +233 -0
  92. package/src/components/skills/SkillCards.tsx +210 -0
  93. package/src/components/skills/SkillsProjectPicker.tsx +178 -0
  94. package/src/components/ui/alert-dialog.tsx +179 -0
  95. package/src/components/ui/alert.tsx +60 -0
  96. package/src/components/ui/badge.tsx +46 -0
  97. package/src/components/ui/bubble.tsx +125 -0
  98. package/src/components/ui/button.tsx +62 -0
  99. package/src/components/ui/card.tsx +75 -0
  100. package/src/components/ui/collapsible.tsx +19 -0
  101. package/src/components/ui/dialog.tsx +142 -0
  102. package/src/components/ui/dropdown-menu.tsx +225 -0
  103. package/src/components/ui/empty.tsx +94 -0
  104. package/src/components/ui/field.tsx +234 -0
  105. package/src/components/ui/input-group.tsx +161 -0
  106. package/src/components/ui/input.tsx +21 -0
  107. package/src/components/ui/label.tsx +19 -0
  108. package/src/components/ui/message-scroller.tsx +131 -0
  109. package/src/components/ui/message.tsx +85 -0
  110. package/src/components/ui/popover.tsx +46 -0
  111. package/src/components/ui/select.tsx +172 -0
  112. package/src/components/ui/separator.tsx +28 -0
  113. package/src/components/ui/sheet.tsx +138 -0
  114. package/src/components/ui/sidebar.tsx +699 -0
  115. package/src/components/ui/skeleton.tsx +15 -0
  116. package/src/components/ui/sonner.tsx +45 -0
  117. package/src/components/ui/spinner.tsx +16 -0
  118. package/src/components/ui/switch.tsx +32 -0
  119. package/src/components/ui/textarea.tsx +18 -0
  120. package/src/components/ui/tooltip.tsx +51 -0
  121. package/src/config.ts +85 -0
  122. package/src/db.ts +146 -0
  123. package/src/frontend.tsx +51 -0
  124. package/src/hooks/__tests__/useMention.test.ts +79 -0
  125. package/src/hooks/__tests__/useResyncOnReconnect.test.ts +128 -0
  126. package/src/hooks/__tests__/useRouter.test.ts +17 -0
  127. package/src/hooks/__tests__/useSmoothText.react.test.ts +194 -0
  128. package/src/hooks/__tests__/useSmoothText.test.ts +61 -0
  129. package/src/hooks/__tests__/useTheme.test.ts +118 -0
  130. package/src/hooks/use-mobile.ts +16 -0
  131. package/src/hooks/useAgentPicker.ts +97 -0
  132. package/src/hooks/useMediaQuery.ts +49 -0
  133. package/src/hooks/useMention.ts +236 -0
  134. package/src/hooks/useProjects.ts +88 -0
  135. package/src/hooks/useResyncOnReconnect.ts +28 -0
  136. package/src/hooks/useRouter.ts +92 -0
  137. package/src/hooks/useSessionChat.ts +427 -0
  138. package/src/hooks/useSessionContext.ts +106 -0
  139. package/src/hooks/useSessionList.ts +234 -0
  140. package/src/hooks/useSidebarData.ts +108 -0
  141. package/src/hooks/useSmoothText.ts +122 -0
  142. package/src/hooks/useTheme.ts +164 -0
  143. package/src/hooks/useWebSocket.ts +196 -0
  144. package/src/index.css +27 -0
  145. package/src/index.html +14 -0
  146. package/src/index.ts +49 -0
  147. package/src/lib/__tests__/attachments.test.ts +58 -0
  148. package/src/lib/__tests__/avatar-presets.test.ts +25 -0
  149. package/src/lib/__tests__/collapsible.test.ts +101 -0
  150. package/src/lib/__tests__/composer.test.ts +118 -0
  151. package/src/lib/__tests__/highlight.test.ts +42 -0
  152. package/src/lib/__tests__/home-chat.test.ts +64 -0
  153. package/src/lib/__tests__/lock-screen.test.ts +64 -0
  154. package/src/lib/__tests__/model-picker.test.ts +205 -0
  155. package/src/lib/__tests__/onboarding.test.ts +41 -0
  156. package/src/lib/__tests__/permission-info.test.ts +61 -0
  157. package/src/lib/__tests__/project-extensions.test.ts +52 -0
  158. package/src/lib/__tests__/project-overview.test.ts +173 -0
  159. package/src/lib/__tests__/references.test.ts +121 -0
  160. package/src/lib/__tests__/routes.test.ts +134 -0
  161. package/src/lib/__tests__/session-summary.test.ts +115 -0
  162. package/src/lib/__tests__/session-title.test.ts +58 -0
  163. package/src/lib/__tests__/sidebar-groups.test.ts +100 -0
  164. package/src/lib/__tests__/skill-installs.test.ts +134 -0
  165. package/src/lib/__tests__/skills-catalog.test.ts +96 -0
  166. package/src/lib/__tests__/tone.test.ts +37 -0
  167. package/src/lib/__tests__/turns-delta.test.ts +55 -0
  168. package/src/lib/api.ts +306 -0
  169. package/src/lib/attachments.ts +203 -0
  170. package/src/lib/auth.ts +92 -0
  171. package/src/lib/avatar-presets.ts +31 -0
  172. package/src/lib/collapsible.ts +75 -0
  173. package/src/lib/composer.ts +89 -0
  174. package/src/lib/data-events.ts +17 -0
  175. package/src/lib/headlines.ts +30 -0
  176. package/src/lib/highlight.ts +186 -0
  177. package/src/lib/layout.ts +20 -0
  178. package/src/lib/lock-screen.ts +54 -0
  179. package/src/lib/model-picker.ts +133 -0
  180. package/src/lib/onboarding.ts +38 -0
  181. package/src/lib/passkeys.ts +113 -0
  182. package/src/lib/pending-prompt.ts +53 -0
  183. package/src/lib/permission-info.ts +101 -0
  184. package/src/lib/project-extensions.ts +74 -0
  185. package/src/lib/project-overview.ts +117 -0
  186. package/src/lib/prompts.ts +87 -0
  187. package/src/lib/references.ts +106 -0
  188. package/src/lib/routes.ts +115 -0
  189. package/src/lib/session-handoff.ts +25 -0
  190. package/src/lib/session-status.ts +45 -0
  191. package/src/lib/session-summary.ts +68 -0
  192. package/src/lib/session-title.ts +63 -0
  193. package/src/lib/sidebar-groups.ts +78 -0
  194. package/src/lib/skill-installs.ts +360 -0
  195. package/src/lib/skills-catalog.ts +99 -0
  196. package/src/lib/time.ts +22 -0
  197. package/src/lib/tone.ts +80 -0
  198. package/src/lib/turns.ts +320 -0
  199. package/src/lib/utils.ts +6 -0
  200. package/src/logo.svg +31 -0
  201. package/src/pages/projects/ProjectDetailPage.tsx +130 -0
  202. package/src/pages/projects/ProjectsPage.tsx +97 -0
  203. package/src/pages/sessions/SessionPage.tsx +122 -0
  204. package/src/pages/settings/SettingsPage.tsx +299 -0
  205. package/src/pages/settings/__tests__/SettingsPage.test.ts +9 -0
  206. package/src/pages/skills/SkillsPage.tsx +701 -0
  207. package/src/paths.ts +121 -0
  208. package/src/runtime.ts +24 -0
  209. package/src/server/__tests__/app.test.ts +1031 -0
  210. package/src/server/__tests__/auth.test.ts +370 -0
  211. package/src/server/__tests__/config.test.ts +101 -0
  212. package/src/server/__tests__/db.test.ts +485 -0
  213. package/src/server/__tests__/host.test.ts +14 -0
  214. package/src/server/__tests__/tunnel-config.test.ts +38 -0
  215. package/src/server/app.ts +415 -0
  216. package/src/server/db/auth.ts +311 -0
  217. package/src/server/db/messages.ts +50 -0
  218. package/src/server/db/projects.ts +106 -0
  219. package/src/server/db/prompts.ts +78 -0
  220. package/src/server/db/rows.ts +150 -0
  221. package/src/server/db/schema.ts +221 -0
  222. package/src/server/db/sessions.ts +239 -0
  223. package/src/server/db/tunnel.ts +124 -0
  224. package/src/server/host.ts +12 -0
  225. package/src/server/result.ts +15 -0
  226. package/src/server/routes/auth-guard.ts +156 -0
  227. package/src/server/routes/auth.routes.ts +287 -0
  228. package/src/server/routes/helpers.ts +165 -0
  229. package/src/server/routes/meta.routes.ts +24 -0
  230. package/src/server/routes/passkeys.routes.ts +161 -0
  231. package/src/server/routes/projects.routes.ts +157 -0
  232. package/src/server/routes/sessions.routes.ts +197 -0
  233. package/src/server/routes/skills.routes.ts +117 -0
  234. package/src/server/routes/tunnel.routes.ts +132 -0
  235. package/src/server/routes/types.ts +35 -0
  236. package/src/server/routes/uploads.routes.ts +54 -0
  237. package/src/server/routes/workspace.routes.ts +127 -0
  238. package/src/server/services/__tests__/app-info.test.ts +18 -0
  239. package/src/server/services/__tests__/attachments.test.ts +159 -0
  240. package/src/server/services/__tests__/auth-passkeys.test.ts +358 -0
  241. package/src/server/services/__tests__/auth.test.ts +341 -0
  242. package/src/server/services/__tests__/frpc-installer.test.ts +83 -0
  243. package/src/server/services/__tests__/git-info.test.ts +74 -0
  244. package/src/server/services/__tests__/opencode.test.ts +320 -0
  245. package/src/server/services/__tests__/project-manager.test.ts +190 -0
  246. package/src/server/services/__tests__/sandbox.test.ts +141 -0
  247. package/src/server/services/__tests__/session-manager.test.ts +3469 -0
  248. package/src/server/services/__tests__/skill-install-jobs.test.ts +226 -0
  249. package/src/server/services/__tests__/skills-registry.test.ts +359 -0
  250. package/src/server/services/__tests__/tunnel-lhr.test.ts +234 -0
  251. package/src/server/services/__tests__/tunnel.test.ts +367 -0
  252. package/src/server/services/__tests__/websocket-gateway.test.ts +677 -0
  253. package/src/server/services/__tests__/workspace.test.ts +249 -0
  254. package/src/server/services/app-info.ts +29 -0
  255. package/src/server/services/attachments.ts +193 -0
  256. package/src/server/services/auth-passkeys.ts +459 -0
  257. package/src/server/services/auth.ts +633 -0
  258. package/src/server/services/frpc-installer.ts +183 -0
  259. package/src/server/services/git-info.ts +87 -0
  260. package/src/server/services/opencode-client.ts +714 -0
  261. package/src/server/services/opencode-server.ts +319 -0
  262. package/src/server/services/opencode-sse.ts +82 -0
  263. package/src/server/services/project-manager.ts +147 -0
  264. package/src/server/services/sandbox.ts +83 -0
  265. package/src/server/services/session-events.ts +256 -0
  266. package/src/server/services/session-manager.ts +1374 -0
  267. package/src/server/services/skill-install-jobs.ts +262 -0
  268. package/src/server/services/skills-registry.ts +468 -0
  269. package/src/server/services/tunnel-api.ts +207 -0
  270. package/src/server/services/tunnel-config.ts +45 -0
  271. package/src/server/services/tunnel-lhr.ts +387 -0
  272. package/src/server/services/tunnel.ts +633 -0
  273. package/src/server/services/turn-stream.ts +339 -0
  274. package/src/server/services/websocket-gateway.ts +374 -0
  275. package/src/server/services/workspace.ts +223 -0
  276. package/src/server/services/ws-transport.ts +121 -0
  277. package/src/types/context.ts +69 -0
  278. package/src/types/index.ts +13 -0
  279. package/src/types/message.ts +58 -0
  280. package/src/types/project.ts +16 -0
  281. package/src/types/prompt.ts +118 -0
  282. package/src/types/session.ts +52 -0
  283. package/src/types/turns.ts +47 -0
  284. package/src/ws-protocol.ts +93 -0
  285. package/styles/globals.css +311 -0
  286. package/tsconfig.json +37 -0
@@ -0,0 +1,142 @@
1
+ import { XIcon } from "lucide-react";
2
+ import { Dialog as DialogPrimitive } from "radix-ui";
3
+ import type * as React from "react";
4
+
5
+ import { Button } from "@/components/ui/button";
6
+ import { cn } from "@/lib/utils";
7
+
8
+ function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {
9
+ return <DialogPrimitive.Root data-slot="dialog" {...props} />;
10
+ }
11
+
12
+ function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
13
+ return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
14
+ }
15
+
16
+ function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
17
+ return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
18
+ }
19
+
20
+ function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {
21
+ return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
22
+ }
23
+
24
+ function DialogOverlay({
25
+ className,
26
+ ...props
27
+ }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
28
+ return (
29
+ <DialogPrimitive.Overlay
30
+ data-slot="dialog-overlay"
31
+ className={cn(
32
+ "fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
33
+ className,
34
+ )}
35
+ {...props}
36
+ />
37
+ );
38
+ }
39
+
40
+ function DialogContent({
41
+ className,
42
+ children,
43
+ showCloseButton = true,
44
+ ...props
45
+ }: React.ComponentProps<typeof DialogPrimitive.Content> & {
46
+ showCloseButton?: boolean;
47
+ }) {
48
+ return (
49
+ <DialogPortal data-slot="dialog-portal">
50
+ <DialogOverlay />
51
+ <DialogPrimitive.Content
52
+ data-slot="dialog-content"
53
+ className={cn(
54
+ "fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg",
55
+ className,
56
+ )}
57
+ {...props}
58
+ >
59
+ {children}
60
+ {showCloseButton && (
61
+ <DialogPrimitive.Close
62
+ data-slot="dialog-close"
63
+ className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
64
+ >
65
+ <XIcon />
66
+ <span className="sr-only">Close</span>
67
+ </DialogPrimitive.Close>
68
+ )}
69
+ </DialogPrimitive.Content>
70
+ </DialogPortal>
71
+ );
72
+ }
73
+
74
+ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
75
+ return (
76
+ <div
77
+ data-slot="dialog-header"
78
+ className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
79
+ {...props}
80
+ />
81
+ );
82
+ }
83
+
84
+ function DialogFooter({
85
+ className,
86
+ showCloseButton = false,
87
+ children,
88
+ ...props
89
+ }: React.ComponentProps<"div"> & {
90
+ showCloseButton?: boolean;
91
+ }) {
92
+ return (
93
+ <div
94
+ data-slot="dialog-footer"
95
+ className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
96
+ {...props}
97
+ >
98
+ {children}
99
+ {showCloseButton && (
100
+ <DialogPrimitive.Close asChild>
101
+ <Button variant="outline">Close</Button>
102
+ </DialogPrimitive.Close>
103
+ )}
104
+ </div>
105
+ );
106
+ }
107
+
108
+ function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {
109
+ return (
110
+ <DialogPrimitive.Title
111
+ data-slot="dialog-title"
112
+ className={cn("min-w-0 wrap-anywhere text-lg leading-none font-semibold", className)}
113
+ {...props}
114
+ />
115
+ );
116
+ }
117
+
118
+ function DialogDescription({
119
+ className,
120
+ ...props
121
+ }: React.ComponentProps<typeof DialogPrimitive.Description>) {
122
+ return (
123
+ <DialogPrimitive.Description
124
+ data-slot="dialog-description"
125
+ className={cn("min-w-0 wrap-anywhere text-sm text-muted-foreground", className)}
126
+ {...props}
127
+ />
128
+ );
129
+ }
130
+
131
+ export {
132
+ Dialog,
133
+ DialogClose,
134
+ DialogContent,
135
+ DialogDescription,
136
+ DialogFooter,
137
+ DialogHeader,
138
+ DialogOverlay,
139
+ DialogPortal,
140
+ DialogTitle,
141
+ DialogTrigger,
142
+ };
@@ -0,0 +1,225 @@
1
+ "use client";
2
+
3
+ import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
4
+ import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
5
+ import type * as React from "react";
6
+
7
+ import { cn } from "@/lib/utils";
8
+
9
+ function DropdownMenu({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
10
+ return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
11
+ }
12
+
13
+ function DropdownMenuPortal({
14
+ ...props
15
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
16
+ return <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />;
17
+ }
18
+
19
+ function DropdownMenuTrigger({
20
+ ...props
21
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
22
+ return <DropdownMenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />;
23
+ }
24
+
25
+ function DropdownMenuContent({
26
+ className,
27
+ sideOffset = 4,
28
+ ...props
29
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
30
+ return (
31
+ <DropdownMenuPrimitive.Portal>
32
+ <DropdownMenuPrimitive.Content
33
+ data-slot="dropdown-menu-content"
34
+ sideOffset={sideOffset}
35
+ className={cn(
36
+ "z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
37
+ className,
38
+ )}
39
+ {...props}
40
+ />
41
+ </DropdownMenuPrimitive.Portal>
42
+ );
43
+ }
44
+
45
+ function DropdownMenuGroup({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
46
+ return <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />;
47
+ }
48
+
49
+ function DropdownMenuItem({
50
+ className,
51
+ inset,
52
+ variant = "default",
53
+ ...props
54
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
55
+ inset?: boolean;
56
+ variant?: "default" | "destructive";
57
+ }) {
58
+ return (
59
+ <DropdownMenuPrimitive.Item
60
+ data-slot="dropdown-menu-item"
61
+ data-inset={inset}
62
+ data-variant={variant}
63
+ className={cn(
64
+ // min-h-9: target sentuh nyaman di layar HP (bukan baris tipis).
65
+ "relative flex min-h-9 cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden transition-colors select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:[&_svg:not([class*='text-'])]:text-destructive",
66
+ className,
67
+ )}
68
+ {...props}
69
+ />
70
+ );
71
+ }
72
+
73
+ function DropdownMenuCheckboxItem({
74
+ className,
75
+ children,
76
+ checked,
77
+ ...props
78
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
79
+ return (
80
+ <DropdownMenuPrimitive.CheckboxItem
81
+ data-slot="dropdown-menu-checkbox-item"
82
+ className={cn(
83
+ "relative flex min-h-9 cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden transition-colors select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
84
+ className,
85
+ )}
86
+ checked={checked}
87
+ {...props}
88
+ >
89
+ <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
90
+ <DropdownMenuPrimitive.ItemIndicator>
91
+ <CheckIcon className="size-4" />
92
+ </DropdownMenuPrimitive.ItemIndicator>
93
+ </span>
94
+ {children}
95
+ </DropdownMenuPrimitive.CheckboxItem>
96
+ );
97
+ }
98
+
99
+ function DropdownMenuRadioGroup({
100
+ ...props
101
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
102
+ return <DropdownMenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />;
103
+ }
104
+
105
+ function DropdownMenuRadioItem({
106
+ className,
107
+ children,
108
+ ...props
109
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
110
+ return (
111
+ <DropdownMenuPrimitive.RadioItem
112
+ data-slot="dropdown-menu-radio-item"
113
+ className={cn(
114
+ "relative flex min-h-9 cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden transition-colors select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
115
+ className,
116
+ )}
117
+ {...props}
118
+ >
119
+ <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
120
+ <DropdownMenuPrimitive.ItemIndicator>
121
+ <CircleIcon className="size-2 fill-current" />
122
+ </DropdownMenuPrimitive.ItemIndicator>
123
+ </span>
124
+ {children}
125
+ </DropdownMenuPrimitive.RadioItem>
126
+ );
127
+ }
128
+
129
+ function DropdownMenuLabel({
130
+ className,
131
+ inset,
132
+ ...props
133
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & { inset?: boolean }) {
134
+ return (
135
+ <DropdownMenuPrimitive.Label
136
+ data-slot="dropdown-menu-label"
137
+ data-inset={inset}
138
+ className={cn("px-2 py-1.5 text-sm font-medium data-[inset]:pl-8", className)}
139
+ {...props}
140
+ />
141
+ );
142
+ }
143
+
144
+ function DropdownMenuSeparator({
145
+ className,
146
+ ...props
147
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
148
+ return (
149
+ <DropdownMenuPrimitive.Separator
150
+ data-slot="dropdown-menu-separator"
151
+ className={cn("-mx-1 my-1 h-px bg-border", className)}
152
+ {...props}
153
+ />
154
+ );
155
+ }
156
+
157
+ function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<"span">) {
158
+ return (
159
+ <span
160
+ data-slot="dropdown-menu-shortcut"
161
+ className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)}
162
+ {...props}
163
+ />
164
+ );
165
+ }
166
+
167
+ function DropdownMenuSub({ ...props }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
168
+ return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
169
+ }
170
+
171
+ function DropdownMenuSubTrigger({
172
+ className,
173
+ inset,
174
+ children,
175
+ ...props
176
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & { inset?: boolean }) {
177
+ return (
178
+ <DropdownMenuPrimitive.SubTrigger
179
+ data-slot="dropdown-menu-sub-trigger"
180
+ data-inset={inset}
181
+ className={cn(
182
+ "flex min-h-9 cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[inset]:pl-8 data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
183
+ className,
184
+ )}
185
+ {...props}
186
+ >
187
+ {children}
188
+ <ChevronRightIcon className="ml-auto size-4" />
189
+ </DropdownMenuPrimitive.SubTrigger>
190
+ );
191
+ }
192
+
193
+ function DropdownMenuSubContent({
194
+ className,
195
+ ...props
196
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
197
+ return (
198
+ <DropdownMenuPrimitive.SubContent
199
+ data-slot="dropdown-menu-sub-content"
200
+ className={cn(
201
+ "z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
202
+ className,
203
+ )}
204
+ {...props}
205
+ />
206
+ );
207
+ }
208
+
209
+ export {
210
+ DropdownMenu,
211
+ DropdownMenuCheckboxItem,
212
+ DropdownMenuContent,
213
+ DropdownMenuGroup,
214
+ DropdownMenuItem,
215
+ DropdownMenuLabel,
216
+ DropdownMenuPortal,
217
+ DropdownMenuRadioGroup,
218
+ DropdownMenuRadioItem,
219
+ DropdownMenuSeparator,
220
+ DropdownMenuShortcut,
221
+ DropdownMenuSub,
222
+ DropdownMenuSubContent,
223
+ DropdownMenuSubTrigger,
224
+ DropdownMenuTrigger,
225
+ };
@@ -0,0 +1,94 @@
1
+ import { cva, type VariantProps } from "class-variance-authority";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ function Empty({ className, ...props }: React.ComponentProps<"div">) {
6
+ return (
7
+ <div
8
+ data-slot="empty"
9
+ className={cn(
10
+ "flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12",
11
+ className,
12
+ )}
13
+ {...props}
14
+ />
15
+ );
16
+ }
17
+
18
+ function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
19
+ return (
20
+ <div
21
+ data-slot="empty-header"
22
+ className={cn("flex max-w-sm flex-col items-center gap-2 text-center", className)}
23
+ {...props}
24
+ />
25
+ );
26
+ }
27
+
28
+ const emptyMediaVariants = cva(
29
+ "mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
30
+ {
31
+ variants: {
32
+ variant: {
33
+ default: "bg-transparent",
34
+ icon: "flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-6",
35
+ },
36
+ },
37
+ defaultVariants: {
38
+ variant: "default",
39
+ },
40
+ },
41
+ );
42
+
43
+ function EmptyMedia({
44
+ className,
45
+ variant = "default",
46
+ ...props
47
+ }: React.ComponentProps<"div"> & VariantProps<typeof emptyMediaVariants>) {
48
+ return (
49
+ <div
50
+ data-slot="empty-icon"
51
+ data-variant={variant}
52
+ className={cn(emptyMediaVariants({ variant, className }))}
53
+ {...props}
54
+ />
55
+ );
56
+ }
57
+
58
+ function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
59
+ return (
60
+ <div
61
+ data-slot="empty-title"
62
+ className={cn("text-xl font-medium tracking-tight", className)}
63
+ {...props}
64
+ />
65
+ );
66
+ }
67
+
68
+ function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
69
+ return (
70
+ <div
71
+ data-slot="empty-description"
72
+ className={cn(
73
+ "text-base/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
74
+ className,
75
+ )}
76
+ {...props}
77
+ />
78
+ );
79
+ }
80
+
81
+ function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
82
+ return (
83
+ <div
84
+ data-slot="empty-content"
85
+ className={cn(
86
+ "flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-base text-balance",
87
+ className,
88
+ )}
89
+ {...props}
90
+ />
91
+ );
92
+ }
93
+
94
+ export { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle };
@@ -0,0 +1,234 @@
1
+ import { cva, type VariantProps } from "class-variance-authority";
2
+ import { useMemo } from "react";
3
+ import { Label } from "@/components/ui/label";
4
+ import { Separator } from "@/components/ui/separator";
5
+ import { cn } from "@/lib/utils";
6
+
7
+ function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
8
+ return (
9
+ <fieldset
10
+ data-slot="field-set"
11
+ className={cn(
12
+ "flex flex-col gap-6",
13
+ "has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
14
+ className,
15
+ )}
16
+ {...props}
17
+ />
18
+ );
19
+ }
20
+
21
+ function FieldLegend({
22
+ className,
23
+ variant = "legend",
24
+ ...props
25
+ }: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
26
+ return (
27
+ <legend
28
+ data-slot="field-legend"
29
+ data-variant={variant}
30
+ className={cn(
31
+ "mb-3 font-medium",
32
+ "data-[variant=legend]:text-base",
33
+ "data-[variant=label]:text-sm",
34
+ className,
35
+ )}
36
+ {...props}
37
+ />
38
+ );
39
+ }
40
+
41
+ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
42
+ return (
43
+ <div
44
+ data-slot="field-group"
45
+ className={cn(
46
+ "group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4",
47
+ className,
48
+ )}
49
+ {...props}
50
+ />
51
+ );
52
+ }
53
+
54
+ const fieldVariants = cva("group/field flex w-full gap-3 data-[invalid=true]:text-destructive", {
55
+ variants: {
56
+ orientation: {
57
+ vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"],
58
+ horizontal: [
59
+ "flex-row items-center",
60
+ "[&>[data-slot=field-label]]:flex-auto",
61
+ "has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
62
+ ],
63
+ responsive: [
64
+ "flex-col @md/field-group:flex-row @md/field-group:items-center [&>*]:w-full @md/field-group:[&>*]:w-auto [&>.sr-only]:w-auto",
65
+ "@md/field-group:[&>[data-slot=field-label]]:flex-auto",
66
+ "@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
67
+ ],
68
+ },
69
+ },
70
+ defaultVariants: {
71
+ orientation: "vertical",
72
+ },
73
+ });
74
+
75
+ function Field({
76
+ className,
77
+ orientation = "vertical",
78
+ ...props
79
+ }: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
80
+ return (
81
+ // biome-ignore lint/a11y/useSemanticElements: div role=group dari shadcn upstream
82
+ <div
83
+ role="group"
84
+ data-slot="field"
85
+ data-orientation={orientation}
86
+ className={cn(fieldVariants({ orientation }), className)}
87
+ {...props}
88
+ />
89
+ );
90
+ }
91
+
92
+ function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
93
+ return (
94
+ <div
95
+ data-slot="field-content"
96
+ className={cn("group/field-content flex flex-1 flex-col gap-1.5 leading-snug", className)}
97
+ {...props}
98
+ />
99
+ );
100
+ }
101
+
102
+ function FieldLabel({ className, ...props }: React.ComponentProps<typeof Label>) {
103
+ return (
104
+ <Label
105
+ data-slot="field-label"
106
+ className={cn(
107
+ "group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50",
108
+ "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4",
109
+ "has-data-[state=checked]:border-primary has-data-[state=checked]:bg-primary/5 dark:has-data-[state=checked]:bg-primary/10",
110
+ className,
111
+ )}
112
+ {...props}
113
+ />
114
+ );
115
+ }
116
+
117
+ function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
118
+ return (
119
+ <div
120
+ data-slot="field-label"
121
+ className={cn(
122
+ "flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50",
123
+ className,
124
+ )}
125
+ {...props}
126
+ />
127
+ );
128
+ }
129
+
130
+ function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
131
+ return (
132
+ <p
133
+ data-slot="field-description"
134
+ className={cn(
135
+ "text-sm leading-normal font-normal text-muted-foreground group-has-[[data-orientation=horizontal]]/field:text-balance",
136
+ "last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5",
137
+ "[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
138
+ className,
139
+ )}
140
+ {...props}
141
+ />
142
+ );
143
+ }
144
+
145
+ function FieldSeparator({
146
+ children,
147
+ className,
148
+ ...props
149
+ }: React.ComponentProps<"div"> & {
150
+ children?: React.ReactNode;
151
+ }) {
152
+ return (
153
+ <div
154
+ data-slot="field-separator"
155
+ data-content={!!children}
156
+ className={cn(
157
+ "relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
158
+ className,
159
+ )}
160
+ {...props}
161
+ >
162
+ <Separator className="absolute inset-0 top-1/2" />
163
+ {children && (
164
+ <span
165
+ className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
166
+ data-slot="field-separator-content"
167
+ >
168
+ {children}
169
+ </span>
170
+ )}
171
+ </div>
172
+ );
173
+ }
174
+
175
+ function FieldError({
176
+ className,
177
+ children,
178
+ errors,
179
+ ...props
180
+ }: React.ComponentProps<"div"> & {
181
+ errors?: Array<{ message?: string } | undefined>;
182
+ }) {
183
+ const content = useMemo(() => {
184
+ if (children) {
185
+ return children;
186
+ }
187
+
188
+ if (!errors?.length) {
189
+ return null;
190
+ }
191
+
192
+ const uniqueErrors = [...new Map(errors.map((error) => [error?.message, error])).values()];
193
+
194
+ if (uniqueErrors?.length === 1) {
195
+ return uniqueErrors[0]?.message;
196
+ }
197
+
198
+ return (
199
+ <ul className="ml-4 flex list-disc flex-col gap-1">
200
+ {uniqueErrors.map((error) =>
201
+ error?.message ? <li key={error.message}>{error.message}</li> : null,
202
+ )}
203
+ </ul>
204
+ );
205
+ }, [children, errors]);
206
+
207
+ if (!content) {
208
+ return null;
209
+ }
210
+
211
+ return (
212
+ <div
213
+ role="alert"
214
+ data-slot="field-error"
215
+ className={cn("text-sm font-normal text-destructive", className)}
216
+ {...props}
217
+ >
218
+ {content}
219
+ </div>
220
+ );
221
+ }
222
+
223
+ export {
224
+ Field,
225
+ FieldContent,
226
+ FieldDescription,
227
+ FieldError,
228
+ FieldGroup,
229
+ FieldLabel,
230
+ FieldLegend,
231
+ FieldSeparator,
232
+ FieldSet,
233
+ FieldTitle,
234
+ };