beez-ui 0.8.2 → 0.9.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +12 -10
  3. package/dist/components/account-menu.d.ts +10 -0
  4. package/dist/components/account-menu.js +139 -118
  5. package/dist/components/file-upload.d.ts +14 -4
  6. package/dist/components/file-upload.js +304 -206
  7. package/dist/components/filter-presets-bar.d.ts +73 -0
  8. package/dist/components/filter-presets-bar.js +510 -0
  9. package/dist/components/reaction-button.d.ts +7 -2
  10. package/dist/components/reaction-button.js +59 -48
  11. package/dist/hooks.d.ts +10 -0
  12. package/dist/hooks.js +10 -0
  13. package/dist/index.d.ts +1 -23
  14. package/dist/index.js +1 -23
  15. package/dist/styles.css +1 -1
  16. package/package.json +11 -126
  17. package/dist/components/bouncing-dots-loader.d.ts +0 -19
  18. package/dist/components/bouncing-dots-loader.js +0 -47
  19. package/dist/components/confirm-delete-button.d.ts +0 -29
  20. package/dist/components/confirm-delete-button.js +0 -254
  21. package/dist/components/empty-state.d.ts +0 -24
  22. package/dist/components/empty-state.js +0 -37
  23. package/dist/components/error-state.d.ts +0 -20
  24. package/dist/components/error-state.js +0 -145
  25. package/dist/components/external-browser-handoff.d.ts +0 -27
  26. package/dist/components/external-browser-handoff.js +0 -306
  27. package/dist/components/info-popover.d.ts +0 -23
  28. package/dist/components/info-popover.js +0 -158
  29. package/dist/components/month-calendar-header.d.ts +0 -65
  30. package/dist/components/month-calendar-header.js +0 -337
  31. package/dist/components/month-grid.d.ts +0 -59
  32. package/dist/components/month-grid.js +0 -301
  33. package/dist/components/notification-bell.d.ts +0 -50
  34. package/dist/components/notification-bell.js +0 -338
  35. package/dist/components/notification-panel.d.ts +0 -56
  36. package/dist/components/notification-panel.js +0 -281
  37. package/dist/components/open-in-browser-cta.d.ts +0 -22
  38. package/dist/components/open-in-browser-cta.js +0 -61
  39. package/dist/components/pwa-update-control.d.ts +0 -18
  40. package/dist/components/pwa-update-control.js +0 -119
  41. package/dist/emoji-picker.d.ts +0 -27
  42. package/dist/emoji-picker.js +0 -283
  43. package/dist/hooks/use-minute-clock.d.ts +0 -23
  44. package/dist/hooks/use-minute-clock.js +0 -147
  45. package/dist/hooks/use-month-transition-direction.d.ts +0 -21
  46. package/dist/hooks/use-month-transition-direction.js +0 -110
  47. package/dist/lib/browser-navigation.d.ts +0 -21
  48. package/dist/lib/browser-navigation.js +0 -42
  49. package/dist/lib/in-app-browser.d.ts +0 -35
  50. package/dist/lib/in-app-browser.js +0 -80
  51. package/dist/lib/month-grid.d.ts +0 -42
  52. package/dist/lib/month-grid.js +0 -74
