@hanzo/ui 8.0.7 → 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 (214) 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 +6 -8
  75. package/{types → dist}/product/SlideOver.d.ts +0 -1
  76. package/{types → dist}/product/StatusTag.d.ts +0 -1
  77. package/{types → dist}/product/ThemeToggle.d.ts +0 -1
  78. package/{types → dist}/product/ThemeToggleNext.d.ts +0 -1
  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 +0 -1
  85. package/dist/product/index.js +5 -0
  86. package/dist/product/index.js.map +1 -0
  87. package/{types → dist}/product/menu/ContextMenu.d.ts +3 -6
  88. package/dist/product/menu/DropdownMenu.d.ts +29 -0
  89. package/dist/product/menu/FloatingMenu.d.ts +36 -0
  90. package/{types → dist}/product/menu/index.d.ts +1 -1
  91. package/{types → dist}/product/menu/items.d.ts +0 -1
  92. package/{types → dist}/product/menu/portal-theme.d.ts +0 -1
  93. package/{types → dist}/product/menu/roving.d.ts +0 -1
  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 +54 -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 -127
  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 -80
  139. package/src/product/SlideOver.tsx +0 -197
  140. package/src/product/StatusTag.tsx +0 -49
  141. package/src/product/ThemeToggle.tsx +0 -135
  142. package/src/product/ThemeToggleNext.tsx +0 -27
  143. package/src/product/Toast.tsx +0 -166
  144. package/src/product/color.ts +0 -17
  145. package/src/product/combobox/filter.test.ts +0 -51
  146. package/src/product/combobox/filter.ts +0 -39
  147. package/src/product/index.ts +0 -76
  148. package/src/product/menu/ContextMenu.tsx +0 -114
  149. package/src/product/menu/DropdownMenu.tsx +0 -76
  150. package/src/product/menu/index.ts +0 -15
  151. package/src/product/menu/items.tsx +0 -230
  152. package/src/product/menu/portal-theme.tsx +0 -33
  153. package/src/product/menu/roving.ts +0 -34
  154. package/src/product/scope.test.ts +0 -92
  155. package/src/product/scope.ts +0 -123
  156. package/src/product/surfaces.data.ts +0 -42
  157. package/src/usage.ts +0 -8
  158. package/src/wallet.ts +0 -11
  159. package/types/billing.d.ts.map +0 -1
  160. package/types/canvas.d.ts +0 -2
  161. package/types/canvas.d.ts.map +0 -1
  162. package/types/cd/layout.d.ts.map +0 -1
  163. package/types/cd/status.d.ts.map +0 -1
  164. package/types/cd/time.d.ts.map +0 -1
  165. package/types/cd/types.d.ts.map +0 -1
  166. package/types/dashboard.d.ts +0 -2
  167. package/types/dashboard.d.ts.map +0 -1
  168. package/types/data.d.ts +0 -2
  169. package/types/data.d.ts.map +0 -1
  170. package/types/gitops.d.ts +0 -2
  171. package/types/gitops.d.ts.map +0 -1
  172. package/types/index.d.ts.map +0 -1
  173. package/types/network.d.ts.map +0 -1
  174. package/types/primitives/bases/data/index.d.ts +0 -2
  175. package/types/primitives/bases/data/index.d.ts.map +0 -1
  176. package/types/product/AppHeader.d.ts.map +0 -1
  177. package/types/product/BrandMark.d.ts.map +0 -1
  178. package/types/product/Charts.d.ts.map +0 -1
  179. package/types/product/ComboBox.d.ts.map +0 -1
  180. package/types/product/DataTable.d.ts.map +0 -1
  181. package/types/product/Donut.d.ts.map +0 -1
  182. package/types/product/EmptyState.d.ts.map +0 -1
  183. package/types/product/FadeIn.d.ts.map +0 -1
  184. package/types/product/Field.d.ts.map +0 -1
  185. package/types/product/HanzoMark.d.ts.map +0 -1
  186. package/types/product/Metric.d.ts.map +0 -1
  187. package/types/product/OrgSwitcher.d.ts.map +0 -1
  188. package/types/product/PageHeader.d.ts.map +0 -1
  189. package/types/product/PrimaryButton.d.ts.map +0 -1
  190. package/types/product/ProductIcon.d.ts.map +0 -1
  191. package/types/product/ProviderLogo.d.ts.map +0 -1
  192. package/types/product/Reorder.d.ts.map +0 -1
  193. package/types/product/SelectMenu.d.ts.map +0 -1
  194. package/types/product/SlideOver.d.ts.map +0 -1
  195. package/types/product/StatusTag.d.ts.map +0 -1
  196. package/types/product/ThemeToggle.d.ts.map +0 -1
  197. package/types/product/ThemeToggleNext.d.ts.map +0 -1
  198. package/types/product/Toast.d.ts.map +0 -1
  199. package/types/product/color.d.ts.map +0 -1
  200. package/types/product/combobox/filter.d.ts.map +0 -1
  201. package/types/product/index.d.ts.map +0 -1
  202. package/types/product/menu/ContextMenu.d.ts.map +0 -1
  203. package/types/product/menu/DropdownMenu.d.ts +0 -34
  204. package/types/product/menu/DropdownMenu.d.ts.map +0 -1
  205. package/types/product/menu/index.d.ts.map +0 -1
  206. package/types/product/menu/items.d.ts.map +0 -1
  207. package/types/product/menu/portal-theme.d.ts.map +0 -1
  208. package/types/product/menu/roving.d.ts.map +0 -1
  209. package/types/product/scope.d.ts.map +0 -1
  210. package/types/product/surfaces.data.d.ts.map +0 -1
  211. package/types/usage.d.ts +0 -2
  212. package/types/usage.d.ts.map +0 -1
  213. package/types/wallet.d.ts.map +0 -1
  214. /package/{src → dist}/styles/hanzo-motion.css +0 -0
