@hanzo/ui 8.0.6 → 8.0.8

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 (200) 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-GDGVXU7W.js +1999 -0
  22. package/dist/chunk-GDGVXU7W.js.map +1 -0
  23. package/dist/chunk-HSPMQQVJ.cjs +2066 -0
  24. package/dist/chunk-HSPMQQVJ.cjs.map +1 -0
  25. package/dist/chunk-YBXVUV7P.cjs +103 -0
  26. package/dist/chunk-YBXVUV7P.cjs.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 +291 -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/primitives/bases/data/index.cjs +15 -0
  53. package/dist/primitives/bases/data/index.cjs.map +1 -0
  54. package/dist/primitives/bases/data/index.d.ts +1 -0
  55. package/dist/primitives/bases/data/index.js +4 -0
  56. package/dist/primitives/bases/data/index.js.map +1 -0
  57. package/{types → dist}/product/AppHeader.d.ts +0 -1
  58. package/{types → dist}/product/BrandMark.d.ts +0 -1
  59. package/{types → dist}/product/Charts.d.ts +0 -1
  60. package/{types → dist}/product/ComboBox.d.ts +0 -1
  61. package/{types → dist}/product/DataTable.d.ts +0 -1
  62. package/{types → dist}/product/Donut.d.ts +0 -1
  63. package/{types → dist}/product/EmptyState.d.ts +0 -1
  64. package/{types → dist}/product/FadeIn.d.ts +0 -1
  65. package/{types → dist}/product/Field.d.ts +0 -1
  66. package/{types → dist}/product/HanzoMark.d.ts +0 -1
  67. package/{types → dist}/product/Metric.d.ts +0 -1
  68. package/{types → dist}/product/OrgSwitcher.d.ts +0 -1
  69. package/{types → dist}/product/PageHeader.d.ts +0 -1
  70. package/{types → dist}/product/PrimaryButton.d.ts +0 -1
  71. package/{types → dist}/product/ProductIcon.d.ts +0 -1
  72. package/{types → dist}/product/ProviderLogo.d.ts +0 -1
  73. package/{types → dist}/product/Reorder.d.ts +0 -1
  74. package/{types → dist}/product/SelectMenu.d.ts +7 -6
  75. package/{types → dist}/product/SlideOver.d.ts +0 -1
  76. package/{types → dist}/product/StatusTag.d.ts +0 -1
  77. package/dist/product/ThemeToggle.d.ts +21 -0
  78. package/dist/product/ThemeToggleNext.d.ts +4 -0
  79. package/{types → dist}/product/Toast.d.ts +0 -1
  80. package/{types → dist}/product/color.d.ts +0 -1
  81. package/{types → dist}/product/combobox/filter.d.ts +0 -1
  82. package/dist/product/index.cjs +278 -0
  83. package/dist/product/index.cjs.map +1 -0
  84. package/{types → dist}/product/index.d.ts +2 -1
  85. package/dist/product/index.js +5 -0
  86. package/dist/product/index.js.map +1 -0
  87. package/dist/product/menu/ContextMenu.d.ts +21 -0
  88. package/dist/product/menu/DropdownMenu.d.ts +29 -0
  89. package/dist/product/menu/FloatingMenu.d.ts +36 -0
  90. package/dist/product/menu/index.d.ts +5 -0
  91. package/dist/product/menu/items.d.ts +68 -0
  92. package/dist/product/menu/portal-theme.d.ts +28 -0
  93. package/dist/product/menu/roving.d.ts +8 -0
  94. package/{types → dist}/product/scope.d.ts +0 -1
  95. package/{types → dist}/product/surfaces.data.d.ts +0 -1
  96. package/dist/usage.cjs +15 -0
  97. package/dist/usage.cjs.map +1 -0
  98. package/dist/usage.d.ts +1 -0
  99. package/dist/usage.js +4 -0
  100. package/dist/usage.js.map +1 -0
  101. package/dist/wallet.cjs +15 -0
  102. package/dist/wallet.cjs.map +1 -0
  103. package/{types → dist}/wallet.d.ts +0 -1
  104. package/dist/wallet.js +4 -0
  105. package/dist/wallet.js.map +1 -0
  106. package/package.json +58 -32
  107. package/src/billing.ts +0 -9
  108. package/src/canvas.ts +0 -11
  109. package/src/cd/layout.ts +0 -184
  110. package/src/cd/status.ts +0 -158
  111. package/src/cd/time.ts +0 -52
  112. package/src/cd/types.ts +0 -318
  113. package/src/dashboard.ts +0 -8
  114. package/src/data.ts +0 -12
  115. package/src/gitops.ts +0 -5
  116. package/src/gui-env.d.ts +0 -27
  117. package/src/index.ts +0 -22
  118. package/src/network.ts +0 -11
  119. package/src/primitives/bases/data/index.ts +0 -13
  120. package/src/product/AppHeader.tsx +0 -183
  121. package/src/product/BrandMark.tsx +0 -27
  122. package/src/product/Charts.tsx +0 -471
  123. package/src/product/ComboBox.tsx +0 -147
  124. package/src/product/DataTable.tsx +0 -99
  125. package/src/product/Donut.tsx +0 -83
  126. package/src/product/EmptyState.tsx +0 -129
  127. package/src/product/FadeIn.tsx +0 -38
  128. package/src/product/Field.tsx +0 -160
  129. package/src/product/HanzoMark.tsx +0 -31
  130. package/src/product/Metric.tsx +0 -253
  131. package/src/product/OrgSwitcher.tsx +0 -332
  132. package/src/product/PageHeader.tsx +0 -31
  133. package/src/product/PrimaryButton.tsx +0 -17
  134. package/src/product/ProductIcon.tsx +0 -61
  135. package/src/product/ProviderLogo.tsx +0 -137
  136. package/src/product/Reorder.test.ts +0 -41
  137. package/src/product/Reorder.tsx +0 -110
  138. package/src/product/SelectMenu.tsx +0 -94
  139. package/src/product/SlideOver.tsx +0 -197
  140. package/src/product/StatusTag.tsx +0 -49
  141. package/src/product/ThemeToggle.tsx +0 -25
  142. package/src/product/Toast.tsx +0 -166
  143. package/src/product/color.ts +0 -17
  144. package/src/product/combobox/filter.test.ts +0 -51
  145. package/src/product/combobox/filter.ts +0 -39
  146. package/src/product/index.ts +0 -70
  147. package/src/product/scope.test.ts +0 -92
  148. package/src/product/scope.ts +0 -123
  149. package/src/product/surfaces.data.ts +0 -42
  150. package/src/usage.ts +0 -8
  151. package/src/wallet.ts +0 -11
  152. package/types/billing.d.ts.map +0 -1
  153. package/types/canvas.d.ts +0 -2
  154. package/types/canvas.d.ts.map +0 -1
  155. package/types/cd/layout.d.ts.map +0 -1
  156. package/types/cd/status.d.ts.map +0 -1
  157. package/types/cd/time.d.ts.map +0 -1
  158. package/types/cd/types.d.ts.map +0 -1
  159. package/types/dashboard.d.ts +0 -2
  160. package/types/dashboard.d.ts.map +0 -1
  161. package/types/data.d.ts +0 -2
  162. package/types/data.d.ts.map +0 -1
  163. package/types/gitops.d.ts +0 -2
  164. package/types/gitops.d.ts.map +0 -1
  165. package/types/index.d.ts.map +0 -1
  166. package/types/network.d.ts.map +0 -1
  167. package/types/primitives/bases/data/index.d.ts +0 -2
  168. package/types/primitives/bases/data/index.d.ts.map +0 -1
  169. package/types/product/AppHeader.d.ts.map +0 -1
  170. package/types/product/BrandMark.d.ts.map +0 -1
  171. package/types/product/Charts.d.ts.map +0 -1
  172. package/types/product/ComboBox.d.ts.map +0 -1
  173. package/types/product/DataTable.d.ts.map +0 -1
  174. package/types/product/Donut.d.ts.map +0 -1
  175. package/types/product/EmptyState.d.ts.map +0 -1
  176. package/types/product/FadeIn.d.ts.map +0 -1
  177. package/types/product/Field.d.ts.map +0 -1
  178. package/types/product/HanzoMark.d.ts.map +0 -1
  179. package/types/product/Metric.d.ts.map +0 -1
  180. package/types/product/OrgSwitcher.d.ts.map +0 -1
  181. package/types/product/PageHeader.d.ts.map +0 -1
  182. package/types/product/PrimaryButton.d.ts.map +0 -1
  183. package/types/product/ProductIcon.d.ts.map +0 -1
  184. package/types/product/ProviderLogo.d.ts.map +0 -1
  185. package/types/product/Reorder.d.ts.map +0 -1
  186. package/types/product/SelectMenu.d.ts.map +0 -1
  187. package/types/product/SlideOver.d.ts.map +0 -1
  188. package/types/product/StatusTag.d.ts.map +0 -1
  189. package/types/product/ThemeToggle.d.ts +0 -2
  190. package/types/product/ThemeToggle.d.ts.map +0 -1
  191. package/types/product/Toast.d.ts.map +0 -1
  192. package/types/product/color.d.ts.map +0 -1
  193. package/types/product/combobox/filter.d.ts.map +0 -1
  194. package/types/product/index.d.ts.map +0 -1
  195. package/types/product/scope.d.ts.map +0 -1
  196. package/types/product/surfaces.data.d.ts.map +0 -1
  197. package/types/usage.d.ts +0 -2
  198. package/types/usage.d.ts.map +0 -1
  199. package/types/wallet.d.ts.map +0 -1
  200. /package/{src → dist}/styles/hanzo-motion.css +0 -0
