hazo_ui 4.6.1 → 4.8.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 CHANGED
@@ -5,6 +5,31 @@ 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
+ ## v4.7.0 (2026-07-01)
9
+
10
+ ### New
11
+ - **`HazoUiDialog` acknowledge/OK-only footer** — two new props enable a
12
+ single self-closing button without hand-rolling `footerContent`:
13
+ - `showActionButton?: boolean` (default `true`) — symmetric with
14
+ `showCancelButton`; set `false` to hide the confirm button.
15
+ - `closeOnConfirm?: boolean` (default `false`) — when `true`, clicking the
16
+ action button calls `onConfirm` (if any) then `onOpenChange(false)`.
17
+ Default preserves the prior "consumer controls close" behavior.
18
+
19
+ Usage for a plain "Okay" acknowledgment dialog:
20
+ ```tsx
21
+ <HazoUiDialog
22
+ open={open}
23
+ onOpenChange={setOpen}
24
+ title="Preview"
25
+ actionButtonText="Okay"
26
+ showCancelButton={false}
27
+ closeOnConfirm
28
+ >
29
+ {children}
30
+ </HazoUiDialog>
31
+ ```
32
+
8
33
  ## v4.6.0 (2026-06-24)
9
34
 
10
35
  > Versions 4.4.0–4.5.0 were internal bumps that were never published to the
package/README.md CHANGED
@@ -2283,6 +2283,8 @@ interface HazoUiDialogProps {
2283
2283
  actionButtonVariant?: ButtonVariant; // default: "default"
2284
2284
  cancelButtonText?: string; // default: "Cancel"
2285
2285
  showCancelButton?: boolean; // default: true
2286
+ showActionButton?: boolean; // default: true - hide for a cancel-only footer
2287
+ closeOnConfirm?: boolean; // default: false - auto-close (calls onOpenChange(false)) after onConfirm
2286
2288
 
2287
2289
  // Action Button Enhancement
2288
2290
  actionButtonLoading?: boolean; // default: false - Shows spinner, disables button
@@ -3078,6 +3080,8 @@ function FormDialog() {
3078
3080
  | `actionButtonVariant` | `ButtonVariant` | `"default"` | Action button style |
3079
3081
  | `cancelButtonText` | `string` | `"Cancel"` | Cancel button label |
3080
3082
  | `showCancelButton` | `boolean` | `true` | Show cancel button |
3083
+ | `showActionButton` | `boolean` | `true` | Show action button; set `false` for a cancel-only footer |
3084
+ | `closeOnConfirm` | `boolean` | `false` | Auto-close (calls `onOpenChange(false)`) after `onConfirm` fires |
3081
3085
  | `actionButtonLoading` | `boolean` | `false` | Shows loading spinner and disables action button |
3082
3086
  | `actionButtonDisabled` | `boolean` | `false` | Disables the action button |
3083
3087
  | `actionButtonIcon` | `React.ReactNode` | - | Icon element rendered before action button text |
package/dist/index.cjs CHANGED
@@ -4704,6 +4704,12 @@ function CanvasTextToolbar({
4704
4704
  onUnderline,
4705
4705
  onColor,
4706
4706
  onAlign,
4707
+ verticalAlign = "middle",
4708
+ font = "sans-serif",
4709
+ bullet,
4710
+ onVerticalAlign,
4711
+ onFont,
4712
+ onBullet,
4707
4713
  className,
4708
4714
  style
4709
4715
  }) {
@@ -4784,7 +4790,55 @@ function CanvasTextToolbar({
4784
4790
  )
4785
4791
  ]
4786
4792
  }
4787
- )
4793
+ ),
4794
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-1 h-6 w-px bg-border" }),
4795
+ /* @__PURE__ */ jsxRuntime.jsx(
4796
+ ToolbarButton,
4797
+ {
4798
+ is_active: verticalAlign === "top",
4799
+ onClick: () => onVerticalAlign?.("top"),
4800
+ tooltip: "Align top",
4801
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuAlignVerticalJustifyStart, { className: "h-4 w-4" })
4802
+ }
4803
+ ),
4804
+ /* @__PURE__ */ jsxRuntime.jsx(
4805
+ ToolbarButton,
4806
+ {
4807
+ is_active: verticalAlign === "middle",
4808
+ onClick: () => onVerticalAlign?.("middle"),
4809
+ tooltip: "Align middle",
4810
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuAlignVerticalJustifyCenter, { className: "h-4 w-4" })
4811
+ }
4812
+ ),
4813
+ /* @__PURE__ */ jsxRuntime.jsx(
4814
+ ToolbarButton,
4815
+ {
4816
+ is_active: verticalAlign === "bottom",
4817
+ onClick: () => onVerticalAlign?.("bottom"),
4818
+ tooltip: "Align bottom",
4819
+ children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuAlignVerticalJustifyEnd, { className: "h-4 w-4" })
4820
+ }
4821
+ ),
4822
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-1 h-6 w-px bg-border" }),
4823
+ /* @__PURE__ */ jsxRuntime.jsxs(
4824
+ "select",
4825
+ {
4826
+ value: font,
4827
+ onChange: (e) => onFont?.(e.target.value),
4828
+ className: "h-8 text-xs rounded-md border border-border bg-background px-1 text-foreground focus:outline-none",
4829
+ title: "Font family",
4830
+ children: [
4831
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "sans-serif", children: "Sans" }),
4832
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "serif", children: "Serif" }),
4833
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "monospace", children: "Mono" }),
4834
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "Arial", children: "Arial" }),
4835
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "Georgia", children: "Georgia" }),
4836
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "Courier New", children: "Courier" })
4837
+ ]
4838
+ }
4839
+ ),
4840
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-1 h-6 w-px bg-border" }),
4841
+ /* @__PURE__ */ jsxRuntime.jsx(ToolbarButton, { is_active: bullet, onClick: onBullet, tooltip: "Bullet list", children: /* @__PURE__ */ jsxRuntime.jsx(lu.LuList, { className: "h-4 w-4" }) })
4788
4842
  ]
