@tangle-network/ui 1.0.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 (220) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE +21 -0
  3. package/README.md +33 -0
  4. package/dist/active-sessions-store-CeOmXgv5.d.ts +85 -0
  5. package/dist/artifact-pane-DvJyPWV4.d.ts +24 -0
  6. package/dist/auth.d.ts +74 -0
  7. package/dist/auth.js +15 -0
  8. package/dist/button-CMQuQEW_.d.ts +17 -0
  9. package/dist/chat.d.ts +232 -0
  10. package/dist/chat.js +30 -0
  11. package/dist/chunk-2NFQRQOD.js +1009 -0
  12. package/dist/chunk-2VH6PUXD.js +186 -0
  13. package/dist/chunk-34A66VBG.js +214 -0
  14. package/dist/chunk-3OI2QKFD.js +0 -0
  15. package/dist/chunk-4CLN43XT.js +45 -0
  16. package/dist/chunk-54SQQMMM.js +156 -0
  17. package/dist/chunk-5Z5ZYMOJ.js +0 -0
  18. package/dist/chunk-66BNMOVT.js +167 -0
  19. package/dist/chunk-6BGQA4BQ.js +0 -0
  20. package/dist/chunk-7UO2ZMRQ.js +133 -0
  21. package/dist/chunk-BX6AQMUS.js +183 -0
  22. package/dist/chunk-CD53GZOM.js +59 -0
  23. package/dist/chunk-CSAIKY36.js +54 -0
  24. package/dist/chunk-EEE55AVS.js +1201 -0
  25. package/dist/chunk-GYPQXTJU.js +230 -0
  26. package/dist/chunk-HFL6R6IF.js +37 -0
  27. package/dist/chunk-HJKCSXCH.js +737 -0
  28. package/dist/chunk-LISXUB4D.js +1222 -0
  29. package/dist/chunk-LQS34IGP.js +0 -0
  30. package/dist/chunk-MKTSMWVD.js +109 -0
  31. package/dist/chunk-NKDZ7GZE.js +192 -0
  32. package/dist/chunk-OEX7NZE3.js +321 -0
  33. package/dist/chunk-Q56BYXQF.js +61 -0
  34. package/dist/chunk-Q7EIIWTC.js +0 -0
  35. package/dist/chunk-REJESC5U.js +117 -0
  36. package/dist/chunk-RQGKSCEZ.js +0 -0
  37. package/dist/chunk-RQHJBTEU.js +10 -0
  38. package/dist/chunk-TMFOPHHN.js +299 -0
  39. package/dist/chunk-XGKULLYE.js +40 -0
  40. package/dist/chunk-XIHMJ7ZQ.js +614 -0
  41. package/dist/chunk-YJ2G3XO5.js +1048 -0
  42. package/dist/chunk-YNN4O57I.js +754 -0
  43. package/dist/code-block-DjXf8eOG.d.ts +19 -0
  44. package/dist/document-editor-pane-A5LT5H4N.js +12 -0
  45. package/dist/document-editor-pane-DyDEX_Zm.d.ts +124 -0
  46. package/dist/editor.d.ts +120 -0
  47. package/dist/editor.js +34 -0
  48. package/dist/files.d.ts +175 -0
  49. package/dist/files.js +20 -0
  50. package/dist/hooks.d.ts +56 -0
  51. package/dist/hooks.js +41 -0
  52. package/dist/index.d.ts +43 -0
  53. package/dist/index.js +446 -0
  54. package/dist/markdown.d.ts +15 -0
  55. package/dist/markdown.js +14 -0
  56. package/dist/message-BHWbxBtT.d.ts +15 -0
  57. package/dist/openui.d.ts +115 -0
  58. package/dist/openui.js +12 -0
  59. package/dist/parts-dj7AcUg0.d.ts +36 -0
  60. package/dist/primitives.d.ts +332 -0
  61. package/dist/primitives.js +191 -0
  62. package/dist/run-PfLmDAox.d.ts +41 -0
  63. package/dist/run.d.ts +69 -0
  64. package/dist/run.js +36 -0
  65. package/dist/sdk-hooks.d.ts +285 -0
  66. package/dist/sdk-hooks.js +31 -0
  67. package/dist/stores.d.ts +17 -0
  68. package/dist/stores.js +76 -0
  69. package/dist/tool-call-feed-Bs3MyQMT.d.ts +68 -0
  70. package/dist/tool-display-z4JcDmMQ.d.ts +32 -0
  71. package/dist/tool-previews.d.ts +48 -0
  72. package/dist/tool-previews.js +21 -0
  73. package/dist/types.d.ts +19 -0
  74. package/dist/types.js +1 -0
  75. package/dist/utils.d.ts +45 -0
  76. package/dist/utils.js +32 -0
  77. package/package.json +193 -0
  78. package/src/auth/auth.tsx +228 -0
  79. package/src/auth/index.ts +13 -0
  80. package/src/auth/login-layout.tsx +46 -0
  81. package/src/chat/agent-timeline.stories.tsx +429 -0
  82. package/src/chat/agent-timeline.tsx +360 -0
  83. package/src/chat/chat-container.tsx +486 -0
  84. package/src/chat/chat-input.stories.tsx +142 -0
  85. package/src/chat/chat-input.tsx +389 -0
  86. package/src/chat/chat-message.stories.tsx +237 -0
  87. package/src/chat/chat-message.tsx +129 -0
  88. package/src/chat/index.ts +18 -0
  89. package/src/chat/message-list.stories.tsx +336 -0
  90. package/src/chat/message-list.tsx +79 -0
  91. package/src/chat/thinking-indicator.stories.tsx +56 -0
  92. package/src/chat/thinking-indicator.tsx +30 -0
  93. package/src/chat/user-message.stories.tsx +92 -0
  94. package/src/chat/user-message.tsx +43 -0
  95. package/src/editor/document-editor-pane.tsx +351 -0
  96. package/src/editor/editor-provider.tsx +428 -0
  97. package/src/editor/editor-toolbar.tsx +130 -0
  98. package/src/editor/index.ts +31 -0
  99. package/src/editor/markdown-conversion.ts +21 -0
  100. package/src/editor/markdown-document-editor.tsx +137 -0
  101. package/src/editor/tiptap-editor.tsx +331 -0
  102. package/src/editor/use-editor.ts +221 -0
  103. package/src/files/file-artifact-pane.tsx +183 -0
  104. package/src/files/file-preview.tsx +342 -0
  105. package/src/files/file-tabs.tsx +71 -0
  106. package/src/files/file-tree.tsx +258 -0
  107. package/src/files/index.ts +17 -0
  108. package/src/files/rich-file-tree.stories.tsx +104 -0
  109. package/src/files/rich-file-tree.test.tsx +42 -0
  110. package/src/files/rich-file-tree.tsx +232 -0
  111. package/src/hooks/index.ts +10 -0
  112. package/src/hooks/use-auth.ts +153 -0
  113. package/src/hooks/use-auto-scroll.ts +59 -0
  114. package/src/hooks/use-dropdown-menu.ts +40 -0
  115. package/src/hooks/use-live-time.test.tsx +40 -0
  116. package/src/hooks/use-live-time.ts +27 -0
  117. package/src/hooks/use-realtime-session.ts +319 -0
  118. package/src/hooks/use-run-collapse-state.ts +25 -0
  119. package/src/hooks/use-run-groups.ts +111 -0
  120. package/src/hooks/use-sdk-session.ts +575 -0
  121. package/src/hooks/use-sse-stream.ts +475 -0
  122. package/src/hooks/use-tool-call-stream.ts +96 -0
  123. package/src/index.ts +14 -0
  124. package/src/lib/utils.ts +6 -0
  125. package/src/markdown/code-block.tsx +198 -0
  126. package/src/markdown/index.ts +2 -0
  127. package/src/markdown/markdown.stories.tsx +190 -0
  128. package/src/markdown/markdown.tsx +62 -0
  129. package/src/openui/index.ts +20 -0
  130. package/src/openui/openui-artifact-renderer.tsx +542 -0
  131. package/src/primitives/artifact-pane.tsx +91 -0
  132. package/src/primitives/avatar.stories.tsx +95 -0
  133. package/src/primitives/avatar.tsx +47 -0
  134. package/src/primitives/badge.stories.tsx +57 -0
  135. package/src/primitives/badge.tsx +97 -0
  136. package/src/primitives/button.stories.tsx +48 -0
  137. package/src/primitives/button.tsx +115 -0
  138. package/src/primitives/card.stories.tsx +53 -0
  139. package/src/primitives/card.tsx +98 -0
  140. package/src/primitives/code-block.stories.tsx +115 -0
  141. package/src/primitives/code-block.tsx +22 -0
  142. package/src/primitives/design-tokens.stories.tsx +162 -0
  143. package/src/primitives/dialog.stories.tsx +176 -0
  144. package/src/primitives/dialog.tsx +137 -0
  145. package/src/primitives/drop-zone.stories.tsx +123 -0
  146. package/src/primitives/drop-zone.tsx +131 -0
  147. package/src/primitives/dropdown-menu.stories.tsx +122 -0
  148. package/src/primitives/dropdown-menu.tsx +214 -0
  149. package/src/primitives/empty-state.stories.tsx +81 -0
  150. package/src/primitives/empty-state.tsx +40 -0
  151. package/src/primitives/index.ts +118 -0
  152. package/src/primitives/input.stories.tsx +113 -0
  153. package/src/primitives/input.tsx +136 -0
  154. package/src/primitives/label.stories.tsx +84 -0
  155. package/src/primitives/label.tsx +24 -0
  156. package/src/primitives/progress.stories.tsx +93 -0
  157. package/src/primitives/progress.tsx +50 -0
  158. package/src/primitives/segmented-control.test.tsx +328 -0
  159. package/src/primitives/segmented-control.tsx +154 -0
  160. package/src/primitives/select.stories.tsx +164 -0
  161. package/src/primitives/select.tsx +158 -0
  162. package/src/primitives/sidebar-drop-zone.stories.tsx +100 -0
  163. package/src/primitives/sidebar-drop-zone.tsx +149 -0
  164. package/src/primitives/skeleton.stories.tsx +79 -0
  165. package/src/primitives/skeleton.tsx +55 -0
  166. package/src/primitives/stat-card.stories.tsx +137 -0
  167. package/src/primitives/stat-card.tsx +97 -0
  168. package/src/primitives/switch.stories.tsx +85 -0
  169. package/src/primitives/switch.tsx +28 -0
  170. package/src/primitives/table.stories.tsx +170 -0
  171. package/src/primitives/table.tsx +116 -0
  172. package/src/primitives/tabs.stories.tsx +180 -0
  173. package/src/primitives/tabs.tsx +71 -0
  174. package/src/primitives/terminal-display.stories.tsx +191 -0
  175. package/src/primitives/terminal-display.tsx +189 -0
  176. package/src/primitives/theme-toggle.stories.tsx +32 -0
  177. package/src/primitives/theme-toggle.tsx +96 -0
  178. package/src/primitives/toast.stories.tsx +155 -0
  179. package/src/primitives/toast.tsx +190 -0
  180. package/src/primitives/upload-progress.stories.tsx +120 -0
  181. package/src/primitives/upload-progress.tsx +110 -0
  182. package/src/run/expanded-tool-detail.stories.tsx +182 -0
  183. package/src/run/expanded-tool-detail.tsx +186 -0
  184. package/src/run/index.ts +13 -0
  185. package/src/run/inline-thinking-item.stories.tsx +136 -0
  186. package/src/run/inline-thinking-item.tsx +120 -0
  187. package/src/run/inline-tool-item.stories.tsx +222 -0
  188. package/src/run/inline-tool-item.tsx +190 -0
  189. package/src/run/run-group.stories.tsx +322 -0
  190. package/src/run/run-group.tsx +569 -0
  191. package/src/run/run-item-primitives.tsx +17 -0
  192. package/src/run/tool-call-feed.stories.tsx +294 -0
  193. package/src/run/tool-call-feed.tsx +192 -0
  194. package/src/run/tool-call-step.stories.tsx +198 -0
  195. package/src/run/tool-call-step.tsx +240 -0
  196. package/src/sdk-hooks.ts +38 -0
  197. package/src/stores/active-sessions-store.ts +455 -0
  198. package/src/stores/chat-store.ts +43 -0
  199. package/src/stores/index.ts +2 -0
  200. package/src/tool-previews/command-preview.tsx +116 -0
  201. package/src/tool-previews/diff-preview.tsx +85 -0
  202. package/src/tool-previews/glob-results-preview.tsx +98 -0
  203. package/src/tool-previews/grep-results-preview.tsx +157 -0
  204. package/src/tool-previews/index.ts +22 -0
  205. package/src/tool-previews/preview-primitives.tsx +84 -0
  206. package/src/tool-previews/question-preview.tsx +101 -0
  207. package/src/tool-previews/web-search-preview.tsx +117 -0
  208. package/src/tool-previews/write-file-preview.tsx +80 -0
  209. package/src/types/branding.ts +11 -0
  210. package/src/types/index.ts +5 -0
  211. package/src/types/message.ts +13 -0
  212. package/src/types/parts.ts +51 -0
  213. package/src/types/run.ts +56 -0
  214. package/src/types/tool-display.ts +41 -0
  215. package/src/utils/copy-text.ts +30 -0
  216. package/src/utils/format.test.ts +43 -0
  217. package/src/utils/format.ts +56 -0
  218. package/src/utils/index.ts +10 -0
  219. package/src/utils/time-ago.ts +9 -0
  220. package/src/utils/tool-display.ts +238 -0
