hazo_ui 2.6.0 → 2.6.2

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/README.md CHANGED
@@ -1707,6 +1707,27 @@ function CustomHeightExample() {
1707
1707
 
1708
1708
  A flexible, standardized dialog component with customizable animations, sizes, and theming. Built on Radix UI Dialog primitives with a consistent header/body/footer layout.
1709
1709
 
1710
+ ### Two Usage Patterns
1711
+
1712
+ HazoUiDialog offers two approaches depending on your needs:
1713
+
1714
+ 1. **Props-based API (HazoUiDialog)** - For simple confirmations, alerts, and standard forms with predefined header/body/footer layout
1715
+ 2. **Compositional API (Primitives)** - For complex layouts with custom headers, tabs, avatars, and full layout control
1716
+
1717
+ #### When to Use Each Approach
1718
+
1719
+ **Use HazoUiDialog (props-based) when:**
1720
+ - You need a standard confirmation dialog
1721
+ - Your content fits a simple header/body/footer layout
1722
+ - You want quick implementation with minimal code
1723
+ - Examples: confirmations, alerts, simple forms
1724
+
1725
+ **Use Compositional API (primitives) when:**
1726
+ - You need custom headers with avatars, icons, or complex layouts
1727
+ - Your dialog has tabs or multi-section content
1728
+ - You want full control over the dialog structure
1729
+ - Examples: client details with tabs, complex forms, custom workflows
1730
+
1710
1731
  #### Features
1711
1732
 
1712
1733
  - **Flexible Sizing**: 5 size presets from small (400px) to full-width (98vw), plus custom sizing
@@ -1743,6 +1764,14 @@ interface HazoUiDialogProps {
1743
1764
  cancelButtonText?: string; // default: "Cancel"
1744
1765
  showCancelButton?: boolean; // default: true
1745
1766
 
1767
+ // Action Button Enhancement
1768
+ actionButtonLoading?: boolean; // default: false - Shows spinner, disables button
1769
+ actionButtonDisabled?: boolean; // default: false - Disables action button
1770
+ actionButtonIcon?: React.ReactNode; // Icon before button text (replaced by spinner when loading)
1771
+
1772
+ // Custom Footer
1773
+ footerContent?: React.ReactNode; // Custom footer content (replaces default buttons)
1774
+
1746
1775
  // Size Configuration
1747
1776
  sizeWidth?: string; // default: "min(90vw, 600px)"
1748
1777
  sizeHeight?: string; // default: "min(80vh, 800px)"
@@ -1776,7 +1805,9 @@ type AnimationPreset = 'zoom' | 'slide' | 'fade' | 'bounce' | 'scale-up' | 'flip
1776
1805
  type ButtonVariant = "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
1777
1806
  ```
1778
1807
 
1779
- #### Basic Usage
1808
+ #### Pattern 1: Basic Usage (Props-based API)
1809
+
1810
+ For simple dialogs with standard layout:
1780
1811
 
1781
1812
  ```tsx
1782
1813
  import { HazoUiDialog } from 'hazo_ui';
@@ -1811,6 +1842,384 @@ function ConfirmDialog() {
1811
1842
  }
1812
1843
  ```
1813
1844
 
1845
+ #### Pattern 2: Compositional API (Complex Layouts)
1846
+
1847
+ For dialogs with custom headers, tabs, or complex layouts:
1848
+
1849
+ ```tsx
1850
+ import {
1851
+ HazoUiDialogRoot,
1852
+ HazoUiDialogContent,
1853
+ HazoUiDialogHeader,
1854
+ HazoUiDialogTitle,
1855
+ HazoUiDialogDescription,
1856
+ HazoUiDialogFooter,
1857
+ } from 'hazo_ui';
1858
+ import { useState } from 'react';
1859
+ import { User } from 'lucide-react';
1860
+
1861
+ function ClientDetailsDialog() {
1862
+ const [isOpen, setIsOpen] = useState(false);
1863
+ const [activeTab, setActiveTab] = useState('personal');
1864
+
1865
+ return (
1866
+ <>
1867
+ <button onClick={() => setIsOpen(true)}>
1868
+ Open Client Details
1869
+ </button>
1870
+
1871
+ <HazoUiDialogRoot open={isOpen} onOpenChange={setIsOpen}>
1872
+ <HazoUiDialogContent className="sm:max-w-2xl w-[90vw] h-[80vh] flex flex-col p-0">
1873
+ {/* Custom Header with Avatar */}
1874
+ <HazoUiDialogHeader className="bg-navbar text-navbar-foreground p-6 rounded-t-lg">
1875
+ <div className="flex items-center gap-3">
1876
+ <div className="w-12 h-12 rounded-full bg-primary/20 flex items-center justify-center">
1877
+ <User className="h-6 w-6 text-primary-foreground" />
1878
+ </div>
1879
+ <div>
1880
+ <HazoUiDialogTitle className="text-white text-left">
1881
+ John Doe
1882
+ </HazoUiDialogTitle>
1883
+ <HazoUiDialogDescription className="text-white/80 text-left">
1884
+ john.doe@example.com
1885
+ </HazoUiDialogDescription>
1886
+ </div>
1887
+ </div>
1888
+ </HazoUiDialogHeader>
1889
+
1890
+ {/* Tabs Navigation */}
1891
+ <div className="border-b px-6">
1892
+ <div className="flex gap-4">
1893
+ <button
1894
+ onClick={() => setActiveTab('personal')}
1895
+ className={`py-3 px-1 text-sm font-medium border-b-2 ${
1896
+ activeTab === 'personal'
1897
+ ? 'border-primary text-primary'
1898
+ : 'border-transparent text-muted-foreground'
1899
+ }`}
1900
+ >
1901
+ Personal
1902
+ </button>
1903
+ <button
1904
+ onClick={() => setActiveTab('address')}
1905
+ className={`py-3 px-1 text-sm font-medium border-b-2 ${
1906
+ activeTab === 'address'
1907
+ ? 'border-primary text-primary'
1908
+ : 'border-transparent text-muted-foreground'
1909
+ }`}
1910
+ >
1911
+ Address
1912
+ </button>
1913
+ <button
1914
+ onClick={() => setActiveTab('contact')}
1915
+ className={`py-3 px-1 text-sm font-medium border-b-2 ${
1916
+ activeTab === 'contact'
1917
+ ? 'border-primary text-primary'
1918
+ : 'border-transparent text-muted-foreground'
1919
+ }`}
1920
+ >
1921
+ Contact
1922
+ </button>
1923
+ </div>
1924
+ </div>
1925
+
1926
+ {/* Scrollable Tab Content */}
1927
+ <div className="flex-1 overflow-y-auto p-6">
1928
+ {activeTab === 'personal' && (
1929
+ <div className="space-y-4">
1930
+ <div>
1931
+ <label className="text-sm font-medium">Full Name</label>
1932
+ <input
1933
+ type="text"
1934
+ className="w-full px-3 py-2 border rounded-md mt-1"
1935
+ />
1936
+ </div>
1937
+ {/* More personal fields */}
1938
+ </div>
1939
+ )}
1940
+ {activeTab === 'address' && (
1941
+ <div className="space-y-4">
1942
+ {/* Address fields */}
1943
+ </div>
1944
+ )}
1945
+ {activeTab === 'contact' && (
1946
+ <div className="space-y-4">
1947
+ {/* Contact fields */}
1948
+ </div>
1949
+ )}
1950
+ </div>
1951
+
1952
+ {/* Footer */}
1953
+ <HazoUiDialogFooter className="p-6 border-t">
1954
+ <button
1955
+ onClick={() => setIsOpen(false)}
1956
+ className="px-4 py-2 border rounded-md hover:bg-accent"
1957
+ >
1958
+ Cancel
1959
+ </button>
1960
+ <button
1961
+ onClick={() => setIsOpen(false)}
1962
+ className="px-4 py-2 bg-primary text-primary-foreground rounded-md"
1963
+ >
1964
+ Save Changes
1965
+ </button>
1966
+ </HazoUiDialogFooter>
1967
+ </HazoUiDialogContent>
1968
+ </HazoUiDialogRoot>
1969
+ </>
1970
+ );
1971
+ }
1972
+ ```
1973
+
1974
+ **Available Compositional Components:**
1975
+
1976
+ ```tsx
1977
+ import {
1978
+ HazoUiDialogRoot, // Root dialog container (replaces Dialog)
1979
+ HazoUiDialogTrigger, // Trigger button (optional)
1980
+ HazoUiDialogContent, // Dialog content wrapper
1981
+ HazoUiDialogHeader, // Header section
1982
+ HazoUiDialogTitle, // Title text
1983
+ HazoUiDialogDescription, // Description text
1984
+ HazoUiDialogFooter, // Footer section
1985
+ HazoUiDialogPortal, // Portal wrapper
1986
+ HazoUiDialogOverlay, // Backdrop overlay
1987
+ HazoUiDialogClose, // Close button
1988
+ } from 'hazo_ui';
1989
+ ```
1990
+
1991
+ **Key Differences:**
1992
+
1993
+ | Feature | Props-based API | Compositional API |
1994
+ |---------|----------------|-------------------|
1995
+ | **Layout** | Predefined header/body/footer | Build your own structure |
1996
+ | **Configuration** | Via props | Via component composition |
1997
+ | **Complexity** | Simple, quick | Flexible, powerful |
1998
+ | **Best for** | Confirmations, alerts | Tabs, avatars, custom layouts |
1999
+ | **Code** | Minimal | More verbose |
2000
+
2001
+ **Compositional API Tips:**
2002
+
2003
+ - Use `flex flex-col` on `HazoUiDialogContent` for layouts with headers/footers
2004
+ - Set `p-0` on `HazoUiDialogContent` and add padding to individual sections
2005
+ - Use `overflow-y-auto` on the scrollable content area
2006
+ - Apply `bg-navbar text-navbar-foreground` for dark headers
2007
+ - Use `border-t` on footer for visual separation
2008
+
2009
+ #### Action Button States and Custom Footers
2010
+
2011
+ The dialog supports enhanced action buttons with loading states, icons, and fully custom footers for complex UX scenarios.
2012
+
2013
+ ##### 1. Form Dialog with Loading State
2014
+
2015
+ ```tsx
2016
+ import { HazoUiDialog } from 'hazo_ui';
2017
+ import { useState } from 'react';
2018
+
2019
+ function SaveFormDialog() {
2020
+ const [isOpen, setIsOpen] = useState(false);
2021
+ const [isSaving, setIsSaving] = useState(false);
2022
+
2023
+ const handleSave = async () => {
2024
+ setIsSaving(true);
2025
+ try {
2026
+ await saveFormData();
2027
+ setIsOpen(false);
2028
+ } catch (error) {
2029
+ console.error('Save failed:', error);
2030
+ } finally {
2031
+ setIsSaving(false);
2032
+ }
2033
+ };
2034
+
2035
+ return (
2036
+ <HazoUiDialog
2037
+ open={isOpen}
2038
+ onOpenChange={setIsOpen}
2039
+ title="Save Changes"
2040
+ description="Your changes will be saved to the server."
2041
+ actionButtonText={isSaving ? "Saving..." : "Save"}
2042
+ actionButtonLoading={isSaving}
2043
+ onConfirm={handleSave}
2044
+ >
2045
+ <p>Click Save to see the loading spinner and disabled button.</p>
2046
+ </HazoUiDialog>
2047
+ );
2048
+ }
2049
+ ```
2050
+
2051
+ ##### 2. Confirmation Dialog with Icon
2052
+
2053
+ ```tsx
2054
+ import { HazoUiDialog } from 'hazo_ui';
2055
+ import { Send } from 'lucide-react';
2056
+ import { useState } from 'react';
2057
+
2058
+ function SendEmailDialog() {
2059
+ const [isOpen, setIsOpen] = useState(false);
2060
+
2061
+ return (
2062
+ <HazoUiDialog
2063
+ open={isOpen}
2064
+ onOpenChange={setIsOpen}
2065
+ title="Send Email"
2066
+ description="This will send the email to all recipients."
2067
+ actionButtonText="Send Email"
2068
+ actionButtonIcon={<Send className="h-4 w-4" />}
2069
+ onConfirm={() => {
2070
+ sendEmail();
2071
+ setIsOpen(false);
2072
+ }}
2073
+ >
2074
+ <p>The Send icon appears before the button text.</p>
2075
+ </HazoUiDialog>
2076
+ );
2077
+ }
2078
+ ```
2079
+
2080
+ ##### 3. Destructive Action with Loading
2081
+
2082
+ ```tsx
2083
+ import { HazoUiDialog } from 'hazo_ui';
2084
+ import { Lock } from 'lucide-react';
2085
+ import { useState } from 'react';
2086
+
2087
+ function CloseAccountDialog() {
2088
+ const [isOpen, setIsOpen] = useState(false);
2089
+ const [isClosing, setIsClosing] = useState(false);
2090
+
2091
+ const handleClose = async () => {
2092
+ setIsClosing(true);
2093
+ try {
2094
+ await closeAccount();
2095
+ setIsOpen(false);
2096
+ } catch (error) {
2097
+ console.error('Close failed:', error);
2098
+ } finally {
2099
+ setIsClosing(false);
2100
+ }
2101
+ };
2102
+
2103
+ return (
2104
+ <HazoUiDialog
2105
+ open={isOpen}
2106
+ onOpenChange={setIsOpen}
2107
+ title="Close Account"
2108
+ description="This will permanently close your account."
2109
+ actionButtonText={isClosing ? "Closing..." : "Close"}
2110
+ actionButtonIcon={<Lock className="h-4 w-4" />}
2111
+ actionButtonLoading={isClosing}
2112
+ onConfirm={handleClose}
2113
+ >
2114
+ <p>Lock icon is replaced by spinner when loading.</p>
2115
+ </HazoUiDialog>
2116
+ );
2117
+ }
2118
+ ```
2119
+
2120
+ ##### 4. Complex Footer with Stats
2121
+
2122
+ ```tsx
2123
+ import { HazoUiDialog } from 'hazo_ui';
2124
+ import { useState } from 'react';
2125
+
2126
+ function ReviewItemsDialog() {
2127
+ const [isOpen, setIsOpen] = useState(false);
2128
+ const [stats, setStats] = useState({ keep: 0, accept: 0, skip: 0 });
2129
+
2130
+ return (
2131
+ <HazoUiDialog
2132
+ open={isOpen}
2133
+ onOpenChange={setIsOpen}
2134
+ title="Review Items"
2135
+ description="Review and process the items below."
2136
+ footerContent={
2137
+ <div className="flex items-center justify-between w-full">
2138
+ <div className="text-sm text-muted-foreground">
2139
+ Keep: {stats.keep} | Accept: {stats.accept} | Skip: {stats.skip}
2140
+ </div>
2141
+ <div className="flex gap-2">
2142
+ <button
2143
+ onClick={() => setStats({ ...stats, skip: stats.skip + 1 })}
2144
+ className="px-3 py-1.5 text-sm border rounded-md hover:bg-muted"
2145
+ >
2146
+ Skip
2147
+ </button>
2148
+ <button
2149
+ onClick={() => setStats({ ...stats, keep: stats.keep + 1 })}
2150
+ className="px-3 py-1.5 text-sm bg-blue-500 text-white rounded-md"
2151
+ >
2152
+ Keep
2153
+ </button>
2154
+ <button
2155
+ onClick={() => {
2156
+ setStats({ ...stats, accept: stats.accept + 1 });
2157
+ setIsOpen(false);
2158
+ }}
2159
+ className="px-3 py-1.5 text-sm bg-green-500 text-white rounded-md"
2160
+ >
2161
+ Accept
2162
+ </button>
2163
+ </div>
2164
+ </div>
2165
+ }
2166
+ >
2167
+ <p>Custom footer shows stats and multiple action buttons.</p>
2168
+ </HazoUiDialog>
2169
+ );
2170
+ }
2171
+ ```
2172
+
2173
+ ##### 5. Progress Dialog with No Footer
2174
+
2175
+ ```tsx
2176
+ import { HazoUiDialog } from 'hazo_ui';
2177
+ import { useState, useEffect } from 'react';
2178
+
2179
+ function ProgressDialog() {
2180
+ const [isOpen, setIsOpen] = useState(false);
2181
+ const [progress, setProgress] = useState(0);
2182
+
2183
+ useEffect(() => {
2184
+ if (isOpen) {
2185
+ const interval = setInterval(() => {
2186
+ setProgress((prev) => {
2187
+ const next = prev + 10;
2188
+ if (next >= 100) {
2189
+ clearInterval(interval);
2190
+ setTimeout(() => setIsOpen(false), 500);
2191
+ return 100;
2192
+ }
2193
+ return next;
2194
+ });
2195
+ }, 300);
2196
+ return () => clearInterval(interval);
2197
+ }
2198
+ }, [isOpen]);
2199
+
2200
+ return (
2201
+ <HazoUiDialog
2202
+ open={isOpen}
2203
+ onOpenChange={setIsOpen}
2204
+ title="Processing..."
2205
+ description="Please wait while we process your request."
2206
+ showCloseButton={false}
2207
+ footerContent={<div />}
2208
+ >
2209
+ <div className="space-y-4">
2210
+ <div className="w-full bg-muted rounded-full h-2">
2211
+ <div
2212
+ className="bg-primary h-2 rounded-full transition-all"
2213
+ style={{ width: `${progress}%` }}
2214
+ />
2215
+ </div>
2216
+ <p className="text-sm text-center">{progress}% complete</p>
2217
+ </div>
2218
+ </HazoUiDialog>
2219
+ );
2220
+ }
2221
+ ```
2222
+
1814
2223
  #### Size Variants
1815
2224
 
1816
2225
  ```tsx
@@ -2141,6 +2550,10 @@ function FormDialog() {
2141
2550
  | `actionButtonVariant` | `ButtonVariant` | `"default"` | Action button style |
2142
2551
  | `cancelButtonText` | `string` | `"Cancel"` | Cancel button label |
2143
2552
  | `showCancelButton` | `boolean` | `true` | Show cancel button |
2553
+ | `actionButtonLoading` | `boolean` | `false` | Shows loading spinner and disables action button |
2554
+ | `actionButtonDisabled` | `boolean` | `false` | Disables the action button |
2555
+ | `actionButtonIcon` | `React.ReactNode` | - | Icon element rendered before action button text |
2556
+ | `footerContent` | `React.ReactNode` | - | Custom footer content that replaces default buttons |
2144
2557
  | `sizeWidth` | `string` | `"min(90vw, 600px)"` | Dialog width |
2145
2558
  | `sizeHeight` | `string` | `"min(80vh, 800px)"` | Dialog max height |
2146
2559
  | `openAnimation` | `AnimationPreset \| string` | `"zoom"` | Open animation |
package/dist/index.cjs CHANGED
@@ -217,6 +217,7 @@ Button.displayName = "Button";
217
217
  var Dialog = DialogPrimitive__namespace.Root;
218
218
  var DialogTrigger = DialogPrimitive__namespace.Trigger;
219
219
  var DialogPortal = DialogPrimitive__namespace.Portal;
220
+ var DialogClose = DialogPrimitive__namespace.Close;
220
221
  var DialogOverlay = React27__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
221
222
  DialogPrimitive__namespace.Overlay,
222
223
  {
@@ -6087,6 +6088,10 @@ function HazoUiDialog({
6087
6088
  actionButtonVariant = "default",
6088
6089
  cancelButtonText = "Cancel",
6089
6090
  showCancelButton = true,
6091
+ actionButtonLoading = false,
6092
+ actionButtonDisabled = false,
6093
+ actionButtonIcon,
6094
+ footerContent,
6090
6095
  sizeWidth = "min(90vw, 600px)",
6091
6096
  sizeHeight = "min(80vh, 800px)",
6092
6097
  openAnimation = "zoom",
@@ -6207,34 +6212,44 @@ function HazoUiDialog({
6207
6212
  children
6208
6213
  }
6209
6214
  ),
6210
- /* @__PURE__ */ jsxRuntime.jsxs(
6215
+ /* @__PURE__ */ jsxRuntime.jsx(
6211
6216
  DialogFooter,
6212
6217
  {
6213
6218
  className: cn("cls_dialog_footer p-6 pt-4", footerClassName),
6214
6219
  style: footerStyles,
6215
- children: [
6216
- showCancelButton && /* @__PURE__ */ jsxRuntime.jsx(
6217
- Button,
6218
- {
6219
- type: "button",
6220
- className: "cls_cancel_button",
6221
- variant: "outline",
6222
- onClick: handleCancel,
6223
- children: cancelButtonText
6224
- }
6225
- ),
6226
- /* @__PURE__ */ jsxRuntime.jsx(
6227
- Button,
6228
- {
6229
- type: "button",
6230
- className: "cls_confirm_button",
6231
- variant: actionButtonVariant,
6232
- onClick: handleConfirm,
6233
- style: actionButtonStyles,
6234
- children: actionButtonText
6235
- }
6236
- )
6237
- ]
6220
+ children: footerContent ? (
6221
+ // Custom footer content replaces default buttons
6222
+ footerContent
6223
+ ) : (
6224
+ // Default footer with action and cancel buttons
6225
+ /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6226
+ showCancelButton && /* @__PURE__ */ jsxRuntime.jsx(
6227
+ Button,
6228
+ {
6229
+ type: "button",
6230
+ className: "cls_cancel_button",
6231
+ variant: "outline",
6232
+ onClick: handleCancel,
6233
+ children: cancelButtonText
6234
+ }
6235
+ ),
6236
+ /* @__PURE__ */ jsxRuntime.jsxs(
6237
+ Button,
6238
+ {
6239
+ type: "button",
6240
+ className: "cls_confirm_button",
6241
+ variant: actionButtonVariant,
6242
+ onClick: handleConfirm,
6243
+ style: actionButtonStyles,
6244
+ disabled: actionButtonLoading || actionButtonDisabled,
6245
+ children: [
6246
+ actionButtonLoading ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 mr-2 animate-spin" }) : actionButtonIcon,
6247
+ actionButtonText
6248
+ ]
6249
+ }
6250
+ )
6251
+ ] })
6252
+ )
6238
6253
  }
6239
6254
  ),
6240
6255
  showCloseButton && /* @__PURE__ */ jsxRuntime.jsxs(
@@ -6258,6 +6273,16 @@ exports.CommandNodeExtension = CommandNodeExtension;
6258
6273
  exports.CommandPill = CommandPill;
6259
6274
  exports.CommandPopover = CommandPopover;
6260
6275
  exports.HazoUiDialog = HazoUiDialog;
6276
+ exports.HazoUiDialogClose = DialogClose;
6277
+ exports.HazoUiDialogContent = DialogContent;
6278
+ exports.HazoUiDialogDescription = DialogDescription;
6279
+ exports.HazoUiDialogFooter = DialogFooter;
6280
+ exports.HazoUiDialogHeader = DialogHeader;
6281
+ exports.HazoUiDialogOverlay = DialogOverlay;
6282
+ exports.HazoUiDialogPortal = DialogPortal;
6283
+ exports.HazoUiDialogRoot = Dialog;
6284
+ exports.HazoUiDialogTitle = DialogTitle;
6285
+ exports.HazoUiDialogTrigger = DialogTrigger;
6261
6286
  exports.HazoUiFlexInput = HazoUiFlexInput;
6262
6287
  exports.HazoUiFlexRadio = HazoUiFlexRadio;
6263
6288
  exports.HazoUiMultiFilterDialog = HazoUiMultiFilterDialog;