@@ -0,0 +1,73 @@
1
+ import type { FilterQualifierConfig } from "./filter-query-grammar.js";
2
+ /** A named, reusable query of the filter bar. */
3
+ export interface FilterPreset {
4
+ name: string;
5
+ query: string;
6
+ }
7
+ /**
8
+ * Validates presets restored from untrusted storage, trimming names and queries and dropping
9
+ * malformed or empty entries.
10
+ * @param value - Parsed stored value, such as the result of `JSON.parse`.
11
+ * @returns The valid presets, or an empty list when the value is not a list.
12
+ */
13
+ export declare function parseFilterPresets(value: unknown): FilterPreset[];
14
+ export interface FilterPresetLabels {
15
+ saveAction: string;
16
+ saveConfirm: string;
17
+ saveConfirmAriaLabel: string;
18
+ presetName: string;
19
+ presetNamePlaceholder: string;
20
+ presetQuery: string;
21
+ presetQueryPlaceholder: string;
22
+ savedPresets: string;
23
+ editConfirmAriaLabel: string;
24
+ cancel: string;
25
+ delete: string;
26
+ emptyNameError: string;
27
+ emptyCurrentQueryError: string;
28
+ emptyPresetQueryError: string;
29
+ duplicateNameError: string;
30
+ applyPresetAriaLabel: (presetName: string) => string;
31
+ editPresetAriaLabel: (presetName: string) => string;
32
+ deletePresetAriaLabel: (presetName: string) => string;
33
+ deleteConfirmation: (presetName: string) => string;
34
+ }
35
+ export declare const FILTER_PRESET_DEFAULT_LABELS: FilterPresetLabels;
36
+ export interface FilterPresetSaveButtonProps {
37
+ /** Whether the filter bar currently has a query worth saving. */
38
+ canSaveCurrentQuery: boolean;
39
+ /** Saves the current query under the given name. Returns `false` when there is no query to save. */
40
+ onSaveCurrentQuery: (presetName: string) => boolean;
41
+ labels?: Partial<FilterPresetLabels>;
42
+ /** Classes of the icon button, for example to adapt it to the action slot of an input. */
43
+ className?: string;
44
+ }
45
+ /**
46
+ * Compact icon button that saves the current query as a named preset. Fits the action slot of
47
+ * the filter bar input.
48
+ * @param props - Save availability, save callback and optional labels.
49
+ * @returns The button and its naming popover.
50
+ */
51
+ export declare function FilterPresetSaveButton({ canSaveCurrentQuery, onSaveCurrentQuery, labels, className }: FilterPresetSaveButtonProps): import("react").JSX.Element;
52
+ export interface FilterPresetsBarProps {
53
+ presets: readonly FilterPreset[];
54
+ /** Qualifiers of the filter bar, offered as suggestions while editing a preset query. */
55
+ queryFilterConfigs: FilterQualifierConfig[];
56
+ onApplyPreset: (preset: FilterPreset) => void;
57
+ onDeletePreset: (presetName: string) => void;
58
+ /** Replaces the preset named `originalName` with the given name and query. */
59
+ onUpdatePreset: (update: {
60
+ name: string;
61
+ originalName: string;
62
+ query: string;
63
+ }) => void;
64
+ labels?: Partial<FilterPresetLabels>;
65
+ className?: string;
66
+ }
67
+ /**
68
+ * Renders one chip per saved preset: its name applies it, the pencil edits its name and query,
69
+ * and the cross deletes it after a confirmation. Renders nothing without presets.
70
+ * @param props - Presets, filter configs, callbacks and optional labels.
71
+ * @returns The presets row, or nothing.
72
+ */
73
+ export declare function FilterPresetsBar({ presets, queryFilterConfigs, onApplyPreset, onDeletePreset, onUpdatePreset, labels, className, }: FilterPresetsBarProps): import("react").JSX.Element | null;
@@ -0,0 +1,510 @@
1
+ "use client";
2
+ import { c as _c } from "react/compiler-runtime";
3
+ /**
4
+ * Saved queries for `FilterQueryBar`: a button that stores the current query under a name and a
5
+ * row of chips to apply, edit or delete them. Persistence stays with the application.
6
+ */
7
+ import { useId, useState } from "react";
8
+ import { Bookmark, Pencil, X } from "lucide-react";
9
+ import { cn } from "../lib/utils.js";
10
+ import { Button } from "./button.js";
11
+ import { FilterQueryBar } from "./filter-query-bar.js";
12
+ import { Input } from "./input.js";
13
+ import { Label } from "./label.js";
14
+ import { Popover, PopoverContent, PopoverTrigger } from "./popover.js";
15
+ import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip.js";
16
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
17
+ const ENTER_KEY = "Enter";
18
+ /** Suggestions of the query editor render in their own popover, outside the edit popover. */
19
+ const NESTED_POPOVER_SELECTOR = "[data-slot=\"popover-content\"]";
20
+ /**
21
+ * Tells whether a value is a non-null object.
22
+ * @param value - Untrusted value.
23
+ * @returns Whether its properties can be read.
24
+ */
25
+ function isRecord(value) {
26
+ return typeof value === "object" && value !== null;
27
+ }
28
+ /**
29
+ * Validates presets restored from untrusted storage, trimming names and queries and dropping
30
+ * malformed or empty entries.
31
+ * @param value - Parsed stored value, such as the result of `JSON.parse`.
32
+ * @returns The valid presets, or an empty list when the value is not a list.
33
+ */
34
+ export function parseFilterPresets(value) {
35
+ if (!Array.isArray(value)) return [];
36
+ return value.flatMap((entry) => {
37
+ if (!isRecord(entry) || typeof entry.name !== "string" || typeof entry.query !== "string") return [];
38
+ const name = entry.name.trim();
39
+ const query = entry.query.trim();
40
+ return name && query ? [{
41
+ name,
42
+ query
43
+ }] : [];
44
+ });
45
+ }
46
+ export const FILTER_PRESET_DEFAULT_LABELS = {
47
+ saveAction: "Guardar filtro",
48
+ saveConfirm: "Guardar",
49
+ saveConfirmAriaLabel: "Guardar filtro con nombre",
50
+ presetName: "Nombre del filtro",
51
+ presetNamePlaceholder: "Mi filtro",
52
+ presetQuery: "Búsqueda del filtro",
53
+ presetQueryPlaceholder: "",
54
+ savedPresets: "Filtros guardados:",
55
+ editConfirmAriaLabel: "Guardar cambios del filtro",
56
+ cancel: "Cancelar",
57
+ delete: "Eliminar",
58
+ emptyNameError: "Ingresá un nombre para el filtro.",
59
+ emptyCurrentQueryError: "Escribí una búsqueda en la barra antes de guardarla.",
60
+ emptyPresetQueryError: "Ingresá una búsqueda para el filtro.",
61
+ duplicateNameError: "Ya existe un filtro con ese nombre.",
62
+ applyPresetAriaLabel: (presetName) => `Aplicar filtro guardado ${presetName}`,
63
+ editPresetAriaLabel: (presetName) => `Editar filtro guardado ${presetName}`,
64
+ deletePresetAriaLabel: (presetName) => `Eliminar filtro guardado ${presetName}`,
65
+ deleteConfirmation: (presetName) => `¿Querés eliminar el filtro guardado "${presetName}"?`
66
+ };
67
+ const FORM_POPOVER_CLASS_NAME = "grid w-64 gap-2";
68
+ const ERROR_CLASS_NAME = "m-0 text-xs text-destructive";
69
+ const CHIP_ICON_BUTTON_CLASS_NAME = "inline-flex cursor-pointer items-center border-0 bg-transparent py-[0.35rem] text-muted-foreground [&_svg]:size-3";
70
+ /**
71
+ * Compact icon button that saves the current query as a named preset. Fits the action slot of
72
+ * the filter bar input.
73
+ * @param props - Save availability, save callback and optional labels.
74
+ * @returns The button and its naming popover.
75
+ */
76
+ export function FilterPresetSaveButton(t0) {
77
+ const $ = _c(46);
78
+ const { canSaveCurrentQuery, onSaveCurrentQuery, labels, className } = t0;
79
+ let t1;
80
+ if ($[0] !== labels) {
81
+ t1 = {
82
+ ...FILTER_PRESET_DEFAULT_LABELS,
83
+ ...labels
84
+ };
85
+ $[0] = labels;
86
+ $[1] = t1;
87
+ } else {
88
+ t1 = $[1];
89
+ }
90
+ const resolvedLabels = t1;
91
+ const nameInputId = useId();
92
+ const [isOpen, setIsOpen] = useState(false);
93
+ const [nameDraft, setNameDraft] = useState("");
94
+ const [error, setError] = useState(null);
95
+ let t2;
96
+ if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
97
+ t2 = (nextOpen) => {
98
+ setIsOpen(nextOpen);
99
+ if (!nextOpen) {
100
+ setNameDraft("");
101
+ setError(null);
102
+ }
103
+ };
104
+ $[2] = t2;
105
+ } else {
106
+ t2 = $[2];
107
+ }
108
+ const handleOpenChange = t2;
109
+ let t3;
110
+ if ($[3] !== nameDraft || $[4] !== onSaveCurrentQuery || $[5] !== resolvedLabels.emptyCurrentQueryError || $[6] !== resolvedLabels.emptyNameError) {
111
+ t3 = () => {
112
+ const presetName = nameDraft.trim();
113
+ if (!presetName) {
114
+ setError(resolvedLabels.emptyNameError);
115
+ return;
116
+ }
117
+ if (!onSaveCurrentQuery(presetName)) {
118
+ setError(resolvedLabels.emptyCurrentQueryError);
119
+ return;
120
+ }
121
+ handleOpenChange(false);
122
+ };
123
+ $[3] = nameDraft;
124
+ $[4] = onSaveCurrentQuery;
125
+ $[5] = resolvedLabels.emptyCurrentQueryError;
126
+ $[6] = resolvedLabels.emptyNameError;
127
+ $[7] = t3;
128
+ } else {
129
+ t3 = $[7];
130
+ }
131
+ const handleConfirmSave = t3;
132
+ const t4 = !canSaveCurrentQuery;
133
+ let t5;
134
+ if ($[8] === Symbol.for("react.memo_cache_sentinel")) {
135
+ t5 = /* @__PURE__ */ _jsx(Bookmark, { "aria-hidden": "true" });
136
+ $[8] = t5;
137
+ } else {
138
+ t5 = $[8];
139
+ }
140
+ let t6;
141
+ if ($[9] !== className || $[10] !== resolvedLabels.saveAction || $[11] !== t4) {
142
+ t6 = /* @__PURE__ */ _jsx(TooltipTrigger, {
143
+ asChild: true,
144
+ children: /* @__PURE__ */ _jsx(PopoverTrigger, {
145
+ asChild: true,
146
+ children: /* @__PURE__ */ _jsx(Button, {
147
+ "data-slot": "filter-preset-save-button",
148
+ "aria-label": resolvedLabels.saveAction,
149
+ className,
150
+ disabled: t4,
151
+ size: "icon-xs",
152
+ type: "button",
153
+ variant: "ghost",
154
+ children: t5
155
+ })
156
+ })
157
+ });
158
+ $[9] = className;
159
+ $[10] = resolvedLabels.saveAction;
160
+ $[11] = t4;
161
+ $[12] = t6;
162
+ } else {
163
+ t6 = $[12];
164
+ }
165
+ let t7;
166
+ if ($[13] !== resolvedLabels.saveAction) {
167
+ t7 = /* @__PURE__ */ _jsx(TooltipContent, { children: resolvedLabels.saveAction });
168
+ $[13] = resolvedLabels.saveAction;
169
+ $[14] = t7;
170
+ } else {
171
+ t7 = $[14];
172
+ }
173
+ let t8;
174
+ if ($[15] !== t6 || $[16] !== t7) {
175
+ t8 = /* @__PURE__ */ _jsxs(Tooltip, { children: [t6, t7] });
176
+ $[15] = t6;
177
+ $[16] = t7;
178
+ $[17] = t8;
179
+ } else {
180
+ t8 = $[17];
181
+ }
182
+ let t9;
183
+ if ($[18] !== nameInputId || $[19] !== resolvedLabels.presetName) {
184
+ t9 = /* @__PURE__ */ _jsx(Label, {
185
+ htmlFor: nameInputId,
186
+ children: resolvedLabels.presetName
187
+ });
188
+ $[18] = nameInputId;
189
+ $[19] = resolvedLabels.presetName;
190
+ $[20] = t9;
191
+ } else {
192
+ t9 = $[20];
193
+ }
194
+ let t10;
195
+ if ($[21] !== error) {
196
+ t10 = (event) => {
197
+ setNameDraft(event.target.value);
198
+ if (error) {
199
+ setError(null);
200
+ }
201
+ };
202
+ $[21] = error;
203
+ $[22] = t10;
204
+ } else {
205
+ t10 = $[22];
206
+ }
207
+ let t11;
208
+ if ($[23] !== handleConfirmSave) {
209
+ t11 = (event_0) => {
210
+ if (event_0.key !== ENTER_KEY) {
211
+ return;
212
+ }
213
+ event_0.preventDefault();
214
+ handleConfirmSave();
215
+ };
216
+ $[23] = handleConfirmSave;
217
+ $[24] = t11;
218
+ } else {
219
+ t11 = $[24];
220
+ }
221
+ let t12;
222
+ if ($[25] !== nameDraft || $[26] !== nameInputId || $[27] !== resolvedLabels.presetNamePlaceholder || $[28] !== t10 || $[29] !== t11) {
223
+ t12 = /* @__PURE__ */ _jsx(Input, {
224
+ id: nameInputId,
225
+ onChange: t10,
226
+ onKeyDown: t11,
227
+ placeholder: resolvedLabels.presetNamePlaceholder,
228
+ type: "text",
229
+ value: nameDraft
230
+ });
231
+ $[25] = nameDraft;
232
+ $[26] = nameInputId;
233
+ $[27] = resolvedLabels.presetNamePlaceholder;
234
+ $[28] = t10;
235
+ $[29] = t11;
236
+ $[30] = t12;
237
+ } else {
238
+ t12 = $[30];
239
+ }
240
+ let t13;
241
+ if ($[31] !== error) {
242
+ t13 = error ? /* @__PURE__ */ _jsx("p", {
243
+ className: ERROR_CLASS_NAME,
244
+ role: "alert",
245
+ children: error
246
+ }) : null;
247
+ $[31] = error;
248
+ $[32] = t13;
249
+ } else {
250
+ t13 = $[32];
251
+ }
252
+ let t14;
253
+ if ($[33] !== handleConfirmSave || $[34] !== resolvedLabels.saveConfirm || $[35] !== resolvedLabels.saveConfirmAriaLabel) {
254
+ t14 = /* @__PURE__ */ _jsx(Button, {
255
+ "aria-label": resolvedLabels.saveConfirmAriaLabel,
256
+ onClick: handleConfirmSave,
257
+ size: "sm",
258
+ type: "button",
259
+ children: resolvedLabels.saveConfirm
260
+ });
261
+ $[33] = handleConfirmSave;
262
+ $[34] = resolvedLabels.saveConfirm;
263
+ $[35] = resolvedLabels.saveConfirmAriaLabel;
264
+ $[36] = t14;
265
+ } else {
266
+ t14 = $[36];
267
+ }
268
+ let t15;
269
+ if ($[37] !== t12 || $[38] !== t13 || $[39] !== t14 || $[40] !== t9) {
270
+ t15 = /* @__PURE__ */ _jsxs(PopoverContent, {
271
+ align: "end",
272
+ className: FORM_POPOVER_CLASS_NAME,
273
+ children: [
274
+ t9,
275
+ t12,
276
+ t13,
277
+ t14
278
+ ]
279
+ });
280
+ $[37] = t12;
281
+ $[38] = t13;
282
+ $[39] = t14;
283
+ $[40] = t9;
284
+ $[41] = t15;
285
+ } else {
286
+ t15 = $[41];
287
+ }
288
+ let t16;
289
+ if ($[42] !== isOpen || $[43] !== t15 || $[44] !== t8) {
290
+ t16 = /* @__PURE__ */ _jsxs(Popover, {
291
+ onOpenChange: handleOpenChange,
292
+ open: isOpen,
293
+ children: [t8, t15]
294
+ });
295
+ $[42] = isOpen;
296
+ $[43] = t15;
297
+ $[44] = t8;
298
+ $[45] = t16;
299
+ } else {
300
+ t16 = $[45];
301
+ }
302
+ return t16;
303
+ }
304
+ /**
305
+ * Renders one chip per saved preset: its name applies it, the pencil edits its name and query,
306
+ * and the cross deletes it after a confirmation. Renders nothing without presets.
307
+ * @param props - Presets, filter configs, callbacks and optional labels.
308
+ * @returns The presets row, or nothing.
309
+ */
310
+ export function FilterPresetsBar(t0) {
311
+ const $ = _c(4);
312
+ const { presets, queryFilterConfigs, onApplyPreset, onDeletePreset, onUpdatePreset, labels, className } = t0;
313
+ const resolvedLabels = {
314
+ ...FILTER_PRESET_DEFAULT_LABELS,
315
+ ...labels
316
+ };
317
+ const nameInputId = useId();
318
+ const [editingPresetName, setEditingPresetName] = useState(null);
319
+ const [nameDraft, setNameDraft] = useState("");
320
+ const [queryDraft, setQueryDraft] = useState("");
321
+ const [editError, setEditError] = useState(null);
322
+ const [deletingPresetName, setDeletingPresetName] = useState(null);
323
+ let t1;
324
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
325
+ t1 = (preset, nextOpen) => {
326
+ setEditError(null);
327
+ if (!nextOpen) {
328
+ setEditingPresetName(null);
329
+ return;
330
+ }
331
+ setEditingPresetName(preset.name);
332
+ setNameDraft(preset.name);
333
+ setQueryDraft(preset.query);
334
+ };
335
+ $[0] = t1;
336
+ } else {
337
+ t1 = $[0];
338
+ }
339
+ const handleEditOpenChange = t1;
340
+ const handleConfirmEdit = () => {
341
+ if (editingPresetName === null) {
342
+ return;
343
+ }
344
+ const name = nameDraft.trim();
345
+ const query = queryDraft.trim();
346
+ if (!name) {
347
+ setEditError(resolvedLabels.emptyNameError);
348
+ return;
349
+ }
350
+ if (!query) {
351
+ setEditError(resolvedLabels.emptyPresetQueryError);
352
+ return;
353
+ }
354
+ if (presets.some((preset_0) => preset_0.name === name && preset_0.name !== editingPresetName)) {
355
+ setEditError(resolvedLabels.duplicateNameError);
356
+ return;
357
+ }
358
+ onUpdatePreset({
359
+ name,
360
+ originalName: editingPresetName,
361
+ query
362
+ });
363
+ setEditingPresetName(null);
364
+ };
365
+ if (presets.length === 0) {
366
+ return null;
367
+ }
368
+ let t2;
369
+ if ($[1] !== className) {
370
+ t2 = cn("flex flex-wrap items-center gap-[0.4rem]", className);
371
+ $[1] = className;
372
+ $[2] = t2;
373
+ } else {
374
+ t2 = $[2];
375
+ }
376
+ let t3;
377
+ if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
378
+ t3 = /* @__PURE__ */ _jsx(Bookmark, { "aria-hidden": "true" });
379
+ $[3] = t3;
380
+ } else {
381
+ t3 = $[3];
382
+ }
383
+ return /* @__PURE__ */ _jsxs("div", {
384
+ "data-slot": "filter-presets-bar",
385
+ className: t2,
386
+ children: [/* @__PURE__ */ _jsxs("span", {
387
+ className: "inline-flex items-center gap-[0.3rem] text-xs font-medium text-muted-foreground [&_svg]:size-[0.8rem]",
388
+ children: [t3, resolvedLabels.savedPresets]
389
+ }), presets.map((preset_1) => /* @__PURE__ */ _jsxs("span", {
390
+ "data-slot": "filter-preset-chip",
391
+ className: "inline-flex items-center overflow-hidden rounded-full border border-border bg-muted text-xs leading-none",
392
+ children: [
393
+ /* @__PURE__ */ _jsx("button", {
394
+ "aria-label": resolvedLabels.applyPresetAriaLabel(preset_1.name),
395
+ className: "max-w-48 cursor-pointer truncate border-0 bg-transparent py-[0.35rem] pr-1 pl-[0.65rem] text-inherit hover:text-primary focus-visible:text-primary",
396
+ onClick: () => onApplyPreset(preset_1),
397
+ title: preset_1.query,
398
+ type: "button",
399
+ children: preset_1.name
400
+ }),
401
+ /* @__PURE__ */ _jsxs(Popover, {
402
+ onOpenChange: (nextOpen_0) => handleEditOpenChange(preset_1, nextOpen_0),
403
+ open: editingPresetName === preset_1.name,
404
+ children: [/* @__PURE__ */ _jsx(PopoverTrigger, {
405
+ asChild: true,
406
+ children: /* @__PURE__ */ _jsx("button", {
407
+ "aria-label": resolvedLabels.editPresetAriaLabel(preset_1.name),
408
+ className: cn(CHIP_ICON_BUTTON_CLASS_NAME, "px-[0.15rem] hover:text-primary focus-visible:text-primary"),
409
+ type: "button",
410
+ children: /* @__PURE__ */ _jsx(Pencil, { "aria-hidden": "true" })
411
+ })
412
+ }), /* @__PURE__ */ _jsxs(PopoverContent, {
413
+ align: "start",
414
+ className: FORM_POPOVER_CLASS_NAME,
415
+ onInteractOutside: _temp,
416
+ children: [
417
+ /* @__PURE__ */ _jsx(Label, {
418
+ htmlFor: nameInputId,
419
+ children: resolvedLabels.presetName
420
+ }),
421
+ /* @__PURE__ */ _jsx(Input, {
422
+ id: nameInputId,
423
+ onChange: (event_0) => {
424
+ setNameDraft(event_0.target.value);
425
+ if (editError) {
426
+ setEditError(null);
427
+ }
428
+ },
429
+ type: "text",
430
+ value: nameDraft
431
+ }),
432
+ /* @__PURE__ */ _jsx("span", {
433
+ className: "text-sm leading-none font-medium",
434
+ children: resolvedLabels.presetQuery
435
+ }),
436
+ /* @__PURE__ */ _jsx(FilterQueryBar, {
437
+ ariaLabel: resolvedLabels.presetQuery,
438
+ configs: queryFilterConfigs,
439
+ onValueChange: (nextQuery) => {
440
+ setQueryDraft(nextQuery);
441
+ if (editError) {
442
+ setEditError(null);
443
+ }
444
+ },
445
+ placeholder: resolvedLabels.presetQueryPlaceholder,
446
+ value: queryDraft
447
+ }),
448
+ editError ? /* @__PURE__ */ _jsx("p", {
449
+ className: ERROR_CLASS_NAME,
450
+ role: "alert",
451
+ children: editError
452
+ }) : null,
453
+ /* @__PURE__ */ _jsx(Button, {
454
+ "aria-label": resolvedLabels.editConfirmAriaLabel,
455
+ onClick: handleConfirmEdit,
456
+ size: "sm",
457
+ type: "button",
458
+ children: resolvedLabels.saveConfirm
459
+ })
460
+ ]
461
+ })]
462
+ }),
463
+ /* @__PURE__ */ _jsxs(Popover, {
464
+ onOpenChange: (nextOpen_1) => setDeletingPresetName(nextOpen_1 ? preset_1.name : null),
465
+ open: deletingPresetName === preset_1.name,
466
+ children: [/* @__PURE__ */ _jsx(PopoverTrigger, {
467
+ asChild: true,
468
+ children: /* @__PURE__ */ _jsx("button", {
469
+ "aria-label": resolvedLabels.deletePresetAriaLabel(preset_1.name),
470
+ className: cn(CHIP_ICON_BUTTON_CLASS_NAME, "pr-2 pl-[0.15rem] hover:text-destructive focus-visible:text-destructive"),
471
+ type: "button",
472
+ children: /* @__PURE__ */ _jsx(X, { "aria-hidden": "true" })
473
+ })
474
+ }), /* @__PURE__ */ _jsxs(PopoverContent, {
475
+ align: "start",
476
+ className: "grid w-68 gap-3",
477
+ children: [/* @__PURE__ */ _jsx("p", {
478
+ className: "m-0 text-sm",
479
+ children: resolvedLabels.deleteConfirmation(preset_1.name)
480
+ }), /* @__PURE__ */ _jsxs("div", {
481
+ className: "flex justify-end gap-2",
482
+ children: [/* @__PURE__ */ _jsx(Button, {
483
+ onClick: () => setDeletingPresetName(null),
484
+ size: "sm",
485
+ type: "button",
486
+ variant: "outline",
487
+ children: resolvedLabels.cancel
488
+ }), /* @__PURE__ */ _jsx(Button, {
489
+ onClick: () => {
490
+ setDeletingPresetName(null);
491
+ onDeletePreset(preset_1.name);
492
+ },
493
+ size: "sm",
494
+ type: "button",
495
+ variant: "destructive",
496
+ children: resolvedLabels.delete
497
+ })]
498
+ })]
499
+ })]
500
+ })
501
+ ]
502
+ }, preset_1.name))]
503
+ });
504
+ }
505
+ function _temp(event) {
506
+ const interactionTarget = event.target;
507
+ if (interactionTarget instanceof Element && interactionTarget.closest(NESTED_POPOVER_SELECTOR)) {
508
+ event.preventDefault();
509
+ }
510
+ }
@@ -10,8 +10,13 @@ export interface ReactionButtonProps {
10
10
  count: number;
11
11
  onClick: (event: MouseEvent<HTMLButtonElement>) => void;
12
12
  isDisabled?: boolean;
13
- /** Icon of the reaction. While active it is filled with the destructive color; override it with `[&_svg]:` classes. */
13
+ /** Icon of the reaction; it is filled with `activeColor` while active. */
14
14
  icon?: ComponentType<SVGProps<SVGSVGElement>>;
15
+ /**
16
+ * CSS color of the active icon, such as `var(--primary)` or `currentColor` to follow the
17
+ * button text. Defaults to the destructive color, the usual tone of a like.
18
+ */
19
+ activeColor?: string;
15
20
  /** Formats the visible count, for example with a locale-aware compact format. */
16
21
  formatCount?: (count: number) => string;
17
22
  className?: string;
@@ -23,4 +28,4 @@ export interface ReactionButtonProps {
23
28
  * @param props - Accessible name, state, count, click handler and optional icon.
24
29
  * @returns The reaction button.
25
30
  */
26
- export declare function ReactionButton({ ariaLabel, isActive, count, onClick, isDisabled, icon: Icon, formatCount, className, }: ReactionButtonProps): import("react").JSX.Element;
31
+ export declare function ReactionButton({ ariaLabel, isActive, count, onClick, isDisabled, icon: Icon, formatCount, activeColor, className, }: ReactionButtonProps): import("react").JSX.Element;