package/package.json ADDED
@@ -0,0 +1,193 @@
1
+ {
2
+ "name": "@tangle-network/ui",
3
+ "version": "1.0.0",
4
+ "description": "Generic React UI components for Tangle products — primitives, chat, run, files, editor, markdown.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/tangle-network/brand",
10
+ "directory": "packages/ui"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/tangle-network/brand/issues"
14
+ },
15
+ "homepage": "https://github.com/tangle-network/brand",
16
+ "sideEffects": false,
17
+ "files": [
18
+ "dist",
19
+ "src",
20
+ "README.md",
21
+ "CHANGELOG.md",
22
+ "LICENSE"
23
+ ],
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.js",
28
+ "default": "./dist/index.js"
29
+ },
30
+ "./primitives": {
31
+ "types": "./dist/primitives.d.ts",
32
+ "import": "./dist/primitives.js",
33
+ "default": "./dist/primitives.js"
34
+ },
35
+ "./chat": {
36
+ "types": "./dist/chat.d.ts",
37
+ "import": "./dist/chat.js",
38
+ "default": "./dist/chat.js"
39
+ },
40
+ "./run": {
41
+ "types": "./dist/run.d.ts",
42
+ "import": "./dist/run.js",
43
+ "default": "./dist/run.js"
44
+ },
45
+ "./openui": {
46
+ "types": "./dist/openui.d.ts",
47
+ "import": "./dist/openui.js",
48
+ "default": "./dist/openui.js"
49
+ },
50
+ "./files": {
51
+ "types": "./dist/files.d.ts",
52
+ "import": "./dist/files.js",
53
+ "default": "./dist/files.js"
54
+ },
55
+ "./editor": {
56
+ "types": "./dist/editor.d.ts",
57
+ "import": "./dist/editor.js",
58
+ "default": "./dist/editor.js"
59
+ },
60
+ "./markdown": {
61
+ "types": "./dist/markdown.d.ts",
62
+ "import": "./dist/markdown.js",
63
+ "default": "./dist/markdown.js"
64
+ },
65
+ "./auth": {
66
+ "types": "./dist/auth.d.ts",
67
+ "import": "./dist/auth.js",
68
+ "default": "./dist/auth.js"
69
+ },
70
+ "./hooks": {
71
+ "types": "./dist/hooks.d.ts",
72
+ "import": "./dist/hooks.js",
73
+ "default": "./dist/hooks.js"
74
+ },
75
+ "./sdk-hooks": {
76
+ "types": "./dist/sdk-hooks.d.ts",
77
+ "import": "./dist/sdk-hooks.js",
78
+ "default": "./dist/sdk-hooks.js"
79
+ },
80
+ "./stores": {
81
+ "types": "./dist/stores.d.ts",
82
+ "import": "./dist/stores.js",
83
+ "default": "./dist/stores.js"
84
+ },
85
+ "./types": {
86
+ "types": "./dist/types.d.ts",
87
+ "import": "./dist/types.js",
88
+ "default": "./dist/types.js"
89
+ },
90
+ "./utils": {
91
+ "types": "./dist/utils.d.ts",
92
+ "import": "./dist/utils.js",
93
+ "default": "./dist/utils.js"
94
+ },
95
+ "./tool-previews": {
96
+ "types": "./dist/tool-previews.d.ts",
97
+ "import": "./dist/tool-previews.js",
98
+ "default": "./dist/tool-previews.js"
99
+ }
100
+ },
101
+ "dependencies": {
102
+ "@pierre/trees": "1.0.0-beta.3",
103
+ "@radix-ui/react-avatar": "^1.1.0",
104
+ "@radix-ui/react-collapsible": "^1.1.0",
105
+ "@radix-ui/react-dialog": "^1.1.0",
106
+ "@radix-ui/react-dropdown-menu": "^2.1.0",
107
+ "@radix-ui/react-label": "^2.1.0",
108
+ "@radix-ui/react-progress": "^1.1.0",
109
+ "@radix-ui/react-select": "^2.1.0",
110
+ "@radix-ui/react-slot": "^1.1.0",
111
+ "@radix-ui/react-switch": "^1.1.0",
112
+ "@radix-ui/react-tabs": "^1.1.0",
113
+ "@radix-ui/react-toast": "^1.2.0",
114
+ "class-variance-authority": "^0.7.0",
115
+ "clsx": "^2.1.1",
116
+ "lucide-react": "^0.469.0",
117
+ "marked": "^17.0.5",
118
+ "react-markdown": "^10.1.0",
119
+ "react-pdf": "^9.2.1",
120
+ "react-syntax-highlighter": "^16.1.1",
121
+ "rehype-sanitize": "^6.0.0",
122
+ "remark-gfm": "^4.0.1",
123
+ "tailwind-merge": "^3.0.2",
124
+ "turndown": "^7.2.2"
125
+ },
126
+ "peerDependencies": {
127
+ "react": "^18 || ^19",
128
+ "react-dom": "^18 || ^19",
129
+ "@tangle-network/brand": "^0.3.0"
130
+ },
131
+ "peerDependenciesMeta": {
132
+ "@nanostores/react": {
133
+ "optional": true
134
+ },
135
+ "nanostores": {
136
+ "optional": true
137
+ },
138
+ "@tanstack/react-query": {
139
+ "optional": true
140
+ },
141
+ "@hocuspocus/provider": {
142
+ "optional": true
143
+ },
144
+ "@tiptap/core": {
145
+ "optional": true
146
+ },
147
+ "@tiptap/react": {
148
+ "optional": true
149
+ },
150
+ "@tiptap/starter-kit": {
151
+ "optional": true
152
+ },
153
+ "@tiptap/extension-collaboration": {
154
+ "optional": true
155
+ },
156
+ "@tiptap/extension-collaboration-caret": {
157
+ "optional": true
158
+ },
159
+ "yjs": {
160
+ "optional": true
161
+ }
162
+ },
163
+ "devDependencies": {
164
+ "@hocuspocus/provider": "^2.15.0",
165
+ "@nanostores/react": "^0.8.4",
166
+ "@tiptap/core": "^3.20.4",
167
+ "@tiptap/extension-collaboration": "^3.20.4",
168
+ "@tiptap/extension-collaboration-caret": "^3.7.0",
169
+ "@tiptap/react": "^3.20.4",
170
+ "@tiptap/starter-kit": "^3.20.4",
171
+ "@types/react": "^19.0.0",
172
+ "@types/react-syntax-highlighter": "^15.5.13",
173
+ "@types/turndown": "^5.0.5",
174
+ "nanostores": "^0.11.3",
175
+ "react": "^19.0.0",
176
+ "react-dom": "^19.0.0",
177
+ "@storybook/react": "^8.6.18",
178
+ "tsup": "^8.3.5",
179
+ "typescript": "^5.6.0",
180
+ "yjs": "^13.6.0"
181
+ },
182
+ "keywords": [
183
+ "tangle",
184
+ "ui",
185
+ "react",
186
+ "components",
187
+ "design-system"
188
+ ],
189
+ "scripts": {
190
+ "build": "tsup && node ../../scripts/validate-dist.mjs",
191
+ "dev": "tsup --watch"
192
+ }
193
+ }
@@ -0,0 +1,228 @@
1
+ "use client";
2
+
3
+ import type * as React from "react";
4
+ import { cn } from "../lib/utils";
5
+ import { Avatar, AvatarFallback, AvatarImage } from "../primitives/avatar";
6
+ import { Button, type ButtonProps } from "../primitives/button";
7
+ import {
8
+ DropdownMenu,
9
+ DropdownMenuContent,
10
+ DropdownMenuItem,
11
+ DropdownMenuLabel,
12
+ DropdownMenuSeparator,
13
+ DropdownMenuTrigger,
14
+ } from "../primitives/dropdown-menu";
15
+
16
+ // GitHub icon SVG
17
+ function GitHubIcon({ className }: { className?: string }) {
18
+ return (
19
+ <svg
20
+ className={className}
21
+ viewBox="0 0 24 24"
22
+ fill="currentColor"
23
+ aria-hidden="true"
24
+ >
25
+ <path
26
+ fillRule="evenodd"
27
+ clipRule="evenodd"
28
+ d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
29
+ />
30
+ </svg>
31
+ );
32
+ }
33
+
34
+ export interface SessionUser {
35
+ customer_id: string;
36
+ email: string;
37
+ name?: string;
38
+ tier?: string;
39
+ github?: {
40
+ login: string;
41
+ connected: boolean;
42
+ } | null;
43
+ session_expires_at: string;
44
+ }
45
+
46
+ export interface GitHubLoginButtonProps extends Omit<ButtonProps, "onClick"> {
47
+ /** API base URL (defaults to /auth/github) */
48
+ authUrl?: string;
49
+ /** Product variant for styling */
50
+ variant?: "sandbox" | "default" | "outline";
51
+ }
52
+
53
+ export function GitHubLoginButton({
54
+ authUrl = "/auth/github",
55
+ variant = "default",
56
+ className,
57
+ children,
58
+ ...props
59
+ }: GitHubLoginButtonProps) {
60
+ return (
61
+ <Button
62
+ variant={variant}
63
+ className={cn("gap-2", className)}
64
+ onClick={() => {
65
+ window.location.href = authUrl;
66
+ }}
67
+ {...props}
68
+ >
69
+ <GitHubIcon className="h-5 w-5" />
70
+ {children ?? "Sign in with GitHub"}
71
+ </Button>
72
+ );
73
+ }
74
+
75
+ export interface UserMenuProps {
76
+ user: SessionUser;
77
+ /** API base URL for logout */
78
+ logoutUrl?: string;
79
+ /** Links to show in menu */
80
+ links?: Array<{
81
+ href: string;
82
+ label: string;
83
+ icon?: React.ReactNode;
84
+ }>;
85
+ /** Product variant for styling */
86
+ variant?: "sandbox";
87
+ /** Callback when logout is clicked */
88
+ onLogout?: () => void;
89
+ }
90
+
91
+ export function UserMenu({
92
+ user,
93
+ logoutUrl = "/auth/logout",
94
+ links = [],
95
+ variant = "sandbox",
96
+ onLogout,
97
+ }: UserMenuProps) {
98
+ const initials = user.name
99
+ ? user.name
100
+ .split(" ")
101
+ .map((n) => n[0])
102
+ .join("")
103
+ .toUpperCase()
104
+ .slice(0, 2)
105
+ : user.email.slice(0, 2).toUpperCase();
106
+
107
+ const avatarGradient = {
108
+ sandbox: "bg-[image:var(--accent-gradient-strong)]",
109
+ }[variant];
110
+
111
+ const handleLogout = () => {
112
+ if (onLogout) {
113
+ onLogout();
114
+ }
115
+ window.location.href = logoutUrl;
116
+ };
117
+
118
+ return (
119
+ <DropdownMenu>
120
+ <DropdownMenuTrigger asChild>
121
+ <button
122
+ type="button"
123
+ className="flex items-center gap-2 rounded-lg p-1.5 transition-colors hover:bg-accent focus:outline-none focus:ring-2 focus:ring-ring"
124
+ >
125
+ <Avatar className="h-8 w-8">
126
+ <AvatarImage src={undefined} alt={user.name ?? user.email} />
127
+ <AvatarFallback
128
+ className={cn(
129
+ "text-white text-xs",
130
+ avatarGradient,
131
+ )}
132
+ >
133
+ {initials}
134
+ </AvatarFallback>
135
+ </Avatar>
136
+ <div className="hidden text-left md:block">
137
+ <p className="font-medium text-sm">{user.name ?? user.email}</p>
138
+ {user.tier && (
139
+ <p className="text-muted-foreground text-xs capitalize">
140
+ {user.tier} Plan
141
+ </p>
142
+ )}
143
+ </div>
144
+ </button>
145
+ </DropdownMenuTrigger>
146
+ <DropdownMenuContent align="end" className="w-56">
147
+ <DropdownMenuLabel className="font-normal">
148
+ <div className="flex flex-col space-y-1">
149
+ <p className="font-medium text-sm">{user.name ?? "Account"}</p>
150
+ <p className="text-muted-foreground text-xs">{user.email}</p>
151
+ </div>
152
+ </DropdownMenuLabel>
153
+ <DropdownMenuSeparator />
154
+ {links.map((link) => (
155
+ <DropdownMenuItem key={link.href} asChild>
156
+ <a href={link.href} className="flex items-center gap-2">
157
+ {link.icon}
158
+ {link.label}
159
+ </a>
160
+ </DropdownMenuItem>
161
+ ))}
162
+ {links.length > 0 && <DropdownMenuSeparator />}
163
+ <DropdownMenuItem
164
+ onClick={handleLogout}
165
+ className="text-[var(--surface-danger-text)] focus:text-[var(--surface-danger-text)]"
166
+ >
167
+ Sign out
168
+ </DropdownMenuItem>
169
+ </DropdownMenuContent>
170
+ </DropdownMenu>
171
+ );
172
+ }
173
+
174
+ export interface AuthHeaderProps {
175
+ /** Current session user (null if not logged in) */
176
+ user: SessionUser | null;
177
+ /** Whether session is loading */
178
+ loading?: boolean;
179
+ /** Product variant */
180
+ variant?: "sandbox";
181
+ /** API base URL */
182
+ apiBaseUrl?: string;
183
+ /** Links for user menu */
184
+ menuLinks?: UserMenuProps["links"];
185
+ /** Custom className */
186
+ className?: string;
187
+ }
188
+
189
+ export function AuthHeader({
190
+ user,
191
+ loading = false,
192
+ variant = "sandbox",
193
+ apiBaseUrl = "",
194
+ menuLinks,
195
+ className,
196
+ }: AuthHeaderProps) {
197
+ if (loading) {
198
+ return (
199
+ <div className={cn("flex items-center gap-2", className)}>
200
+ <div className="h-8 w-8 animate-pulse rounded-full bg-muted" />
201
+ <div className="hidden md:block">
202
+ <div className="h-4 w-20 animate-pulse rounded bg-muted" />
203
+ </div>
204
+ </div>
205
+ );
206
+ }
207
+
208
+ if (!user) {
209
+ return (
210
+ <div className={cn("flex items-center gap-2", className)}>
211
+ <GitHubLoginButton
212
+ authUrl={`${apiBaseUrl}/auth/github`}
213
+ variant={variant}
214
+ size="sm"
215
+ />
216
+ </div>
217
+ );
218
+ }
219
+
220
+ return (
221
+ <UserMenu
222
+ user={user}
223
+ variant={variant}
224
+ logoutUrl={`${apiBaseUrl}/auth/logout`}
225
+ links={menuLinks}
226
+ />
227
+ );
228
+ }
@@ -0,0 +1,13 @@
1
+ export {
2
+ GitHubLoginButton,
3
+ type GitHubLoginButtonProps,
4
+ UserMenu,
5
+ type UserMenuProps,
6
+ AuthHeader,
7
+ type AuthHeaderProps,
8
+ type SessionUser,
9
+ } from "./auth";
10
+ export {
11
+ LoginLayout,
12
+ type LoginLayoutProps,
13
+ } from "./login-layout";
@@ -0,0 +1,46 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cn } from "../lib/utils"
5
+ import { Terminal } from "lucide-react"
6
+
7
+ export interface LoginLayoutProps {
8
+ title?: React.ReactNode
9
+ subtitle?: React.ReactNode
10
+ brandIcon?: React.ReactNode
11
+ children: React.ReactNode
12
+ className?: string
13
+ /** (Deprecated) terminal lines from legacy layout, kept for backwards compatibility */
14
+ terminalLines?: string[]
15
+ /** (Deprecated) tagline, kept for backwards compatibility */
16
+ tagline?: React.ReactNode
17
+ /** (Deprecated) footerLinks, kept for backwards compatibility */
18
+ footerLinks?: { label: string; href: string }[]
19
+ }
20
+
21
+ export function LoginLayout({
22
+ title = "Welcome Back",
23
+ subtitle = "Sign in to your workspace.",
24
+ brandIcon,
25
+ children,
26
+ className,
27
+ }: LoginLayoutProps) {
28
+ return (
29
+ <div className={cn("relative flex min-h-screen items-center justify-center bg-background overflow-hidden antialiased font-sans flex-col", className)}>
30
+ <div className="z-10 w-full max-w-md px-6 animate-in flex flex-col items-center">
31
+ {/* Header / Brand */}
32
+ <div className="mb-10 text-center flex flex-col items-center">
33
+ <div className="inline-flex h-14 w-14 mb-4 items-center justify-center rounded-lg bg-muted border border-border">
34
+ {brandIcon || <Terminal className="h-7 w-7 text-foreground" />}
35
+ </div>
36
+ <h1 className="text-2xl font-bold tracking-tight text-foreground font-display">{title}</h1>
37
+ <p className="mt-2 text-sm text-muted-foreground">{subtitle}</p>
38
+ </div>
39
+
40
+ <div className="w-full bg-card p-8 border border-border rounded-lg">
41
+ {children}
42
+ </div>
43
+ </div>
44
+ </div>
45
+ )
46
+ }