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,161 @@
1
+ import { cva, type VariantProps } from "class-variance-authority";
2
+ import type * as React from "react";
3
+ import { Button } from "@/components/ui/button";
4
+ import { Input } from "@/components/ui/input";
5
+ import { Textarea } from "@/components/ui/textarea";
6
+ import { cn } from "@/lib/utils";
7
+
8
+ function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
9
+ return (
10
+ // biome-ignore lint/a11y/useSemanticElements: div role=group dari shadcn upstream
11
+ <div
12
+ data-slot="input-group"
13
+ role="group"
14
+ className={cn(
15
+ "group/input-group relative flex w-full items-center rounded-md border border-input shadow-xs transition-[color,box-shadow] outline-none dark:bg-input/30",
16
+ "h-9 min-w-0 has-[>textarea]:h-auto",
17
+
18
+ // Variants based on alignment.
19
+ "has-[>[data-align=inline-start]]:[&>input]:pl-2",
20
+ "has-[>[data-align=inline-end]]:[&>input]:pr-2",
21
+ "has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
22
+ "has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
23
+
24
+ // Focus state.
25
+ "has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-[3px] has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50",
26
+
27
+ // Error state.
28
+ "has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
29
+
30
+ className,
31
+ )}
32
+ {...props}
33
+ />
34
+ );
35
+ }
36
+
37
+ const inputGroupAddonVariants = cva(
38
+ "flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",
39
+ {
40
+ variants: {
41
+ align: {
42
+ "inline-start": "order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]",
43
+ "inline-end": "order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]",
44
+ "block-start":
45
+ "order-first w-full justify-start px-3 pt-3 group-has-[>input]/input-group:pt-2.5 [.border-b]:pb-3",
46
+ "block-end":
47
+ "order-last w-full justify-start px-3 pb-3 group-has-[>input]/input-group:pb-2.5 [.border-t]:pt-3",
48
+ },
49
+ },
50
+ defaultVariants: {
51
+ align: "inline-start",
52
+ },
53
+ },
54
+ );
55
+
56
+ function InputGroupAddon({
57
+ className,
58
+ align = "inline-start",
59
+ ...props
60
+ }: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {
61
+ return (
62
+ // biome-ignore lint/a11y/useSemanticElements: div role=group dari shadcn upstream
63
+ // biome-ignore lint/a11y/useKeyWithClickEvents: klik area addon hanya memfokuskan kontrol (kontrol sendiri sudah dapat difokus keyboard)
64
+ <div
65
+ role="group"
66
+ data-slot="input-group-addon"
67
+ data-align={align}
68
+ className={cn(inputGroupAddonVariants({ align }), className)}
69
+ onClick={(e) => {
70
+ // Klik dari konten portal (Dialog/Popover milik tombol di addon)
71
+ // tetap bubble lewat React — jangan curi fokusnya ke textarea.
72
+ if (!e.currentTarget.contains(e.target as Node)) return;
73
+ if ((e.target as HTMLElement).closest("button")) {
74
+ return;
75
+ }
76
+ e.currentTarget.parentElement?.querySelector<HTMLElement>("input, textarea")?.focus();
77
+ }}
78
+ {...props}
79
+ />
80
+ );
81
+ }
82
+
83
+ const inputGroupButtonVariants = cva("flex items-center gap-2 text-sm shadow-none", {
84
+ variants: {
85
+ size: {
86
+ xs: "h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-2 has-[>svg]:px-2 [&>svg:not([class*='size-'])]:size-3.5",
87
+ sm: "h-8 gap-1.5 rounded-md px-2.5 has-[>svg]:px-2.5",
88
+ "icon-xs": "size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
89
+ "icon-sm": "size-8 p-0 has-[>svg]:p-0",
90
+ },
91
+ },
92
+ defaultVariants: {
93
+ size: "xs",
94
+ },
95
+ });
96
+
97
+ function InputGroupButton({
98
+ className,
99
+ type = "button",
100
+ variant = "ghost",
101
+ size = "xs",
102
+ ...props
103
+ }: Omit<React.ComponentProps<typeof Button>, "size"> &
104
+ VariantProps<typeof inputGroupButtonVariants>) {
105
+ return (
106
+ <Button
107
+ type={type}
108
+ data-size={size}
109
+ variant={variant}
110
+ className={cn(inputGroupButtonVariants({ size }), className)}
111
+ {...props}
112
+ />
113
+ );
114
+ }
115
+
116
+ function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
117
+ return (
118
+ <span
119
+ className={cn(
120
+ "flex items-center gap-2 text-sm text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
121
+ className,
122
+ )}
123
+ {...props}
124
+ />
125
+ );
126
+ }
127
+
128
+ function InputGroupInput({ className, ...props }: React.ComponentProps<"input">) {
129
+ return (
130
+ <Input
131
+ data-slot="input-group-control"
132
+ className={cn(
133
+ "flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent",
134
+ className,
135
+ )}
136
+ {...props}
137
+ />
138
+ );
139
+ }
140
+
141
+ function InputGroupTextarea({ className, ...props }: React.ComponentProps<"textarea">) {
142
+ return (
143
+ <Textarea
144
+ data-slot="input-group-control"
145
+ className={cn(
146
+ "flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent",
147
+ className,
148
+ )}
149
+ {...props}
150
+ />
151
+ );
152
+ }
153
+
154
+ export {
155
+ InputGroup,
156
+ InputGroupAddon,
157
+ InputGroupButton,
158
+ InputGroupInput,
159
+ InputGroupText,
160
+ InputGroupTextarea,
161
+ };
@@ -0,0 +1,21 @@
1
+ import type * as React from "react";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
6
+ return (
7
+ <input
8
+ type={type}
9
+ data-slot="input"
10
+ className={cn(
11
+ "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
12
+ "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
13
+ "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
14
+ className,
15
+ )}
16
+ {...props}
17
+ />
18
+ );
19
+ }
20
+
21
+ export { Input };
@@ -0,0 +1,19 @@
1
+ import { Label as LabelPrimitive } from "radix-ui";
2
+ import type * as React from "react";
3
+
4
+ import { cn } from "@/lib/utils";
5
+
6
+ function Label({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) {
7
+ return (
8
+ <LabelPrimitive.Root
9
+ data-slot="label"
10
+ className={cn(
11
+ "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
12
+ className,
13
+ )}
14
+ {...props}
15
+ />
16
+ );
17
+ }
18
+
19
+ export { Label };
@@ -0,0 +1,131 @@
1
+ import {
2
+ MessageScroller as MessageScrollerPrimitive,
3
+ useMessageScroller,
4
+ useMessageScrollerScrollable,
5
+ useMessageScrollerVisibility,
6
+ } from "@shadcn/react/message-scroller";
7
+ import { ArrowDownIcon } from "lucide-react";
8
+ import type * as React from "react";
9
+ import { Button } from "@/components/ui/button";
10
+ import { cn } from "@/lib/utils";
11
+
12
+ function MessageScrollerProvider(
13
+ props: React.ComponentProps<typeof MessageScrollerPrimitive.Provider>,
14
+ ) {
15
+ return <MessageScrollerPrimitive.Provider {...props} />;
16
+ }
17
+
18
+ function MessageScroller({
19
+ className,
20
+ ...props
21
+ }: React.ComponentProps<typeof MessageScrollerPrimitive.Root>) {
22
+ return (
23
+ <MessageScrollerPrimitive.Root
24
+ data-slot="message-scroller"
25
+ className={cn(
26
+ "group/message-scroller relative flex size-full min-h-0 flex-col overflow-hidden",
27
+ className,
28
+ )}
29
+ {...props}
30
+ />
31
+ );
32
+ }
33
+
34
+ function MessageScrollerViewport({
35
+ className,
36
+ ...props
37
+ }: React.ComponentProps<typeof MessageScrollerPrimitive.Viewport>) {
38
+ return (
39
+ <MessageScrollerPrimitive.Viewport
40
+ data-slot="message-scroller-viewport"
41
+ className={cn(
42
+ "size-full min-h-0 min-w-0 scroll-fade-b scrollbar-thin scrollbar-gutter-stable overflow-y-auto overscroll-contain contain-content data-autoscrolling:scrollbar-none",
43
+ className,
44
+ )}
45
+ {...props}
46
+ />
47
+ );
48
+ }
49
+
50
+ function MessageScrollerContent({
51
+ className,
52
+ ...props
53
+ }: React.ComponentProps<typeof MessageScrollerPrimitive.Content>) {
54
+ return (
55
+ <MessageScrollerPrimitive.Content
56
+ data-slot="message-scroller-content"
57
+ className={cn("flex h-max min-h-full flex-col gap-8", className)}
58
+ {...props}
59
+ />
60
+ );
61
+ }
62
+
63
+ function MessageScrollerItem({
64
+ className,
65
+ scrollAnchor = false,
66
+ ...props
67
+ }: React.ComponentProps<typeof MessageScrollerPrimitive.Item>) {
68
+ return (
69
+ <MessageScrollerPrimitive.Item
70
+ data-slot="message-scroller-item"
71
+ scrollAnchor={scrollAnchor}
72
+ className={cn(
73
+ "min-w-0 shrink-0 [contain-intrinsic-size:auto_10rem] [content-visibility:auto]",
74
+ className,
75
+ )}
76
+ {...props}
77
+ />
78
+ );
79
+ }
80
+
81
+ function MessageScrollerButton({
82
+ direction = "end",
83
+ className,
84
+ children,
85
+ render,
86
+ variant = "secondary",
87
+ size = "icon-sm",
88
+ ...props
89
+ }: React.ComponentProps<typeof MessageScrollerPrimitive.Button> &
90
+ Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
91
+ return (
92
+ <MessageScrollerPrimitive.Button
93
+ data-slot="message-scroller-button"
94
+ data-direction={direction}
95
+ data-variant={variant}
96
+ data-size={size}
97
+ direction={direction}
98
+ className={cn(
99
+ // `left-1/2` + `-translate-x-1/2` memusatkan tombol secara horizontal.
100
+ // Sebelumnya `inset-s-1/2` dipakai, tapi itu bukan utility Tailwind
101
+ // yang valid — tidak ada CSS yang di-emit, sehingga tombol kehilangan
102
+ // posisi horizontalnya dan tergeser ke kiri oleh translate.
103
+ "absolute left-1/2 -translate-x-1/2 border-border bg-background text-foreground transition-[translate,scale,opacity] duration-200 hover:bg-muted hover:text-foreground data-[active=false]:pointer-events-none data-[active=false]:scale-95 data-[active=false]:opacity-0 data-[active=false]:duration-400 data-[active=false]:ease-[cubic-bezier(0.7,0,0.84,0)] data-[active=true]:translate-y-0 data-[active=true]:scale-100 data-[active=true]:opacity-100 data-[active=true]:ease-[cubic-bezier(0.23,1,0.32,1)] data-[direction=end]:bottom-4 data-[direction=end]:data-[active=false]:translate-y-full data-[direction=start]:top-4 data-[direction=start]:data-[active=false]:-translate-y-full data-[direction=start]:[&_svg]:rotate-180",
104
+ className,
105
+ )}
106
+ render={render ?? <Button variant={variant} size={size} />}
107
+ {...props}
108
+ >
109
+ {children ?? (
110
+ <>
111
+ <ArrowDownIcon />
112
+ <span className="sr-only">
113
+ {direction === "end" ? "Scroll to end" : "Scroll to start"}
114
+ </span>
115
+ </>
116
+ )}
117
+ </MessageScrollerPrimitive.Button>
118
+ );
119
+ }
120
+
121
+ export {
122
+ MessageScroller,
123
+ MessageScrollerButton,
124
+ MessageScrollerContent,
125
+ MessageScrollerItem,
126
+ MessageScrollerProvider,
127
+ MessageScrollerViewport,
128
+ useMessageScroller,
129
+ useMessageScrollerScrollable,
130
+ useMessageScrollerVisibility,
131
+ };
@@ -0,0 +1,85 @@
1
+ import type * as React from "react";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ function MessageGroup({ className, ...props }: React.ComponentProps<"div">) {
6
+ return (
7
+ <div
8
+ data-slot="message-group"
9
+ className={cn("flex min-w-0 flex-col gap-2", className)}
10
+ {...props}
11
+ />
12
+ );
13
+ }
14
+
15
+ function Message({
16
+ className,
17
+ align = "start",
18
+ ...props
19
+ }: React.ComponentProps<"div"> & { align?: "start" | "end" }) {
20
+ return (
21
+ <div
22
+ data-slot="message"
23
+ data-align={align}
24
+ className={cn(
25
+ "group/message relative flex w-full min-w-0 gap-2 text-base data-[align=end]:flex-row-reverse",
26
+ className,
27
+ )}
28
+ {...props}
29
+ />
30
+ );
31
+ }
32
+
33
+ function MessageAvatar({ className, ...props }: React.ComponentProps<"div">) {
34
+ return (
35
+ <div
36
+ data-slot="message-avatar"
37
+ className={cn(
38
+ "flex w-fit min-w-8 shrink-0 items-center justify-center self-end overflow-hidden rounded-full bg-muted group-has-data-[slot=message-footer]/message:-translate-y-8",
39
+ className,
40
+ )}
41
+ {...props}
42
+ />
43
+ );
44
+ }
45
+
46
+ function MessageContent({ className, ...props }: React.ComponentProps<"div">) {
47
+ return (
48
+ <div
49
+ data-slot="message-content"
50
+ className={cn(
51
+ "flex w-full min-w-0 flex-col gap-2.5 wrap-break-word group-data-[align=end]/message:*:data-slot:self-end",
52
+ className,
53
+ )}
54
+ {...props}
55
+ />
56
+ );
57
+ }
58
+
59
+ function MessageHeader({ className, ...props }: React.ComponentProps<"div">) {
60
+ return (
61
+ <div
62
+ data-slot="message-header"
63
+ className={cn(
64
+ "flex max-w-full min-w-0 items-center px-3 text-xs font-medium text-muted-foreground group-has-data-[variant=ghost]/message:px-0",
65
+ className,
66
+ )}
67
+ {...props}
68
+ />
69
+ );
70
+ }
71
+
72
+ function MessageFooter({ className, ...props }: React.ComponentProps<"div">) {
73
+ return (
74
+ <div
75
+ data-slot="message-footer"
76
+ className={cn(
77
+ "flex max-w-full min-w-0 items-center px-3 text-xs font-medium text-muted-foreground group-has-data-[variant=ghost]/message:px-0 group-data-[align=end]/message:justify-end",
78
+ className,
79
+ )}
80
+ {...props}
81
+ />
82
+ );
83
+ }
84
+
85
+ export { Message, MessageAvatar, MessageContent, MessageFooter, MessageGroup, MessageHeader };
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Popover — pembungkus tipis `radix-ui` Popover (pola komponen ui lain:
3
+ * dropdown-menu, dialog). Konten melayang dengan panah opsional.
4
+ */
5
+ import { Popover as PopoverPrimitive } from "radix-ui";
6
+ import type * as React from "react";
7
+ import { cn } from "@/lib/utils";
8
+
9
+ function Popover({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Root>) {
10
+ return <PopoverPrimitive.Root data-slot="popover" {...props} />;
11
+ }
12
+
13
+ function PopoverTrigger({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
14
+ return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;
15
+ }
16
+
17
+ function PopoverContent({
18
+ className,
19
+ align = "center",
20
+ sideOffset = 4,
21
+ ...props
22
+ }: React.ComponentProps<typeof PopoverPrimitive.Content>) {
23
+ return (
24
+ <PopoverPrimitive.Portal>
25
+ <PopoverPrimitive.Content
26
+ data-slot="popover-content"
27
+ align={align}
28
+ sideOffset={sideOffset}
29
+ className={cn(
30
+ "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out",
31
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95",
32
+ "data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2",
33
+ "z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-1 shadow-md outline-hidden",
34
+ className,
35
+ )}
36
+ {...props}
37
+ />
38
+ </PopoverPrimitive.Portal>
39
+ );
40
+ }
41
+
42
+ function PopoverAnchor({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
43
+ return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;
44
+ }
45
+
46
+ export { Popover, PopoverAnchor, PopoverContent, PopoverTrigger };
@@ -0,0 +1,172 @@
1
+ "use client";
2
+
3
+ import * as SelectPrimitive from "@radix-ui/react-select";
4
+ import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
5
+ import type * as React from "react";
6
+
7
+ import { cn } from "@/lib/utils";
8
+
9
+ function Select({ ...props }: React.ComponentProps<typeof SelectPrimitive.Root>) {
10
+ return <SelectPrimitive.Root data-slot="select" {...props} />;
11
+ }
12
+
13
+ function SelectGroup({ ...props }: React.ComponentProps<typeof SelectPrimitive.Group>) {
14
+ return <SelectPrimitive.Group data-slot="select-group" {...props} />;
15
+ }
16
+
17
+ function SelectValue({ ...props }: React.ComponentProps<typeof SelectPrimitive.Value>) {
18
+ return <SelectPrimitive.Value data-slot="select-value" {...props} />;
19
+ }
20
+
21
+ function SelectTrigger({
22
+ className,
23
+ size = "default",
24
+ children,
25
+ ...props
26
+ }: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
27
+ size?: "sm" | "default";
28
+ }) {
29
+ return (
30
+ <SelectPrimitive.Trigger
31
+ data-slot="select-trigger"
32
+ data-size={size}
33
+ className={cn(
34
+ "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
35
+ className,
36
+ )}
37
+ {...props}
38
+ >
39
+ {children}
40
+ <SelectPrimitive.Icon asChild>
41
+ <ChevronDownIcon className="size-4 opacity-50" />
42
+ </SelectPrimitive.Icon>
43
+ </SelectPrimitive.Trigger>
44
+ );
45
+ }
46
+
47
+ function SelectContent({
48
+ className,
49
+ children,
50
+ position = "popper",
51
+ align = "center",
52
+ ...props
53
+ }: React.ComponentProps<typeof SelectPrimitive.Content>) {
54
+ return (
55
+ <SelectPrimitive.Portal>
56
+ <SelectPrimitive.Content
57
+ data-slot="select-content"
58
+ className={cn(
59
+ "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 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 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
60
+ position === "popper" &&
61
+ "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
62
+ className,
63
+ )}
64
+ position={position}
65
+ align={align}
66
+ {...props}
67
+ >
68
+ <SelectScrollUpButton />
69
+ <SelectPrimitive.Viewport
70
+ className={cn(
71
+ "p-1",
72
+ position === "popper" &&
73
+ "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1",
74
+ )}
75
+ >
76
+ {children}
77
+ </SelectPrimitive.Viewport>
78
+ <SelectScrollDownButton />
79
+ </SelectPrimitive.Content>
80
+ </SelectPrimitive.Portal>
81
+ );
82
+ }
83
+
84
+ function SelectLabel({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Label>) {
85
+ return (
86
+ <SelectPrimitive.Label
87
+ data-slot="select-label"
88
+ className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
89
+ {...props}
90
+ />
91
+ );
92
+ }
93
+
94
+ function SelectItem({
95
+ className,
96
+ children,
97
+ ...props
98
+ }: React.ComponentProps<typeof SelectPrimitive.Item>) {
99
+ return (
100
+ <SelectPrimitive.Item
101
+ data-slot="select-item"
102
+ className={cn(
103
+ "focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
104
+ className,
105
+ )}
106
+ {...props}
107
+ >
108
+ <span className="absolute right-2 flex size-3.5 items-center justify-center">
109
+ <SelectPrimitive.ItemIndicator>
110
+ <CheckIcon className="size-4" />
111
+ </SelectPrimitive.ItemIndicator>
112
+ </span>
113
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
114
+ </SelectPrimitive.Item>
115
+ );
116
+ }
117
+
118
+ function SelectSeparator({
119
+ className,
120
+ ...props
121
+ }: React.ComponentProps<typeof SelectPrimitive.Separator>) {
122
+ return (
123
+ <SelectPrimitive.Separator
124
+ data-slot="select-separator"
125
+ className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
126
+ {...props}
127
+ />
128
+ );
129
+ }
130
+
131
+ function SelectScrollUpButton({
132
+ className,
133
+ ...props
134
+ }: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
135
+ return (
136
+ <SelectPrimitive.ScrollUpButton
137
+ data-slot="select-scroll-up-button"
138
+ className={cn("flex cursor-default items-center justify-center py-1", className)}
139
+ {...props}
140
+ >
141
+ <ChevronUpIcon className="size-4" />
142
+ </SelectPrimitive.ScrollUpButton>
143
+ );
144
+ }
145
+
146
+ function SelectScrollDownButton({
147
+ className,
148
+ ...props
149
+ }: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
150
+ return (
151
+ <SelectPrimitive.ScrollDownButton
152
+ data-slot="select-scroll-down-button"
153
+ className={cn("flex cursor-default items-center justify-center py-1", className)}
154
+ {...props}
155
+ >
156
+ <ChevronDownIcon className="size-4" />
157
+ </SelectPrimitive.ScrollDownButton>
158
+ );
159
+ }
160
+
161
+ export {
162
+ Select,
163
+ SelectContent,
164
+ SelectGroup,
165
+ SelectItem,
166
+ SelectLabel,
167
+ SelectScrollDownButton,
168
+ SelectScrollUpButton,
169
+ SelectSeparator,
170
+ SelectTrigger,
171
+ SelectValue,
172
+ };
@@ -0,0 +1,28 @@
1
+ "use client";
2
+
3
+ import { Separator as SeparatorPrimitive } from "radix-ui";
4
+ import type * as React from "react";
5
+
6
+ import { cn } from "@/lib/utils";
7
+
8
+ function Separator({
9
+ className,
10
+ orientation = "horizontal",
11
+ decorative = true,
12
+ ...props
13
+ }: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
14
+ return (
15
+ <SeparatorPrimitive.Root
16
+ data-slot="separator"
17
+ decorative={decorative}
18
+ orientation={orientation}
19
+ className={cn(
20
+ "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
21
+ className,
22
+ )}
23
+ {...props}
24
+ />
25
+ );
26
+ }
27
+
28
+ export { Separator };