@hanzo/ui 8.0.7 → 8.0.11

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 (227) hide show
  1. package/dist/ThemeToggleNext-7727UR5W.js +4 -0
  2. package/dist/ThemeToggleNext-7727UR5W.js.map +1 -0
  3. package/dist/ThemeToggleNext-ICB5KL5J.cjs +19 -0
  4. package/dist/ThemeToggleNext-ICB5KL5J.cjs.map +1 -0
  5. package/dist/billing.cjs +15 -0
  6. package/dist/billing.cjs.map +1 -0
  7. package/{types → dist}/billing.d.ts +0 -1
  8. package/dist/billing.js +4 -0
  9. package/dist/billing.js.map +1 -0
  10. package/dist/canvas.cjs +15 -0
  11. package/dist/canvas.cjs.map +1 -0
  12. package/dist/canvas.d.ts +1 -0
  13. package/dist/canvas.js +4 -0
  14. package/dist/canvas.js.map +1 -0
  15. package/{types → dist}/cd/layout.d.ts +0 -1
  16. package/{types → dist}/cd/status.d.ts +0 -1
  17. package/{types → dist}/cd/time.d.ts +0 -1
  18. package/{types → dist}/cd/types.d.ts +0 -1
  19. package/dist/chunk-42CU5GSF.js +98 -0
  20. package/dist/chunk-42CU5GSF.js.map +1 -0
  21. package/dist/chunk-M2ILMK44.cjs +2104 -0
  22. package/dist/chunk-M2ILMK44.cjs.map +1 -0
  23. package/dist/chunk-YBXVUV7P.cjs +103 -0
  24. package/dist/chunk-YBXVUV7P.cjs.map +1 -0
  25. package/dist/chunk-YSURJUOA.js +2035 -0
  26. package/dist/chunk-YSURJUOA.js.map +1 -0
  27. package/dist/dashboard.cjs +15 -0
  28. package/dist/dashboard.cjs.map +1 -0
  29. package/dist/dashboard.d.ts +1 -0
  30. package/dist/dashboard.js +4 -0
  31. package/dist/dashboard.js.map +1 -0
  32. package/dist/data.cjs +15 -0
  33. package/dist/data.cjs.map +1 -0
  34. package/dist/data.d.ts +1 -0
  35. package/dist/data.js +4 -0
  36. package/dist/data.js.map +1 -0
  37. package/dist/gitops.cjs +15 -0
  38. package/dist/gitops.cjs.map +1 -0
  39. package/dist/gitops.d.ts +1 -0
  40. package/dist/gitops.js +4 -0
  41. package/dist/gitops.js.map +1 -0
  42. package/dist/index.cjs +299 -0
  43. package/dist/index.cjs.map +1 -0
  44. package/{types → dist}/index.d.ts +0 -1
  45. package/dist/index.js +6 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/network.cjs +15 -0
  48. package/dist/network.cjs.map +1 -0
  49. package/{types → dist}/network.d.ts +0 -1
  50. package/dist/network.js +4 -0
  51. package/dist/network.js.map +1 -0
  52. package/dist/oss/blueprint.d.ts +37 -0
  53. package/dist/oss/catalog.d.ts +67 -0
  54. package/dist/oss.cjs +190 -0
  55. package/dist/oss.cjs.map +1 -0
  56. package/dist/oss.d.ts +2 -0
  57. package/dist/oss.js +180 -0
  58. package/dist/oss.js.map +1 -0
  59. package/dist/primitives/bases/data/index.cjs +15 -0
  60. package/dist/primitives/bases/data/index.cjs.map +1 -0
  61. package/dist/primitives/bases/data/index.d.ts +1 -0
  62. package/dist/primitives/bases/data/index.js +4 -0
  63. package/dist/primitives/bases/data/index.js.map +1 -0
  64. package/{types → dist}/product/AppHeader.d.ts +0 -1
  65. package/{types → dist}/product/BrandMark.d.ts +0 -1
  66. package/{types → dist}/product/Charts.d.ts +0 -1
  67. package/{types → dist}/product/ComboBox.d.ts +0 -1
  68. package/{types → dist}/product/DataTable.d.ts +0 -1
  69. package/{types → dist}/product/Donut.d.ts +0 -1
  70. package/{types → dist}/product/EmptyState.d.ts +0 -1
  71. package/{types → dist}/product/FadeIn.d.ts +0 -1
  72. package/{types → dist}/product/Field.d.ts +0 -1
  73. package/{types → dist}/product/HanzoMark.d.ts +0 -1
  74. package/{types → dist}/product/Metric.d.ts +0 -1
  75. package/dist/product/OrgMark.d.ts +21 -0
  76. package/{types → dist}/product/OrgSwitcher.d.ts +8 -2
  77. package/{types → dist}/product/PageHeader.d.ts +0 -1
  78. package/{types → dist}/product/PrimaryButton.d.ts +0 -1
  79. package/{types → dist}/product/ProductIcon.d.ts +0 -1
  80. package/{types → dist}/product/ProviderLogo.d.ts +0 -1
  81. package/{types → dist}/product/Reorder.d.ts +0 -1
  82. package/{types → dist}/product/SelectMenu.d.ts +6 -8
  83. package/{types → dist}/product/SlideOver.d.ts +0 -1
  84. package/{types → dist}/product/StatusTag.d.ts +0 -1
  85. package/{types → dist}/product/ThemeToggle.d.ts +0 -1
  86. package/{types → dist}/product/ThemeToggleNext.d.ts +0 -1
  87. package/{types → dist}/product/Toast.d.ts +0 -1
  88. package/{types → dist}/product/color.d.ts +0 -1
  89. package/{types → dist}/product/combobox/filter.d.ts +0 -1
  90. package/dist/product/index.cjs +286 -0
  91. package/dist/product/index.cjs.map +1 -0
  92. package/{types → dist}/product/index.d.ts +1 -1
  93. package/dist/product/index.js +5 -0
  94. package/dist/product/index.js.map +1 -0
  95. package/{types → dist}/product/menu/ContextMenu.d.ts +3 -6
  96. package/dist/product/menu/DropdownMenu.d.ts +29 -0
  97. package/dist/product/menu/FloatingMenu.d.ts +36 -0
  98. package/{types → dist}/product/menu/index.d.ts +1 -1
  99. package/{types → dist}/product/menu/items.d.ts +0 -1
  100. package/{types → dist}/product/menu/portal-theme.d.ts +0 -1
  101. package/{types → dist}/product/menu/roving.d.ts +0 -1
  102. package/{types → dist}/product/scope.d.ts +0 -1
  103. package/{types → dist}/product/surfaces.data.d.ts +0 -1
  104. package/dist/telemetry.cjs +15 -0
  105. package/dist/telemetry.cjs.map +1 -0
  106. package/dist/telemetry.d.ts +1 -0
  107. package/dist/telemetry.js +4 -0
  108. package/dist/telemetry.js.map +1 -0
  109. package/dist/usage.cjs +15 -0
  110. package/dist/usage.cjs.map +1 -0
  111. package/dist/usage.d.ts +1 -0
  112. package/dist/usage.js +4 -0
  113. package/dist/usage.js.map +1 -0
  114. package/dist/wallet.cjs +15 -0
  115. package/dist/wallet.cjs.map +1 -0
  116. package/{types → dist}/wallet.d.ts +0 -1
  117. package/dist/wallet.js +4 -0
  118. package/dist/wallet.js.map +1 -0
  119. package/package.json +69 -34
  120. package/src/billing.ts +0 -9
  121. package/src/canvas.ts +0 -11
  122. package/src/cd/layout.ts +0 -184
  123. package/src/cd/status.ts +0 -158
  124. package/src/cd/time.ts +0 -52
  125. package/src/cd/types.ts +0 -318
  126. package/src/dashboard.ts +0 -8
  127. package/src/data.ts +0 -12
  128. package/src/gitops.ts +0 -5
  129. package/src/gui-env.d.ts +0 -27
  130. package/src/index.ts +0 -22
  131. package/src/network.ts +0 -11
  132. package/src/primitives/bases/data/index.ts +0 -13
  133. package/src/product/AppHeader.tsx +0 -183
  134. package/src/product/BrandMark.tsx +0 -27
  135. package/src/product/Charts.tsx +0 -471
  136. package/src/product/ComboBox.tsx +0 -127
  137. package/src/product/DataTable.tsx +0 -99
  138. package/src/product/Donut.tsx +0 -83
  139. package/src/product/EmptyState.tsx +0 -129
  140. package/src/product/FadeIn.tsx +0 -38
  141. package/src/product/Field.tsx +0 -160
  142. package/src/product/HanzoMark.tsx +0 -31
  143. package/src/product/Metric.tsx +0 -253
  144. package/src/product/OrgSwitcher.tsx +0 -332
  145. package/src/product/PageHeader.tsx +0 -31
  146. package/src/product/PrimaryButton.tsx +0 -17
  147. package/src/product/ProductIcon.tsx +0 -61
  148. package/src/product/ProviderLogo.tsx +0 -137
  149. package/src/product/Reorder.test.ts +0 -41
  150. package/src/product/Reorder.tsx +0 -110
  151. package/src/product/SelectMenu.tsx +0 -80
  152. package/src/product/SlideOver.tsx +0 -197
  153. package/src/product/StatusTag.tsx +0 -49
  154. package/src/product/ThemeToggle.tsx +0 -135
  155. package/src/product/ThemeToggleNext.tsx +0 -27
  156. package/src/product/Toast.tsx +0 -166
  157. package/src/product/color.ts +0 -17
  158. package/src/product/combobox/filter.test.ts +0 -51
  159. package/src/product/combobox/filter.ts +0 -39
  160. package/src/product/index.ts +0 -76
  161. package/src/product/menu/ContextMenu.tsx +0 -114
  162. package/src/product/menu/DropdownMenu.tsx +0 -76
  163. package/src/product/menu/index.ts +0 -15
  164. package/src/product/menu/items.tsx +0 -230
  165. package/src/product/menu/portal-theme.tsx +0 -33
  166. package/src/product/menu/roving.ts +0 -34
  167. package/src/product/scope.test.ts +0 -92
  168. package/src/product/scope.ts +0 -123
  169. package/src/product/surfaces.data.ts +0 -42
  170. package/src/usage.ts +0 -8
  171. package/src/wallet.ts +0 -11
  172. package/types/billing.d.ts.map +0 -1
  173. package/types/canvas.d.ts +0 -2
  174. package/types/canvas.d.ts.map +0 -1
  175. package/types/cd/layout.d.ts.map +0 -1
  176. package/types/cd/status.d.ts.map +0 -1
  177. package/types/cd/time.d.ts.map +0 -1
  178. package/types/cd/types.d.ts.map +0 -1
  179. package/types/dashboard.d.ts +0 -2
  180. package/types/dashboard.d.ts.map +0 -1
  181. package/types/data.d.ts +0 -2
  182. package/types/data.d.ts.map +0 -1
  183. package/types/gitops.d.ts +0 -2
  184. package/types/gitops.d.ts.map +0 -1
  185. package/types/index.d.ts.map +0 -1
  186. package/types/network.d.ts.map +0 -1
  187. package/types/primitives/bases/data/index.d.ts +0 -2
  188. package/types/primitives/bases/data/index.d.ts.map +0 -1
  189. package/types/product/AppHeader.d.ts.map +0 -1
  190. package/types/product/BrandMark.d.ts.map +0 -1
  191. package/types/product/Charts.d.ts.map +0 -1
  192. package/types/product/ComboBox.d.ts.map +0 -1
  193. package/types/product/DataTable.d.ts.map +0 -1
  194. package/types/product/Donut.d.ts.map +0 -1
  195. package/types/product/EmptyState.d.ts.map +0 -1
  196. package/types/product/FadeIn.d.ts.map +0 -1
  197. package/types/product/Field.d.ts.map +0 -1
  198. package/types/product/HanzoMark.d.ts.map +0 -1
  199. package/types/product/Metric.d.ts.map +0 -1
  200. package/types/product/OrgSwitcher.d.ts.map +0 -1
  201. package/types/product/PageHeader.d.ts.map +0 -1
  202. package/types/product/PrimaryButton.d.ts.map +0 -1
  203. package/types/product/ProductIcon.d.ts.map +0 -1
  204. package/types/product/ProviderLogo.d.ts.map +0 -1
  205. package/types/product/Reorder.d.ts.map +0 -1
  206. package/types/product/SelectMenu.d.ts.map +0 -1
  207. package/types/product/SlideOver.d.ts.map +0 -1
  208. package/types/product/StatusTag.d.ts.map +0 -1
  209. package/types/product/ThemeToggle.d.ts.map +0 -1
  210. package/types/product/ThemeToggleNext.d.ts.map +0 -1
  211. package/types/product/Toast.d.ts.map +0 -1
  212. package/types/product/color.d.ts.map +0 -1
  213. package/types/product/combobox/filter.d.ts.map +0 -1
  214. package/types/product/index.d.ts.map +0 -1
  215. package/types/product/menu/ContextMenu.d.ts.map +0 -1
  216. package/types/product/menu/DropdownMenu.d.ts +0 -34
  217. package/types/product/menu/DropdownMenu.d.ts.map +0 -1
  218. package/types/product/menu/index.d.ts.map +0 -1
  219. package/types/product/menu/items.d.ts.map +0 -1
  220. package/types/product/menu/portal-theme.d.ts.map +0 -1
  221. package/types/product/menu/roving.d.ts.map +0 -1
  222. package/types/product/scope.d.ts.map +0 -1
  223. package/types/product/surfaces.data.d.ts.map +0 -1
  224. package/types/usage.d.ts +0 -2
  225. package/types/usage.d.ts.map +0 -1
  226. package/types/wallet.d.ts.map +0 -1
  227. /package/{src → dist}/styles/hanzo-motion.css +0 -0
