@akira-io/ui 1.0.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/dist/blocks.js ADDED
@@ -0,0 +1,921 @@
1
+ import { CommandDialog, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, Popover, PopoverTrigger, PopoverContent, Tabs, TabsList, TabsTrigger, TabsContent, Label, Textarea, Switch, Calendar, Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from './chunk-42BJBH56.js';
2
+ export { DEFAULT_TOUR_LABELS, TourProvider, resolveSteps, shouldStartTour, stepsForBreakpoint, useTour, useTourController } from './chunk-UXL33CZ4.js';
3
+ import { Button, cn, Input, elevatedSurface, recessedSurface } from './chunk-POE7OYET.js';
4
+ import { createContext, useState, useCallback, useEffect, useContext } from 'react';
5
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
6
+ import { CalendarDays, ChevronLeft, ArrowLeftToLine, X, ArrowRight, ArrowUpRight, ArrowDownRight } from 'lucide-react';
7
+ import { subYears, subQuarters, subMonths, subWeeks, subDays, endOfYear, endOfQuarter, endOfMonth, endOfDay, startOfYear, startOfQuarter, startOfMonth, startOfDay, isSameYear, format, endOfWeek, startOfWeek } from 'date-fns';
8
+ import { pt } from 'date-fns/locale';
9
+
10
+ function CommandPalette({
11
+ open,
12
+ onOpenChange,
13
+ groups,
14
+ onSelect,
15
+ query,
16
+ onQueryChange,
17
+ placeholder = "Search...",
18
+ noResultsLabel = "No results found",
19
+ emptyState,
20
+ className
21
+ }) {
22
+ const resolvedEmptyState = emptyState ?? noResultsLabel;
23
+ return /* @__PURE__ */ jsxs(
24
+ CommandDialog,
25
+ {
26
+ open,
27
+ onOpenChange,
28
+ className,
29
+ children: [
30
+ /* @__PURE__ */ jsx(
31
+ CommandInput,
32
+ {
33
+ placeholder,
34
+ value: query,
35
+ onValueChange: onQueryChange
36
+ }
37
+ ),
38
+ /* @__PURE__ */ jsxs(CommandList, { className: "max-h-[360px]", children: [
39
+ /* @__PURE__ */ jsx(CommandEmpty, { children: resolvedEmptyState }),
40
+ groups.map((group) => /* @__PURE__ */ jsx(CommandGroup, { heading: group.heading, children: group.items.map((item) => /* @__PURE__ */ jsxs(
41
+ CommandItem,
42
+ {
43
+ value: item.value ?? item.label,
44
+ onSelect: () => onSelect(item),
45
+ children: [
46
+ item.icon && /* @__PURE__ */ jsx(item.icon, { className: "mr-2 size-4" }),
47
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: item.label }),
48
+ item.hint && /* @__PURE__ */ jsx("span", { className: "text-xs ml-auto text-muted-foreground", children: item.hint })
49
+ ]
50
+ },
51
+ item.id
52
+ )) }, group.heading))
53
+ ] })
54
+ ]
55
+ }
56
+ );
57
+ }
58
+ function useCommandPalette(initialOpen = false) {
59
+ const [open, setOpen] = useState(initialOpen);
60
+ const toggle = useCallback(() => setOpen((previous) => !previous), []);
61
+ useEffect(() => {
62
+ const handler = (event) => {
63
+ if (event.key === "k" && (event.metaKey || event.ctrlKey)) {
64
+ event.preventDefault();
65
+ toggle();
66
+ }
67
+ };
68
+ document.addEventListener("keydown", handler);
69
+ return () => document.removeEventListener("keydown", handler);
70
+ }, [toggle]);
71
+ return { open, setOpen, toggle };
72
+ }
73
+ var DateFilterContext = createContext(null);
74
+ var DateFilterProvider = DateFilterContext.Provider;
75
+ function useDateFilter() {
76
+ const context = useContext(DateFilterContext);
77
+ if (context === null) {
78
+ throw new Error("DateFilter parts must be used inside <DateFilter>.");
79
+ }
80
+ return context;
81
+ }
82
+ var CALENDAR_START = new Date(2020, 0, 1);
83
+ var CALENDAR_END = new Date((/* @__PURE__ */ new Date()).getFullYear() + 1, 11, 31);
84
+ var toDate = (value) => value ? /* @__PURE__ */ new Date(`${value}T00:00:00`) : void 0;
85
+ var toIso = (value) => value ? format(value, "yyyy-MM-dd") : void 0;
86
+ function DateFilterFixedPanel() {
87
+ const { draft, operators, labels, setDraft, backToRoot, commit } = useDateFilter();
88
+ const operator = draft.operator ?? "between";
89
+ const isRange = operator === "between";
90
+ const canApply = Boolean(draft.start) && (!isRange || Boolean(draft.end));
91
+ return /* @__PURE__ */ jsxs("div", { className: "w-auto", children: [
92
+ /* @__PURE__ */ jsxs("div", { className: "gap-1 px-2 py-2 flex items-center border-b", children: [
93
+ /* @__PURE__ */ jsx(
94
+ Button,
95
+ {
96
+ variant: "ghost",
97
+ size: "icon",
98
+ onClick: backToRoot,
99
+ "aria-label": labels.back,
100
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4" })
101
+ }
102
+ ),
103
+ operators.map((item) => /* @__PURE__ */ jsx(
104
+ "button",
105
+ {
106
+ type: "button",
107
+ onClick: () => setDraft({
108
+ ...draft,
109
+ operator: item.value
110
+ }),
111
+ className: cn(
112
+ "px-3 py-1.5 text-sm font-medium rounded-xl cursor-pointer transition-colors",
113
+ operator === item.value ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:text-foreground"
114
+ ),
115
+ children: item.label
116
+ },
117
+ item.value
118
+ ))
119
+ ] }),
120
+ /* @__PURE__ */ jsx("div", { className: "p-3", children: isRange ? /* @__PURE__ */ jsx(
121
+ Calendar,
122
+ {
123
+ mode: "range",
124
+ numberOfMonths: 2,
125
+ locale: pt,
126
+ captionLayout: "dropdown",
127
+ startMonth: CALENDAR_START,
128
+ endMonth: CALENDAR_END,
129
+ defaultMonth: toDate(draft.start),
130
+ selected: {
131
+ from: toDate(draft.start),
132
+ to: toDate(draft.end)
133
+ },
134
+ onSelect: (range) => setDraft({
135
+ ...draft,
136
+ start: toIso(range?.from),
137
+ end: toIso(range?.to)
138
+ })
139
+ }
140
+ ) : /* @__PURE__ */ jsx(
141
+ Calendar,
142
+ {
143
+ mode: "single",
144
+ locale: pt,
145
+ captionLayout: "dropdown",
146
+ startMonth: CALENDAR_START,
147
+ endMonth: CALENDAR_END,
148
+ defaultMonth: toDate(draft.start),
149
+ selected: toDate(draft.start),
150
+ onSelect: (date) => setDraft({ ...draft, start: toIso(date) })
151
+ }
152
+ ) }),
153
+ /* @__PURE__ */ jsx("div", { className: "px-3 py-2 flex justify-end border-t", children: /* @__PURE__ */ jsx(Button, { disabled: !canApply, onClick: () => commit(draft), children: labels.apply }) })
154
+ ] });
155
+ }
156
+ var WEEK_OPTIONS = { weekStartsOn: 1 };
157
+ var subtract = {
158
+ day: subDays,
159
+ week: subWeeks,
160
+ month: subMonths,
161
+ quarter: subQuarters,
162
+ year: subYears
163
+ };
164
+ var startOf = {
165
+ day: startOfDay,
166
+ week: (date) => startOfWeek(date, WEEK_OPTIONS),
167
+ month: startOfMonth,
168
+ quarter: startOfQuarter,
169
+ year: startOfYear
170
+ };
171
+ var endOf = {
172
+ day: endOfDay,
173
+ week: (date) => endOfWeek(date, WEEK_OPTIONS),
174
+ month: endOfMonth,
175
+ quarter: endOfQuarter,
176
+ year: endOfYear
177
+ };
178
+ function resolveRelativeRange(unit, amount, includeCurrent, now = /* @__PURE__ */ new Date(), offsetAmount = 0, offsetUnit) {
179
+ if (amount < 1 || offsetAmount < 0) {
180
+ return null;
181
+ }
182
+ let anchor = includeCurrent ? now : subtract[unit](now, 1);
183
+ if (offsetAmount > 0) {
184
+ anchor = subtract[offsetUnit ?? unit](anchor, offsetAmount);
185
+ }
186
+ return {
187
+ start: startOf[unit](subtract[unit](anchor, amount - 1)),
188
+ end: endOf[unit](anchor)
189
+ };
190
+ }
191
+ function formatRangePreview(range) {
192
+ const sameYear = isSameYear(range.start, range.end);
193
+ const startPattern = sameYear ? "d MMM" : "d MMM yyyy";
194
+ return `${format(range.start, startPattern, { locale: pt })} - ${format(range.end, "d MMM yyyy", { locale: pt })}`;
195
+ }
196
+ function DateFilterRelativePanel() {
197
+ const { draft, units, labels, setDraft, backToRoot, commit } = useDateFilter();
198
+ const hasOffset = (draft.offset_amount ?? 0) > 0;
199
+ return /* @__PURE__ */ jsxs("div", { className: "w-96", children: [
200
+ /* @__PURE__ */ jsxs("div", { className: "gap-2 px-2 py-2 flex items-center border-b", children: [
201
+ /* @__PURE__ */ jsx(
202
+ Button,
203
+ {
204
+ variant: "ghost",
205
+ size: "icon",
206
+ onClick: backToRoot,
207
+ "aria-label": labels.back,
208
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4" })
209
+ }
210
+ ),
211
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: labels.relativeTitle })
212
+ ] }),
213
+ /* @__PURE__ */ jsxs("div", { className: "gap-3 p-3 flex flex-col", children: [
214
+ /* @__PURE__ */ jsxs("div", { className: "gap-2 flex items-center", children: [
215
+ /* @__PURE__ */ jsx("span", { className: "text-sm", children: labels.latest }),
216
+ /* @__PURE__ */ jsx(
217
+ Input,
218
+ {
219
+ type: "number",
220
+ min: 1,
221
+ max: 120,
222
+ value: draft.amount ?? 1,
223
+ onChange: (event) => setDraft({
224
+ ...draft,
225
+ amount: Number(event.target.value)
226
+ }),
227
+ className: "w-20"
228
+ }
229
+ ),
230
+ /* @__PURE__ */ jsxs(
231
+ Select,
232
+ {
233
+ value: draft.unit ?? "month",
234
+ onValueChange: (unit) => setDraft({ ...draft, unit }),
235
+ children: [
236
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "flex-1", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
237
+ /* @__PURE__ */ jsx(SelectContent, { children: units.map((unit) => /* @__PURE__ */ jsx(SelectItem, { value: unit.value, children: unit.label }, unit.value)) })
238
+ ]
239
+ }
240
+ ),
241
+ !hasOffset && /* @__PURE__ */ jsx(
242
+ Button,
243
+ {
244
+ variant: "ghost",
245
+ size: "icon",
246
+ "aria-label": labels.startingAgo,
247
+ title: labels.startingAgo,
248
+ onClick: () => setDraft({
249
+ ...draft,
250
+ offset_amount: 1,
251
+ offset_unit: draft.unit ?? "month"
252
+ }),
253
+ children: /* @__PURE__ */ jsx(ArrowLeftToLine, { className: "size-4" })
254
+ }
255
+ )
256
+ ] }),
257
+ hasOffset && /* @__PURE__ */ jsxs("div", { className: "gap-2 flex items-center", children: [
258
+ /* @__PURE__ */ jsx("span", { className: "text-sm", children: labels.startingAgo }),
259
+ /* @__PURE__ */ jsx(
260
+ Input,
261
+ {
262
+ type: "number",
263
+ min: 1,
264
+ max: 120,
265
+ value: draft.offset_amount ?? 1,
266
+ onChange: (event) => setDraft({
267
+ ...draft,
268
+ offset_amount: Number(event.target.value)
269
+ }),
270
+ className: "w-20"
271
+ }
272
+ ),
273
+ /* @__PURE__ */ jsxs(
274
+ Select,
275
+ {
276
+ value: draft.offset_unit ?? draft.unit ?? "month",
277
+ onValueChange: (unit) => setDraft({
278
+ ...draft,
279
+ offset_unit: unit
280
+ }),
281
+ children: [
282
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "flex-1", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
283
+ /* @__PURE__ */ jsx(SelectContent, { children: units.map((unit) => /* @__PURE__ */ jsx(
284
+ SelectItem,
285
+ {
286
+ value: unit.value,
287
+ children: unit.label
288
+ },
289
+ unit.value
290
+ )) })
291
+ ]
292
+ }
293
+ ),
294
+ /* @__PURE__ */ jsx(
295
+ Button,
296
+ {
297
+ variant: "ghost",
298
+ size: "icon",
299
+ "aria-label": labels.removeOffset,
300
+ onClick: () => setDraft({
301
+ ...draft,
302
+ offset_amount: void 0,
303
+ offset_unit: void 0
304
+ }),
305
+ children: /* @__PURE__ */ jsx(X, { className: "size-4" })
306
+ }
307
+ )
308
+ ] }),
309
+ /* @__PURE__ */ jsxs("label", { className: "text-sm flex items-center justify-between", children: [
310
+ labels.includeCurrent,
311
+ /* @__PURE__ */ jsx(
312
+ Switch,
313
+ {
314
+ checked: draft.include_current ?? false,
315
+ onCheckedChange: (checked) => setDraft({ ...draft, include_current: checked })
316
+ }
317
+ )
318
+ ] })
319
+ ] }),
320
+ /* @__PURE__ */ jsxs("div", { className: "gap-3 px-3 py-2 flex items-center justify-between border-t", children: [
321
+ /* @__PURE__ */ jsx(RangePreview, { draft }),
322
+ /* @__PURE__ */ jsx(
323
+ Button,
324
+ {
325
+ disabled: !draft.unit || !draft.amount || draft.amount < 1,
326
+ onClick: () => commit(draft),
327
+ children: labels.apply
328
+ }
329
+ )
330
+ ] })
331
+ ] });
332
+ }
333
+ function RangePreview({ draft }) {
334
+ const range = resolveRelativeRange(
335
+ draft.unit ?? "month",
336
+ draft.amount ?? 0,
337
+ draft.include_current ?? false,
338
+ /* @__PURE__ */ new Date(),
339
+ draft.offset_amount ?? 0,
340
+ draft.offset_unit
341
+ );
342
+ if (!range) {
343
+ return /* @__PURE__ */ jsx("span", {});
344
+ }
345
+ return /* @__PURE__ */ jsxs("span", { className: "gap-2 text-sm flex items-center text-muted-foreground", children: [
346
+ /* @__PURE__ */ jsx(CalendarDays, { className: "size-4" }),
347
+ formatRangePreview(range)
348
+ ] });
349
+ }
350
+ var readable = (value) => value ? format(/* @__PURE__ */ new Date(`${value}T00:00:00`), "d 'de' MMMM 'de' yyyy", {
351
+ locale: pt
352
+ }) : "";
353
+ function summariseDateFilter(value, presets, operators, units, labels) {
354
+ if (value.mode === "preset") {
355
+ return presets.find((preset) => preset.value === value.preset)?.label ?? labels.fallback;
356
+ }
357
+ if (value.mode === "fixed") {
358
+ const operator = operators.find((item) => item.value === value.operator)?.label ?? "";
359
+ return value.operator === "between" ? `${readable(value.start)} - ${readable(value.end)}` : `${operator} ${readable(value.start)}`;
360
+ }
361
+ if (value.mode === "relative") {
362
+ const unitLabel = (unit) => units.find((item) => item.value === unit)?.label ?? "";
363
+ const base = `${labels.latest} ${value.amount ?? 1} ${unitLabel(value.unit)}`;
364
+ return (value.offset_amount ?? 0) > 0 ? `${base}, ${labels.ago} ${value.offset_amount} ${unitLabel(value.offset_unit ?? value.unit)}` : base;
365
+ }
366
+ return labels.all;
367
+ }
368
+
369
+ // src/blocks/date-filter/types.ts
370
+ var DEFAULT_PRESETS = [
371
+ { value: "today", label: "Today" },
372
+ { value: "yesterday", label: "Yesterday" },
373
+ { value: "previous_week", label: "Previous week" },
374
+ { value: "previous_7_days", label: "Last 7 days" },
375
+ { value: "previous_30_days", label: "Last 30 days" },
376
+ { value: "previous_month", label: "Previous month" },
377
+ { value: "previous_3_months", label: "Last 3 months" },
378
+ { value: "previous_12_months", label: "Last 12 months" }
379
+ ];
380
+ var DEFAULT_OPERATORS = [
381
+ { value: "between", label: "Between" },
382
+ { value: "before", label: "Before" },
383
+ { value: "on", label: "On" },
384
+ { value: "after", label: "After" }
385
+ ];
386
+ var DEFAULT_UNITS = [
387
+ { value: "day", label: "days" },
388
+ { value: "week", label: "weeks" },
389
+ { value: "month", label: "months" },
390
+ { value: "quarter", label: "quarters" },
391
+ { value: "year", label: "years" }
392
+ ];
393
+ var DEFAULT_LABELS = {
394
+ all: "All time",
395
+ fixed: "Fixed range...",
396
+ relative: "Relative range...",
397
+ relativeTitle: "Relative range",
398
+ apply: "Apply filter",
399
+ back: "Back",
400
+ latest: "Last",
401
+ ago: "offset",
402
+ includeCurrent: "Include current period",
403
+ startingAgo: "Starting",
404
+ removeOffset: "Remove offset",
405
+ fallback: "Date"
406
+ };
407
+ function DateFilter({
408
+ value,
409
+ onChange,
410
+ presets = DEFAULT_PRESETS,
411
+ operators = DEFAULT_OPERATORS,
412
+ units = DEFAULT_UNITS,
413
+ labels,
414
+ children
415
+ }) {
416
+ const [open, setOpen] = useState(false);
417
+ const [panel, setPanel] = useState("root");
418
+ const [draft, setDraft] = useState(value);
419
+ const context = {
420
+ value,
421
+ draft,
422
+ labels: { ...DEFAULT_LABELS, ...labels },
423
+ presets,
424
+ operators,
425
+ units,
426
+ panel,
427
+ setDraft,
428
+ openPanel: (next, seed) => {
429
+ setDraft(seed);
430
+ setPanel(next);
431
+ },
432
+ backToRoot: () => setPanel("root"),
433
+ commit: (next) => {
434
+ onChange(next);
435
+ setOpen(false);
436
+ setPanel("root");
437
+ }
438
+ };
439
+ return /* @__PURE__ */ jsx(DateFilterProvider, { value: context, children: /* @__PURE__ */ jsx(
440
+ Popover,
441
+ {
442
+ open,
443
+ onOpenChange: (next) => {
444
+ setOpen(next);
445
+ if (!next) {
446
+ setPanel("root");
447
+ }
448
+ },
449
+ children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
450
+ /* @__PURE__ */ jsx(DateFilterTrigger, {}),
451
+ /* @__PURE__ */ jsxs(DateFilterContent, { children: [
452
+ /* @__PURE__ */ jsx(DateFilterAll, {}),
453
+ /* @__PURE__ */ jsx(DateFilterSeparator, {}),
454
+ /* @__PURE__ */ jsx(DateFilterPresets, {}),
455
+ /* @__PURE__ */ jsx(DateFilterSeparator, {}),
456
+ /* @__PURE__ */ jsx(DateFilterFixed, {}),
457
+ /* @__PURE__ */ jsx(DateFilterRelative, {})
458
+ ] })
459
+ ] })
460
+ }
461
+ ) });
462
+ }
463
+ function DateFilterTrigger({
464
+ className,
465
+ children
466
+ }) {
467
+ const { value, presets, operators, units, labels } = useDateFilter();
468
+ return /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
469
+ Button,
470
+ {
471
+ variant: "outline",
472
+ className: cn("gap-2 justify-start", className),
473
+ children: [
474
+ /* @__PURE__ */ jsx(CalendarDays, { className: "size-4" }),
475
+ children ?? summariseDateFilter(
476
+ value,
477
+ presets,
478
+ operators,
479
+ units,
480
+ labels
481
+ )
482
+ ]
483
+ }
484
+ ) });
485
+ }
486
+ function DateFilterContent({
487
+ children,
488
+ className
489
+ }) {
490
+ const { panel } = useDateFilter();
491
+ return /* @__PURE__ */ jsxs(PopoverContent, { align: "start", className: cn("p-0 w-auto", className), children: [
492
+ panel === "root" && /* @__PURE__ */ jsx("div", { className: "w-64 py-2 flex flex-col", children }),
493
+ panel === "fixed" && /* @__PURE__ */ jsx(DateFilterFixedPanel, {}),
494
+ panel === "relative" && /* @__PURE__ */ jsx(DateFilterRelativePanel, {})
495
+ ] });
496
+ }
497
+ function DateFilterItem({
498
+ active,
499
+ onSelect,
500
+ children
501
+ }) {
502
+ return /* @__PURE__ */ jsx(
503
+ "button",
504
+ {
505
+ type: "button",
506
+ onClick: onSelect,
507
+ className: cn(
508
+ "px-4 py-2 text-sm font-medium cursor-pointer text-left transition-colors",
509
+ active ? "bg-accent text-accent-foreground" : "text-foreground hover:bg-accent/50"
510
+ ),
511
+ children
512
+ }
513
+ );
514
+ }
515
+ function DateFilterSeparator() {
516
+ return /* @__PURE__ */ jsx("div", { className: "my-2 border-t" });
517
+ }
518
+ function DateFilterAll({ children }) {
519
+ const { value, labels, commit } = useDateFilter();
520
+ return /* @__PURE__ */ jsx(
521
+ DateFilterItem,
522
+ {
523
+ active: value.mode === "all",
524
+ onSelect: () => commit({ mode: "all" }),
525
+ children: children ?? labels.all
526
+ }
527
+ );
528
+ }
529
+ function DateFilterPresets({ only }) {
530
+ const { value, presets, commit } = useDateFilter();
531
+ const visible = only ? presets.filter((preset) => only.includes(preset.value)) : presets;
532
+ return /* @__PURE__ */ jsx(Fragment, { children: visible.map((preset) => /* @__PURE__ */ jsx(
533
+ DateFilterItem,
534
+ {
535
+ active: value.mode === "preset" && value.preset === preset.value,
536
+ onSelect: () => commit({ mode: "preset", preset: preset.value }),
537
+ children: preset.label
538
+ },
539
+ preset.value
540
+ )) });
541
+ }
542
+ function DateFilterFixed({ children }) {
543
+ const { value, labels, openPanel } = useDateFilter();
544
+ return /* @__PURE__ */ jsx(
545
+ DateFilterItem,
546
+ {
547
+ active: value.mode === "fixed",
548
+ onSelect: () => openPanel("fixed", {
549
+ mode: "fixed",
550
+ operator: value.operator ?? "between",
551
+ start: value.start,
552
+ end: value.end
553
+ }),
554
+ children: children ?? labels.fixed
555
+ }
556
+ );
557
+ }
558
+ function DateFilterRelative({ children }) {
559
+ const { value, labels, openPanel } = useDateFilter();
560
+ return /* @__PURE__ */ jsx(
561
+ DateFilterItem,
562
+ {
563
+ active: value.mode === "relative",
564
+ onSelect: () => openPanel("relative", {
565
+ mode: "relative",
566
+ unit: value.unit ?? "month",
567
+ amount: value.amount ?? 3,
568
+ include_current: value.include_current ?? false
569
+ }),
570
+ children: children ?? labels.relative
571
+ }
572
+ );
573
+ }
574
+
575
+ // src/blocks/date-filter/encode.ts
576
+ var RANGE_SEPARATOR = "~";
577
+ var RELATIVE_PREFIX = "previous";
578
+ function encodeDateFilter(filter) {
579
+ if (filter.mode === "preset") {
580
+ return filter.preset ?? null;
581
+ }
582
+ if (filter.mode === "fixed") {
583
+ return encodeFixed(filter);
584
+ }
585
+ if (filter.mode === "relative") {
586
+ return encodeRelative(filter);
587
+ }
588
+ return null;
589
+ }
590
+ function encodeFixed(filter) {
591
+ if (!filter.start) {
592
+ return null;
593
+ }
594
+ if (filter.operator === "before") {
595
+ return `${RANGE_SEPARATOR}${filter.start}`;
596
+ }
597
+ if (filter.operator === "after") {
598
+ return `${filter.start}${RANGE_SEPARATOR}`;
599
+ }
600
+ if (filter.operator === "on") {
601
+ return filter.start;
602
+ }
603
+ return filter.end ? `${filter.start}${RANGE_SEPARATOR}${filter.end}` : null;
604
+ }
605
+ function encodeRelative(filter) {
606
+ if (!filter.unit || !filter.amount || filter.amount < 1) {
607
+ return null;
608
+ }
609
+ let value = `${RELATIVE_PREFIX}:${filter.amount}:${filter.unit}`;
610
+ if (filter.include_current) {
611
+ value += ":current";
612
+ }
613
+ if (filter.offset_amount && filter.offset_amount > 0) {
614
+ value += `:offset:${filter.offset_amount}:${filter.offset_unit ?? filter.unit}`;
615
+ }
616
+ return value;
617
+ }
618
+ function InfoField({
619
+ icon: Icon,
620
+ label,
621
+ value,
622
+ iconClassName,
623
+ className
624
+ }) {
625
+ return /* @__PURE__ */ jsxs("div", { className: cn("gap-3 flex items-center", className), children: [
626
+ /* @__PURE__ */ jsx("div", { className: cn("p-2 rounded-xl bg-muted", iconClassName), children: /* @__PURE__ */ jsx(Icon, { className: "size-5 text-muted-foreground" }) }),
627
+ /* @__PURE__ */ jsxs("div", { children: [
628
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium tracking-wider text-muted-foreground uppercase", children: label }),
629
+ /* @__PURE__ */ jsx("p", { className: "font-semibold", children: value })
630
+ ] })
631
+ ] });
632
+ }
633
+ function InfoFieldGroup({ children, className }) {
634
+ return /* @__PURE__ */ jsx("div", { className: cn("space-y-4", className), children });
635
+ }
636
+ function LocalizedFields({
637
+ locales,
638
+ localeLabels,
639
+ fields,
640
+ values,
641
+ onChange,
642
+ errors,
643
+ defaultLocale,
644
+ className
645
+ }) {
646
+ return /* @__PURE__ */ jsxs(
647
+ Tabs,
648
+ {
649
+ defaultValue: defaultLocale ?? locales[0],
650
+ className: cn("w-full", className),
651
+ children: [
652
+ /* @__PURE__ */ jsx(TabsList, { className: "mb-4 rounded-2xl p-1.5 flex h-auto w-full justify-start overflow-x-auto bg-muted", children: locales.map((locale) => /* @__PURE__ */ jsx(
653
+ TabsTrigger,
654
+ {
655
+ value: locale,
656
+ className: "px-4 font-medium h-8 rounded-xl",
657
+ children: localeLabels?.[locale] ?? locale.toUpperCase()
658
+ },
659
+ locale
660
+ )) }),
661
+ locales.map((locale) => /* @__PURE__ */ jsx(TabsContent, { value: locale, className: "mt-0", children: /* @__PURE__ */ jsx("div", { className: "gap-4 lg:grid-cols-2 grid", children: fields.map((field) => {
662
+ const fieldId = `${field.name}-${locale}`;
663
+ const error = errors?.[`${field.name}.${locale}`];
664
+ const value = values[field.name]?.[locale] ?? "";
665
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
666
+ /* @__PURE__ */ jsx(Label, { htmlFor: fieldId, children: field.label }),
667
+ field.type === "textarea" ? /* @__PURE__ */ jsx(
668
+ Textarea,
669
+ {
670
+ id: fieldId,
671
+ rows: field.rows ?? 4,
672
+ value,
673
+ onChange: (event) => onChange(
674
+ field.name,
675
+ locale,
676
+ event.target.value
677
+ )
678
+ }
679
+ ) : /* @__PURE__ */ jsx(
680
+ Input,
681
+ {
682
+ id: fieldId,
683
+ value,
684
+ onChange: (event) => onChange(
685
+ field.name,
686
+ locale,
687
+ event.target.value
688
+ )
689
+ }
690
+ ),
691
+ error && /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-destructive", children: error })
692
+ ] }, field.name);
693
+ }) }) }, locale))
694
+ ]
695
+ }
696
+ );
697
+ }
698
+ function SectionHeader({
699
+ icon,
700
+ title,
701
+ description,
702
+ control,
703
+ className
704
+ }) {
705
+ return /* @__PURE__ */ jsxs(
706
+ "div",
707
+ {
708
+ className: cn(
709
+ "gap-4 sm:flex-row sm:items-center sm:justify-between flex flex-col",
710
+ className
711
+ ),
712
+ children: [
713
+ /* @__PURE__ */ jsxs("div", { className: "gap-3 flex items-center", children: [
714
+ icon,
715
+ /* @__PURE__ */ jsxs("div", { children: [
716
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold text-foreground", children: title }),
717
+ description && /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-muted-foreground", children: description })
718
+ ] })
719
+ ] }),
720
+ control
721
+ ]
722
+ }
723
+ );
724
+ }
725
+ function SettingsCard({
726
+ icon: Icon,
727
+ iconClassName,
728
+ title,
729
+ description,
730
+ control,
731
+ children,
732
+ inset = false,
733
+ className
734
+ }) {
735
+ return /* @__PURE__ */ jsxs(
736
+ "section",
737
+ {
738
+ "data-slot": "settings-card",
739
+ "data-inset": inset || void 0,
740
+ className: cn(
741
+ elevatedSurface,
742
+ "space-y-5 p-6 bg-card/60 text-card-foreground",
743
+ inset && recessedSurface,
744
+ className
745
+ ),
746
+ children: [
747
+ /* @__PURE__ */ jsxs("div", { className: "gap-4 sm:flex-row sm:items-center sm:justify-between flex flex-col", children: [
748
+ /* @__PURE__ */ jsxs("div", { className: "gap-3 flex items-center", children: [
749
+ /* @__PURE__ */ jsx(
750
+ "div",
751
+ {
752
+ className: cn(
753
+ "size-11 rounded-2xl flex items-center justify-center",
754
+ iconClassName ?? "bg-muted text-muted-foreground"
755
+ ),
756
+ children: /* @__PURE__ */ jsx(Icon, { className: "size-5" })
757
+ }
758
+ ),
759
+ /* @__PURE__ */ jsxs("div", { children: [
760
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold text-foreground", children: title }),
761
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-muted-foreground", children: description })
762
+ ] })
763
+ ] }),
764
+ control
765
+ ] }),
766
+ children
767
+ ]
768
+ }
769
+ );
770
+ }
771
+ function SettingsPanel({
772
+ title,
773
+ description,
774
+ children,
775
+ inset = true,
776
+ className
777
+ }) {
778
+ return /* @__PURE__ */ jsxs(
779
+ "div",
780
+ {
781
+ "data-slot": "settings-panel",
782
+ "data-inset": inset || void 0,
783
+ className: cn(
784
+ elevatedSurface,
785
+ "space-y-4 p-5 bg-card/60 text-card-foreground",
786
+ inset && recessedSurface,
787
+ className
788
+ ),
789
+ children: [
790
+ (title || description) && /* @__PURE__ */ jsxs("div", { children: [
791
+ title && /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-foreground", children: title }),
792
+ description && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs font-medium text-muted-foreground", children: description })
793
+ ] }),
794
+ children
795
+ ]
796
+ }
797
+ );
798
+ }
799
+ function ToggleRow({
800
+ id,
801
+ label,
802
+ description,
803
+ checked,
804
+ onChange,
805
+ disabled
806
+ }) {
807
+ return /* @__PURE__ */ jsxs("div", { className: "gap-4 py-1 flex items-center justify-between", children: [
808
+ /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
809
+ /* @__PURE__ */ jsx(
810
+ Label,
811
+ {
812
+ htmlFor: id,
813
+ className: "text-sm font-medium text-foreground",
814
+ children: label
815
+ }
816
+ ),
817
+ description && /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: description })
818
+ ] }),
819
+ /* @__PURE__ */ jsx(
820
+ Switch,
821
+ {
822
+ id,
823
+ checked,
824
+ onCheckedChange: onChange,
825
+ disabled
826
+ }
827
+ )
828
+ ] });
829
+ }
830
+ function resolveTrend(trend) {
831
+ if (trend === null || trend === void 0 || Number.isNaN(trend)) {
832
+ return null;
833
+ }
834
+ const normalized = Math.abs(trend) < 0.05 ? 0 : trend;
835
+ if (normalized === 0) {
836
+ return { label: "0%", tone: "neutral", icon: ArrowRight };
837
+ }
838
+ if (normalized > 0) {
839
+ return {
840
+ label: `+${normalized.toFixed(1)}%`,
841
+ tone: "positive",
842
+ icon: ArrowUpRight
843
+ };
844
+ }
845
+ return {
846
+ label: `${normalized.toFixed(1)}%`,
847
+ tone: "negative",
848
+ icon: ArrowDownRight
849
+ };
850
+ }
851
+ var trendToneClass = {
852
+ positive: "text-success",
853
+ negative: "text-destructive",
854
+ neutral: "text-muted-foreground"
855
+ };
856
+ function StatCard({
857
+ title,
858
+ value,
859
+ icon: Icon,
860
+ iconClassName = "bg-muted text-muted-foreground",
861
+ trend,
862
+ comparisonLabel,
863
+ inset = false,
864
+ className
865
+ }) {
866
+ const resolvedTrend = resolveTrend(trend);
867
+ return /* @__PURE__ */ jsxs(
868
+ "div",
869
+ {
870
+ "data-slot": "stat-card",
871
+ "data-inset": inset || void 0,
872
+ className: cn(
873
+ elevatedSurface,
874
+ "p-6 min-w-0 flex h-full flex-col justify-between bg-card/60 text-card-foreground",
875
+ inset && recessedSurface,
876
+ className
877
+ ),
878
+ children: [
879
+ /* @__PURE__ */ jsxs("div", { className: "mb-4 gap-4 flex items-start justify-between", children: [
880
+ /* @__PURE__ */ jsx("div", { className: cn("rounded-2xl p-3 shrink-0", iconClassName), children: /* @__PURE__ */ jsx(Icon, { className: "size-6" }) }),
881
+ resolvedTrend && /* @__PURE__ */ jsxs("div", { className: "min-w-0 text-right", children: [
882
+ /* @__PURE__ */ jsxs(
883
+ "span",
884
+ {
885
+ className: cn(
886
+ "text-sm font-semibold inline-flex items-center",
887
+ trendToneClass[resolvedTrend.tone]
888
+ ),
889
+ children: [
890
+ resolvedTrend.label,
891
+ /* @__PURE__ */ jsx(resolvedTrend.icon, { className: "ml-1 size-4" })
892
+ ]
893
+ }
894
+ ),
895
+ comparisonLabel && /* @__PURE__ */ jsx("p", { className: "mt-1 font-medium tracking-wider text-[10px] whitespace-nowrap text-muted-foreground uppercase", children: comparisonLabel })
896
+ ] })
897
+ ] }),
898
+ /* @__PURE__ */ jsxs("div", { children: [
899
+ /* @__PURE__ */ jsx("p", { className: "mb-1 text-xs font-medium tracking-wider break-words text-muted-foreground uppercase", children: title }),
900
+ /* @__PURE__ */ jsx("p", { className: "text-3xl font-bold truncate text-foreground tabular-nums", children: value })
901
+ ] })
902
+ ]
903
+ }
904
+ );
905
+ }
906
+ function StatsGrid({ children, className }) {
907
+ return /* @__PURE__ */ jsx(
908
+ "div",
909
+ {
910
+ className: cn(
911
+ "gap-6 [&>*]:min-w-0 grid [grid-template-columns:repeat(auto-fit,minmax(18rem,1fr))]",
912
+ className
913
+ ),
914
+ children
915
+ }
916
+ );
917
+ }
918
+
919
+ export { CommandPalette, DEFAULT_LABELS as DATE_FILTER_LABELS, DEFAULT_OPERATORS as DATE_FILTER_OPERATORS, DEFAULT_PRESETS as DATE_FILTER_PRESETS, DEFAULT_UNITS as DATE_FILTER_UNITS, DateFilter, DateFilterAll, DateFilterContent, DateFilterFixed, DateFilterItem, DateFilterPresets, DateFilterRelative, DateFilterSeparator, DateFilterTrigger, InfoField, InfoFieldGroup, LocalizedFields, SectionHeader, SettingsCard, SettingsPanel, StatCard, StatsGrid, ToggleRow, encodeDateFilter, formatRangePreview, resolveRelativeRange, summariseDateFilter, useCommandPalette, useDateFilter };
920
+ //# sourceMappingURL=blocks.js.map
921
+ //# sourceMappingURL=blocks.js.map