4789
4843
  }
4790
4844
  ) });
@@ -6758,6 +6812,8 @@ function HazoUiDialog({
6758
6812
  actionButtonVariant,
6759
6813
  cancelButtonText = "Cancel",
6760
6814
  showCancelButton = true,
6815
+ showActionButton = true,
6816
+ closeOnConfirm = false,
6761
6817
  actionButtonLoading = false,
6762
6818
  actionButtonDisabled = false,
6763
6819
  actionButtonIcon,
@@ -6791,6 +6847,9 @@ function HazoUiDialog({
6791
6847
  const resolved_action_button_variant = actionButtonVariant ?? variant_preset?.action_button_variant ?? "default";
6792
6848
  const handleConfirm = () => {
6793
6849
  onConfirm?.();
6850
+ if (closeOnConfirm) {
6851
+ onOpenChange?.(false);
6852
+ }
6794
6853
  };
6795
6854
  const handleCancel = () => {
6796
6855
  onCancel?.();
@@ -6973,7 +7032,7 @@ function HazoUiDialog({
6973
7032
  children: cancelButtonText
6974
7033
  }
6975
7034
  ),
6976
- /* @__PURE__ */ jsxRuntime.jsxs(
7035
+ showActionButton && /* @__PURE__ */ jsxRuntime.jsxs(
6977
7036
  Button,
6978
7037
  {
6979
7038
  type: "button",
@@ -7108,14 +7167,14 @@ function HazoUiConfirmDialog({
7108
7167
  "bg-background shadow-lg",
7109
7168
  "duration-200",
7110
7169
  animation_classes,
7111
- "overflow-hidden",
7112
- "focus:outline-none"
7170
+ "overflow-hidden"
7113
7171
  ),
7114
7172
  style: {
7115
7173
  width: "min(90vw, 420px)",
7116
7174
  borderRadius: "12px",
7117
7175
  borderTop: `4px solid ${resolved_accent_color}`,
7118
- boxShadow: "0 20px 60px rgba(0,0,0,0.15)"
7176
+ boxShadow: "0 20px 60px rgba(0,0,0,0.15)",
7177
+ outline: "none"
7119
7178
  },
7120
7179
  onOpenAutoFocus: (e) => {
7121
7180
  if ((variant === "destructive" || variant === "warning") && showCancelButton && cancel_ref.current) {
@@ -7135,7 +7194,7 @@ function HazoUiConfirmDialog({
7135
7194
  {
7136
7195
  ref: cancel_ref,
7137
7196
  type: "button",
7138
- className: "cls_confirm_cancel_button",
7197
+ className: "cls_confirm_cancel_button focus-visible:ring-0 focus-visible:ring-offset-0",
7139
7198
  variant: "outline",
7140
7199
  onClick: handle_cancel,
7141
7200
  disabled: is_loading,
@@ -11167,6 +11226,233 @@ function HazoUiEtaProgress({
11167
11226
  );
11168
11227
  }
11169
11228
 
11229
+ // src/components/hazo_ui_memory_dropdown/store.ts
11230
+ var MAX_ENTRIES = 50;
11231
+ function filterEntries(entries, query) {
11232
+ const q = query.trim().toLowerCase();
11233
+ if (!q) return entries;
11234
+ return entries.filter((e) => e.label.toLowerCase().includes(q));
11235
+ }
11236
+ function upsertEntry(entries, entry) {
11237
+ const without = entries.filter((e) => e.id !== entry.id);
11238
+ const updated = [entry, ...without];
11239
+ return updated.slice(0, MAX_ENTRIES);
11240
+ }
11241
+ function removeEntry(entries, id) {
11242
+ return entries.filter((e) => e.id !== id);
11243
+ }
11244
+ function HazoUiMemoryDropdown({
11245
+ history_key,
11246
+ placeholder = "Search history\u2026",
11247
+ on_select,
11248
+ add_entry,
11249
+ empty_label = "No history yet"
11250
+ }) {
11251
+ const { value: stored, setValue } = client$1.useHazoState(history_key, {
11252
+ level: "scope",
11253
+ fallback: []
11254
+ });
11255
+ const storedEntries = Array.isArray(stored) ? stored : [];
11256
+ const [query, setQuery] = React26__namespace.useState("");
11257
+ React26__namespace.useEffect(() => {
11258
+ if (!add_entry) return;
11259
+ const updated = upsertEntry(storedEntries, add_entry);
11260
+ setValue(updated);
11261
+ }, [add_entry, setValue]);
11262
+ const filtered = filterEntries(storedEntries, query);
11263
+ function handleDelete(id, e) {
11264
+ e.stopPropagation();
11265
+ const updated = removeEntry(storedEntries, id);
11266
+ setValue(updated);
11267
+ }
11268
+ return /* @__PURE__ */ jsxRuntime.jsxs(
11269
+ "div",
11270
+ {
11271
+ style: {
11272
+ display: "flex",
11273
+ flexDirection: "column",
11274
+ border: "1px solid #DAE1E7",
11275
+ borderRadius: 6,
11276
+ background: "#F0F4F8",
11277
+ overflow: "hidden",
11278
+ fontSize: 14,
11279
+ color: "#1A202C"
11280
+ },
11281
+ children: [
11282
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "8px 10px", borderBottom: "1px solid #DAE1E7" }, children: /* @__PURE__ */ jsxRuntime.jsx(
11283
+ "input",
11284
+ {
11285
+ type: "text",
11286
+ value: query,
11287
+ onChange: (e) => setQuery(e.target.value),
11288
+ placeholder,
11289
+ style: {
11290
+ width: "100%",
11291
+ background: "#FFFFFF",
11292
+ border: "1px solid #DAE1E7",
11293
+ borderRadius: 4,
11294
+ padding: "5px 8px",
11295
+ fontSize: 13,
11296
+ color: "#1A202C",
11297
+ outline: "none",
11298
+ boxSizing: "border-box"
11299
+ }
11300
+ }
11301
+ ) }),
11302
+ /* @__PURE__ */ jsxRuntime.jsx(
11303
+ "div",
11304
+ {
11305
+ style: {
11306
+ overflowY: "auto",
11307
+ maxHeight: 240
11308
+ },
11309
+ children: filtered.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
11310
+ "div",
11311
+ {
11312
+ style: {
11313
+ padding: "10px 12px",
11314
+ color: "#94A3B8",
11315
+ fontStyle: "italic",
11316
+ fontSize: 13
11317
+ },
11318
+ children: empty_label
11319
+ }
11320
+ ) : filtered.map((entry) => /* @__PURE__ */ jsxRuntime.jsx(
11321
+ EntryRow,
11322
+ {
11323
+ entry,
11324
+ on_select,
11325
+ onDelete: handleDelete
11326
+ },
11327
+ entry.id
11328
+ ))
11329
+ }
11330
+ )
11331
+ ]
11332
+ }
11333
+ );
11334
+ }
11335
+ function EntryRow({ entry, on_select, onDelete }) {
11336
+ const [deleteHovered, setDeleteHovered] = React26__namespace.useState(false);
11337
+ const [rowHovered, setRowHovered] = React26__namespace.useState(false);
11338
+ return /* @__PURE__ */ jsxRuntime.jsxs(
11339
+ "div",
11340
+ {
11341
+ onClick: () => on_select(entry),
11342
+ onMouseEnter: () => setRowHovered(true),
11343
+ onMouseLeave: () => setRowHovered(false),
11344
+ style: {
11345
+ display: "flex",
11346
+ alignItems: "center",
11347
+ justifyContent: "space-between",
11348
+ padding: "7px 12px",
11349
+ cursor: "pointer",
11350
+ background: rowHovered ? "#DAE1E7" : "transparent",
11351
+ borderBottom: "1px solid #DAE1E7",
11352
+ gap: 8
11353
+ },
11354
+ children: [
11355
+ /* @__PURE__ */ jsxRuntime.jsx(
11356
+ "span",
11357
+ {
11358
+ style: {
11359
+ flex: 1,
11360
+ overflow: "hidden",
11361
+ textOverflow: "ellipsis",
11362
+ whiteSpace: "nowrap",
11363
+ fontSize: 13,
11364
+ color: "#1A202C"
11365
+ },
11366
+ children: entry.label
11367
+ }
11368
+ ),
11369
+ /* @__PURE__ */ jsxRuntime.jsx(
11370
+ "button",
11371
+ {
11372
+ onClick: (e) => onDelete(entry.id, e),
11373
+ onMouseEnter: () => setDeleteHovered(true),
11374
+ onMouseLeave: () => setDeleteHovered(false),
11375
+ "aria-label": `Remove ${entry.label} from history`,
11376
+ style: {
11377
+ flexShrink: 0,
11378
+ background: "none",
11379
+ border: "none",
11380
+ cursor: "pointer",
11381
+ padding: "2px 4px",
11382
+ borderRadius: 3,
11383
+ color: deleteHovered ? "#EF4444" : "#94A3B8",
11384
+ fontSize: 14,
11385
+ lineHeight: 1
11386
+ },
11387
+ children: "\xD7"
11388
+ }
11389
+ )
11390
+ ]
11391
+ }
11392
+ );
11393
+ }
11394
+ var ThemeContext = React26__namespace.createContext(void 0);
11395
+ function HazoThemeProvider({
11396
+ children,
11397
+ defaultTheme = "system",
11398
+ storageKey = "theme"
11399
+ }) {
11400
+ const [theme, setTheme] = useLocalStorage(storageKey, defaultTheme);
11401
+ const prefersDark = useMediaQuery("(prefers-color-scheme: dark)");
11402
+ const resolvedTheme = theme === "system" ? prefersDark ? "dark" : "light" : theme;
11403
+ React26__namespace.useEffect(() => {
11404
+ document.documentElement.classList.toggle("dark", resolvedTheme === "dark");
11405
+ }, [resolvedTheme]);
11406
+ const toggleTheme = React26__namespace.useCallback(() => {
11407
+ setTheme(resolvedTheme === "dark" ? "light" : "dark");
11408
+ }, [resolvedTheme, setTheme]);
11409
+ const value = React26__namespace.useMemo(
11410
+ () => ({ theme, setTheme, resolvedTheme, toggleTheme }),
11411
+ [theme, setTheme, resolvedTheme, toggleTheme]
11412
+ );
11413
+ return /* @__PURE__ */ jsxRuntime.jsx(ThemeContext.Provider, { value, children });
11414
+ }
11415
+ function useTheme() {
11416
+ const context = React26__namespace.useContext(ThemeContext);
11417
+ if (context === void 0) {
11418
+ throw new Error("useTheme must be used within a <HazoThemeProvider>");
11419
+ }
11420
+ return context;
11421
+ }
11422
+ function ThemeScript({ storageKey = "theme" }) {
11423
+ const script = `(function(){try{var k=${JSON.stringify(
11424
+ storageKey
11425
+ )};var raw=localStorage.getItem(k);var t="system";if(raw){try{t=JSON.parse(raw);}catch(e){t=String(raw).replace(/^"|"$/g,"");}}var m=window.matchMedia("(prefers-color-scheme: dark)").matches;var isDark=t==="dark"||((t==="system"||!t)&&m);var c=document.documentElement.classList;if(isDark){c.add("dark");}else{c.remove("dark");}}catch(e){}})();`;
11426
+ return /* @__PURE__ */ jsxRuntime.jsx("script", { dangerouslySetInnerHTML: { __html: script } });
11427
+ }
11428
+ var ThemeToggle = React26__namespace.forwardRef(
11429
+ ({ className, ...props }, ref) => {
11430
+ const { resolvedTheme, toggleTheme } = useTheme();
11431
+ const [mounted, setMounted] = React26__namespace.useState(false);
11432
+ React26__namespace.useEffect(() => {
11433
+ setMounted(true);
11434
+ }, []);
11435
+ const isDark = mounted && resolvedTheme === "dark";
11436
+ const label = isDark ? "Switch to light theme" : "Switch to dark theme";
11437
+ return /* @__PURE__ */ jsxRuntime.jsx(
11438
+ "button",
11439
+ {
11440
+ type: "button",
11441
+ ref,
11442
+ onClick: toggleTheme,
11443
+ "aria-label": label,
11444
+ className: cn(
11445
+ "inline-flex h-9 w-9 items-center justify-center rounded-full text-foreground transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50",
11446
+ className
11447
+ ),
11448
+ ...props,
11449
+ children: isDark ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sun, { className: "h-4 w-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Moon, { className: "h-4 w-4" })
11450
+ }
11451
+ );
11452
+ }
11453
+ );
11454
+ ThemeToggle.displayName = "ThemeToggle";
11455
+
11170
11456
  Object.defineProperty(exports, "rawToast", {
11171
11457
  enumerable: true,
11172
11458
  get: function () { return sonner.toast; }
@@ -11239,6 +11525,7 @@ exports.ErrorBanner = ErrorBanner;
11239
11525
  exports.ErrorPage = ErrorPage;
11240
11526
  exports.FunnelChart = FunnelChart;
11241
11527
  exports.HazoContextProvider = HazoContextProvider;
11528
+ exports.HazoThemeProvider = HazoThemeProvider;
11242
11529
  exports.HazoUiConfirmDialog = HazoUiConfirmDialog;
11243
11530
  exports.HazoUiDialog = HazoUiDialog;
11244
11531
  exports.HazoUiDialogClose = DialogClose;
@@ -11258,6 +11545,7 @@ exports.HazoUiImageCropper = HazoUiImageCropper;
11258
11545
  exports.HazoUiImageCropperDialog = HazoUiImageCropperDialog;
11259
11546
  exports.HazoUiKanban = HazoUiKanban;
11260
11547
  exports.HazoUiKanbanFilter = HazoUiKanbanFilter;
11548
+ exports.HazoUiMemoryDropdown = HazoUiMemoryDropdown;
11261
11549
  exports.HazoUiMultiFilterDialog = HazoUiMultiFilterDialog;
11262
11550
  exports.HazoUiMultiSortDialog = HazoUiMultiSortDialog;
11263
11551
  exports.HazoUiPillRadio = HazoUiPillRadio;
@@ -11327,6 +11615,8 @@ exports.TabsContent = TabsContent;
11327
11615
  exports.TabsList = TabsList;
11328
11616
  exports.TabsTrigger = TabsTrigger;
11329
11617
  exports.Textarea = Textarea;
11618
+ exports.ThemeScript = ThemeScript;
11619
+ exports.ThemeToggle = ThemeToggle;
11330
11620
  exports.Toggle = Toggle;
11331
11621
  exports.ToggleGroup = ToggleGroup;
11332
11622
  exports.ToggleGroupItem = ToggleGroupItem;
@@ -11356,6 +11646,7 @@ exports.set_logger = set_logger;
11356
11646
  exports.successToast = successToast;
11357
11647
  exports.text_to_tiptap_content = text_to_tiptap_content;
11358
11648
  exports.toggleVariants = toggleVariants;
11649
+ exports.upsertMemoryEntry = upsertEntry;
11359
11650
  exports.useClickOutside = useClickOutside;
11360
11651
  exports.useCopyToClipboard = useCopyToClipboard;
11361
11652
  exports.useDebounce = useDebounce;
@@ -11367,6 +11658,7 @@ exports.useLoadingState = useLoadingState;
11367
11658
  exports.useLocalStorage = useLocalStorage;
11368
11659
  exports.useMediaQuery = useMediaQuery;
11369
11660
  exports.useSessionStorage = useSessionStorage;
11661
+ exports.useTheme = useTheme;
11370
11662
  exports.useViewport = useViewport;
11371
11663
  exports.useWakeLock = useWakeLock;
11372
11664
  exports.use_fullscreen = use_fullscreen;