hazo_ui 2.8.1 → 2.11.0

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 ADDED
@@ -0,0 +1,319 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [2.11.0] - 2026-05-16
9
+
10
+ ### Fixed
11
+ - **Button inline-style fallback rendering as transparent** — the v2-era inline-style fallback used bare `var(--primary)` etc., which resolved to invalid CSS when consumers (per shadcn convention) store CSS variables as raw HSL channels (e.g. `--primary: 222.2 47.4% 11.2%`). All `Button` variants (default, destructive, outline, secondary) now wrap their fallback values in `hsl(...)`, matching the documented theme convention and Tailwind preset. Buttons rendered as transparent rectangles with text-only labels before this fix.
12
+ - `destructive` variant's inline-style `color` switched from hardcoded `"white"` to `hsl(var(--destructive-foreground))` for theme consistency.
13
+
14
+ ### Notes
15
+ - No API changes — pure visual fix. Consumers on the standard shadcn theme convention will see Buttons render correctly without any further changes.
16
+ - Consumers who set CSS variables as full color values (hex/rgb) instead of HSL channels should override via the `style` prop (this was never the documented convention).
17
+
18
+ ## [2.10.0] - 2026-05-16
19
+
20
+ ### Added
21
+ - **State primitives sub-module** — `Skeleton`, `SkeletonCircle`, `SkeletonBar`, `SkeletonRect`, `SkeletonGroup` with shimmer animation (respects `prefers-reduced-motion`).
22
+ - `EmptyState` component (icon + title + description + action).
23
+ - `ErrorBanner` component with `severity: 'warning' | 'error'`, auto icon, optional title/action/dismiss.
24
+ - `ErrorPage` component for full-page error fallback (title, description, errorCode, correlationId, actions).
25
+ - `LoadingTimeout` wrapper with 5s/15s/30s escalation (silent → gentle → firm → expired).
26
+ - `ProgressiveImage` (grey → blur LQIP → sharp).
27
+ - `HazoUiToaster` + `successToast(opts)` + `errorToast(opts)` imperative helpers — wraps `sonner`.
28
+ - `useLoadingState()` hook — `{ isLoading, setLoading, withLoading }`.
29
+ - `useErrorDisplay()` hook — passive `{ error, setError, clearError }`.
30
+
31
+ ### Changed
32
+ - Added `sonner ^2.0.7` to dependencies.
33
+ - Added `@keyframes hazo-shimmer` and `.hazo-shimmer` utility class to `src/styles/hazo-ui.css`.
34
+
35
+ ### Notes
36
+ - All new components use shadcn semantic tokens (`bg-muted`, `bg-destructive`, etc.) and direct utility classes for amber tones (no `--warning` token assumed).
37
+ - Components are exported flat & unprefixed from the top-level barrel — matches the existing `Button`/`Spinner`/`Card` convention.
38
+
39
+ ## [2.9.0] - 2026-05-12
40
+
41
+ ### Added
42
+ - **Drawer primitive** (`src/components/ui/drawer.tsx`): vaul-backed bottom sheet component following the shadcn/ui Drawer pattern. Exports `Drawer`, `DrawerTrigger`, `DrawerPortal`, `DrawerOverlay`, `DrawerContent`, `DrawerHeader`, `DrawerFooter`, `DrawerTitle`, `DrawerDescription`, `DrawerClose`. Uses dynamic viewport units (`max-h-[96dvh]`) so iOS Safari's address-bar toggling doesn't crop the sheet.
43
+ - **`useMediaQuery` hook** (`src/hooks/use_media_query.ts`): SSR-safe React hook returning whether a CSS media query matches. Re-renders on match changes. Use to fork mobile/desktop rendering (e.g. swap `Dialog` ↔ `Drawer` at `(max-width: 640px)`).
44
+ - **New dependency**: `vaul@^1.1.2` (~6 KB gz, MIT). React/react-dom peers are already covered by hazo_ui's existing peer range.
45
+
46
+ ### Notes for consumers
47
+ - The Drawer is intended for mobile bottom sheets. On desktop, prefer `Dialog`. Pattern:
48
+ ```typescript
49
+ import { Drawer, Dialog, useMediaQuery } from "hazo_ui";
50
+ const is_mobile = useMediaQuery("(max-width: 640px)");
51
+ const Shell = is_mobile ? Drawer : Dialog;
52
+ ```
53
+ - Snap points, side-anchored drawers (`top`/`left`/`right`), and nested drawers are not in scope for this primitive — pass through to vaul's API directly if needed in the meantime.
54
+
55
+ ## [2.8.0] - 2026-05-11
56
+
57
+ ### Added
58
+ - **shadcn/ui Primitive Re-exports (9 new primitives)**: AlertDialog, ButtonGroup, Card, Collapsible, ScrollArea, Separator, Spinner, Toggle, ToggleGroup
59
+ - All new primitives follow the existing shadcn/ui pattern and are re-exported from the package root
60
+ - Enables sibling hazo_* packages to import these UI primitives from a single source without managing separate Radix UI dependencies
61
+ - **6 new Radix UI dependencies**: `@radix-ui/react-alert-dialog`, `@radix-ui/react-collapsible`, `@radix-ui/react-scroll-area`, `@radix-ui/react-separator`, `@radix-ui/react-toggle`, `@radix-ui/react-toggle-group`
62
+
63
+ ### Fixed
64
+ - **CardTitle ref type**: Corrected generic type from `HTMLParagraphElement` to `HTMLHeadingElement` — the rendered element is `<h3>` so the ref must be typed accordingly
65
+
66
+ ## [2.7.3] - 2026-05-10
67
+
68
+ ### Changed
69
+ - **BREAKING (build/styling)**: Migrated internal build pipeline to **Tailwind CSS v4** (`tailwindcss@^4.2.4`).
70
+ - Replaced `tailwindcss-animate` with `tw-animate-css@^1.4.0`
71
+ - Replaced PostCSS plugins `tailwindcss + autoprefixer` with single `@tailwindcss/postcss` plugin (Lightning CSS handles vendor prefixing)
72
+ - Updated `src/styles/globals.css` to use Tailwind v4 entry: `@import "tailwindcss"` + `@import "tw-animate-css"` (replaces `@tailwind base/components/utilities`)
73
+ - Updated `tailwind.preset.js` to be v4-compatible (removed `tailwindcss-animate` plugin import)
74
+ - Bumped canonical workspace versions: `clsx@^2.1.1`, `tailwind-merge@^3.5.0`, `lucide-react@^0.553.0`, `typescript@^5.7.2`, `@types/react@^18.3.3`
75
+ - Dev-app migrated to Tailwind v4 mirror of the library configuration
76
+ - `HazoUiTextbox` / `HazoUiTextarea` test pages updated to use object-form `color` prop (`{ bg, fg, border }`) for prefixes — aligns with the production pill color model
77
+
78
+ ### Migration Notes for Consumers
79
+ - If you use the published `tailwind.preset.js`, no API changes are required.
80
+ - If you previously installed `tailwindcss-animate` for hazo_ui, replace it with `tw-animate-css` and load via `@import "tw-animate-css";` in your CSS.
81
+ - Tailwind v4 consumers must keep the `@source "../node_modules/hazo_ui/dist";` directive (already documented).
82
+
83
+ ## [2.7.2] - 2026-03-31
84
+
85
+ ### Added
86
+ - **HazoUiPillRadio**: Pill-shaped radio selection component
87
+ - Optional icons (react-icons compatible)
88
+ - Customizable accent colors per option
89
+ - Size variants (sm / md / lg)
90
+ - Horizontal or vertical layout
91
+ - Equal-width option for uniform pill widths
92
+ - Auto-fit vertical layout
93
+
94
+ ## [2.7.1] - 2026-03-27
95
+
96
+ ### Changed
97
+ - **BREAKING**: Renamed `DialogVariant` value `'danger'` to `'destructive'` in HazoUiDialog for consistency with HazoUiConfirmDialog
98
+ - Extracted shared `ANIMATION_PRESETS` and `resolve_animation_classes` into `src/lib/animations.ts` (used by both dialog components)
99
+ - Removed dead `example_component` placeholder from source
100
+
101
+ ### Added
102
+ - Re-exported `ANIMATION_PRESETS` and `resolve_animation_classes` from package root for consumer use
103
+ - Added `.superpowers` and `design` to `.npmignore`
104
+
105
+ ### Removed
106
+ - Unnecessary `esbuild` devDependency (tsup bundles its own)
107
+
108
+ ## [2.7.0] - 2026-03-26
109
+
110
+ ### Added
111
+ - **HazoUiConfirmDialog**: Compact confirmation dialog with variant-driven styling
112
+ - Variant system: default, destructive, warning, info, success
113
+ - Accent top border colored by variant
114
+ - Async `onConfirm` with auto-loading state
115
+ - Configurable buttons (confirm + optional cancel)
116
+ - ReactNode children or simple description string
117
+ - Focus management: cancel button receives focus for destructive/warning variants
118
+ - **shadcn/ui Primitive Re-exports**: All shadcn/ui base components now re-exported from hazo_ui
119
+ - Accordion, Button, Calendar, Checkbox, Command, DropdownMenu, HoverCard, Input, Label, Popover, RadioGroup, Select, Switch, Tabs, Textarea, Tooltip
120
+ - Enables sibling hazo_* packages to import UI primitives from a single source
121
+ - New Radix UI dependencies: `@radix-ui/react-accordion`, `@radix-ui/react-checkbox`, `@radix-ui/react-dropdown-menu`, `@radix-ui/react-hover-card`
122
+
123
+ ## [2.6.6] - 2026-03-25
124
+
125
+ ### Fixed
126
+ - Dialog title overlapping close button and not word-wrapping
127
+
128
+ ## [2.6.5] - 2026-03-24
129
+
130
+ ### Added
131
+ - `fixedSize` prop on HazoUiDialog — when true, `sizeHeight` becomes a fixed height instead of maxHeight
132
+
133
+ ## [2.6.4] - 2026-03-23
134
+
135
+ ### Fixed
136
+ - Button styling in dialog footer
137
+
138
+ ## [2.6.3] - 2026-03-22
139
+
140
+ ### Added
141
+ - Dialog `variant` prop with preset color themes (info, success, warning, destructive)
142
+ - Global `hazo_ui_config` system for theming buttons and headers
143
+ - Per-component color override props
144
+
145
+ ## [2.6.2] - 2026-03-21
146
+
147
+ ### Added
148
+ - Compositional Dialog API: re-exported shadcn/ui Dialog primitives as `HazoUiDialogRoot`, `HazoUiDialogContent`, etc. for complex layouts
149
+
150
+ ## [2.6.0] - 2026-03-20
151
+
152
+ ### Added
153
+ - **HazoUiDialog**: Standardized dialog component with comprehensive features
154
+ - Standardized header/footer layout with customizable content area
155
+ - Flexible animation system (9 presets + custom Tailwind classes)
156
+ - Responsive sizing with viewport-relative dimensions
157
+ - Background overlay customization
158
+ - CSS variable-based theming
159
+ - Header bar mode with colored full-width header
160
+ - Loading state and custom footer content support
161
+
162
+ ## [2.5.2] - 2026-03-19
163
+
164
+ ### Added
165
+ - Documentation updates for HazoUiDialog
166
+
167
+ ## [2.5.1] - 2026-03-18
168
+
169
+ ### Added
170
+ - `instance_id` prop documentation for command components
171
+
172
+ ## [2.5.0] - 2026-03-17
173
+
174
+ ### Added
175
+ - Multiple simultaneous command instances support
176
+
177
+ ## [2.4.0] - 2026-03-15
178
+
179
+ ### Added
180
+ - **HazoUiCommand**: Headless command/mention system for Tiptap
181
+ - Command pill rendering with prefix-based colors
182
+ - Popover suggestion menu
183
+ - Multiple prefix support (@, #, /, etc.)
184
+ - `parse_commands_from_text` and `text_to_tiptap_content` utilities
185
+ - **HazoUiTextbox**: Single-line input with command pill support
186
+ - **HazoUiTextarea**: Multi-line input with command pill support
187
+
188
+ ## [2.3.0] - 2026-03-10
189
+
190
+ ### Added
191
+ - **HazoUiRte**: Rich Text Editor component based on Tiptap v3
192
+ - Variable insertion system
193
+ - File attachment support
194
+ - HTML and plain text output modes
195
+ - Comprehensive toolbar with formatting options
196
+ - Font size and font family extensions
197
+ - Tailwind preset type declarations (`tailwind.preset.d.ts`)
198
+
199
+ ### Fixed
200
+ - SSR hydration issues
201
+
202
+ ## [2.2.0] - 2025-12-17
203
+
204
+ ### Added
205
+ - **HazoUiMultiFilterDialog**: Added optional `title` prop (default: "Filter") to customize dialog title
206
+ - **HazoUiMultiFilterDialog**: Added optional `description` prop (default: "Add multiple fields to filter by...") to customize dialog description
207
+ - **HazoUiMultiSortDialog**: Added optional `title` prop (default: "Sort") to customize dialog title
208
+ - **HazoUiMultiSortDialog**: Added optional `description` prop (default: "Add multiple fields to sort by...") to customize dialog description
209
+ - **CommandItem**: Added hover styling (`hover:bg-accent hover:text-accent-foreground cursor-pointer`) for better user experience
210
+ - **Documentation**: Added troubleshooting entries for dialog backdrop and dropdown styling issues
211
+
212
+ **Design Decision**: The `title` and `description` props were added to provide better customization flexibility for different use cases. For example, a product filtering dialog might want to display "Filter Products" instead of the generic "Filter", making the interface more contextual and user-friendly. These props maintain backward compatibility by providing sensible defaults.
213
+
214
+ ## [2.1.2] - 2025-12-17
215
+
216
+ ### Added
217
+ - Color support enhancements
218
+
219
+ ## [2.1.0] - 2025-12-17
220
+
221
+ ### Added
222
+ - **HazoUiFlexInput**: New enhanced input component with type validation, character restrictions, and error messaging
223
+ - Supports multiple input types: mixed (text), numeric, alpha (letters only), and email
224
+ - Real-time character filtering for numeric and alpha types
225
+ - Validation on blur with clear error messages
226
+ - Numeric constraints: min/max value validation and decimal precision control
227
+ - Length constraints: configurable minimum and maximum character lengths
228
+ - Custom regex pattern support
229
+ - Optional format guide helper text
230
+ - Fully typed TypeScript interfaces
231
+
232
+ **Design Decision**: HazoUiFlexInput extends the shadcn Input component to provide comprehensive validation without requiring external form libraries. The validation-on-blur approach prevents disruptive real-time error messages while users are typing, improving the overall user experience.
233
+
234
+ ## [2.0.0] - 2025-12-17
235
+
236
+ ### Added
237
+ - **HazoUiFlexRadio**: New flexible radio button/icon selection component
238
+ - Support for single and multi-selection modes
239
+ - Layout options: horizontal or vertical
240
+ - Style variants: radio button style or icon-only button style
241
+ - Integration with react-icons library (supports 10+ icon sets: fa, md, hi, bi, ai, bs, fi, io, ri, tb)
242
+ - Label control with show/hide options
243
+ - Tooltips with 1-second delay
244
+ - Fully controlled component with value/onChange pattern
245
+ - TypeScript support with complete type definitions
246
+ - Accessibility features using Radix UI primitives
247
+
248
+ **Design Decision**: HazoUiFlexRadio was designed to provide maximum flexibility for both traditional radio button interfaces and modern icon-based selection patterns. The dual-mode support (single/multi selection) reduces the need for separate checkbox implementations.
249
+
250
+ ## [1.0.0] - 2025-12-17
251
+
252
+ ### Added
253
+ - **HazoUiMultiFilterDialog**: Multi-field filtering component
254
+ - Support for text, number, combobox, boolean, and date field types
255
+ - Operator support for number and date fields (equals, greater than, less than, etc.)
256
+ - Dynamic field addition and removal
257
+ - Built-in field validation for text length, number ranges, and decimal precision
258
+ - Visual feedback with active filters tooltip
259
+ - Clear all filters functionality
260
+ - Responsive design for mobile and desktop
261
+ - TypeScript support with complete interfaces
262
+ - Accessibility using Radix UI primitives
263
+
264
+ - **HazoUiMultiSortDialog**: Multi-field sorting component with drag-and-drop
265
+ - Multiple sort fields with priority ordering
266
+ - Drag-and-drop reordering using @dnd-kit
267
+ - Direction toggle for each field (ascending/descending)
268
+ - Visual feedback during drag operations
269
+ - Clear all sorts functionality
270
+ - Tooltip display of active sort configuration
271
+ - Keyboard navigation support
272
+ - Responsive design
273
+ - TypeScript support
274
+ - Accessibility features
275
+
276
+ - **Base UI Components**: Shadcn/ui components integration
277
+ - Button, Dialog, Command, Popover, Select, Input, Label, Switch, Tooltip, Calendar
278
+ - Tailwind CSS theming with CSS variable support
279
+ - Light and dark mode support
280
+
281
+ - **Build Configuration**:
282
+ - tsup bundler for ESM + CJS outputs
283
+ - TypeScript declarations generation
284
+ - Tree-shakeable exports
285
+ - "use client" directive for Next.js compatibility
286
+
287
+ - **Development Tools**:
288
+ - Storybook integration for component development
289
+ - Next.js dev app for integration testing
290
+ - TypeScript strict mode
291
+ - ESLint configuration
292
+
293
+ **Design Decision**: The library was built on shadcn/ui and Radix UI primitives to ensure accessibility, maintainability, and consistency with modern React patterns. The choice of @dnd-kit for drag-and-drop provides excellent accessibility support compared to alternatives like react-beautiful-dnd.
294
+
295
+ ## [Unreleased]
296
+
297
+ ### Planned
298
+ - Additional field types for filtering (time, datetime, multi-select)
299
+ - Preset filter configurations for common use cases
300
+ - Export/import filter configurations
301
+ - Advanced sort options (null handling, case sensitivity)
302
+ - Performance optimizations for large datasets
303
+
304
+ ---
305
+
306
+ ## Version History Reference
307
+
308
+ - **2.7.3** - Tailwind v4 migration (internal build), tw-animate-css, canonical version sync
309
+ - **2.7.2** - HazoUiPillRadio component
310
+ - **2.7.1** - Rename 'danger' to 'destructive', extract shared animations, shadcn/ui re-exports
311
+ - **2.7.0** - HazoUiConfirmDialog, shadcn/ui primitive re-exports
312
+ - **2.6.x** - HazoUiDialog variants, compositional API, fixedSize, bug fixes
313
+ - **2.5.x** - Multiple command instances, dialog docs
314
+ - **2.4.0** - HazoUiCommand, HazoUiTextbox, HazoUiTextarea
315
+ - **2.3.0** - HazoUiRte rich text editor
316
+ - **2.2.0** - Customizable dialog titles and descriptions
317
+ - **2.1.0** - HazoUiFlexInput component
318
+ - **2.0.0** - HazoUiFlexRadio component
319
+ - **1.0.0** - Initial release with filter and sort dialogs
package/README.md CHANGED
@@ -202,6 +202,22 @@ The following components support both global config and prop-level color overrid
202
202
 
203
203
  - **[HazoUiConfirmDialog](#hazouiconfirmdialog)** - A compact, opinionated confirmation dialog with accent top border, variant system (destructive, warning, info, success), async loading support, and configurable buttons. Perfect for delete confirmations, unsaved changes warnings, and simple acknowledgments.
204
204
 
205
+ - **[Drawer](#drawer)** - A `vaul`-backed bottom sheet primitive for mobile UIs. Pair with `useMediaQuery` to swap between `Dialog` and `Drawer` based on viewport width.
206
+
207
+ ### State Primitives (v2.10.0)
208
+
209
+ Lightweight, opinionated components for the four ubiquitous async states: **loading**, **empty**, **error**, and **success**.
210
+
211
+ - **[Skeleton](#skeleton)** family (`Skeleton`, `SkeletonCircle`, `SkeletonBar`, `SkeletonRect`, `SkeletonGroup`) - Shimmer placeholders. Respects `prefers-reduced-motion`.
212
+ - **[EmptyState](#emptystate)** - Icon + title + description + CTA for empty lists, search misses, no-data screens.
213
+ - **[ErrorBanner](#errorbanner)** - Inline `warning` or `error` strip with optional title, action button, and dismiss.
214
+ - **[ErrorPage](#errorpage)** - Full-page error fallback with title, description, error code tag, correlation id, and CTAs.
215
+ - **[LoadingTimeout](#loadingtimeout)** - Wraps a loading region and escalates messaging at 5s / 15s / 30s thresholds before showing a retry banner.
216
+ - **[ProgressiveImage](#progressiveimage)** - Three-stage image render: grey placeholder → blurred LQIP → sharp final image.
217
+ - **[HazoUiToaster + toast helpers](#toasts-hazouitoaster--successtoast--errortoast)** - `sonner`-backed toaster with `successToast()` and `errorToast()` imperative helpers.
218
+ - **[useLoadingState](#useloadingstate)** hook - `{ isLoading, setLoading, withLoading }` with an async wrapper.
219
+ - **[useErrorDisplay](#useerrordisplay)** hook - Passive `{ error, setError, clearError }` that coerces `Error` instances to strings.
220
+
205
221
  ### shadcn/ui Primitive Re-exports
206
222
 
207
223
  All shadcn/ui base components are re-exported from hazo_ui, so sibling hazo_* packages (and consumers) can import UI primitives from a single source without installing shadcn/ui separately:
@@ -2965,6 +2981,340 @@ function FormDialog() {
2965
2981
  | `footerClassName` | `string` | - | Footer CSS classes |
2966
2982
  | `showCloseButton` | `boolean` | `true` | Show X close button |
2967
2983
 
2984
+ ## Drawer
2985
+
2986
+ A vaul-backed bottom sheet primitive. Intended for mobile UIs; on desktop, prefer `Dialog`.
2987
+
2988
+ ```typescript
2989
+ import {
2990
+ Drawer,
2991
+ DrawerTrigger,
2992
+ DrawerContent,
2993
+ DrawerHeader,
2994
+ DrawerTitle,
2995
+ DrawerDescription,
2996
+ DrawerFooter,
2997
+ DrawerClose,
2998
+ useMediaQuery,
2999
+ } from "hazo_ui";
3000
+
3001
+ function Example() {
3002
+ const is_mobile = useMediaQuery("(max-width: 640px)");
3003
+
3004
+ if (!is_mobile) {
3005
+ // Render a Dialog instead on desktop
3006
+ return null;
3007
+ }
3008
+
3009
+ return (
3010
+ <Drawer>
3011
+ <DrawerTrigger asChild>
3012
+ <button>Open</button>
3013
+ </DrawerTrigger>
3014
+ <DrawerContent>
3015
+ <DrawerHeader>
3016
+ <DrawerTitle>Title</DrawerTitle>
3017
+ <DrawerDescription>Optional description</DrawerDescription>
3018
+ </DrawerHeader>
3019
+ <DrawerFooter>
3020
+ <DrawerClose asChild>
3021
+ <button>Close</button>
3022
+ </DrawerClose>
3023
+ </DrawerFooter>
3024
+ </DrawerContent>
3025
+ </Drawer>
3026
+ );
3027
+ }
3028
+ ```
3029
+
3030
+ **Behavior:**
3031
+ - Drag the grab handle downward to dismiss
3032
+ - ESC closes
3033
+ - Click overlay to dismiss
3034
+ - `max-h-[96dvh]` keeps the sheet usable on iOS Safari when the address bar toggles
3035
+ - Focus trap, `aria-labelledby`, and background `aria-hidden` are wired automatically by vaul
3036
+
3037
+ **Not yet supported (deferred):** snap points, side-anchored drawers, nested drawers.
3038
+
3039
+ ---
3040
+
3041
+ ## State Primitives
3042
+
3043
+ Components and hooks for the four ubiquitous async states. All are exported flat (unprefixed) from the package root — `import { Skeleton, EmptyState, ErrorBanner, ErrorPage, LoadingTimeout, ProgressiveImage, HazoUiToaster, successToast, errorToast, useLoadingState, useErrorDisplay } from "hazo_ui"`.
3044
+
3045
+ ### Skeleton
3046
+
3047
+ Shimmer placeholders for loading content. The shimmer animation respects `prefers-reduced-motion` (renders as a static grey block instead).
3048
+
3049
+ ```tsx
3050
+ import { Skeleton, SkeletonCircle, SkeletonBar, SkeletonRect, SkeletonGroup } from "hazo_ui";
3051
+
3052
+ <SkeletonGroup label="Loading user profile">
3053
+ <div className="flex items-center gap-3">
3054
+ <SkeletonCircle size={40} />
3055
+ <div className="flex-1 space-y-2">
3056
+ <SkeletonBar width="60%" height={14} />
3057
+ <SkeletonBar width="40%" height={10} />
3058
+ </div>
3059
+ </div>
3060
+ <SkeletonRect height={120} radius={8} />
3061
+ </SkeletonGroup>
3062
+ ```
3063
+
3064
+ | Variant | Props |
3065
+ |---|---|
3066
+ | `Skeleton` | All standard `<div>` props. Base shimmer block. |
3067
+ | `SkeletonCircle` | `size?: number` (default 40), `className?: string` |
3068
+ | `SkeletonBar` | `width?: number \| string` (default "100%"), `height?: number` (default 12), `className?: string` |
3069
+ | `SkeletonRect` | `width?`, `height?`, `radius?: number \| string` (default 6), `className?: string` |
3070
+ | `SkeletonGroup` | `label?: string` (default "Loading content"), `children: ReactNode` — wraps a region with `role="status"` + `aria-busy` + visually-hidden label. |
3071
+
3072
+ ### EmptyState
3073
+
3074
+ Standardized empty-list / no-data / no-results display.
3075
+
3076
+ ```tsx
3077
+ import { EmptyState, Button } from "hazo_ui";
3078
+ import { InboxIcon } from "lucide-react";
3079
+
3080
+ <EmptyState
3081
+ icon={<InboxIcon />}
3082
+ title="No messages yet"
3083
+ description="When you receive a message, it'll show up here."
3084
+ action={<Button onClick={onCompose}>Send your first message</Button>}
3085
+ size="md"
3086
+ />
3087
+ ```
3088
+
3089
+ | Prop | Type | Default | Description |
3090
+ |---|---|---|---|
3091
+ | `title` | `string` | **required** | Main heading |
3092
+ | `icon` | `ReactNode` | — | Icon element (recommended 48×48) |
3093
+ | `description` | `ReactNode` | — | Secondary description |
3094
+ | `action` | `ReactNode` | — | CTA region (typically a `<Button>`) |
3095
+ | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Visual size for inline cards (`sm`) vs full pages (`lg`) |
3096
+ | `className` | `string` | — | Additional classes |
3097
+
3098
+ ### ErrorBanner
3099
+
3100
+ Inline error or warning strip. `role="alert"`, with `aria-live="assertive"` for errors / `"polite"` for warnings.
3101
+
3102
+ ```tsx
3103
+ import { ErrorBanner, Button } from "hazo_ui";
3104
+
3105
+ <ErrorBanner
3106
+ severity="error"
3107
+ title="Couldn't save your changes"
3108
+ message="We hit a network error. Your draft is safe locally."
3109
+ action={<Button size="sm" onClick={onRetry}>Retry</Button>}
3110
+ onDismiss={() => setBannerVisible(false)}
3111
+ />
3112
+
3113
+ <ErrorBanner severity="warning" message="Your session expires in 2 minutes." />
3114
+ ```
3115
+
3116
+ | Prop | Type | Default | Description |
3117
+ |---|---|---|---|
3118
+ | `message` | `ReactNode` | **required** | Body text |
3119
+ | `severity` | `"warning" \| "error"` | `"error"` | Drives colour & icon |
3120
+ | `title` | `string` | — | Bold heading above the message |
3121
+ | `icon` | `ReactNode` | auto | Override the auto-selected `AlertTriangle` / `OctagonAlert` |
3122
+ | `action` | `ReactNode` | — | CTA region (typically a `<Button>`) |
3123
+ | `onDismiss` | `() => void` | — | When provided, renders a dismiss X button |
3124
+ | `className` | `string` | — | Additional classes |
3125
+
3126
+ ### ErrorPage
3127
+
3128
+ Full-page error fallback, ideal for route-level error boundaries.
3129
+
3130
+ ```tsx
3131
+ import { ErrorPage, Button } from "hazo_ui";
3132
+
3133
+ <ErrorPage
3134
+ title="Something went wrong"
3135
+ description="We couldn't load this page. The issue has been reported."
3136
+ errorCode="500"
3137
+ correlationId="req_a8f3c12e4d"
3138
+ actions={
3139
+ <>
3140
+ <Button onClick={onRetry}>Try again</Button>
3141
+ <Button variant="outline" onClick={onGoHome}>Go home</Button>
3142
+ </>
3143
+ }
3144
+ />
3145
+ ```
3146
+
3147
+ | Prop | Type | Default | Description |
3148
+ |---|---|---|---|
3149
+ | `title` | `string` | `"Something went wrong"` | Main heading |
3150
+ | `description` | `ReactNode` | — | Explanation paragraph(s) |
3151
+ | `errorCode` | `string` | — | Short symbolic code rendered as a tag (`"500"`, `"NOT_FOUND"`) |
3152
+ | `correlationId` | `string` | — | Correlation id (typically from `hazo_logs`) — rendered in a copyable mono block |
3153
+ | `actions` | `ReactNode` | — | CTA region |
3154
+ | `illustration` | `ReactNode` | `<OctagonAlert />` | Override the default icon |
3155
+ | `className` | `string` | — | Additional classes |
3156
+
3157
+ ### LoadingTimeout
3158
+
3159
+ Wraps a loading region and escalates messaging if the load takes too long. Four phases:
3160
+
3161
+ | Phase | When | What renders |
3162
+ |---|---|---|
3163
+ | `silent` | 0 – 5s | The provided `skeleton` (no message) |
3164
+ | `gentle` | 5s – 15s | Skeleton + "Loading {label}…" |
3165
+ | `firm` | 15s – 30s | Skeleton + "Still working on it — almost there." |
3166
+ | `expired` | 30s+ | `<ErrorBanner severity="error">` with a "Try again" button |
3167
+
3168
+ ```tsx
3169
+ import { LoadingTimeout, SkeletonGroup, SkeletonBar } from "hazo_ui";
3170
+
3171
+ <LoadingTimeout
3172
+ active={isLoading}
3173
+ label="dashboard"
3174
+ onRetry={refetch}
3175
+ skeleton={
3176
+ <SkeletonGroup>
3177
+ <SkeletonBar width="80%" />
3178
+ <SkeletonBar width="60%" />
3179
+ </SkeletonGroup>
3180
+ }
3181
+ >
3182
+ <Dashboard data={data} />
3183
+ </LoadingTimeout>
3184
+ ```
3185
+
3186
+ | Prop | Type | Default | Description |
3187
+ |---|---|---|---|
3188
+ | `active` | `boolean` | **required** | When `true`, runs the timeout escalation; when `false`, renders `children` |
3189
+ | `children` | `ReactNode` | — | Content shown when `active` is `false` |
3190
+ | `skeleton` | `ReactNode` | — | Placeholder rendered during the `silent`/`gentle`/`firm` phases |
3191
+ | `onRetry` | `() => void` | — | Called when the user clicks Retry in the `expired` phase |
3192
+ | `thresholds` | `{ gentle?, firm?, expired? }` | `{5000, 15000, 30000}` | Override timeout thresholds (ms) |
3193
+ | `label` | `string` | `"content"` | Used in gentle/firm/expired messages |
3194
+ | `className` | `string` | — | Additional classes |
3195
+
3196
+ ### ProgressiveImage
3197
+
3198
+ Three-stage image render: grey placeholder → blurred low-quality image (`lqip`) → sharp final image. Sets a stable container box so layout doesn't shift.
3199
+
3200
+ ```tsx
3201
+ import { ProgressiveImage } from "hazo_ui";
3202
+
3203
+ <ProgressiveImage
3204
+ src="/photos/large.jpg"
3205
+ lqip="data:image/jpeg;base64,/9j/4AAQ…"
3206
+ alt="Sunset over the lake"
3207
+ width={400}
3208
+ height={300}
3209
+ fit="cover"
3210
+ loading="lazy"
3211
+ />
3212
+ ```
3213
+
3214
+ | Prop | Type | Default | Description |
3215
+ |---|---|---|---|
3216
+ | `src` | `string` | **required** | Final image src |
3217
+ | `alt` | `string` | **required** | Alt text (empty string allowed for decorative) |
3218
+ | `width` | `number \| string` | **required** | Container width (px or any CSS length) |
3219
+ | `height` | `number \| string` | **required** | Container height |
3220
+ | `lqip` | `string` | — | Low-quality placeholder (data URL or tiny image URL) |
3221
+ | `loading` | `"eager" \| "lazy"` | `"lazy"` | Native loading attribute |
3222
+ | `fit` | `"cover" \| "contain" \| "fill" \| "none" \| "scale-down"` | `"cover"` | Object-fit for the final image |
3223
+ | `onLoad` | `() => void` | — | Called when the final image loads |
3224
+ | `onError` | `() => void` | — | Called if the final image errors |
3225
+ | `className` | `string` | — | Additional classes |
3226
+
3227
+ ### Toasts (HazoUiToaster + successToast / errorToast)
3228
+
3229
+ A `sonner`-backed toaster plus two opinionated imperative helpers.
3230
+
3231
+ **Mount the toaster once** near the root of your app:
3232
+
3233
+ ```tsx
3234
+ import { HazoUiToaster } from "hazo_ui";
3235
+
3236
+ export default function RootLayout({ children }) {
3237
+ return (
3238
+ <>
3239
+ {children}
3240
+ <HazoUiToaster position="bottom-right" />
3241
+ </>
3242
+ );
3243
+ }
3244
+ ```
3245
+
3246
+ **Fire toasts imperatively from anywhere:**
3247
+
3248
+ ```tsx
3249
+ import { successToast, errorToast } from "hazo_ui";
3250
+
3251
+ await save();
3252
+ successToast({ title: "Saved", description: "Your changes have been published." });
3253
+
3254
+ try { await sync(); } catch (e) {
3255
+ errorToast({
3256
+ title: "Sync failed",
3257
+ description: "Check your connection and try again.",
3258
+ action: { label: "Retry", onClick: () => sync() },
3259
+ });
3260
+ }
3261
+ ```
3262
+
3263
+ `HazoUiToaster` props:
3264
+
3265
+ | Prop | Type | Default |
3266
+ |---|---|---|
3267
+ | `position` | `"top-left" \| "top-right" \| "bottom-left" \| "bottom-right" \| "top-center" \| "bottom-center"` | `"bottom-right"` |
3268
+ | `closeButton` | `boolean` | `true` |
3269
+ | `visibleToasts` | `number` | `5` |
3270
+
3271
+ `ToastOptions` (for `successToast` / `errorToast`):
3272
+
3273
+ | Prop | Type | Default |
3274
+ |---|---|---|
3275
+ | `title` | `string` | **required** |
3276
+ | `description` | `string` | — |
3277
+ | `duration` | `number` (ms) | `3000` success / `5000` error |
3278
+ | `action` | `{ label: string; onClick: () => void }` | — |
3279
+
3280
+ Also exports `rawToast` (re-export of sonner's `toast`) for advanced use cases.
3281
+
3282
+ ### useLoadingState
3283
+
3284
+ Hook that returns a controlled loading flag plus an async wrapper.
3285
+
3286
+ ```tsx
3287
+ import { useLoadingState } from "hazo_ui";
3288
+
3289
+ const { isLoading, setLoading, withLoading } = useLoadingState(false);
3290
+
3291
+ async function onSubmit() {
3292
+ await withLoading(async () => {
3293
+ await api.save(data);
3294
+ });
3295
+ }
3296
+
3297
+ return <Button disabled={isLoading} onClick={onSubmit}>{isLoading ? "Saving…" : "Save"}</Button>;
3298
+ ```
3299
+
3300
+ Returns `{ isLoading: boolean; setLoading: (v: boolean) => void; withLoading: <T>(fn: () => Promise<T>) => Promise<T> }`. `withLoading` sets the flag to `true` before the call and clears it in a `finally` block — even on errors.
3301
+
3302
+ ### useErrorDisplay
3303
+
3304
+ Passive error state. Coerces `Error` instances to their `.message`, strings pass through, anything else is `String()`-cast.
3305
+
3306
+ ```tsx
3307
+ import { useErrorDisplay, ErrorBanner } from "hazo_ui";
3308
+
3309
+ const { error, setError, clearError } = useErrorDisplay();
3310
+
3311
+ try { await save(); } catch (e) { setError(e); }
3312
+
3313
+ return error ? <ErrorBanner message={error} onDismiss={clearError} /> : null;
3314
+ ```
3315
+
3316
+ Returns `{ error: string | null; setError: (v: unknown) => void; clearError: () => void }`. Pass `null` (or any nullish value) to `setError` to clear.
3317
+
2968
3318
  ---
2969
3319
 
2970
3320
  ## Troubleshooting