@@ -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,80 +0,0 @@
1
- 'use client'
2
-
3
- /**
4
- * SelectMenu — a compact, reusable dropdown select on @hanzo/gui Popover. Renders a
5
- * labelled trigger ("All types", or the chosen option) and a menu list with a check on
6
- * the active option. Prop-driven + self-contained. `value === null` is the
7
- * "all"/unfiltered state.
8
- *
9
- * Uses the ONE shared menu spec (MenuPanel + MenuItemView) so it is pixel-identical to
10
- * DropdownMenu/ContextMenu, and the SAME portal-theme fix (PortalTheme) so it renders
11
- * correctly through the portal under a nested `<Theme>`, light or dark.
12
- */
13
- import type { ReactElement } from 'react'
14
- import { useState } from 'react'
15
- import { Button, Popover, Text } from '@hanzo/gui'
16
- import { ChevronDown } from '@hanzogui/lucide-icons-2'
17
- import { MenuItemView, MenuPanel } from './menu/items'
18
- import { PortalTheme, useThemeName } from './menu/portal-theme'
19
- import { menuKeyDown } from './menu/roving'
20
-
21
- export type SelectOption<T extends string> = { key: T; label: string }
22
-
23
- export function SelectMenu<T extends string>({
24
- options,
25
- value,
26
- onChange,
27
- allLabel = 'All',
28
- icon,
29
- minWidth = 130,
30
- }: {
31
- options: SelectOption<T>[]
32
- value: T | null
33
- onChange: (v: T | null) => void
34
- /** Label shown (and first menu row) for the unfiltered `null` state. */
35
- allLabel?: string
36
- /** Optional leading icon in the trigger. */
37
- icon?: ReactElement
38
- minWidth?: number
39
- }) {
40
- const [open, setOpen] = useState(false)
41
- const themeName = useThemeName()
42
- const active = value === null ? null : options.find((o) => o.key === value) ?? null
43
- const triggerLabel = active ? active.label : allLabel
44
-
45
- const pick = (v: T | null) => {
46
- onChange(v)
47
- setOpen(false)
48
- }
49
-
50
- return (
51
- <Popover open={open} onOpenChange={setOpen} placement="bottom-start">
52
- <Popover.Trigger asChild>
53
- <Button
54
- size="$2"
55
- minW={minWidth}
56
- justify="space-between"
57
- borderWidth={1}
58
- borderColor="$borderColor"
59
- bg={value !== null ? '$color5' : 'transparent'}
60
- icon={icon}
61
- iconAfter={<ChevronDown size={14} opacity={0.6} />}
62
- >
63
- <Text fontSize="$2" color="$color12" numberOfLines={1} flex={1}>
64
- {triggerLabel}
65
- </Text>
66
- </Button>
67
- </Popover.Trigger>
68
- <Popover.Content bg="transparent" borderWidth={0} p={0} elevation={0}>
69
- <PortalTheme name={themeName}>
70
- <MenuPanel minWidth={minWidth} maxHeight={320} onKeyDown={(e) => menuKeyDown(e, () => setOpen(false))}>
71
- <MenuItemView label={allLabel} selected={value === null} onSelect={() => pick(null)} />
72
- {options.map((o) => (
73
- <MenuItemView key={o.key} label={o.label} selected={value === o.key} onSelect={() => pick(o.key)} />
74
- ))}
75
- </MenuPanel>
76
- </PortalTheme>
77
- </Popover.Content>
78
- </Popover>
79
- )
80
- }
@@ -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,135 +0,0 @@
1
- 'use client'
2
-
3
- /**
4
- * ThemeToggle — flips between dark and light. Framework-agnostic by default so
5
- * Vite / Tauri / Express hosts can use it with NO Next dependency:
6
- *
7
- * • Controlled: <ThemeToggle theme={theme} onToggle={setTheme} /> (host owns theme)
8
- * • Uncontrolled (agnostic): <ThemeToggle onThemeChange={fn} /> (toggles the DOM
9
- * `.dark` class + persists to localStorage)
10
- * • Uncontrolled (no props): <ThemeToggle /> (console/Next —
11
- * falls back to the OPTIONAL @hanzogui/next-theme path;
12
- * if it cannot load, degrades to the agnostic DOM toggle)
13
- *
14
- * Shows the action's target icon (a sun in dark mode, a moon in light mode).
15
- */
16
- import type { ReactNode } from 'react'
17
- import { Component, Suspense, lazy, useState } from 'react'
18
- import { Button } from '@hanzo/gui'
19
- import { Moon, Sun } from '@hanzogui/lucide-icons-2'
20
-
21
- export type ThemeMode = 'light' | 'dark'
22
-
23
- export type ThemeToggleProps = {
24
- /** Controlled theme. Pair with `onToggle`/`onThemeChange` to fully control it. */
25
- theme?: ThemeMode
26
- /** Seed for uncontrolled mode (default: read from the DOM `.dark` class, else 'dark'). */
27
- defaultTheme?: ThemeMode
28
- /** Called with the next theme on toggle (alias of `onThemeChange`). */
29
- onToggle?: (next: ThemeMode) => void
30
- /** Called with the next theme on toggle. */
31
- onThemeChange?: (next: ThemeMode) => void
32
- /** @hanzo/gui Button size token (default "$2"). */
33
- size?: string
34
- /** aria-label override. */
35
- label?: string
36
- }
37
-
38
- const STORAGE_KEY = 'theme'
39
-
40
- function readDomTheme(): ThemeMode | undefined {
41
- if (typeof document === 'undefined') return undefined
42
- const el = document.documentElement
43
- if (el.classList.contains('dark')) return 'dark'
44
- if (el.classList.contains('light')) return 'light'
45
- try {
46
- const ls = window.localStorage.getItem(STORAGE_KEY)
47
- if (ls === 'light' || ls === 'dark') return ls
48
- } catch {
49
- /* localStorage may be unavailable */
50
- }
51
- return undefined
52
- }
53
-
54
- function applyDomTheme(next: ThemeMode): void {
55
- if (typeof document === 'undefined') return
56
- const el = document.documentElement
57
- el.classList.toggle('dark', next === 'dark')
58
- el.classList.toggle('light', next === 'light')
59
- el.style.colorScheme = next
60
- try {
61
- window.localStorage.setItem(STORAGE_KEY, next)
62
- } catch {
63
- /* ignore */
64
- }
65
- }
66
-
67
- /**
68
- * The framework-agnostic toggle button. Controlled when `theme` is provided; otherwise
69
- * self-managed via the DOM `.dark` class. No framework dependency — safe on any host.
70
- */
71
- export function AgnosticThemeToggle({
72
- theme,
73
- defaultTheme,
74
- onToggle,
75
- onThemeChange,
76
- size = '$2',
77
- label,
78
- }: ThemeToggleProps) {
79
- const controlled = theme !== undefined
80
- const [internal, setInternal] = useState<ThemeMode>(() => theme ?? defaultTheme ?? readDomTheme() ?? 'dark')
81
- const current: ThemeMode = controlled ? (theme as ThemeMode) : internal
82
- const isDark = current === 'dark'
83
-
84
- const toggle = () => {
85
- const next: ThemeMode = isDark ? 'light' : 'dark'
86
- if (!controlled) {
87
- setInternal(next)
88
- applyDomTheme(next)
89
- }
90
- onToggle?.(next)
91
- onThemeChange?.(next)
92
- }
93
-
94
- return (
95
- <Button
96
- size={size as never}
97
- chromeless
98
- icon={isDark ? <Sun size={16} /> : <Moon size={16} />}
99
- onPress={toggle}
100
- aria-label={label ?? (isDark ? 'Switch to light theme' : 'Switch to dark theme')}
101
- />
102
- )
103
- }
104
-
105
- // @hanzogui/next-theme is an OPTIONAL dependency — loaded only on the uncontrolled,
106
- // no-props path (console/Next). Lazily code-split so non-Next hosts never need it at
107
- // runtime; if it fails to load, the boundary below degrades to the agnostic toggle.
108
- const NextThemeToggle = lazy(() => import('./ThemeToggleNext'))
109
-
110
- class NextThemeBoundary extends Component<{ fallback: ReactNode; children: ReactNode }, { failed: boolean }> {
111
- state = { failed: false }
112
- static getDerivedStateFromError() {
113
- return { failed: true }
114
- }
115
- render() {
116
- return this.state.failed ? this.props.fallback : this.props.children
117
- }
118
- }
119
-
120
- export function ThemeToggle(props: ThemeToggleProps) {
121
- const injected =
122
- props.theme !== undefined || props.onToggle !== undefined || props.onThemeChange !== undefined
123
- if (injected) return <AgnosticThemeToggle {...props} />
124
-
125
- // No props → keep the existing console/Next behaviour via the optional next-theme
126
- // path, degrading to the agnostic DOM toggle if it is not installed.
127
- const fallback = <AgnosticThemeToggle {...props} />
128
- return (
129
- <NextThemeBoundary fallback={fallback}>
130
- <Suspense fallback={fallback}>
131
- <NextThemeToggle size={props.size} label={props.label} />
132
- </Suspense>
133
- </NextThemeBoundary>
134
- )
135
- }
@@ -1,27 +0,0 @@
1
- 'use client'
2
-
3
- /**
4
- * ThemeToggleNext — the @hanzogui/next-theme binding (console / Next). This is the ONLY
5
- * module that imports @hanzogui/next-theme, so it stays an OPTIONAL dependency: the base
6
- * ThemeToggle lazy-loads it (default export) and non-Next hosts never pull it in unless
7
- * they import this component directly. UI is delegated to the framework-agnostic button
8
- * in controlled mode, so every toggle looks identical.
9
- */
10
- import { useThemeSetting } from '@hanzogui/next-theme'
11
- import { AgnosticThemeToggle, type ThemeToggleProps } from './ThemeToggle'
12
-
13
- function ThemeToggleNext({ size, label }: Pick<ThemeToggleProps, 'size' | 'label'>) {
14
- const { current, resolvedTheme, set } = useThemeSetting()
15
- const isDark = (resolvedTheme ?? current ?? 'dark') !== 'light'
16
- return (
17
- <AgnosticThemeToggle
18
- theme={isDark ? 'dark' : 'light'}
19
- onToggle={(next) => set(next)}
20
- size={size}
21
- label={label}
22
- />
23
- )
24
- }
25
-
26
- export default ThemeToggleNext
27
- export { ThemeToggleNext }