@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.
- package/dist/ThemeToggleNext-7727UR5W.js +4 -0
- package/dist/ThemeToggleNext-7727UR5W.js.map +1 -0
- package/dist/ThemeToggleNext-ICB5KL5J.cjs +19 -0
- package/dist/ThemeToggleNext-ICB5KL5J.cjs.map +1 -0
- package/dist/billing.cjs +15 -0
- package/dist/billing.cjs.map +1 -0
- package/{types → dist}/billing.d.ts +0 -1
- package/dist/billing.js +4 -0
- package/dist/billing.js.map +1 -0
- package/dist/canvas.cjs +15 -0
- package/dist/canvas.cjs.map +1 -0
- package/dist/canvas.d.ts +1 -0
- package/dist/canvas.js +4 -0
- package/dist/canvas.js.map +1 -0
- package/{types → dist}/cd/layout.d.ts +0 -1
- package/{types → dist}/cd/status.d.ts +0 -1
- package/{types → dist}/cd/time.d.ts +0 -1
- package/{types → dist}/cd/types.d.ts +0 -1
- package/dist/chunk-42CU5GSF.js +98 -0
- package/dist/chunk-42CU5GSF.js.map +1 -0
- package/dist/chunk-GDGVXU7W.js +1999 -0
- package/dist/chunk-GDGVXU7W.js.map +1 -0
- package/dist/chunk-HSPMQQVJ.cjs +2066 -0
- package/dist/chunk-HSPMQQVJ.cjs.map +1 -0
- package/dist/chunk-YBXVUV7P.cjs +103 -0
- package/dist/chunk-YBXVUV7P.cjs.map +1 -0
- package/dist/dashboard.cjs +15 -0
- package/dist/dashboard.cjs.map +1 -0
- package/dist/dashboard.d.ts +1 -0
- package/dist/dashboard.js +4 -0
- package/dist/dashboard.js.map +1 -0
- package/dist/data.cjs +15 -0
- package/dist/data.cjs.map +1 -0
- package/dist/data.d.ts +1 -0
- package/dist/data.js +4 -0
- package/dist/data.js.map +1 -0
- package/dist/gitops.cjs +15 -0
- package/dist/gitops.cjs.map +1 -0
- package/dist/gitops.d.ts +1 -0
- package/dist/gitops.js +4 -0
- package/dist/gitops.js.map +1 -0
- package/dist/index.cjs +291 -0
- package/dist/index.cjs.map +1 -0
- package/{types → dist}/index.d.ts +0 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/network.cjs +15 -0
- package/dist/network.cjs.map +1 -0
- package/{types → dist}/network.d.ts +0 -1
- package/dist/network.js +4 -0
- package/dist/network.js.map +1 -0
- package/dist/primitives/bases/data/index.cjs +15 -0
- package/dist/primitives/bases/data/index.cjs.map +1 -0
- package/dist/primitives/bases/data/index.d.ts +1 -0
- package/dist/primitives/bases/data/index.js +4 -0
- package/dist/primitives/bases/data/index.js.map +1 -0
- package/{types → dist}/product/AppHeader.d.ts +0 -1
- package/{types → dist}/product/BrandMark.d.ts +0 -1
- package/{types → dist}/product/Charts.d.ts +0 -1
- package/{types → dist}/product/ComboBox.d.ts +0 -1
- package/{types → dist}/product/DataTable.d.ts +0 -1
- package/{types → dist}/product/Donut.d.ts +0 -1
- package/{types → dist}/product/EmptyState.d.ts +0 -1
- package/{types → dist}/product/FadeIn.d.ts +0 -1
- package/{types → dist}/product/Field.d.ts +0 -1
- package/{types → dist}/product/HanzoMark.d.ts +0 -1
- package/{types → dist}/product/Metric.d.ts +0 -1
- package/{types → dist}/product/OrgSwitcher.d.ts +0 -1
- package/{types → dist}/product/PageHeader.d.ts +0 -1
- package/{types → dist}/product/PrimaryButton.d.ts +0 -1
- package/{types → dist}/product/ProductIcon.d.ts +0 -1
- package/{types → dist}/product/ProviderLogo.d.ts +0 -1
- package/{types → dist}/product/Reorder.d.ts +0 -1
- package/{types → dist}/product/SelectMenu.d.ts +6 -8
- package/{types → dist}/product/SlideOver.d.ts +0 -1
- package/{types → dist}/product/StatusTag.d.ts +0 -1
- package/{types → dist}/product/ThemeToggle.d.ts +0 -1
- package/{types → dist}/product/ThemeToggleNext.d.ts +0 -1
- package/{types → dist}/product/Toast.d.ts +0 -1
- package/{types → dist}/product/color.d.ts +0 -1
- package/{types → dist}/product/combobox/filter.d.ts +0 -1
- package/dist/product/index.cjs +278 -0
- package/dist/product/index.cjs.map +1 -0
- package/{types → dist}/product/index.d.ts +0 -1
- package/dist/product/index.js +5 -0
- package/dist/product/index.js.map +1 -0
- package/{types → dist}/product/menu/ContextMenu.d.ts +3 -6
- package/dist/product/menu/DropdownMenu.d.ts +29 -0
- package/dist/product/menu/FloatingMenu.d.ts +36 -0
- package/{types → dist}/product/menu/index.d.ts +1 -1
- package/{types → dist}/product/menu/items.d.ts +0 -1
- package/{types → dist}/product/menu/portal-theme.d.ts +0 -1
- package/{types → dist}/product/menu/roving.d.ts +0 -1
- package/{types → dist}/product/scope.d.ts +0 -1
- package/{types → dist}/product/surfaces.data.d.ts +0 -1
- package/dist/usage.cjs +15 -0
- package/dist/usage.cjs.map +1 -0
- package/dist/usage.d.ts +1 -0
- package/dist/usage.js +4 -0
- package/dist/usage.js.map +1 -0
- package/dist/wallet.cjs +15 -0
- package/dist/wallet.cjs.map +1 -0
- package/{types → dist}/wallet.d.ts +0 -1
- package/dist/wallet.js +4 -0
- package/dist/wallet.js.map +1 -0
- package/package.json +54 -32
- package/src/billing.ts +0 -9
- package/src/canvas.ts +0 -11
- package/src/cd/layout.ts +0 -184
- package/src/cd/status.ts +0 -158
- package/src/cd/time.ts +0 -52
- package/src/cd/types.ts +0 -318
- package/src/dashboard.ts +0 -8
- package/src/data.ts +0 -12
- package/src/gitops.ts +0 -5
- package/src/gui-env.d.ts +0 -27
- package/src/index.ts +0 -22
- package/src/network.ts +0 -11
- package/src/primitives/bases/data/index.ts +0 -13
- package/src/product/AppHeader.tsx +0 -183
- package/src/product/BrandMark.tsx +0 -27
- package/src/product/Charts.tsx +0 -471
- package/src/product/ComboBox.tsx +0 -127
- package/src/product/DataTable.tsx +0 -99
- package/src/product/Donut.tsx +0 -83
- package/src/product/EmptyState.tsx +0 -129
- package/src/product/FadeIn.tsx +0 -38
- package/src/product/Field.tsx +0 -160
- package/src/product/HanzoMark.tsx +0 -31
- package/src/product/Metric.tsx +0 -253
- package/src/product/OrgSwitcher.tsx +0 -332
- package/src/product/PageHeader.tsx +0 -31
- package/src/product/PrimaryButton.tsx +0 -17
- package/src/product/ProductIcon.tsx +0 -61
- package/src/product/ProviderLogo.tsx +0 -137
- package/src/product/Reorder.test.ts +0 -41
- package/src/product/Reorder.tsx +0 -110
- package/src/product/SelectMenu.tsx +0 -80
- package/src/product/SlideOver.tsx +0 -197
- package/src/product/StatusTag.tsx +0 -49
- package/src/product/ThemeToggle.tsx +0 -135
- package/src/product/ThemeToggleNext.tsx +0 -27
- package/src/product/Toast.tsx +0 -166
- package/src/product/color.ts +0 -17
- package/src/product/combobox/filter.test.ts +0 -51
- package/src/product/combobox/filter.ts +0 -39
- package/src/product/index.ts +0 -76
- package/src/product/menu/ContextMenu.tsx +0 -114
- package/src/product/menu/DropdownMenu.tsx +0 -76
- package/src/product/menu/index.ts +0 -15
- package/src/product/menu/items.tsx +0 -230
- package/src/product/menu/portal-theme.tsx +0 -33
- package/src/product/menu/roving.ts +0 -34
- package/src/product/scope.test.ts +0 -92
- package/src/product/scope.ts +0 -123
- package/src/product/surfaces.data.ts +0 -42
- package/src/usage.ts +0 -8
- package/src/wallet.ts +0 -11
- package/types/billing.d.ts.map +0 -1
- package/types/canvas.d.ts +0 -2
- package/types/canvas.d.ts.map +0 -1
- package/types/cd/layout.d.ts.map +0 -1
- package/types/cd/status.d.ts.map +0 -1
- package/types/cd/time.d.ts.map +0 -1
- package/types/cd/types.d.ts.map +0 -1
- package/types/dashboard.d.ts +0 -2
- package/types/dashboard.d.ts.map +0 -1
- package/types/data.d.ts +0 -2
- package/types/data.d.ts.map +0 -1
- package/types/gitops.d.ts +0 -2
- package/types/gitops.d.ts.map +0 -1
- package/types/index.d.ts.map +0 -1
- package/types/network.d.ts.map +0 -1
- package/types/primitives/bases/data/index.d.ts +0 -2
- package/types/primitives/bases/data/index.d.ts.map +0 -1
- package/types/product/AppHeader.d.ts.map +0 -1
- package/types/product/BrandMark.d.ts.map +0 -1
- package/types/product/Charts.d.ts.map +0 -1
- package/types/product/ComboBox.d.ts.map +0 -1
- package/types/product/DataTable.d.ts.map +0 -1
- package/types/product/Donut.d.ts.map +0 -1
- package/types/product/EmptyState.d.ts.map +0 -1
- package/types/product/FadeIn.d.ts.map +0 -1
- package/types/product/Field.d.ts.map +0 -1
- package/types/product/HanzoMark.d.ts.map +0 -1
- package/types/product/Metric.d.ts.map +0 -1
- package/types/product/OrgSwitcher.d.ts.map +0 -1
- package/types/product/PageHeader.d.ts.map +0 -1
- package/types/product/PrimaryButton.d.ts.map +0 -1
- package/types/product/ProductIcon.d.ts.map +0 -1
- package/types/product/ProviderLogo.d.ts.map +0 -1
- package/types/product/Reorder.d.ts.map +0 -1
- package/types/product/SelectMenu.d.ts.map +0 -1
- package/types/product/SlideOver.d.ts.map +0 -1
- package/types/product/StatusTag.d.ts.map +0 -1
- package/types/product/ThemeToggle.d.ts.map +0 -1
- package/types/product/ThemeToggleNext.d.ts.map +0 -1
- package/types/product/Toast.d.ts.map +0 -1
- package/types/product/color.d.ts.map +0 -1
- package/types/product/combobox/filter.d.ts.map +0 -1
- package/types/product/index.d.ts.map +0 -1
- package/types/product/menu/ContextMenu.d.ts.map +0 -1
- package/types/product/menu/DropdownMenu.d.ts +0 -34
- package/types/product/menu/DropdownMenu.d.ts.map +0 -1
- package/types/product/menu/index.d.ts.map +0 -1
- package/types/product/menu/items.d.ts.map +0 -1
- package/types/product/menu/portal-theme.d.ts.map +0 -1
- package/types/product/menu/roving.d.ts.map +0 -1
- package/types/product/scope.d.ts.map +0 -1
- package/types/product/surfaces.data.d.ts.map +0 -1
- package/types/usage.d.ts +0 -2
- package/types/usage.d.ts.map +0 -1
- package/types/wallet.d.ts.map +0 -1
- /package/{src → dist}/styles/hanzo-motion.css +0 -0
package/src/product/Donut.tsx
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Donut — a tiny dependency-free SVG ring for a categorical breakdown
|
|
5
|
-
* (e.g. machines by status). Standard library first: no chart dependency for a
|
|
6
|
-
* single ring. It renders ONLY the real segments it is given and returns `null`
|
|
7
|
-
* when the total is zero, so the caller shows an honest empty state instead of an
|
|
8
|
-
* empty circle. Purely presentational — all values come from the caller.
|
|
9
|
-
*/
|
|
10
|
-
export type DonutSegment = { label: string; value: number; color: string }
|
|
11
|
-
|
|
12
|
-
export function Donut({
|
|
13
|
-
segments,
|
|
14
|
-
size = 160,
|
|
15
|
-
thickness = 18,
|
|
16
|
-
centerValue,
|
|
17
|
-
centerLabel,
|
|
18
|
-
trackColor = '#2a2d2e',
|
|
19
|
-
valueColor = '#ecedee',
|
|
20
|
-
labelColor = '#9ba1a6',
|
|
21
|
-
}: {
|
|
22
|
-
segments: DonutSegment[]
|
|
23
|
-
size?: number
|
|
24
|
-
thickness?: number
|
|
25
|
-
/** Big number in the middle (e.g. total). */
|
|
26
|
-
centerValue?: string
|
|
27
|
-
/** Caption under the center value. */
|
|
28
|
-
centerLabel?: string
|
|
29
|
-
/** Unfilled-ring color (explicit hex so it never depends on theme var names). */
|
|
30
|
-
trackColor?: string
|
|
31
|
-
valueColor?: string
|
|
32
|
-
labelColor?: string
|
|
33
|
-
}) {
|
|
34
|
-
const total = segments.reduce((n, s) => n + Math.max(0, s.value), 0)
|
|
35
|
-
if (total <= 0) return null
|
|
36
|
-
|
|
37
|
-
const r = (size - thickness) / 2
|
|
38
|
-
const cx = size / 2
|
|
39
|
-
const cy = size / 2
|
|
40
|
-
const circ = 2 * Math.PI * r
|
|
41
|
-
|
|
42
|
-
let offset = 0
|
|
43
|
-
const arcs = segments
|
|
44
|
-
.filter((s) => s.value > 0)
|
|
45
|
-
.map((s) => {
|
|
46
|
-
const frac = s.value / total
|
|
47
|
-
const dash = frac * circ
|
|
48
|
-
const node = (
|
|
49
|
-
<circle
|
|
50
|
-
key={s.label}
|
|
51
|
-
cx={cx}
|
|
52
|
-
cy={cy}
|
|
53
|
-
r={r}
|
|
54
|
-
fill="none"
|
|
55
|
-
stroke={s.color}
|
|
56
|
-
strokeWidth={thickness}
|
|
57
|
-
strokeDasharray={`${dash} ${circ - dash}`}
|
|
58
|
-
strokeDashoffset={-offset}
|
|
59
|
-
/>
|
|
60
|
-
)
|
|
61
|
-
offset += dash
|
|
62
|
-
return node
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
return (
|
|
66
|
-
<svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} role="img" aria-label="Donut chart">
|
|
67
|
-
{/* track */}
|
|
68
|
-
<circle cx={cx} cy={cy} r={r} fill="none" stroke={trackColor} strokeWidth={thickness} />
|
|
69
|
-
{/* segments, drawn from 12 o'clock */}
|
|
70
|
-
<g transform={`rotate(-90 ${cx} ${cy})`}>{arcs}</g>
|
|
71
|
-
{centerValue ? (
|
|
72
|
-
<text x={cx} y={cy - 2} textAnchor="middle" fontSize={size * 0.2} fontWeight="800" fill={valueColor}>
|
|
73
|
-
{centerValue}
|
|
74
|
-
</text>
|
|
75
|
-
) : null}
|
|
76
|
-
{centerLabel ? (
|
|
77
|
-
<text x={cx} y={cy + size * 0.13} textAnchor="middle" fontSize={size * 0.08} fill={labelColor}>
|
|
78
|
-
{centerLabel}
|
|
79
|
-
</text>
|
|
80
|
-
) : null}
|
|
81
|
-
</svg>
|
|
82
|
-
)
|
|
83
|
-
}
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Rich empty state — the ONE way the console turns a real, honest "nothing here
|
|
5
|
-
* yet" into a helpful onboarding moment (DigitalOcean / Vercel class) instead of
|
|
6
|
-
* a dead screen.
|
|
7
|
-
*
|
|
8
|
-
* It is NOT a fabricated-data fallback: callers render it only when a load
|
|
9
|
-
* genuinely succeeded with zero rows (or a resource has no surface yet). It shows
|
|
10
|
-
* the product icon, a one-line "what this is", a couple of guidance bullets, a
|
|
11
|
-
* single primary call-to-action (Deploy / Create / Connect …) and an optional
|
|
12
|
-
* secondary link (Docs / CLI). Orthogonal to `BackendStateCard` (failures) and
|
|
13
|
-
* `DataTable`'s thin inline empty (in-table) — this is the full-bleed first-run
|
|
14
|
-
* surface a module shows in place of its table.
|
|
15
|
-
*/
|
|
16
|
-
import type { ReactElement, ReactNode } from 'react'
|
|
17
|
-
import { Button, Card, Text, XStack, YStack } from '@hanzo/gui'
|
|
18
|
-
import { ArrowRight, Check, ExternalLink } from '@hanzogui/lucide-icons-2'
|
|
19
|
-
|
|
20
|
-
import type { IconLike } from './color'
|
|
21
|
-
import { PrimaryButton } from './PrimaryButton'
|
|
22
|
-
|
|
23
|
-
/** A single call-to-action — primary (filled) or a secondary/external link. */
|
|
24
|
-
export type EmptyAction = {
|
|
25
|
-
label: string
|
|
26
|
-
/** In-app navigation / handler. */
|
|
27
|
-
onPress?: () => void
|
|
28
|
-
/** External URL — opens in a new tab with an external-link affordance. */
|
|
29
|
-
href?: string
|
|
30
|
-
icon?: ReactElement
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** Open an external URL in a new tab (no-opener), guarded for SSR. */
|
|
34
|
-
function openHref(href: string): void {
|
|
35
|
-
if (typeof window !== 'undefined') window.open(href, '_blank', 'noopener')
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function EmptyState({
|
|
39
|
-
icon: Icon,
|
|
40
|
-
title,
|
|
41
|
-
description,
|
|
42
|
-
bullets,
|
|
43
|
-
primary,
|
|
44
|
-
secondary,
|
|
45
|
-
}: {
|
|
46
|
-
/** Product/section icon — any lucide/Gui icon component (size + optional color). */
|
|
47
|
-
icon: IconLike
|
|
48
|
-
title: string
|
|
49
|
-
description: string
|
|
50
|
-
/** Optional "here's how to get started" guidance bullets. */
|
|
51
|
-
bullets?: string[]
|
|
52
|
-
/** The single high-emphasis action (Deploy / Create / Connect). */
|
|
53
|
-
primary?: EmptyAction
|
|
54
|
-
/** An optional lower-emphasis action (Docs / CLI / Learn more). */
|
|
55
|
-
secondary?: EmptyAction
|
|
56
|
-
}) {
|
|
57
|
-
return (
|
|
58
|
-
<Card
|
|
59
|
-
borderWidth={1}
|
|
60
|
-
borderColor="$borderColor"
|
|
61
|
-
borderStyle="dashed"
|
|
62
|
-
p="$6"
|
|
63
|
-
gap="$4"
|
|
64
|
-
items="center"
|
|
65
|
-
maxWidth={640}
|
|
66
|
-
self="center"
|
|
67
|
-
width="100%"
|
|
68
|
-
>
|
|
69
|
-
<YStack
|
|
70
|
-
width={48}
|
|
71
|
-
height={48}
|
|
72
|
-
items="center"
|
|
73
|
-
justify="center"
|
|
74
|
-
rounded="$4"
|
|
75
|
-
bg="$color3"
|
|
76
|
-
>
|
|
77
|
-
<Icon size={24} />
|
|
78
|
-
</YStack>
|
|
79
|
-
|
|
80
|
-
<YStack gap="$2" items="center" maxW={480}>
|
|
81
|
-
<Text fontSize="$6" fontWeight="800" text="center">
|
|
82
|
-
{title}
|
|
83
|
-
</Text>
|
|
84
|
-
<Text fontSize="$3" color="$color11" text="center">
|
|
85
|
-
{description}
|
|
86
|
-
</Text>
|
|
87
|
-
</YStack>
|
|
88
|
-
|
|
89
|
-
{bullets && bullets.length ? (
|
|
90
|
-
<YStack gap="$2" self="stretch" maxW={420} mx="auto">
|
|
91
|
-
{bullets.map((b) => (
|
|
92
|
-
<XStack key={b} gap="$2" items="flex-start">
|
|
93
|
-
<YStack pt="$1">
|
|
94
|
-
<Check size={14} color="$color10" />
|
|
95
|
-
</YStack>
|
|
96
|
-
<Text fontSize="$3" color="$color11" flex={1}>
|
|
97
|
-
{b}
|
|
98
|
-
</Text>
|
|
99
|
-
</XStack>
|
|
100
|
-
))}
|
|
101
|
-
</YStack>
|
|
102
|
-
) : null}
|
|
103
|
-
|
|
104
|
-
{primary || secondary ? (
|
|
105
|
-
<XStack gap="$2" items="center" flexWrap="wrap" justify="center">
|
|
106
|
-
{primary ? (
|
|
107
|
-
<PrimaryButton
|
|
108
|
-
icon={primary.icon}
|
|
109
|
-
iconAfter={primary.href ? <ExternalLink size={15} /> : <ArrowRight size={15} />}
|
|
110
|
-
onPress={() => (primary.href ? openHref(primary.href) : primary.onPress?.())}
|
|
111
|
-
>
|
|
112
|
-
{primary.label}
|
|
113
|
-
</PrimaryButton>
|
|
114
|
-
) : null}
|
|
115
|
-
{secondary ? (
|
|
116
|
-
<Button
|
|
117
|
-
chromeless
|
|
118
|
-
icon={secondary.icon}
|
|
119
|
-
iconAfter={secondary.href ? <ExternalLink size={14} /> : undefined}
|
|
120
|
-
onPress={() => (secondary.href ? openHref(secondary.href) : secondary.onPress?.())}
|
|
121
|
-
>
|
|
122
|
-
{secondary.label}
|
|
123
|
-
</Button>
|
|
124
|
-
) : null}
|
|
125
|
-
</XStack>
|
|
126
|
-
) : null}
|
|
127
|
-
</Card>
|
|
128
|
-
)
|
|
129
|
-
}
|
package/src/product/FadeIn.tsx
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* FadeIn — the ONE entrance animation for the console.
|
|
5
|
-
*
|
|
6
|
-
* A thin wrapper that applies the shared `.hz-fade-up` keyframe (defined once in
|
|
7
|
-
* globals.css) plus an optional stagger delay, so sections and cards rise in the
|
|
8
|
-
* way the hanzo.ai marketing surface does. Framework-free (no animation driver,
|
|
9
|
-
* no JS observers) so it works under static export and honors
|
|
10
|
-
* `prefers-reduced-motion` (handled in the CSS). Pass `index` to stagger a list
|
|
11
|
-
* (delay = index * step), or `delayMs` for an explicit delay.
|
|
12
|
-
*/
|
|
13
|
-
import type { CSSProperties, ReactNode } from 'react'
|
|
14
|
-
|
|
15
|
-
export function FadeIn({
|
|
16
|
-
children,
|
|
17
|
-
index = 0,
|
|
18
|
-
step = 50,
|
|
19
|
-
delayMs,
|
|
20
|
-
style,
|
|
21
|
-
}: {
|
|
22
|
-
children: ReactNode
|
|
23
|
-
/** Position in a list — staggers the entrance by `index * step` ms. */
|
|
24
|
-
index?: number
|
|
25
|
-
/** Per-index stagger in ms (default 50). */
|
|
26
|
-
step?: number
|
|
27
|
-
/** Explicit delay in ms; overrides the index-based stagger. */
|
|
28
|
-
delayMs?: number
|
|
29
|
-
/** Extra styles for the wrapper (e.g. layout/centering). */
|
|
30
|
-
style?: CSSProperties
|
|
31
|
-
}) {
|
|
32
|
-
const delay = delayMs ?? index * step
|
|
33
|
-
return (
|
|
34
|
-
<div className="hz-fade-up" style={{ animationDelay: `${delay}ms`, ...style }}>
|
|
35
|
-
{children}
|
|
36
|
-
</div>
|
|
37
|
-
)
|
|
38
|
-
}
|
package/src/product/Field.tsx
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Form field primitives — labeled rows over Hanzo GUI inputs.
|
|
5
|
-
*
|
|
6
|
-
* One way to render a labeled control. Every editor field goes through these, so
|
|
7
|
-
* spacing, disabled styling, and the label column stay consistent. Style props
|
|
8
|
-
* use the v5 shorthand set (p/px/items/justify/...).
|
|
9
|
-
*/
|
|
10
|
-
import type { ReactNode } from 'react'
|
|
11
|
-
import {
|
|
12
|
-
Input,
|
|
13
|
-
Label,
|
|
14
|
-
Select,
|
|
15
|
-
Switch,
|
|
16
|
-
TextArea,
|
|
17
|
-
Slider,
|
|
18
|
-
Text,
|
|
19
|
-
XStack,
|
|
20
|
-
YStack,
|
|
21
|
-
} from '@hanzo/gui'
|
|
22
|
-
import { ChevronDown } from '@hanzogui/lucide-icons-2'
|
|
23
|
-
|
|
24
|
-
/** Labeled row: fixed label column + flexing control. */
|
|
25
|
-
export function FieldRow({ label, children }: { label: string; children: ReactNode }) {
|
|
26
|
-
return (
|
|
27
|
-
<XStack gap="$3" items="flex-start" flexWrap="wrap">
|
|
28
|
-
<Label width={180} pt="$2" color="$color11" fontSize="$3">
|
|
29
|
-
{label}
|
|
30
|
-
</Label>
|
|
31
|
-
<YStack flex={1} minW={240}>
|
|
32
|
-
{children}
|
|
33
|
-
</YStack>
|
|
34
|
-
</XStack>
|
|
35
|
-
)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function FieldText({
|
|
39
|
-
value,
|
|
40
|
-
onChange,
|
|
41
|
-
disabled,
|
|
42
|
-
secure,
|
|
43
|
-
placeholder,
|
|
44
|
-
}: {
|
|
45
|
-
value: string
|
|
46
|
-
onChange: (v: string) => void
|
|
47
|
-
disabled?: boolean
|
|
48
|
-
secure?: boolean
|
|
49
|
-
placeholder?: string
|
|
50
|
-
}) {
|
|
51
|
-
return (
|
|
52
|
-
<Input
|
|
53
|
-
value={value}
|
|
54
|
-
onChangeText={onChange}
|
|
55
|
-
disabled={disabled}
|
|
56
|
-
secureTextEntry={secure}
|
|
57
|
-
placeholder={placeholder}
|
|
58
|
-
autoCapitalize="none"
|
|
59
|
-
/>
|
|
60
|
-
)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function FieldTextArea({
|
|
64
|
-
value,
|
|
65
|
-
onChange,
|
|
66
|
-
disabled,
|
|
67
|
-
rows = 6,
|
|
68
|
-
}: {
|
|
69
|
-
value: string
|
|
70
|
-
onChange: (v: string) => void
|
|
71
|
-
disabled?: boolean
|
|
72
|
-
rows?: number
|
|
73
|
-
}) {
|
|
74
|
-
return (
|
|
75
|
-
<TextArea value={value} onChangeText={onChange} disabled={disabled} numberOfLines={rows} />
|
|
76
|
-
)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export function FieldSwitch({
|
|
80
|
-
checked,
|
|
81
|
-
onChange,
|
|
82
|
-
disabled,
|
|
83
|
-
}: {
|
|
84
|
-
checked: boolean
|
|
85
|
-
onChange: (v: boolean) => void
|
|
86
|
-
disabled?: boolean
|
|
87
|
-
}) {
|
|
88
|
-
return (
|
|
89
|
-
<Switch checked={checked} onCheckedChange={onChange} disabled={disabled} size="$3">
|
|
90
|
-
<Switch.Thumb />
|
|
91
|
-
</Switch>
|
|
92
|
-
)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export function FieldSelect({
|
|
96
|
-
value,
|
|
97
|
-
options,
|
|
98
|
-
onChange,
|
|
99
|
-
disabled,
|
|
100
|
-
}: {
|
|
101
|
-
value: string
|
|
102
|
-
options: string[]
|
|
103
|
-
onChange: (v: string) => void
|
|
104
|
-
disabled?: boolean
|
|
105
|
-
}) {
|
|
106
|
-
return (
|
|
107
|
-
<Select value={value} onValueChange={onChange} disablePreventBodyScroll native>
|
|
108
|
-
<Select.Trigger disabled={disabled} iconAfter={<ChevronDown size={16} />}>
|
|
109
|
-
<Select.Value placeholder="Select…" />
|
|
110
|
-
</Select.Trigger>
|
|
111
|
-
<Select.Content>
|
|
112
|
-
<Select.Viewport>
|
|
113
|
-
{options.map((opt, i) => (
|
|
114
|
-
<Select.Item key={opt} index={i} value={opt}>
|
|
115
|
-
<Select.ItemText>{opt}</Select.ItemText>
|
|
116
|
-
</Select.Item>
|
|
117
|
-
))}
|
|
118
|
-
</Select.Viewport>
|
|
119
|
-
</Select.Content>
|
|
120
|
-
</Select>
|
|
121
|
-
)
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export function FieldSlider({
|
|
125
|
-
value,
|
|
126
|
-
min,
|
|
127
|
-
max,
|
|
128
|
-
step,
|
|
129
|
-
onChange,
|
|
130
|
-
disabled,
|
|
131
|
-
}: {
|
|
132
|
-
value: number
|
|
133
|
-
min: number
|
|
134
|
-
max: number
|
|
135
|
-
step: number
|
|
136
|
-
onChange: (v: number) => void
|
|
137
|
-
disabled?: boolean
|
|
138
|
-
}) {
|
|
139
|
-
return (
|
|
140
|
-
<XStack gap="$3" items="center">
|
|
141
|
-
<Text width={56} fontSize="$3" color="$color11">
|
|
142
|
-
{value}
|
|
143
|
-
</Text>
|
|
144
|
-
<Slider
|
|
145
|
-
flex={1}
|
|
146
|
-
min={min}
|
|
147
|
-
max={max}
|
|
148
|
-
step={step}
|
|
149
|
-
value={[value]}
|
|
150
|
-
onValueChange={(v) => onChange(v[0] ?? min)}
|
|
151
|
-
disabled={disabled}
|
|
152
|
-
>
|
|
153
|
-
<Slider.Track>
|
|
154
|
-
<Slider.TrackActive />
|
|
155
|
-
</Slider.Track>
|
|
156
|
-
<Slider.Thumb index={0} circular />
|
|
157
|
-
</Slider>
|
|
158
|
-
</XStack>
|
|
159
|
-
)
|
|
160
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The Hanzo "H" mark — the canonical 7-path shaded glyph: five body blocks +
|
|
5
|
-
* two shade slivers (geometry canon: @hanzo/logo `MARK_PATHS`).
|
|
6
|
-
*
|
|
7
|
-
* Rendered as inline SVG with `fill: currentColor` (shade at reduced opacity),
|
|
8
|
-
* so it inherits the surrounding text color and adapts to the dark/light theme
|
|
9
|
-
* with no per-theme asset. ONE source for the mark across the chrome.
|
|
10
|
-
*/
|
|
11
|
-
export function HanzoMark({ size = 22, color = 'currentColor' }: { size?: number; color?: string }) {
|
|
12
|
-
return (
|
|
13
|
-
<svg
|
|
14
|
-
width={size}
|
|
15
|
-
height={size}
|
|
16
|
-
viewBox="0 0 67 67"
|
|
17
|
-
role="img"
|
|
18
|
-
aria-label="Hanzo"
|
|
19
|
-
fill={color}
|
|
20
|
-
style={{ display: 'block', flexShrink: 0 }}
|
|
21
|
-
>
|
|
22
|
-
<path d="M22.21 67V44.6369H0V67H22.21Z" />
|
|
23
|
-
<path opacity={0.55} d="M0 44.6369L22.21 46.8285V44.6369H0Z" />
|
|
24
|
-
<path d="M66.7038 22.3184H22.2534L0.0878906 44.6367H44.4634L66.7038 22.3184Z" />
|
|
25
|
-
<path d="M22.21 0H0V22.3184H22.21V0Z" />
|
|
26
|
-
<path d="M66.7198 0H44.5098V22.3184H66.7198V0Z" />
|
|
27
|
-
<path opacity={0.55} d="M66.6753 22.3185L44.5098 20.0822V22.3185H66.6753Z" />
|
|
28
|
-
<path d="M66.7198 67V44.6369H44.5098V67H66.7198Z" />
|
|
29
|
-
</svg>
|
|
30
|
-
)
|
|
31
|
-
}
|
package/src/product/Metric.tsx
DELETED
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Console metric primitives — the ONE shared set of stat tiles, panels, legend
|
|
5
|
-
* rows, util bars, and "real action or honest-disabled" buttons that every
|
|
6
|
-
* resource console (GPUs, Kubernetes, Containers, Training, Tasks) renders.
|
|
7
|
-
*
|
|
8
|
-
* These were first written for the GPU surface; promoted here so a sibling
|
|
9
|
-
* product never reaches into `gpus/` for shell chrome (separation of concerns).
|
|
10
|
-
* `gpus/charts.tsx` now re-exports them, so there is exactly one definition.
|
|
11
|
-
*
|
|
12
|
-
* Honest by construction: a `MetricCard` shows the pre-formatted `value` the
|
|
13
|
-
* caller passes (an absent metric is an em-dash, never a fabricated number); a
|
|
14
|
-
* `Sparkline`/`MiniBars` with no real series renders nothing; a `HintButton`
|
|
15
|
-
* with no `onPress` is visibly disabled with a tooltip saying WHY. Inline SVG is
|
|
16
|
-
* the house pattern (see ui/Loader, ui/HanzoMark); style props use the v5
|
|
17
|
-
* shorthand set.
|
|
18
|
-
*/
|
|
19
|
-
import type { ReactElement, ReactNode } from 'react'
|
|
20
|
-
import { Button, Card, Text, XStack, YStack } from '@hanzo/gui'
|
|
21
|
-
|
|
22
|
-
/** Distinct, theme-neutral series colors (mid-saturation reads on dark + light). */
|
|
23
|
-
export const SERIES = ['#6ea8fe', '#7ee787', '#f0a868', '#c792ea', '#56d4c4', '#e879a6', '#d6c15a', '#8b9bb4'] as const
|
|
24
|
-
export const colorForIndex = (i: number): string => SERIES[i % SERIES.length]
|
|
25
|
-
const TRACK = 'rgba(128,128,128,0.18)'
|
|
26
|
-
|
|
27
|
-
/** Tone for a utilization/temperature value (green calm → red hot). */
|
|
28
|
-
export function utilColor(pct: number): string {
|
|
29
|
-
if (pct >= 90) return '#e5534b'
|
|
30
|
-
if (pct >= 70) return '#f0a868'
|
|
31
|
-
return '#7ee787'
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* A single-series sparkline over real points. Renders nothing meaningful for <2
|
|
36
|
-
* points (the caller shows `—` instead). The y-range is the data's own min/max.
|
|
37
|
-
*/
|
|
38
|
-
export function Sparkline({
|
|
39
|
-
points,
|
|
40
|
-
width = 104,
|
|
41
|
-
height = 30,
|
|
42
|
-
color = SERIES[0],
|
|
43
|
-
}: {
|
|
44
|
-
points: number[]
|
|
45
|
-
width?: number
|
|
46
|
-
height?: number
|
|
47
|
-
color?: string
|
|
48
|
-
}): ReactElement | null {
|
|
49
|
-
if (!points || points.length < 2) return null
|
|
50
|
-
const min = Math.min(...points)
|
|
51
|
-
const max = Math.max(...points)
|
|
52
|
-
const span = max - min || 1
|
|
53
|
-
const stepX = width / (points.length - 1)
|
|
54
|
-
const y = (v: number) => height - 2 - ((v - min) / span) * (height - 4)
|
|
55
|
-
const d = points.map((p, i) => `${i === 0 ? 'M' : 'L'}${(i * stepX).toFixed(1)},${y(p).toFixed(1)}`).join(' ')
|
|
56
|
-
return (
|
|
57
|
-
<svg width={width} height={height} viewBox={`0 0 ${width} ${height}`} role="img" aria-label="trend">
|
|
58
|
-
<path d={d} fill="none" stroke={color} strokeWidth={1.5} strokeLinejoin="round" strokeLinecap="round" />
|
|
59
|
-
</svg>
|
|
60
|
-
)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** A small vertical-bar chart (e.g. cost per day). One bar per real datum. */
|
|
64
|
-
export function MiniBars({
|
|
65
|
-
bars,
|
|
66
|
-
width = 320,
|
|
67
|
-
height = 96,
|
|
68
|
-
color = SERIES[0],
|
|
69
|
-
}: {
|
|
70
|
-
bars: { label: string; value: number }[]
|
|
71
|
-
width?: number
|
|
72
|
-
height?: number
|
|
73
|
-
color?: string
|
|
74
|
-
}): ReactElement | null {
|
|
75
|
-
if (!bars.length) return null
|
|
76
|
-
const max = Math.max(...bars.map((b) => b.value), 1)
|
|
77
|
-
const gap = 4
|
|
78
|
-
const bw = (width - gap * (bars.length - 1)) / bars.length
|
|
79
|
-
return (
|
|
80
|
-
<svg width="100%" height={height} viewBox={`0 0 ${width} ${height}`} preserveAspectRatio="none" role="img" aria-label="bars">
|
|
81
|
-
{bars.map((b, i) => {
|
|
82
|
-
const h = Math.max(1, (b.value / max) * (height - 2))
|
|
83
|
-
return (
|
|
84
|
-
<rect key={b.label + i} x={i * (bw + gap)} y={height - h} width={bw} height={h} rx={2} fill={color} opacity={0.85}>
|
|
85
|
-
<title>{`${b.label}: ${b.value}`}</title>
|
|
86
|
-
</rect>
|
|
87
|
-
)
|
|
88
|
-
})}
|
|
89
|
-
</svg>
|
|
90
|
-
)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* A thin horizontal bar (0–100). By default the fill tones by value (green→red,
|
|
95
|
-
* for utilization/temperature). Pass a fixed `color` for a neutral meter like
|
|
96
|
-
* training PROGRESS, where a high value is good, not "hot".
|
|
97
|
-
*/
|
|
98
|
-
export function UtilBar({ value, width = 120, color }: { value: number; width?: number; color?: string }): ReactElement {
|
|
99
|
-
const v = Math.max(0, Math.min(100, value))
|
|
100
|
-
return (
|
|
101
|
-
<svg width={width} height={8} viewBox={`0 0 ${width} 8`} role="img" aria-label={`${Math.round(v)}%`}>
|
|
102
|
-
<rect x={0} y={1} width={width} height={6} rx={3} fill={TRACK} />
|
|
103
|
-
<rect x={0} y={1} width={(v / 100) * width} height={6} rx={3} fill={color ?? utilColor(v)} />
|
|
104
|
-
</svg>
|
|
105
|
-
)
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/** A legend row: color swatch + label + value. */
|
|
109
|
-
export function LegendDot({ color, label, value }: { color: string; label: string; value?: ReactNode }) {
|
|
110
|
-
return (
|
|
111
|
-
<XStack items="center" gap="$2" justify="space-between">
|
|
112
|
-
<XStack items="center" gap="$2">
|
|
113
|
-
<YStack width={10} height={10} rounded="$1" bg={color as never} />
|
|
114
|
-
<Text fontSize="$2" color="$color11">
|
|
115
|
-
{label}
|
|
116
|
-
</Text>
|
|
117
|
-
</XStack>
|
|
118
|
-
{value != null ? (
|
|
119
|
-
<Text fontSize="$2" color="$color12" fontWeight="600">
|
|
120
|
-
{value}
|
|
121
|
-
</Text>
|
|
122
|
-
) : null}
|
|
123
|
-
</XStack>
|
|
124
|
-
)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* One overview metric tile. `value` is pre-formatted by the caller (so an absent
|
|
129
|
-
* metric is an honest `—`, never a fabricated number). The sparkline only appears
|
|
130
|
-
* when the caller has a REAL series; the delta only when a real comparison exists.
|
|
131
|
-
*/
|
|
132
|
-
export function MetricCard({
|
|
133
|
-
icon,
|
|
134
|
-
label,
|
|
135
|
-
value,
|
|
136
|
-
caption,
|
|
137
|
-
spark,
|
|
138
|
-
sparkColor,
|
|
139
|
-
delta,
|
|
140
|
-
}: {
|
|
141
|
-
icon: ReactElement
|
|
142
|
-
label: string
|
|
143
|
-
value: string
|
|
144
|
-
caption?: string
|
|
145
|
-
spark?: number[]
|
|
146
|
-
sparkColor?: string
|
|
147
|
-
delta?: { pct: number }
|
|
148
|
-
}) {
|
|
149
|
-
const up = delta ? delta.pct >= 0 : false
|
|
150
|
-
return (
|
|
151
|
-
<Card p="$3.5" gap="$2" borderWidth={1} borderColor="$borderColor" flex={1} minW={172}>
|
|
152
|
-
<XStack items="center" gap="$2" justify="space-between">
|
|
153
|
-
<XStack items="center" gap="$2">
|
|
154
|
-
{icon}
|
|
155
|
-
<Text fontSize="$2" color="$color11" fontWeight="600">
|
|
156
|
-
{label}
|
|
157
|
-
</Text>
|
|
158
|
-
</XStack>
|
|
159
|
-
{delta ? (
|
|
160
|
-
<Text fontSize="$1" color={up ? '#7ee787' : '#e5534b'}>
|
|
161
|
-
{up ? '▲' : '▼'} {Math.abs(delta.pct)}%
|
|
162
|
-
</Text>
|
|
163
|
-
) : null}
|
|
164
|
-
</XStack>
|
|
165
|
-
<XStack items="flex-end" justify="space-between" gap="$2">
|
|
166
|
-
<Text fontSize="$8" fontWeight="900" color="$color12" numberOfLines={1}>
|
|
167
|
-
{value}
|
|
168
|
-
</Text>
|
|
169
|
-
{spark && spark.length >= 2 ? <Sparkline points={spark} color={sparkColor ?? SERIES[0]} /> : null}
|
|
170
|
-
</XStack>
|
|
171
|
-
{caption ? (
|
|
172
|
-
<Text fontSize="$1" color="$color10" numberOfLines={1}>
|
|
173
|
-
{caption}
|
|
174
|
-
</Text>
|
|
175
|
-
) : null}
|
|
176
|
-
</Card>
|
|
177
|
-
)
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* A button that is either a real action (onPress) or HONESTLY disabled with a
|
|
182
|
-
* native tooltip explaining why (the action has no backend yet). One way to
|
|
183
|
-
* render the "real route or honest-disabled" affordance the consoles use.
|
|
184
|
-
*/
|
|
185
|
-
export function HintButton({
|
|
186
|
-
icon,
|
|
187
|
-
iconAfter,
|
|
188
|
-
children,
|
|
189
|
-
onPress,
|
|
190
|
-
disabled,
|
|
191
|
-
hint,
|
|
192
|
-
theme,
|
|
193
|
-
}: {
|
|
194
|
-
icon?: ReactElement
|
|
195
|
-
iconAfter?: ReactElement
|
|
196
|
-
children: ReactNode
|
|
197
|
-
onPress?: () => void
|
|
198
|
-
disabled?: boolean
|
|
199
|
-
/** Native tooltip text (shown on hover) — say WHY it is disabled. */
|
|
200
|
-
hint?: string
|
|
201
|
-
theme?: 'light'
|
|
202
|
-
}) {
|
|
203
|
-
const btn = (
|
|
204
|
-
<Button
|
|
205
|
-
size="$2"
|
|
206
|
-
theme={theme}
|
|
207
|
-
icon={icon}
|
|
208
|
-
iconAfter={iconAfter}
|
|
209
|
-
disabled={disabled}
|
|
210
|
-
onPress={disabled ? undefined : onPress}
|
|
211
|
-
>
|
|
212
|
-
{children}
|
|
213
|
-
</Button>
|
|
214
|
-
)
|
|
215
|
-
if (!hint) return btn
|
|
216
|
-
return (
|
|
217
|
-
<span title={hint} style={{ display: 'inline-flex' }}>
|
|
218
|
-
{btn}
|
|
219
|
-
</span>
|
|
220
|
-
)
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* A titled panel wrapper used across the consoles (chart + list cards). `grow`
|
|
225
|
-
* (default true) sets `flex={1}` so panels SHARE width in a ROW; pass `grow={false}`
|
|
226
|
-
* inside a vertical rail (a column YStack) so each panel sizes to its content and
|
|
227
|
-
* stacks — flex-grow in a column with no fixed height collapses them onto each other.
|
|
228
|
-
*/
|
|
229
|
-
export function Panel({
|
|
230
|
-
title,
|
|
231
|
-
right,
|
|
232
|
-
children,
|
|
233
|
-
minW = 280,
|
|
234
|
-
grow = true,
|
|
235
|
-
}: {
|
|
236
|
-
title: string
|
|
237
|
-
right?: ReactNode
|
|
238
|
-
children: ReactNode
|
|
239
|
-
minW?: number
|
|
240
|
-
grow?: boolean
|
|
241
|
-
}) {
|
|
242
|
-
return (
|
|
243
|
-
<Card p="$4" gap="$3" borderWidth={1} borderColor="$borderColor" flex={grow ? 1 : undefined} minW={minW} width={grow ? undefined : '100%'}>
|
|
244
|
-
<XStack items="center" justify="space-between" gap="$2">
|
|
245
|
-
<Text fontSize="$4" fontWeight="800" color="$color12">
|
|
246
|
-
{title}
|
|
247
|
-
</Text>
|
|
248
|
-
{right}
|
|
249
|
-
</XStack>
|
|
250
|
-
{children}
|
|
251
|
-
</Card>
|
|
252
|
-
)
|
|
253
|
-
}
|