hazo_ui 2.16.0 → 3.0.1
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/CHANGE_LOG.md +76 -0
- package/README.md +417 -0
- package/SETUP_CHECKLIST.md +10 -0
- package/dist/index.cjs +1301 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +397 -1
- package/dist/index.d.ts +397 -1
- package/dist/index.js +1283 -2
- package/dist/index.js.map +1 -1
- package/dist/test-harness/index.cjs +18033 -0
- package/dist/test-harness/index.cjs.map +1 -0
- package/dist/test-harness/index.d.cts +144 -0
- package/dist/test-harness/index.d.ts +144 -0
- package/dist/test-harness/index.js +18009 -0
- package/dist/test-harness/index.js.map +1 -0
- package/package.json +8 -1
package/CHANGE_LOG.md
CHANGED
|
@@ -5,6 +5,82 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## v3.0.0 — test-harness sub-export
|
|
9
|
+
|
|
10
|
+
**New sub-export:** `hazo_ui/test-harness`
|
|
11
|
+
|
|
12
|
+
Ships a complete in-app test runner for consuming packages. Import from the dedicated sub-export — it is never included in the main `hazo_ui` bundle.
|
|
13
|
+
|
|
14
|
+
- **`SidebarLayout` + `AppSidebar`** — sidebar shell for test-app pages with `NavItem[]` navigation.
|
|
15
|
+
- **`AutoTestProvider` + `useAutoTest`** — React context that tracks run status, per-case results, and timing across all registered scenarios.
|
|
16
|
+
- **`AutoTestRunner`** — renders all registered scenarios with Run / Run All controls and pass/fail/timing output per case.
|
|
17
|
+
- **`CopyAllFailuresButton`** — copies all failed cases as a structured Claude prompt (8-section format: header, what-went-wrong, expected/actual/diff, test code, code under test, error chain, context, ring buffer).
|
|
18
|
+
- **`registerScenario(scenario)`** — registers a scenario at module load time; scenarios are collected into a global registry.
|
|
19
|
+
- **`assertEqual`, `assertThrows`, `assertResolves`, `assertRejects`, `assertMatch`, `assertIncludes`, `HazoAssertionError`** — assertion helpers for use inside `case.run()`.
|
|
20
|
+
- **`formatAsClaudePrompt(failedCases, options?)`** — formats an array of failed cases into a copy-pasteable Claude prompt.
|
|
21
|
+
|
|
22
|
+
## v2.18.0 — CelebrationModal
|
|
23
|
+
|
|
24
|
+
**New:** `CelebrationProvider`, `celebrate()`, `CELEBRATION_GRADIENT`
|
|
25
|
+
|
|
26
|
+
- Imperative confetti + modal overlay triggered by `celebrate({ id, title, ... })`.
|
|
27
|
+
- `<CelebrationProvider />` mounts at app root; `celebrate()` works from anywhere in the tree.
|
|
28
|
+
- **Session gating:** each `id` is shown at most once per browser session (sessionStorage). Cross-session gating is the consumer's responsibility.
|
|
29
|
+
- **FIFO queue:** rapid-fire `celebrate()` calls queue up and show one at a time.
|
|
30
|
+
- **Shareable card** (optional): 1080×1080 node rendered at full size, scaled to 400×400 for preview. Download PNG / Share / Copy image buttons (html-to-image, dynamically imported).
|
|
31
|
+
- **`background?: string`:** CSS background for the card. Default: `CELEBRATION_GRADIENT` export.
|
|
32
|
+
- **`caption?: string`:** falls back to `subtitle` when omitted.
|
|
33
|
+
- **Auto-dismiss:** defaults to `true` (8 s). Configure via `autoDismissDelay`.
|
|
34
|
+
- **Audio:** `audioChime: true` plays the bundled success chime (.mp3 inlined as base64).
|
|
35
|
+
- **Dependencies added:** `canvas-confetti`, `html-to-image`.
|
|
36
|
+
|
|
37
|
+
## [2.17.0] - 2026-05-17
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
- **`hazo_ui_charts` — new chart-primitives sub-section.** Six components
|
|
41
|
+
for the Site Ops Dashboard project, ported from its mockup HTML so the
|
|
42
|
+
app doesn't need a third-party chart lib:
|
|
43
|
+
- `Sparkline` — axisless trend line for KPI cards (12% area fill,
|
|
44
|
+
single endpoint dot, no hover — by design per PRD §6b).
|
|
45
|
+
- `InverseSparkline` — same shape but Y-axis inverted (lower = better)
|
|
46
|
+
for GSC position trends. 62×18 default, used inline in scrollable
|
|
47
|
+
query lists.
|
|
48
|
+
- `LineChart` — full anatomy (3 dashed gridlines, Y ticks max/mid/min,
|
|
49
|
+
X labels start/mid/end, marker dot + dashed guide-line to Y-axis +
|
|
50
|
+
above-left value label). Optional hover tooltip surfaces a cursor +
|
|
51
|
+
bubble at the nearest data index.
|
|
52
|
+
- `MultiLineChart` — shared Y-axis, per-series endpoint marker, no
|
|
53
|
+
area fill (avoids color collisions). Hover bubble shows all series
|
|
54
|
+
stacked. Optional legend below the SVG.
|
|
55
|
+
- `StackedBars` — generic stacked-bar over time. One bar per X
|
|
56
|
+
position; each bar split into top-to-bottom colored bands. Used by
|
|
57
|
+
the GSC position-bucket distribution view.
|
|
58
|
+
- `DateRangeSelector` — segmented control (`value` + `onChange`).
|
|
59
|
+
Framework-agnostic: no `next/navigation` import; consumer wires the
|
|
60
|
+
state. Active pill tints with `--primary`.
|
|
61
|
+
- Pure helpers: `format_num` (compact `1.2k` / `1.3M`),
|
|
62
|
+
`pick_x_label_indices`.
|
|
63
|
+
- Shared types: `ChartDataPoint`, `ChartDataSeries`, `MultiSeries`,
|
|
64
|
+
`StackedBar`, `DateRangeOption`.
|
|
65
|
+
- Dev-app demo at `/charts` with six sections (one per primitive)
|
|
66
|
+
covering rising / falling / flat / with-nulls / all-zeros /
|
|
67
|
+
single-point data shapes.
|
|
68
|
+
|
|
69
|
+
### Notes
|
|
70
|
+
- Hover coordinate mapping uses `getBoundingClientRect()` to translate
|
|
71
|
+
client X → viewBox X. Assumes the SVG renders at `width: 100%` (the
|
|
72
|
+
default we ship). Documented in `src/components/hazo_ui_charts/AGENTS.md`.
|
|
73
|
+
- Axis label color and gridline color are pinned hex (`#8b949e`,
|
|
74
|
+
`#2a3441`) chosen for the Site Ops dark theme. Theming via CSS
|
|
75
|
+
variables is a follow-up if a second consumer needs it.
|
|
76
|
+
- All chart components ship as Client Components by default — the
|
|
77
|
+
library bundle is `"use client"`-stamped at build time, same as the
|
|
78
|
+
rest of hazo_ui. The pure-SVG primitives don't *require* JS to
|
|
79
|
+
render, but they don't get RSC-rendered either.
|
|
80
|
+
|
|
81
|
+
### API
|
|
82
|
+
- No changes to existing components; this is purely additive.
|
|
83
|
+
|
|
8
84
|
## [2.16.0] - 2026-05-17
|
|
9
85
|
|
|
10
86
|
### Changed
|
package/README.md
CHANGED
|
@@ -206,6 +206,8 @@ The following components support both global config and prop-level color overrid
|
|
|
206
206
|
|
|
207
207
|
- **[Drawer](#drawer)** - A `vaul`-backed bottom sheet primitive for mobile UIs. Pair with `useMediaQuery` to swap between `Dialog` and `Drawer` based on viewport width.
|
|
208
208
|
|
|
209
|
+
- **[Chart Primitives](#chart-primitives-v2170)** (v2.17.0) - Pure-SVG chart components for KPI cards and trend dashboards: `Sparkline`, `InverseSparkline`, `LineChart`, `MultiLineChart`, `StackedBars`, and `DateRangeSelector`. Zero third-party chart deps. Gap-aware paths, hover tooltips, and per-series endpoint markers built in.
|
|
210
|
+
|
|
209
211
|
### State Primitives (v2.10.0)
|
|
210
212
|
|
|
211
213
|
Lightweight, opinionated components for the four ubiquitous async states: **loading**, **empty**, **error**, and **success**.
|
|
@@ -3561,6 +3563,421 @@ The package also re-exports the bare shadcn primitives — `Table`,
|
|
|
3561
3563
|
|
|
3562
3564
|
---
|
|
3563
3565
|
|
|
3566
|
+
## Chart Primitives (v2.17.0)
|
|
3567
|
+
|
|
3568
|
+
A set of dependency-free, pure-SVG chart components for KPI cards,
|
|
3569
|
+
trend visualisations, and time-series dashboards. No `recharts`,
|
|
3570
|
+
no `chart.js`, no canvas — every chart is a flat `<svg>` whose math
|
|
3571
|
+
is computed in React. Hover tooltips and gap-aware paths are built in.
|
|
3572
|
+
|
|
3573
|
+
```tsx
|
|
3574
|
+
import {
|
|
3575
|
+
Sparkline,
|
|
3576
|
+
InverseSparkline,
|
|
3577
|
+
LineChart,
|
|
3578
|
+
MultiLineChart,
|
|
3579
|
+
StackedBars,
|
|
3580
|
+
DateRangeSelector,
|
|
3581
|
+
format_num,
|
|
3582
|
+
pick_x_label_indices,
|
|
3583
|
+
type ChartDataSeries,
|
|
3584
|
+
type MultiSeries,
|
|
3585
|
+
type StackedBar,
|
|
3586
|
+
type DateRangeOption,
|
|
3587
|
+
} from 'hazo_ui';
|
|
3588
|
+
```
|
|
3589
|
+
|
|
3590
|
+
### Sparkline
|
|
3591
|
+
|
|
3592
|
+
Axisless single-series trend line for KPI cards. Stretches edge-to-edge
|
|
3593
|
+
across the parent (`width: 100%`), 12% area fill, 2px endpoint dot at the
|
|
3594
|
+
latest non-null value. By design no hover — the numeric value belongs
|
|
3595
|
+
above the sparkline in the KPI card itself.
|
|
3596
|
+
|
|
3597
|
+
```tsx
|
|
3598
|
+
<Sparkline data={[2, 3, 5, 4, 7, 9, 12]} color="#10b981" height={40} />
|
|
3599
|
+
```
|
|
3600
|
+
|
|
3601
|
+
Props:
|
|
3602
|
+
|
|
3603
|
+
| Prop | Type | Default | Description |
|
|
3604
|
+
|-------------|---------------------|---------|--------------------------------------------|
|
|
3605
|
+
| `data` | `(number \| null)[]`| — | Series. `null` entries are skipped. |
|
|
3606
|
+
| `color` | `string` | — | Stroke, dot, and area-fill color. |
|
|
3607
|
+
| `height` | `number` | `40` | Pixel height. Width is always 100%. |
|
|
3608
|
+
| `className` | `string` | — | Container className passthrough. |
|
|
3609
|
+
|
|
3610
|
+
### InverseSparkline
|
|
3611
|
+
|
|
3612
|
+
Same shape as `Sparkline`, but the Y-axis is **inverted** — lower values
|
|
3613
|
+
plot higher on the screen. Built for GSC average-position trends (rank 1
|
|
3614
|
+
= best). Fixed 62×18 default to fit inline in scrollable query lists.
|
|
3615
|
+
No area fill, no dot — the line direction is the only signal.
|
|
3616
|
+
|
|
3617
|
+
```tsx
|
|
3618
|
+
<InverseSparkline data={[12, 10, 8, 5, 3]} color="#3b82f6" />
|
|
3619
|
+
```
|
|
3620
|
+
|
|
3621
|
+
Props:
|
|
3622
|
+
|
|
3623
|
+
| Prop | Type | Default | Description |
|
|
3624
|
+
|-------------|---------------------|---------|--------------------------------------------|
|
|
3625
|
+
| `data` | `(number \| null)[]`| — | Series. `null` entries are skipped. |
|
|
3626
|
+
| `color` | `string` | — | Stroke color. |
|
|
3627
|
+
| `width` | `number` | `62` | Pixel width. |
|
|
3628
|
+
| `height` | `number` | `18` | Pixel height. |
|
|
3629
|
+
| `className` | `string` | — | Container className passthrough. |
|
|
3630
|
+
|
|
3631
|
+
### LineChart
|
|
3632
|
+
|
|
3633
|
+
Full-anatomy single-series chart: three dashed gridlines at 0/50/100% of
|
|
3634
|
+
plot height, Y-axis ticks (max/mid/min) at the left, three X-axis labels
|
|
3635
|
+
(start/mid/end) below, marker dot at the last data point with a dashed
|
|
3636
|
+
guide-line back to the Y-axis and a value label rendered above-left.
|
|
3637
|
+
|
|
3638
|
+
Hover the plot area to surface a vertical cursor line and a value bubble
|
|
3639
|
+
at the nearest data index. Pass `showTooltip={false}` for static use
|
|
3640
|
+
(print export, embeds).
|
|
3641
|
+
|
|
3642
|
+
```tsx
|
|
3643
|
+
<LineChart
|
|
3644
|
+
data={[12, 14, 18, 20, 25, 30, 36, 42]}
|
|
3645
|
+
dates={['May 1', 'May 2', 'May 3', 'May 4', 'May 5', 'May 6', 'May 7', 'May 8']}
|
|
3646
|
+
color="#10b981"
|
|
3647
|
+
unit="%"
|
|
3648
|
+
/>
|
|
3649
|
+
```
|
|
3650
|
+
|
|
3651
|
+
Props:
|
|
3652
|
+
|
|
3653
|
+
| Prop | Type | Default | Description |
|
|
3654
|
+
|---------------|---------------------|---------|----------------------------------------------------------|
|
|
3655
|
+
| `data` | `(number \| null)[]`| — | Series. `null` entries are gaps in the line. |
|
|
3656
|
+
| `dates` | `string[]` | — | X-axis label per data point (length should match `data`).|
|
|
3657
|
+
| `color` | `string` | — | Stroke / area / marker color. |
|
|
3658
|
+
| `width` | `number` | `360` | viewBox width. |
|
|
3659
|
+
| `height` | `number` | `130` | viewBox height. |
|
|
3660
|
+
| `unit` | `string` | — | Suffix on the current-value label (e.g. `"%"`). |
|
|
3661
|
+
| `showTooltip` | `boolean` | `true` | Disable hover tooltip when `false`. |
|
|
3662
|
+
| `className` | `string` | — | Container className passthrough. |
|
|
3663
|
+
|
|
3664
|
+
### MultiLineChart
|
|
3665
|
+
|
|
3666
|
+
Multiple lines sharing a Y-axis. **No area fill** — multi-series uses
|
|
3667
|
+
lines only so colors don't muddy each other. Per-series endpoint marker +
|
|
3668
|
+
value label keeps the latest reading visible without hovering. Hover
|
|
3669
|
+
surfaces one cursor with a stacked bubble showing every series value at
|
|
3670
|
+
that index. Optional legend below the SVG.
|
|
3671
|
+
|
|
3672
|
+
```tsx
|
|
3673
|
+
<MultiLineChart
|
|
3674
|
+
series={[
|
|
3675
|
+
{ label: 'Indexed', color: '#10b981', data: [1, 2, 5, 12, 26, 48, 62] },
|
|
3676
|
+
{ label: 'Crawled', color: '#f59e0b', data: [3, 4, 8, 15, 22, 28, 30] },
|
|
3677
|
+
]}
|
|
3678
|
+
dates={['May 1', 'May 2', 'May 3', 'May 4', 'May 5', 'May 6', 'May 7']}
|
|
3679
|
+
/>
|
|
3680
|
+
```
|
|
3681
|
+
|
|
3682
|
+
Props:
|
|
3683
|
+
|
|
3684
|
+
| Prop | Type | Default | Description |
|
|
3685
|
+
|---------------|-----------------|---------|-------------------------------------------------|
|
|
3686
|
+
| `series` | `MultiSeries[]` | — | One entry per line (`label`, `color`, `data`). |
|
|
3687
|
+
| `dates` | `string[]` | — | X-axis labels. |
|
|
3688
|
+
| `width` | `number` | `360` | viewBox width. |
|
|
3689
|
+
| `height` | `number` | `140` | viewBox height. |
|
|
3690
|
+
| `showTooltip` | `boolean` | `true` | Disable hover tooltip when `false`. |
|
|
3691
|
+
| `showLegend` | `boolean` | `true` | Render legend below the chart. |
|
|
3692
|
+
| `className` | `string` | — | Container className passthrough. |
|
|
3693
|
+
|
|
3694
|
+
### StackedBars
|
|
3695
|
+
|
|
3696
|
+
Generic stacked-bar over time. One bar per X position; each bar split
|
|
3697
|
+
top-to-bottom into colored bands defined by `segments`. Good for
|
|
3698
|
+
distribution-over-time visuals (GSC position buckets, traffic-source mix,
|
|
3699
|
+
category breakdowns).
|
|
3700
|
+
|
|
3701
|
+
```tsx
|
|
3702
|
+
<StackedBars
|
|
3703
|
+
bars={[
|
|
3704
|
+
{ label: 'May 1', segments: [
|
|
3705
|
+
{ value: 4, color: '#10b981' },
|
|
3706
|
+
{ value: 6, color: '#3b82f6' },
|
|
3707
|
+
{ value: 2, color: '#f59e0b' },
|
|
3708
|
+
]},
|
|
3709
|
+
// ...one entry per bar
|
|
3710
|
+
]}
|
|
3711
|
+
/>
|
|
3712
|
+
```
|
|
3713
|
+
|
|
3714
|
+
Props:
|
|
3715
|
+
|
|
3716
|
+
| Prop | Type | Default | Description |
|
|
3717
|
+
|--------------|----------------|---------|-----------------------------------------------------|
|
|
3718
|
+
| `bars` | `StackedBar[]` | — | One entry per column; `segments` are top-to-bottom. |
|
|
3719
|
+
| `width` | `number` | `360` | viewBox width. |
|
|
3720
|
+
| `height` | `number` | `140` | viewBox height. |
|
|
3721
|
+
| `showYAxis` | `boolean` | `true` | Render Y-axis ticks (max / mid / 0). |
|
|
3722
|
+
| `className` | `string` | — | Container className passthrough. |
|
|
3723
|
+
|
|
3724
|
+
### DateRangeSelector
|
|
3725
|
+
|
|
3726
|
+
Framework-agnostic segmented control (`value` + `onChange`) for picking
|
|
3727
|
+
a chart's time window. The active pill is tinted with the
|
|
3728
|
+
`--primary` CSS variable; inactive pills use muted foreground colors.
|
|
3729
|
+
No router coupling — wire the value into URL state, local state, or any
|
|
3730
|
+
store yourself.
|
|
3731
|
+
|
|
3732
|
+
```tsx
|
|
3733
|
+
const [range, setRange] = React.useState('30d');
|
|
3734
|
+
|
|
3735
|
+
<DateRangeSelector
|
|
3736
|
+
value={range}
|
|
3737
|
+
onChange={setRange}
|
|
3738
|
+
options={[
|
|
3739
|
+
{ value: '7d', label: '7d' },
|
|
3740
|
+
{ value: '30d', label: '30d' },
|
|
3741
|
+
{ value: '90d', label: '90d' },
|
|
3742
|
+
]}
|
|
3743
|
+
/>
|
|
3744
|
+
```
|
|
3745
|
+
|
|
3746
|
+
Props:
|
|
3747
|
+
|
|
3748
|
+
| Prop | Type | Default | Description |
|
|
3749
|
+
|-------------|-------------------------------|----------------|--------------------------------------|
|
|
3750
|
+
| `value` | `string` | — | Currently selected option value. |
|
|
3751
|
+
| `onChange` | `(value: string) => void` | — | Fires when the user picks an option. |
|
|
3752
|
+
| `options` | `DateRangeOption[]` | — | `{ value, label }` pairs. |
|
|
3753
|
+
| `ariaLabel` | `string` | `"Date range"` | Aria label for the button group. |
|
|
3754
|
+
| `className` | `string` | — | Container className passthrough. |
|
|
3755
|
+
|
|
3756
|
+
### Helpers
|
|
3757
|
+
|
|
3758
|
+
Two pure helpers are exported for callers that want to format values or
|
|
3759
|
+
pick label positions consistently with the charts:
|
|
3760
|
+
|
|
3761
|
+
```tsx
|
|
3762
|
+
format_num(1234); // "1.2k"
|
|
3763
|
+
format_num(1_500_000); // "1.5M"
|
|
3764
|
+
format_num(5.73); // "5.7"
|
|
3765
|
+
format_num(null); // ""
|
|
3766
|
+
|
|
3767
|
+
pick_x_label_indices(14); // [0, 7, 13] — start / mid / end
|
|
3768
|
+
```
|
|
3769
|
+
|
|
3770
|
+
### Types
|
|
3771
|
+
|
|
3772
|
+
```ts
|
|
3773
|
+
type ChartDataPoint = number | null;
|
|
3774
|
+
type ChartDataSeries = ChartDataPoint[];
|
|
3775
|
+
|
|
3776
|
+
interface MultiSeries {
|
|
3777
|
+
label: string;
|
|
3778
|
+
color: string;
|
|
3779
|
+
data: ChartDataSeries;
|
|
3780
|
+
}
|
|
3781
|
+
|
|
3782
|
+
interface StackedBar {
|
|
3783
|
+
label: string;
|
|
3784
|
+
segments: { value: number; color: string }[];
|
|
3785
|
+
}
|
|
3786
|
+
|
|
3787
|
+
interface DateRangeOption {
|
|
3788
|
+
value: string;
|
|
3789
|
+
label: string;
|
|
3790
|
+
}
|
|
3791
|
+
```
|
|
3792
|
+
|
|
3793
|
+
### Notes
|
|
3794
|
+
|
|
3795
|
+
- **Null handling.** A `null` entry in any series is a *gap* — the line
|
|
3796
|
+
breaks rather than connecting around the missing point. The Y range is
|
|
3797
|
+
computed from non-null values only, so one missing day won't deform the
|
|
3798
|
+
chart.
|
|
3799
|
+
- **Axis label color is pinned.** Gridlines (`#2a3441`) and axis labels
|
|
3800
|
+
(`#8b949e`) are hardcoded for dark-themed dashboards. Theming via CSS
|
|
3801
|
+
variables is a follow-up if a second consumer needs it.
|
|
3802
|
+
- **All charts are Client Components.** The library bundle is
|
|
3803
|
+
`"use client"`-stamped at build time, so charts work in any Next.js app
|
|
3804
|
+
but don't get RSC-rendered.
|
|
3805
|
+
- **Hover assumes `width: 100%`.** `LineChart` / `MultiLineChart` map
|
|
3806
|
+
`clientX` to a viewBox X via `getBoundingClientRect()`. CSS transforms
|
|
3807
|
+
on the wrapper (`scale(...)`) will desync the mapping.
|
|
3808
|
+
|
|
3809
|
+
---
|
|
3810
|
+
|
|
3811
|
+
## Celebration Modal
|
|
3812
|
+
|
|
3813
|
+
Fire a confetti overlay with an optional 1080×1080 shareable card from anywhere in your app.
|
|
3814
|
+
|
|
3815
|
+
### Setup
|
|
3816
|
+
|
|
3817
|
+
Mount `<CelebrationProvider />` once at your app root:
|
|
3818
|
+
|
|
3819
|
+
```tsx
|
|
3820
|
+
// app/layout.tsx
|
|
3821
|
+
import { CelebrationProvider } from "hazo_ui";
|
|
3822
|
+
|
|
3823
|
+
export default function RootLayout({ children }) {
|
|
3824
|
+
return (
|
|
3825
|
+
<html>
|
|
3826
|
+
<body>
|
|
3827
|
+
<CelebrationProvider>
|
|
3828
|
+
{children}
|
|
3829
|
+
</CelebrationProvider>
|
|
3830
|
+
</body>
|
|
3831
|
+
</html>
|
|
3832
|
+
);
|
|
3833
|
+
}
|
|
3834
|
+
```
|
|
3835
|
+
|
|
3836
|
+
### Usage
|
|
3837
|
+
|
|
3838
|
+
```tsx
|
|
3839
|
+
import { celebrate, CELEBRATION_GRADIENT } from "hazo_ui";
|
|
3840
|
+
|
|
3841
|
+
// Basic celebration
|
|
3842
|
+
celebrate({
|
|
3843
|
+
id: "first_login", // sessionStorage dedup key — shown once per session
|
|
3844
|
+
title: "Welcome!",
|
|
3845
|
+
subtitle: "Your account is ready.",
|
|
3846
|
+
});
|
|
3847
|
+
|
|
3848
|
+
// With shareable card
|
|
3849
|
+
celebrate({
|
|
3850
|
+
id: "milestone_100",
|
|
3851
|
+
title: "100 entries!",
|
|
3852
|
+
subtitle: "You just crossed 100 entries.",
|
|
3853
|
+
autoDismissDelay: 10_000,
|
|
3854
|
+
shareableCard: {
|
|
3855
|
+
background: CELEBRATION_GRADIENT, // or any CSS background value
|
|
3856
|
+
foreground: <YourCardContent />,
|
|
3857
|
+
caption: "100 entries logged", // optional; defaults to subtitle
|
|
3858
|
+
},
|
|
3859
|
+
audioChime: true,
|
|
3860
|
+
});
|
|
3861
|
+
```
|
|
3862
|
+
|
|
3863
|
+
### Session gating
|
|
3864
|
+
|
|
3865
|
+
`celebrate()` is a no-op if `hazo_ui_celebration_<id>` is already set in sessionStorage.
|
|
3866
|
+
The key is written when the modal opens. Gating resets when the user opens a new tab or browser session.
|
|
3867
|
+
|
|
3868
|
+
**Cross-session dedup is the consumer's responsibility.** Check your server-side milestone state before calling `celebrate()`.
|
|
3869
|
+
|
|
3870
|
+
### API
|
|
3871
|
+
|
|
3872
|
+
| Prop | Type | Default | Description |
|
|
3873
|
+
|---|---|---|---|
|
|
3874
|
+
| `id` | `string` | required | Dedup key. Used as `hazo_ui_celebration_<id>` in sessionStorage. |
|
|
3875
|
+
| `title` | `string` | required | Modal heading. |
|
|
3876
|
+
| `subtitle` | `string` | — | Subheading; also used as card caption fallback. |
|
|
3877
|
+
| `shareableCard` | `CelebrationShareableCard` | — | Enables card preview + download/share/copy buttons. |
|
|
3878
|
+
| `autoDismiss` | `boolean` | `true` | Auto-close after `autoDismissDelay` ms. |
|
|
3879
|
+
| `autoDismissDelay` | `number` | `8000` | Ms until auto-dismiss. |
|
|
3880
|
+
| `audioChime` | `boolean` | `false` | Play bundled success chime on open. |
|
|
3881
|
+
|
|
3882
|
+
---
|
|
3883
|
+
|
|
3884
|
+
## Test Harness (v3.0.0)
|
|
3885
|
+
|
|
3886
|
+
`hazo_ui/test-harness` is a dedicated sub-export that ships a complete in-app test runner for consuming packages. It is **never included in the main `hazo_ui` bundle** — only imported by test-app code.
|
|
3887
|
+
|
|
3888
|
+
### Setup
|
|
3889
|
+
|
|
3890
|
+
```tsx
|
|
3891
|
+
// test-app/app/layout.tsx
|
|
3892
|
+
import { SidebarLayout, AppSidebar, AutoTestProvider } from 'hazo_ui/test-harness';
|
|
3893
|
+
import type { NavItem } from 'hazo_ui/test-harness';
|
|
3894
|
+
|
|
3895
|
+
const nav: NavItem[] = [
|
|
3896
|
+
{ href: '/', label: 'Overview' },
|
|
3897
|
+
{ href: '/my-feature', label: 'My Feature' },
|
|
3898
|
+
];
|
|
3899
|
+
|
|
3900
|
+
export default function RootLayout({ children }) {
|
|
3901
|
+
return (
|
|
3902
|
+
<html>
|
|
3903
|
+
<body>
|
|
3904
|
+
<AutoTestProvider>
|
|
3905
|
+
<SidebarLayout sidebar={<AppSidebar nav={nav} title="my_pkg" />}>
|
|
3906
|
+
{children}
|
|
3907
|
+
</SidebarLayout>
|
|
3908
|
+
</AutoTestProvider>
|
|
3909
|
+
</body>
|
|
3910
|
+
</html>
|
|
3911
|
+
);
|
|
3912
|
+
}
|
|
3913
|
+
```
|
|
3914
|
+
|
|
3915
|
+
### Registering scenarios
|
|
3916
|
+
|
|
3917
|
+
```ts
|
|
3918
|
+
// test-app/scenarios/my_feature.ts
|
|
3919
|
+
import { registerScenario, assertEqual } from 'hazo_ui/test-harness';
|
|
3920
|
+
|
|
3921
|
+
registerScenario({
|
|
3922
|
+
id: 'my_feature',
|
|
3923
|
+
name: 'My Feature',
|
|
3924
|
+
pkg: 'my_pkg',
|
|
3925
|
+
cases: [
|
|
3926
|
+
{
|
|
3927
|
+
name: 'returns correct value',
|
|
3928
|
+
run: async () => {
|
|
3929
|
+
const result = myFunction(1, 2);
|
|
3930
|
+
assertEqual(result, 3, 'should add two numbers');
|
|
3931
|
+
},
|
|
3932
|
+
},
|
|
3933
|
+
{
|
|
3934
|
+
name: 'throws on invalid input',
|
|
3935
|
+
run: async () => {
|
|
3936
|
+
await assertThrows(() => myFunction(null, 2), 'invalid input');
|
|
3937
|
+
},
|
|
3938
|
+
},
|
|
3939
|
+
],
|
|
3940
|
+
});
|
|
3941
|
+
```
|
|
3942
|
+
|
|
3943
|
+
### Rendering the runner
|
|
3944
|
+
|
|
3945
|
+
```tsx
|
|
3946
|
+
// test-app/app/my-feature/page.tsx
|
|
3947
|
+
import { AutoTestRunner } from 'hazo_ui/test-harness';
|
|
3948
|
+
import '../scenarios/my_feature'; // registers the scenario
|
|
3949
|
+
|
|
3950
|
+
export default function MyFeaturePage() {
|
|
3951
|
+
return <AutoTestRunner scenarioId="my_feature" />;
|
|
3952
|
+
}
|
|
3953
|
+
```
|
|
3954
|
+
|
|
3955
|
+
### Copying failures as a Claude prompt
|
|
3956
|
+
|
|
3957
|
+
`CopyAllFailuresButton` copies all failed cases as a structured prompt with 8 sections (what-went-wrong, expected/actual/diff, test code, code under test, error chain, context, ring buffer). Place it in your sidebar or test page header.
|
|
3958
|
+
|
|
3959
|
+
```tsx
|
|
3960
|
+
import { CopyAllFailuresButton } from 'hazo_ui/test-harness';
|
|
3961
|
+
|
|
3962
|
+
// Inside your layout or sidebar:
|
|
3963
|
+
<CopyAllFailuresButton />
|
|
3964
|
+
```
|
|
3965
|
+
|
|
3966
|
+
### Assertions
|
|
3967
|
+
|
|
3968
|
+
| Function | Description |
|
|
3969
|
+
|---|---|
|
|
3970
|
+
| `assertEqual(actual, expected, msg?)` | Deep-equal assertion |
|
|
3971
|
+
| `assertThrows(fn, msgOrPattern?)` | Sync function must throw |
|
|
3972
|
+
| `assertResolves(promise)` | Promise must resolve |
|
|
3973
|
+
| `assertRejects(promise, msgOrPattern?)` | Promise must reject |
|
|
3974
|
+
| `assertMatch(value, pattern)` | String must match regex or substring |
|
|
3975
|
+
| `assertIncludes(arr, item)` | Array must include item |
|
|
3976
|
+
|
|
3977
|
+
All failures throw `HazoAssertionError` with a structured message.
|
|
3978
|
+
|
|
3979
|
+
---
|
|
3980
|
+
|
|
3564
3981
|
## Troubleshooting
|
|
3565
3982
|
|
|
3566
3983
|
### Styles not applying (Tailwind v4)
|
package/SETUP_CHECKLIST.md
CHANGED
|
@@ -135,6 +135,8 @@ npm install @radix-ui/react-dialog @radix-ui/react-popover @radix-ui/react-selec
|
|
|
135
135
|
- **ProgressiveImage**: No additional dependencies
|
|
136
136
|
- **HazoUiToaster, successToast, errorToast, rawToast**: `sonner`, `lucide-react`
|
|
137
137
|
- **useLoadingState, useErrorDisplay** hooks: No additional dependencies
|
|
138
|
+
- **Celebration (v2.18.0)**:
|
|
139
|
+
- **CelebrationProvider, celebrate, CELEBRATION_GRADIENT**: No additional peer deps — `canvas-confetti` and `html-to-image` are bundled direct dependencies
|
|
138
140
|
|
|
139
141
|
### 4. Configure Tailwind CSS
|
|
140
142
|
|
|
@@ -252,10 +254,18 @@ import {
|
|
|
252
254
|
HazoUiToaster, successToast, errorToast,
|
|
253
255
|
useLoadingState, useErrorDisplay,
|
|
254
256
|
} from 'hazo_ui';
|
|
257
|
+
|
|
258
|
+
// Import celebration (v2.18.0)
|
|
259
|
+
import { CelebrationProvider, celebrate, CELEBRATION_GRADIENT } from 'hazo_ui';
|
|
260
|
+
|
|
261
|
+
// Import test harness (v3.0.0) — test-app only, never in production bundles
|
|
262
|
+
import { AutoTestProvider, AutoTestRunner, SidebarLayout, AppSidebar, registerScenario, assertEqual } from 'hazo_ui/test-harness';
|
|
255
263
|
```
|
|
256
264
|
|
|
257
265
|
**Toaster setup**: Mount `<HazoUiToaster />` once at the root of your app (e.g., in `layout.tsx`) so `successToast()` / `errorToast()` calls have somewhere to render.
|
|
258
266
|
|
|
267
|
+
**Celebration setup**: Mount `<CelebrationProvider />` once at the root of your app (e.g., in `layout.tsx`) so `celebrate({ id, title })` calls have somewhere to render. Session gating (one show per `id` per browser session) runs automatically.
|
|
268
|
+
|
|
259
269
|
[ ] Use the component in your code (see README.md for detailed usage examples)
|
|
260
270
|
|
|
261
271
|
### 6. Verify Installation
|