@@ -1,332 +0,0 @@
1
- 'use client'
2
-
3
- /**
4
- * OrgSwitcher — the Vercel-style org/team switcher, hoisted from the console
5
- * (hanzoai/ui#36) onto @hanzo/gui primitives. Shows the org the surface is
6
- * scoped to, switches between the orgs the caller can see, and creates one.
7
- *
8
- * Host-agnostic: the LIST is a lazy, paged loader the host injects (IAM
9
- * `get-organizations` behind its own proxy) — one page at a time, the search
10
- * term pushed to the server, more on demand (scroll + "Load more"), so it
11
- * scales to thousands of orgs. No loader → the current org alone, synthesized
12
- * from the scope (never fabricated). Selecting re-scopes IN PLACE via
13
- * `scope.switchOrg` (persist + reload → every module refetches under the new
14
- * `X-Org-Id`). Create posts through the injected hook, then scopes into the
15
- * new org.
16
- */
17
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
18
- import { Button, Input, Popover, Spinner, Text, XStack, YStack } from '@hanzo/gui'
19
- import { Check, ChevronsUpDown, LayoutGrid, Plus, Search } from '@hanzogui/lucide-icons-2'
20
-
21
- import { filterOrgs, type Org, type OrgScope } from './scope'
22
-
23
- const titleCase = (s: string) => (s ? s[0].toUpperCase() + s.slice(1) : s)
24
- const initialsOf = (o: Org) =>
25
- (o.displayName || o.name)
26
- .split(/\s+/)
27
- .map((w) => w[0])
28
- .join('')
29
- .slice(0, 2)
30
- .toUpperCase()
31
-
32
- /** An org's avatar — its logo when set, else a monogram tile. */
33
- function OrgAvatar({ org, size = 22 }: { org: Org; size?: number }) {
34
- if (org.logo) {
35
- // eslint-disable-next-line @next/next/no-img-element
36
- return <img src={org.logo} alt="" style={{ height: size, width: size, objectFit: 'contain', display: 'block', borderRadius: 6 }} />
37
- }
38
- return (
39
- <YStack width={size} height={size} rounded="$3" bg="$color4" items="center" justify="center">
40
- <Text fontSize="$1" fontWeight="800" color="$color12">
41
- {initialsOf(org)}
42
- </Text>
43
- </YStack>
44
- )
45
- }
46
-
47
- export type OrgSwitcherProps = {
48
- /** The active-org contract (see `orgScope`). */
49
- scope: OrgScope
50
- /**
51
- * Lazy paged org list: 0-based page + server-pushed search → one page of
52
- * rows. A full page (=== pageSize) implies another. Omit when the caller
53
- * can only see its own org.
54
- */
55
- orgs?: (page: number, query: string) => Promise<Org[]>
56
- /** Rows per page the loader returns. Default 20. */
57
- pageSize?: number
58
- /** Create-org hook → the created org's id; omit to hide the affordance. */
59
- create?: (name: string) => Promise<string>
60
- /** Show the "All organizations" de-scope row (`scope.leaveOrg`). */
61
- picker?: boolean
62
- }
63
-
64
- export function OrgSwitcher({ scope, orgs, pageSize = 20, create, picker = false }: OrgSwitcherProps) {
65
- const currentId = scope.currentOrg()
66
-
67
- const [open, setOpen] = useState(false)
68
- const [query, setQuery] = useState('')
69
- const [debounced, setDebounced] = useState('')
70
- const [rows, setRows] = useState<Org[]>([])
71
- const [loading, setLoading] = useState(false)
72
- const [loadingMore, setLoadingMore] = useState(false)
73
- const [hasMore, setHasMore] = useState(false)
74
- const [creating, setCreating] = useState(false)
75
- const [newName, setNewName] = useState('')
76
- const [busy, setBusy] = useState(false)
77
- const [err, setErr] = useState<string | null>(null)
78
- const pageRef = useRef(0)
79
- const reqRef = useRef(0) // race token — a newer request supersedes older ones
80
-
81
- // Debounce the search so type-as-you-search re-hits the server at most ~4×/s.
82
- useEffect(() => {
83
- const id = setTimeout(() => setDebounced(query.trim()), 250)
84
- return () => clearTimeout(id)
85
- }, [query])
86
-
87
- const fetchPage = useCallback(
88
- async (page: number, q: string, append: boolean) => {
89
- if (!orgs) return
90
- const token = ++reqRef.current
91
- append ? setLoadingMore(true) : setLoading(true)
92
- try {
93
- const incoming = await orgs(page, q)
94
- if (token !== reqRef.current) return
95
- pageRef.current = page
96
- setRows((prev) => {
97
- const merged = append ? [...prev] : []
98
- const seen = new Set(merged.map((o) => o.name))
99
- for (const o of incoming) if (!seen.has(o.name)) merged.push(o)
100
- return merged
101
- })
102
- setHasMore(incoming.length >= pageSize)
103
- } catch {
104
- // Honest empty / keep what's shown — never a fabricated list.
105
- if (token !== reqRef.current) return
106
- if (!append) setRows([])
107
- setHasMore(false)
108
- } finally {
109
- if (token === reqRef.current) {
110
- setLoading(false)
111
- setLoadingMore(false)
112
- }
113
- }
114
- },
115
- [orgs, pageSize],
116
- )
117
-
118
- // (Re)load page 0 when the popover opens or the debounced query changes.
119
- useEffect(() => {
120
- if (!open || !orgs) return
121
- void fetchPage(0, debounced, false)
122
- }, [open, debounced, fetchPage, orgs])
123
-
124
- const loadMore = useCallback(() => {
125
- if (loading || loadingMore || !hasMore) return
126
- void fetchPage(pageRef.current + 1, debounced, true)
127
- }, [loading, loadingMore, hasMore, debounced, fetchPage])
128
-
129
- // Rows to render — the loaded list (client-filtered too), else the current
130
- // org synthesized from the scope.
131
- const visible: Org[] = useMemo(() => {
132
- if (orgs) return filterOrgs(rows, query)
133
- return [{ name: currentId, displayName: titleCase(currentId) }]
134
- }, [orgs, rows, query, currentId])
135
-
136
- const current: Org = useMemo(
137
- () => rows.find((o) => o.name === currentId) ?? { name: currentId, displayName: titleCase(currentId) },
138
- [rows, currentId],
139
- )
140
-
141
- const select = useCallback(
142
- (org: string) => {
143
- setOpen(false)
144
- scope.switchOrg(org)
145
- },
146
- [scope],
147
- )
148
-
149
- const submitCreate = async () => {
150
- const name = newName.trim()
151
- if (!name || !create) return
152
- setBusy(true)
153
- setErr(null)
154
- try {
155
- const org = await create(name)
156
- scope.switchOrg(org)
157
- } catch (e) {
158
- setErr(e instanceof Error ? e.message : 'Could not create the organization.')
159
- setBusy(false)
160
- }
161
- }
162
-
163
- return (
164
- <Popover open={open} onOpenChange={setOpen} placement="bottom-start">
165
- <Popover.Trigger asChild>
166
- <Button size="$2" chromeless icon={<OrgAvatar org={current} size={18} />} iconAfter={<ChevronsUpDown size={13} />}>
167
- {current.displayName || titleCase(current.name)}
168
- </Button>
169
- </Popover.Trigger>
170
- <Popover.Content bordered elevate p="$2" width={300} bg="$color2" borderColor="$borderColor">
171
- {creating ? (
172
- <YStack gap="$2">
173
- <Text fontSize="$2" color="$color12" fontWeight="700">
174
- Create organization
175
- </Text>
176
- <Input
177
- size="$3"
178
- placeholder="Organization name"
179
- value={newName}
180
- onChangeText={setNewName}
181
- autoCapitalize="words"
182
- onSubmitEditing={() => void submitCreate()}
183
- />
184
- {err ? (
185
- <Text fontSize="$1" color="$red10">
186
- {err}
187
- </Text>
188
- ) : null}
189
- <XStack gap="$2" justify="flex-end">
190
- <Button size="$2" chromeless onPress={() => setCreating(false)} disabled={busy}>
191
- Cancel
192
- </Button>
193
- <Button
194
- size="$2"
195
- onPress={() => void submitCreate()}
196
- disabled={busy || !newName.trim()}
197
- icon={busy ? <Spinner size="small" /> : <Plus size={14} />}
198
- >
199
- Create
200
- </Button>
201
- </XStack>
202
- </YStack>
203
- ) : (
204
- <YStack gap="$1">
205
- {orgs ? (
206
- <XStack items="center" gap="$2" px="$2" py="$1" rounded="$3" borderWidth={1} borderColor="$borderColor">
207
- <Search size={13} opacity={0.6} />
208
- <Input
209
- flex={1}
210
- size="$2"
211
- borderWidth={0}
212
- bg="transparent"
213
- placeholder="Find organization…"
214
- value={query}
215
- onChangeText={setQuery}
216
- autoCapitalize="none"
217
- autoCorrect={false}
218
- />
219
- </XStack>
220
- ) : null}
221
-
222
- <div
223
- style={{ maxHeight: 300, overflowY: 'auto', display: 'flex', flexDirection: 'column' }}
224
- onScroll={(e) => {
225
- const el = e.currentTarget
226
- if (el.scrollHeight - el.scrollTop - el.clientHeight < 48) loadMore()
227
- }}
228
- >
229
- {loading ? (
230
- <XStack items="center" gap="$2" px="$2" py="$3">
231
- <Spinner size="small" color="$color11" />
232
- <Text fontSize="$2" color="$color10">
233
- Loading organizations…
234
- </Text>
235
- </XStack>
236
- ) : visible.length === 0 ? (
237
- <Text px="$2" py="$2" fontSize="$2" color="$color10">
238
- {query.trim() ? `No organizations match “${query.trim()}”.` : 'No organizations yet.'}
239
- </Text>
240
- ) : (
241
- visible.map((org) => (
242
- <XStack
243
- key={org.name}
244
- onPress={() => select(org.name)}
245
- cursor="pointer"
246
- items="center"
247
- gap="$2.5"
248
- px="$2"
249
- py="$2"
250
- rounded="$3"
251
- bg={org.name === currentId ? '$color4' : 'transparent'}
252
- hoverStyle={{ bg: '$color5' }}
253
- >
254
- <OrgAvatar org={org} />
255
- <Text flex={1} fontSize="$2" color="$color12" numberOfLines={1}>
256
- {org.displayName || org.name}
257
- </Text>
258
- {org.name === currentId ? <Check size={15} /> : null}
259
- </XStack>
260
- ))
261
- )}
262
-
263
- {loadingMore ? (
264
- <XStack items="center" gap="$2" px="$2" py="$2">
265
- <Spinner size="small" color="$color11" />
266
- <Text fontSize="$1" color="$color10">
267
- Loading more…
268
- </Text>
269
- </XStack>
270
- ) : hasMore ? (
271
- <XStack onPress={loadMore} cursor="pointer" items="center" justify="center" px="$2" py="$1.5" rounded="$3" hoverStyle={{ bg: '$color4' }}>
272
- <Text fontSize="$1" color="$color11" fontWeight="600">
273
- Load more
274
- </Text>
275
- </XStack>
276
- ) : null}
277
- </div>
278
-
279
- {create ? (
280
- <XStack
281
- onPress={() => {
282
- setCreating(true)
283
- setErr(null)
284
- }}
285
- cursor="pointer"
286
- items="center"
287
- gap="$2.5"
288
- px="$2"
289
- py="$2"
290
- mt="$1"
291
- rounded="$3"
292
- borderTopWidth={1}
293
- borderColor="$borderColor"
294
- hoverStyle={{ bg: '$color5' }}
295
- >
296
- <YStack width={22} height={22} rounded="$3" bg="$color3" items="center" justify="center">
297
- <Plus size={14} />
298
- </YStack>
299
- <Text fontSize="$2" color="$color12">
300
- Create organization
301
- </Text>
302
- </XStack>
303
- ) : null}
304
-
305
- {picker ? (
306
- <XStack
307
- onPress={() => {
308
- setOpen(false)
309
- scope.leaveOrg()
310
- }}
311
- cursor="pointer"
312
- items="center"
313
- gap="$2.5"
314
- px="$2"
315
- py="$2"
316
- rounded="$3"
317
- hoverStyle={{ bg: '$color5' }}
318
- >
319
- <YStack width={22} height={22} rounded="$3" bg="$color3" items="center" justify="center">
320
- <LayoutGrid size={14} />
321
- </YStack>
322
- <Text fontSize="$2" color="$color12">
323
- All organizations
324
- </Text>
325
- </XStack>
326
- ) : null}
327
- </YStack>
328
- )}
329
- </Popover.Content>
330
- </Popover>
331
- )
332
- }
@@ -1,31 +0,0 @@
1
- 'use client'
2
-
3
- import type { ReactNode } from 'react'
4
- import { Text, XStack, YStack } from '@hanzo/gui'
5
-
6
- /** Section title + optional subtitle and right-aligned actions. */
7
- export function PageHeader({
8
- title,
9
- subtitle,
10
- actions,
11
- }: {
12
- title: string
13
- subtitle?: string
14
- actions?: ReactNode
15
- }) {
16
- return (
17
- <XStack justify="space-between" items="flex-start" gap="$4">
18
- <YStack gap="$1">
19
- <Text fontSize="$7" fontWeight="800">
20
- {title}
21
- </Text>
22
- {subtitle ? (
23
- <Text fontSize="$3" color="$color11">
24
- {subtitle}
25
- </Text>
26
- ) : null}
27
- </YStack>
28
- {actions ? <XStack gap="$2">{actions}</XStack> : null}
29
- </XStack>
30
- )
31
- }
@@ -1,17 +0,0 @@
1
- 'use client'
2
-
3
- /**
4
- * Primary button — the one white, high-emphasis action for the console.
5
- *
6
- * Monochrome brand: `theme="light"` flips the button to the light theme inside
7
- * the dark console, giving a white fill with a near-black label and icon — no
8
- * hue accent. Use it for the single primary action in a view (sign in, save,
9
- * get started). Secondary and destructive actions use the default neutral
10
- * `Button`.
11
- */
12
- import type { ComponentProps } from 'react'
13
- import { Button } from '@hanzo/gui'
14
-
15
- export function PrimaryButton(props: ComponentProps<typeof Button>) {
16
- return <Button theme="light" {...props} />
17
- }
@@ -1,61 +0,0 @@
1
- 'use client'
2
-
3
- /**
4
- * ProductIcon — the Linear-style product tile: a rounded-square block filled with
5
- * the product's accent color, with the product glyph knocked out in near-white so
6
- * it reads "cut out" of the color. This is the ONE way a product icon renders in
7
- * the console chrome (sidebar nav, command palette, product overview header) — a
8
- * bare tinted glyph is replaced everywhere by this tile.
9
- *
10
- * Prop-driven and pure: the accent hex is resolved by the caller (registry-curated
11
- * → per-user override → stable hash, via `useProductColors().colorOf`) and passed
12
- * in as `color`, so this component owns only the look, never the palette. With no
13
- * color it falls back to a tasteful neutral chip ($color12 fill + $color1 glyph) —
14
- * never a random hue — matching the first-party `ProviderLogo` mark, so first-party
15
- * marks and product tiles read as one family.
16
- *
17
- * The knock-out is the same technique the color-swatch button uses (a raw-hex
18
- * `backgroundColor` fill + a `#ffffff` glyph) so an arbitrary product accent tints
19
- * cleanly outside Gui's token palette, in the ONE sanctioned place.
20
- */
21
- import { XStack } from '@hanzo/gui'
22
-
23
- import { asColor, type IconLike } from './color'
24
-
25
- export function ProductIcon({
26
- icon: Icon,
27
- color,
28
- size = 24,
29
- }: {
30
- icon: IconLike
31
- /** The product's resolved accent hex (e.g. `#5E6AD2`). Omit for the neutral chip. */
32
- color?: string
33
- size?: number
34
- }) {
35
- const radius = Math.round(size * 0.28)
36
- const glyph = Math.round(size * 0.58)
37
- const tinted = typeof color === 'string' && color.trim() !== ''
38
-
39
- // Accent tile — product color fill + near-white glyph (the Linear "cut out").
40
- if (tinted) {
41
- return (
42
- <XStack
43
- width={size}
44
- height={size}
45
- items="center"
46
- justify="center"
47
- rounded={radius}
48
- style={{ backgroundColor: color }}
49
- >
50
- <Icon size={glyph} color={asColor('#ffffff')} />
51
- </XStack>
52
- )
53
- }
54
-
55
- // No accent — a tasteful neutral chip (consistent with ProviderLogo's mark).
56
- return (
57
- <XStack width={size} height={size} items="center" justify="center" rounded={radius} bg="$color12">
58
- <Icon size={glyph} color="$color1" />
59
- </XStack>
60
- )
61
- }
@@ -1,137 +0,0 @@
1
- 'use client'
2
-
3
- /**
4
- * ProviderLogo — a small provider/model avatar resolved from a provider NAME
5
- * alone (no external logo URLs, no network). Self-contained and prop-driven so
6
- * it lifts cleanly into `@hanzo/ui` for hanzo.ai / @hanzo/dev / the desktop app.
7
- *
8
- * Resolution order:
9
- * 1. First-party — Zen renders the real ensō mark, Hanzo the real block-H mark
10
- * (same geometry as @zenlm/logo / @hanzo/logo), knocked out of a filled
11
- * rounded tile so our own models read distinctly and on-brand.
12
- * 2. A known provider → its mapped @hanzogui/lucide-icons-2 glyph.
13
- * 3. Otherwise → a stable initials chip (1–2 letters from the name).
14
- *
15
- * Honest by construction: we never claim an official brand logo we don't ship —
16
- * unknown providers get clean initials, not a guessed icon. Colors are @hanzo/gui
17
- * theme tokens so the mark themes with the shell.
18
- */
19
- import { Text, XStack, useTheme } from '@hanzo/gui'
20
- import { Boxes, Server, Globe, Cpu } from '@hanzogui/lucide-icons-2'
21
-
22
- type IconCmp = typeof Globe
23
-
24
- /** Curated, extendable provider→glyph map (lowercased keys). Brand-neutral avatars. */
25
- const KNOWN: Record<string, IconCmp> = {
26
- openrouter: Globe,
27
- nvidia: Cpu,
28
- }
29
-
30
- const isFirstParty = (provider: string): boolean => {
31
- const p = provider.trim().toLowerCase()
32
- return p === 'hanzo' || p === 'zen'
33
- }
34
-
35
- /** The Zen ensō mark — identical geometry to @zenlm/logo (svg/zen-enso.svg). */
36
- function EnsoMark({ size, color }: { size: number; color: string }) {
37
- return (
38
- <svg width={size} height={size} viewBox="0 0 100 100" aria-hidden="true">
39
- <path d="M66.22 83.26 A37 37 0 1 1 85.57 60.20" fill="none" stroke={color} strokeWidth={11} strokeLinecap="round" />
40
- </svg>
41
- )
42
- }
43
-
44
- /** The Hanzo block-H mark — identical geometry to @hanzo/logo (app/icon.svg). */
45
- const HANZO_PATHS = [
46
- 'M22.21 67V44.6369H0V67H22.21Z',
47
- 'M66.7038 22.3184H22.2534L0.0878906 44.6367H44.4634L66.7038 22.3184Z',
48
- 'M22.21 0H0V22.3184H22.21V0Z',
49
- 'M66.7198 0H44.5098V22.3184H66.7198V0Z',
50
- 'M66.7198 67V44.6369H44.5098V67H66.7198Z',
51
- ]
52
- function HanzoHMark({ size, color }: { size: number; color: string }) {
53
- return (
54
- <svg width={size} height={size} viewBox="0 0 67 67" aria-hidden="true">
55
- {HANZO_PATHS.map((d) => (
56
- <path key={d} d={d} fill={color} />
57
- ))}
58
- </svg>
59
- )
60
- }
61
-
62
- /** 1–2 uppercase initials from a provider name: words→first letters, else first 2 chars. */
63
- export function providerInitials(provider: string): string {
64
- const name = provider.trim()
65
- if (!name) return '•'
66
- const words = name.split(/[\s/_-]+/).filter(Boolean)
67
- if (words.length >= 2) return (words[0][0] + words[1][0]).toUpperCase()
68
- return name.slice(0, 2).toUpperCase()
69
- }
70
-
71
- export function ProviderLogo({ provider, size = 24 }: { provider: string; size?: number }) {
72
- const theme = useTheme()
73
- const radius = Math.round(size * 0.28)
74
- const iconSize = Math.round(size * 0.56)
75
-
76
- // First-party (Zen/Hanzo) — the real brand mark knocked out of a filled tile.
77
- if (isFirstParty(provider)) {
78
- const fg = theme.color1?.get() ?? '#000000' // cut-out mark: the tile's contrast color
79
- const markSize = Math.round(size * 0.66)
80
- const isZen = provider.trim().toLowerCase() === 'zen'
81
- return (
82
- <XStack width={size} height={size} items="center" justify="center" rounded={radius} bg="$color12">
83
- {isZen ? <EnsoMark size={markSize} color={fg} /> : <HanzoHMark size={Math.round(size * 0.56)} color={fg} />}
84
- </XStack>
85
- )
86
- }
87
-
88
- // A known provider with a curated, brand-neutral glyph.
89
- const Known = KNOWN[provider.trim().toLowerCase()]
90
- if (Known) {
91
- return (
92
- <XStack
93
- width={size}
94
- height={size}
95
- items="center"
96
- justify="center"
97
- rounded={radius}
98
- bg="$color3"
99
- borderWidth={1}
100
- borderColor="$borderColor"
101
- >
102
- <Known size={iconSize} color="$color11" />
103
- </XStack>
104
- )
105
- }
106
-
107
- // Otherwise — a clean, stable initials chip (no fabricated brand logo).
108
- return (
109
- <XStack
110
- width={size}
111
- height={size}
112
- items="center"
113
- justify="center"
114
- rounded={radius}
115
- bg="$color3"
116
- borderWidth={1}
117
- borderColor="$borderColor"
118
- >
119
- <Text fontSize={Math.round(size * 0.4)} fontWeight="800" color="$color11">
120
- {providerInitials(provider)}
121
- </Text>
122
- </XStack>
123
- )
124
- }
125
-
126
- /** A generic fallback mark for an unspecified provider (used sparingly). */
127
- export function GenericLogo({ size = 24 }: { size?: number }) {
128
- return (
129
- <XStack width={size} height={size} items="center" justify="center" rounded={Math.round(size * 0.28)} bg="$color3" borderWidth={1} borderColor="$borderColor">
130
- <Server size={Math.round(size * 0.56)} color="$color11" />
131
- </XStack>
132
- )
133
- }
134
-
135
- // Boxes is re-exported as the conventional "custom model" mark for callers that
136
- // render a non-provider tile next to ProviderLogo (keeps the icon source single).
137
- export { Boxes as CustomModelMark }
@@ -1,41 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
-
3
- import { rowShift, targetIndex } from './Reorder'
4
-
5
- describe('targetIndex', () => {
6
- it('rounds travel to the nearest row and clamps to range', () => {
7
- // rowH=44, from=2, drag down 100px → +2.27 → +2 → index 4
8
- expect(targetIndex(2, 100, 44, 6)).toBe(4)
9
- // drag up 100px → −2 → index 0
10
- expect(targetIndex(2, -100, 44, 6)).toBe(0)
11
- // clamp at the ends
12
- expect(targetIndex(5, 999, 44, 6)).toBe(5)
13
- expect(targetIndex(0, -999, 44, 6)).toBe(0)
14
- // sub-half-row travel stays put
15
- expect(targetIndex(3, 20, 44, 6)).toBe(3)
16
- })
17
-
18
- it('is a safe no-op for degenerate inputs', () => {
19
- expect(targetIndex(2, 100, 0, 6)).toBe(2)
20
- expect(targetIndex(2, 100, 44, 0)).toBe(2)
21
- })
22
- })
23
-
24
- describe('rowShift — displacement of siblings during a drag', () => {
25
- const rowH = 44
26
- it('the dragged row follows the pointer', () => {
27
- expect(rowShift(2, 2, 4, 90, rowH)).toBe(90)
28
- })
29
- it('rows between from and to (dragging down) move up by one row', () => {
30
- // from=1 → to=3: rows 2,3 shift up
31
- expect(rowShift(2, 1, 3, 90, rowH)).toBe(-rowH)
32
- expect(rowShift(3, 1, 3, 90, rowH)).toBe(-rowH)
33
- expect(rowShift(4, 1, 3, 90, rowH)).toBe(0)
34
- })
35
- it('rows between to and from (dragging up) move down by one row', () => {
36
- // from=4 → to=2: rows 2,3 shift down
37
- expect(rowShift(2, 4, 2, -90, rowH)).toBe(rowH)
38
- expect(rowShift(3, 4, 2, -90, rowH)).toBe(rowH)
39
- expect(rowShift(1, 4, 2, -90, rowH)).toBe(0)
40
- })
41
- })