@@ -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
- })
@@ -1,110 +0,0 @@
1
- 'use client'
2
-
3
- /**
4
- * Reorder — a tiny pointer-driven drag-to-reorder list. No dependency: it uses
5
- * Pointer Events (so it works with mouse AND touch) and CSS transforms, matching
6
- * the shell's transform-driven motion. Rows are a FIXED height (all pinned rows
7
- * are), which keeps the "where does it drop?" math exact and pure (`targetIndex`)
8
- * and the reflow buttery — the lifted row follows the pointer 1:1 while its
9
- * displaced siblings ease into place.
10
- *
11
- * The list is controlled: `onReorder(from, to)` fires once on drop; the parent
12
- * owns the data (here, the account-backed pins model). `renderItem` gets a
13
- * `handle` to spread on the drag affordance so the rest of the row stays
14
- * interactive (tap to open, etc.).
15
- */
16
- import { useCallback, useRef, useState, type ReactNode } from 'react'
17
- import { YStack } from '@hanzo/gui'
18
-
19
- /** Final index for a drag: start + rounded rows travelled, clamped. PURE. */
20
- export function targetIndex(from: number, dy: number, rowH: number, count: number): number {
21
- if (rowH <= 0 || count <= 0) return from
22
- const raw = from + Math.round(dy / rowH)
23
- return Math.max(0, Math.min(raw, count - 1))
24
- }
25
-
26
- /** Vertical shift for row `i` during a drag from `from`→`to` (the dragged row
27
- * follows the pointer; rows between them make room). PURE. */
28
- export function rowShift(i: number, from: number, to: number, dy: number, rowH: number): number {
29
- if (i === from) return dy
30
- if (from < to && i > from && i <= to) return -rowH
31
- if (from > to && i < from && i >= to) return rowH
32
- return 0
33
- }
34
-
35
- export type DragHandle = { onPointerDown: (e: { clientY?: number; pointerId?: number; nativeEvent?: { clientY?: number } }) => void }
36
-
37
- export function Reorder<T>({
38
- items,
39
- keyOf,
40
- rowHeight = 44,
41
- onReorder,
42
- renderItem,
43
- }: {
44
- items: T[]
45
- keyOf: (t: T) => string
46
- rowHeight?: number
47
- onReorder: (from: number, to: number) => void
48
- renderItem: (item: T, handle: DragHandle, dragging: boolean) => ReactNode
49
- }) {
50
- const [drag, setDrag] = useState<{ from: number; dy: number } | null>(null)
51
- const startRef = useRef<{ from: number; startY: number } | null>(null)
52
-
53
- const to = drag ? targetIndex(drag.from, drag.dy, rowHeight, items.length) : -1
54
-
55
- const begin = useCallback(
56
- (index: number) =>
57
- (e: { clientY?: number; pointerId?: number; nativeEvent?: { clientY?: number } }) => {
58
- const startY = e.clientY ?? e.nativeEvent?.clientY ?? 0
59
- startRef.current = { from: index, startY }
60
- setDrag({ from: index, dy: 0 })
61
-
62
- const move = (ev: PointerEvent) => {
63
- if (!startRef.current) return
64
- setDrag({ from: startRef.current.from, dy: ev.clientY - startRef.current.startY })
65
- }
66
- const end = () => {
67
- const s = startRef.current
68
- window.removeEventListener('pointermove', move)
69
- window.removeEventListener('pointerup', end)
70
- window.removeEventListener('pointercancel', end)
71
- startRef.current = null
72
- setDrag((d) => {
73
- if (s && d) {
74
- const t = targetIndex(s.from, d.dy, rowHeight, items.length)
75
- if (t !== s.from) onReorder(s.from, t)
76
- }
77
- return null
78
- })
79
- }
80
- window.addEventListener('pointermove', move)
81
- window.addEventListener('pointerup', end)
82
- window.addEventListener('pointercancel', end)
83
- },
84
- [items.length, rowHeight, onReorder],
85
- )
86
-
87
- return (
88
- <YStack>
89
- {items.map((item, i) => {
90
- const dragging = drag?.from === i
91
- const shift = drag && to >= 0 ? rowShift(i, drag.from, to, drag.dy, rowHeight) : 0
92
- return (
93
- <YStack
94
- key={keyOf(item)}
95
- height={rowHeight}
96
- justify="center"
97
- className="hz-drag-item"
98
- style={{
99
- transform: `translateY(${shift}px)`,
100
- zIndex: dragging ? 2 : 1,
101
- ...(dragging ? { transition: 'none', boxShadow: '0 8px 24px rgba(0,0,0,0.28)' } : null),
102
- }}
103
- >
104
- {renderItem(item, { onPointerDown: begin(i) }, dragging)}
105
- </YStack>
106
- )
107
- })}
108
- </YStack>
109
- )
110
- }
@@ -1,94 +0,0 @@
1
- 'use client'
2
-
3
- /**
4
- * SelectMenu — a compact, reusable dropdown select on @hanzo/gui Popover (the
5
- * console's proven dropdown idiom, same as OrgSwitcher/ScopeSwitcher). Renders a
6
- * labelled trigger ("All types", or the chosen option) and a popover list with a
7
- * check on the active option. Prop-driven + self-contained so it lifts into
8
- * `@hanzo/ui`. `value === null` is the "all"/unfiltered state.
9
- */
10
- import type { ReactElement } from 'react'
11
- import { useState } from 'react'
12
- import { Button, Popover, Text, XStack, YStack } from '@hanzo/gui'
13
- import { ChevronDown, Check } from '@hanzogui/lucide-icons-2'
14
-
15
- export type SelectOption<T extends string> = { key: T; label: string }
16
-
17
- export function SelectMenu<T extends string>({
18
- options,
19
- value,
20
- onChange,
21
- allLabel = 'All',
22
- icon,
23
- minWidth = 130,
24
- }: {
25
- options: SelectOption<T>[]
26
- value: T | null
27
- onChange: (v: T | null) => void
28
- /** Label shown (and first menu row) for the unfiltered `null` state. */
29
- allLabel?: string
30
- /** Optional leading icon in the trigger. */
31
- icon?: ReactElement
32
- minWidth?: number
33
- }) {
34
- const [open, setOpen] = useState(false)
35
- const active = value === null ? null : options.find((o) => o.key === value) ?? null
36
- const triggerLabel = active ? active.label : allLabel
37
-
38
- const pick = (v: T | null) => {
39
- onChange(v)
40
- setOpen(false)
41
- }
42
-
43
- return (
44
- <Popover open={open} onOpenChange={setOpen} placement="bottom-start">
45
- <Popover.Trigger asChild>
46
- <Button
47
- size="$2"
48
- minW={minWidth}
49
- justify="space-between"
50
- borderWidth={1}
51
- borderColor="$borderColor"
52
- bg={value !== null ? '$color5' : 'transparent'}
53
- icon={icon}
54
- iconAfter={<ChevronDown size={14} opacity={0.6} />}
55
- >
56
- <Text fontSize="$2" color="$color12" numberOfLines={1} flex={1}>
57
- {triggerLabel}
58
- </Text>
59
- </Button>
60
- </Popover.Trigger>
61
- <Popover.Content bordered elevate p="$1.5" minW={minWidth} bg="$color2" borderColor="$borderColor">
62
- <YStack gap="$0.5" minW={minWidth} maxH={320} overflow="scroll">
63
- <Row label={allLabel} active={value === null} onPress={() => pick(null)} />
64
- {options.map((o) => (
65
- <Row key={o.key} label={o.label} active={value === o.key} onPress={() => pick(o.key)} />
66
- ))}
67
- </YStack>
68
- </Popover.Content>
69
- </Popover>
70
- )
71
- }
72
-
73
- function Row({ label, active, onPress }: { label: string; active: boolean; onPress: () => void }) {
74
- return (
75
- <XStack
76
- items="center"
77
- gap="$2"
78
- px="$2.5"
79
- py="$1.5"
80
- rounded="$3"
81
- cursor="pointer"
82
- hoverStyle={{ bg: '$color4' }}
83
- bg={active ? '$color4' : 'transparent'}
84
- onPress={onPress}
85
- >
86
- <XStack width={14} items="center" justify="center">
87
- {active ? <Check size={13} /> : null}
88
- </XStack>
89
- <Text fontSize="$2" color="$color12" flex={1} numberOfLines={1}>
90
- {label}
91
- </Text>
92
- </XStack>
93
- )
94
- }
@@ -1,197 +0,0 @@
1
- 'use client'
2
-
3
- /**
4
- * SlideOver — the ONE right-side (or left) slide-out overlay for the console.
5
- *
6
- * It is the single mechanism behind every off-canvas surface: the mobile nav
7
- * drawer, the item DetailPane, and the account/settings menu. One definition, many
8
- * mounts (DRY) — so every drawer slides, dims, traps, and closes identically.
9
- *
10
- * Smoothness is CSS-transform driven (never a JS layout branch): the panel is
11
- * ALWAYS mounted and toggles `translateX(0) ↔ translateX(±100%)` via the shared
12
- * `.hz-slide` class, and the backdrop cross-fades via `.hz-fade`. Because it never
13
- * unmounts, the EXIT animates too (a `Dialog` that unmounts on close cannot). Both
14
- * classes are reduced-motion-aware in `globals.css`, so the whole thing snaps
15
- * instantly for users who ask for that — no bespoke logic here.
16
- *
17
- * Responsive by construction: FULL-WIDTH (full-screen) below `lg`, a fixed-width
18
- * pane at `lg+` (`size`). Uses `100dvh` so the mobile browser chrome never clips it.
19
- *
20
- * A11y: `role="dialog" aria-modal`, Escape to close, backdrop click to close, body
21
- * scroll lock while open (ref-counted so stacked overlays don't fight), and focus
22
- * moves into the panel on open and returns to the opener on close.
23
- */
24
- import { useEffect, useRef, type ReactNode } from 'react'
25
- import { Button, ScrollView, Text, XStack, YStack } from '@hanzo/gui'
26
- import { X } from '@hanzogui/lucide-icons-2'
27
-
28
- import { asColor, type IconLike } from './color'
29
-
30
- /** The lg breakpoint (px) — matches the shell's one responsive threshold. */
31
- const LG = 1024
32
-
33
- // ── Body scroll lock, ref-counted so nested/stacked overlays don't clobber it ──
34
- let lockCount = 0
35
- let savedOverflow = ''
36
- function lockScroll() {
37
- if (typeof document === 'undefined') return
38
- if (lockCount === 0) {
39
- savedOverflow = document.body.style.overflow
40
- document.body.style.overflow = 'hidden'
41
- }
42
- lockCount++
43
- }
44
- function unlockScroll() {
45
- if (typeof document === 'undefined') return
46
- lockCount = Math.max(0, lockCount - 1)
47
- if (lockCount === 0) document.body.style.overflow = savedOverflow
48
- }
49
-
50
- export type SlideOverProps = {
51
- open: boolean
52
- onClose: () => void
53
- /** Which edge the panel slides from. Default right. */
54
- side?: 'right' | 'left'
55
- /** Panel width at lg+ (px). Below lg it is always full-screen. Default 420. */
56
- size?: number
57
- /** Optional header title — when set, SlideOver renders a header + a scroll body. */
58
- title?: ReactNode
59
- /** Optional header icon (tinted with `iconColor`). */
60
- icon?: IconLike
61
- iconColor?: string
62
- /** Extra header content, right-aligned before the close button. */
63
- headerRight?: ReactNode
64
- /** Accessible label when no visible title is given. */
65
- ariaLabel?: string
66
- /** z-index for stacking (a pane opened over a drawer passes a higher value). */
67
- zIndex?: number
68
- children: ReactNode
69
- }
70
-
71
- export function SlideOver({
72
- open,
73
- onClose,
74
- side = 'right',
75
- size = 420,
76
- title,
77
- icon: Icon,
78
- iconColor,
79
- headerRight,
80
- ariaLabel,
81
- zIndex = 1000,
82
- children,
83
- }: SlideOverProps) {
84
- const panelRef = useRef<HTMLElement | null>(null)
85
- const openerRef = useRef<Element | null>(null)
86
-
87
- // Body scroll lock + focus management, driven by `open`.
88
- useEffect(() => {
89
- if (!open) return
90
- openerRef.current = typeof document !== 'undefined' ? document.activeElement : null
91
- lockScroll()
92
- // Move focus into the panel (next frame so it is laid out + on-screen).
93
- const t = window.setTimeout(() => {
94
- const el = panelRef.current
95
- if (el && typeof el.focus === 'function') el.focus()
96
- }, 0)
97
- return () => {
98
- window.clearTimeout(t)
99
- unlockScroll()
100
- const opener = openerRef.current
101
- if (opener && opener instanceof HTMLElement && typeof opener.focus === 'function') opener.focus()
102
- }
103
- }, [open])
104
-
105
- // Escape closes.
106
- useEffect(() => {
107
- if (!open) return
108
- const onKey = (e: KeyboardEvent) => {
109
- if (e.key === 'Escape') {
110
- e.stopPropagation()
111
- onClose()
112
- }
113
- }
114
- window.addEventListener('keydown', onKey)
115
- return () => window.removeEventListener('keydown', onKey)
116
- }, [open, onClose])
117
-
118
- const offscreen = side === 'right' ? 'translateX(100%)' : 'translateX(-100%)'
119
- const edge = side === 'right' ? { r: 0 } : { l: 0 }
120
-
121
- return (
122
- <YStack
123
- // Fixed to the viewport; overflow hidden so the off-screen panel (absolute
124
- // child) is CLIPPED and never adds a horizontal scrollbar.
125
- overflow="hidden"
126
- pointerEvents={open ? 'auto' : 'none'}
127
- aria-hidden={!open}
128
- style={{ position: 'fixed', inset: 0, zIndex }}
129
- >
130
- {/* Backdrop — cross-fades; click to dismiss. */}
131
- <YStack
132
- position="absolute"
133
- t={0}
134
- l={0}
135
- r={0}
136
- b={0}
137
- bg="rgba(0,0,0,0.55)"
138
- className="hz-fade"
139
- style={{ opacity: open ? 1 : 0 }}
140
- onPress={onClose}
141
- />
142
-
143
- {/* Panel — slides in from the edge. Full-screen below lg, fixed width at lg+. */}
144
- <YStack
145
- // @ts-expect-error — web DOM ref; Gui forwards it to the underlying node.
146
- ref={panelRef}
147
- tabIndex={-1}
148
- role="dialog"
149
- aria-modal={open ? true : undefined}
150
- aria-label={typeof title === 'string' ? title : ariaLabel}
151
- position="absolute"
152
- t={0}
153
- b={0}
154
- {...edge}
155
- width="100%"
156
- $lg={{ width: size, maxW: '100vw' }}
157
- bg="$color1"
158
- borderLeftWidth={side === 'right' ? 1 : 0}
159
- borderRightWidth={side === 'left' ? 1 : 0}
160
- borderColor="$borderColor"
161
- className="hz-slide"
162
- style={{ transform: open ? 'translateX(0)' : offscreen, height: '100dvh' }}
163
- >
164
- {title !== undefined ? (
165
- <>
166
- <XStack
167
- items="center"
168
- gap="$2.5"
169
- px="$4"
170
- height={56}
171
- borderBottomWidth={1}
172
- borderColor="$borderColor"
173
- >
174
- {Icon ? <Icon size={18} color={iconColor ? asColor(iconColor) : undefined} /> : null}
175
- <Text flex={1} fontSize="$5" fontWeight="700" color="$color12" numberOfLines={1}>
176
- {title}
177
- </Text>
178
- {headerRight}
179
- <Button size="$2" chromeless icon={<X size={18} />} onPress={onClose} aria-label="Close" />
180
- </XStack>
181
- <ScrollView flex={1}>
182
- <YStack flex={1} p="$4" gap="$3">
183
- {children}
184
- </YStack>
185
- </ScrollView>
186
- </>
187
- ) : (
188
- // Bare mode — the caller owns the full panel layout (e.g. the nav drawer
189
- // renders its own header + scroll + footer).
190
- children
191
- )}
192
- </YStack>
193
- </YStack>
194
- )
195
- }
196
-
197
- export { LG as SLIDEOVER_LG }
@@ -1,49 +0,0 @@
1
- 'use client'
2
-
3
- /**
4
- * Status pill — maps a resource/cluster status string to a tone, one place.
5
- *
6
- * Provisioned things (managed resources, clusters) report free-form lifecycle
7
- * strings; this normalizes them to green/yellow/red/neutral so every list reads
8
- * the same. Style props use the v5 shorthand set.
9
- */
10
- import { Text } from '@hanzo/gui'
11
-
12
- type Tone = 'green' | 'yellow' | 'red' | 'neutral'
13
-
14
- const toneOf = (status: string): Tone => {
15
- const s = status.toLowerCase()
16
- // Platform health verdicts map straight to a tone (the apps inventory reports
17
- // green/yellow/red directly).
18
- if (s === 'green') return 'green'
19
- if (s === 'yellow') return 'yellow'
20
- if (s === 'red') return 'red'
21
- if (['ready', 'active', 'running', 'available', 'ok'].includes(s)) return 'green'
22
- if (['creating', 'provisioning', 'pending', 'updating', 'attaching'].includes(s)) return 'yellow'
23
- if (['error', 'failed', 'degraded', 'down'].includes(s)) return 'red'
24
- return 'neutral'
25
- }
26
-
27
- // `as const` keeps the literal token types (not widened to `string`) so they
28
- // satisfy the GUI `bg`/`color` token unions.
29
- const TONE_BG = {
30
- green: '$color5',
31
- yellow: '$color4',
32
- red: '$color4',
33
- neutral: '$color3',
34
- } as const
35
- const TONE_FG = {
36
- green: '$color12',
37
- yellow: '$color12',
38
- red: '$color12',
39
- neutral: '$color11',
40
- } as const
41
-
42
- export function StatusTag({ status }: { status?: string }) {
43
- const tone = toneOf(status ?? '')
44
- return (
45
- <Text fontSize="$1" px="$2" py="$1" rounded="$2" bg={TONE_BG[tone]} color={TONE_FG[tone]}>
46
- {status || 'unknown'}
47
- </Text>
48
- )
49
- }
@@ -1,25 +0,0 @@
1
- 'use client'
2
-
3
- /**
4
- * Theme toggle — flips the console between dark and light via next-theme. The
5
- * provider already wires `NextThemeProvider` → `GuiProvider`, so setting the
6
- * theme here re-themes the whole tree. Shows the action's target icon (a sun in
7
- * dark mode, a moon in light mode).
8
- */
9
- import { Button } from '@hanzo/gui'
10
- import { useThemeSetting } from '@hanzogui/next-theme'
11
- import { Moon, Sun } from '@hanzogui/lucide-icons-2'
12
-
13
- export function ThemeToggle() {
14
- const { current, resolvedTheme, set } = useThemeSetting()
15
- const isDark = (resolvedTheme ?? current ?? 'dark') !== 'light'
16
- return (
17
- <Button
18
- size="$2"
19
- chromeless
20
- icon={isDark ? <Sun size={16} /> : <Moon size={16} />}
21
- onPress={() => set(isDark ? 'light' : 'dark')}
22
- aria-label={isDark ? 'Switch to light theme' : 'Switch to dark theme'}
23
- />
24
- )
25
- }