beez-ui 0.6.2 → 0.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.
Files changed (127) hide show
  1. package/CHANGELOG.md +150 -96
  2. package/README.md +33 -6
  3. package/dist/components/account-menu.d.ts +53 -0
  4. package/dist/components/account-menu.js +405 -0
  5. package/dist/components/alert.js +1 -1
  6. package/dist/components/animated-collapse.d.ts +19 -0
  7. package/dist/components/animated-collapse.js +81 -0
  8. package/dist/components/animated-count.d.ts +13 -0
  9. package/dist/components/animated-count.js +105 -0
  10. package/dist/components/animated-list-item.d.ts +25 -0
  11. package/dist/components/animated-list-item.js +132 -0
  12. package/dist/components/avatar.d.ts +2 -1
  13. package/dist/components/avatar.js +4 -3
  14. package/dist/components/bouncing-dots-loader.d.ts +19 -0
  15. package/dist/components/bouncing-dots-loader.js +47 -0
  16. package/dist/components/calendar.js +102 -0
  17. package/dist/components/carousel.js +61 -48
  18. package/dist/components/checkbox.js +1 -1
  19. package/dist/components/confirm-delete-button.d.ts +29 -0
  20. package/dist/components/confirm-delete-button.js +254 -0
  21. package/dist/components/data-table.js +34 -4
  22. package/dist/components/dropdown-menu.js +5 -2
  23. package/dist/components/empty-state.d.ts +24 -0
  24. package/dist/components/empty-state.js +37 -0
  25. package/dist/components/error-state.d.ts +20 -0
  26. package/dist/components/error-state.js +145 -0
  27. package/dist/components/external-browser-handoff.d.ts +27 -0
  28. package/dist/components/external-browser-handoff.js +306 -0
  29. package/dist/components/file-upload.d.ts +78 -0
  30. package/dist/components/file-upload.js +678 -0
  31. package/dist/components/filter-query-bar.js +11 -6
  32. package/dist/components/form.d.ts +5 -0
  33. package/dist/components/form.js +11 -9
  34. package/dist/components/info-popover.d.ts +23 -0
  35. package/dist/components/info-popover.js +158 -0
  36. package/dist/components/input-group.js +1 -1
  37. package/dist/components/input.js +1 -1
  38. package/dist/components/month-calendar-header.d.ts +65 -0
  39. package/dist/components/month-calendar-header.js +337 -0
  40. package/dist/components/month-grid.d.ts +59 -0
  41. package/dist/components/month-grid.js +301 -0
  42. package/dist/components/notification-bell.d.ts +50 -0
  43. package/dist/components/notification-bell.js +338 -0
  44. package/dist/components/notification-panel.d.ts +56 -0
  45. package/dist/components/notification-panel.js +281 -0
  46. package/dist/components/open-in-browser-cta.d.ts +22 -0
  47. package/dist/components/open-in-browser-cta.js +61 -0
  48. package/dist/components/pagination.d.ts +1 -0
  49. package/dist/components/pagination.js +9 -4
  50. package/dist/components/presence-swap.d.ts +22 -0
  51. package/dist/components/presence-swap.js +162 -0
  52. package/dist/components/progress-ring.d.ts +20 -0
  53. package/dist/components/progress-ring.js +53 -0
  54. package/dist/components/pwa-update-control.d.ts +18 -0
  55. package/dist/components/pwa-update-control.js +119 -0
  56. package/dist/components/radio-group.js +1 -1
  57. package/dist/components/reaction-button.d.ts +26 -0
  58. package/dist/components/reaction-button.js +114 -0
  59. package/dist/components/rich-link-editor.d.ts +25 -0
  60. package/dist/components/rich-link-editor.js +320 -0
  61. package/dist/components/rich-markdown-content.d.ts +10 -0
  62. package/dist/components/rich-markdown-content.js +56 -0
  63. package/dist/components/rich-text-content.d.ts +19 -0
  64. package/dist/components/rich-text-content.js +26 -0
  65. package/dist/components/select.js +1 -1
  66. package/dist/components/sheet.js +12 -9
  67. package/dist/components/sidebar.js +9 -5
  68. package/dist/components/skeleton.d.ts +6 -0
  69. package/dist/components/skeleton.js +7 -1
  70. package/dist/components/switch.js +1 -1
  71. package/dist/components/textarea.js +1 -1
  72. package/dist/components/typing-animation.js +42 -15
  73. package/dist/emoji-picker.d.ts +27 -0
  74. package/dist/emoji-picker.js +283 -0
  75. package/dist/hooks/use-horizontal-swipe.d.ts +23 -0
  76. package/dist/hooks/use-horizontal-swipe.js +80 -0
  77. package/dist/hooks/use-is-hydrated.d.ts +6 -0
  78. package/dist/hooks/use-is-hydrated.js +20 -0
  79. package/dist/hooks/use-minute-clock.d.ts +23 -0
  80. package/dist/hooks/use-minute-clock.js +147 -0
  81. package/dist/hooks/use-month-transition-direction.d.ts +21 -0
  82. package/dist/hooks/use-month-transition-direction.js +110 -0
  83. package/dist/hooks/use-rich-link-editor.d.ts +55 -0
  84. package/dist/hooks/use-rich-link-editor.js +515 -0
  85. package/dist/hooks/use-viewer-time-zone.d.ts +6 -0
  86. package/dist/hooks/use-viewer-time-zone.js +20 -0
  87. package/dist/index.d.ts +44 -0
  88. package/dist/index.js +43 -0
  89. package/dist/lib/browser-clipboard.d.ts +13 -0
  90. package/dist/lib/browser-clipboard.js +48 -0
  91. package/dist/lib/browser-navigation.d.ts +21 -0
  92. package/dist/lib/browser-navigation.js +42 -0
  93. package/dist/lib/column-filter-value.d.ts +12 -0
  94. package/dist/lib/column-filter-value.js +52 -0
  95. package/dist/lib/file-acceptance.d.ts +43 -0
  96. package/dist/lib/file-acceptance.js +107 -0
  97. package/dist/lib/format-file-size.d.ts +16 -0
  98. package/dist/lib/format-file-size.js +34 -0
  99. package/dist/lib/fuzzy-search.d.ts +17 -0
  100. package/dist/lib/fuzzy-search.js +183 -0
  101. package/dist/lib/horizontal-swipe.d.ts +23 -0
  102. package/dist/lib/horizontal-swipe.js +29 -0
  103. package/dist/lib/in-app-browser.d.ts +35 -0
  104. package/dist/lib/in-app-browser.js +80 -0
  105. package/dist/lib/month-grid.d.ts +42 -0
  106. package/dist/lib/month-grid.js +74 -0
  107. package/dist/lib/name-initials.d.ts +7 -0
  108. package/dist/lib/name-initials.js +11 -0
  109. package/dist/lib/rich-text/link-markdown-constants.d.ts +147 -0
  110. package/dist/lib/rich-text/link-markdown-constants.js +458 -0
  111. package/dist/lib/rich-text/link-markdown-types.d.ts +53 -0
  112. package/dist/lib/rich-text/link-markdown-types.js +1 -0
  113. package/dist/lib/rich-text/link-markdown.d.ts +193 -0
  114. package/dist/lib/rich-text/link-markdown.js +592 -0
  115. package/dist/lib/rich-text/rich-markdown.d.ts +41 -0
  116. package/dist/lib/rich-text/rich-markdown.js +105 -0
  117. package/dist/lib/rich-text/rich-text-editor-dom.d.ts +26 -0
  118. package/dist/lib/rich-text/rich-text-editor-dom.js +79 -0
  119. package/dist/motion/glide-indicator.d.ts +37 -3
  120. package/dist/motion/glide-indicator.js +37 -3
  121. package/dist/motion/motion-slot.js +47 -18
  122. package/dist/motion/surface-keyframes.js +18 -6
  123. package/dist/motion/tokens.d.ts +41 -2
  124. package/dist/motion/tokens.js +40 -1
  125. package/dist/providers/beez-ui-provider.js +49 -17
  126. package/dist/styles.css +1 -1
  127. package/package.json +212 -2
@@ -49,6 +49,11 @@ const FormField = (t0) => {
49
49
  }
50
50
  return t3;
51
51
  };
52
+ /**
53
+ * Reads the field and item context shared by the form parts.
54
+ * @returns Accessible ids plus the react-hook-form state of the enclosing field.
55
+ * @throws When used outside `<FormField>`, where no field name is available.
56
+ */
52
57
  const useFormField = () => {
53
58
  const $ = _c(13);
54
59
  const fieldContext = React.useContext(FormFieldContext);
@@ -63,6 +68,9 @@ const useFormField = () => {
63
68
  t0 = $[1];
64
69
  }
65
70
  const formState = useFormState(t0);
71
+ if (!fieldContext.name) {
72
+ throw new Error("useFormField: must be used within <FormField>, which provides the field name");
73
+ }
66
74
  let t1;
67
75
  if ($[2] !== fieldContext.name || $[3] !== formState || $[4] !== getFieldState) {
68
76
  t1 = getFieldState(fieldContext.name, formState);
@@ -74,9 +82,6 @@ const useFormField = () => {
74
82
  t1 = $[5];
75
83
  }
76
84
  const fieldState = t1;
77
- if (!fieldContext) {
78
- throw new Error("useFormField should be used within <FormField>");
79
- }
80
85
  const { id } = itemContext;
81
86
  const t2 = `${id}-form-item`;
82
87
  const t3 = `${id}-form-item-description`;
@@ -149,12 +154,9 @@ function FormItem(t0) {
149
154
  }
150
155
  let t4;
151
156
  if ($[10] !== t1 || $[11] !== t3) {
152
- t4 = /* @__PURE__ */ _jsx(MotionSlot, {
153
- kind: "field",
154
- children: /* @__PURE__ */ _jsx(FormItemContext.Provider, {
155
- value: t1,
156
- children: t3
157
- })
157
+ t4 = /* @__PURE__ */ _jsx(FormItemContext.Provider, {
158
+ value: t1,
159
+ children: t3
158
160
  });
159
161
  $[10] = t1;
160
162
  $[11] = t3;
@@ -0,0 +1,23 @@
1
+ /** Icon button that toggles a short explanatory message, for inline help next to labels. */
2
+ import { type ReactNode } from "react";
3
+ export interface InfoPopoverLabels {
4
+ /** Accessible name of the close button inside the message. */
5
+ close?: string;
6
+ /** Accessible name of the icon-only trigger. */
7
+ trigger?: string;
8
+ }
9
+ export interface InfoPopoverProps {
10
+ /** Explanation shown inside the popover. */
11
+ message: ReactNode;
12
+ labels?: InfoPopoverLabels;
13
+ side?: "top" | "right" | "bottom" | "left";
14
+ className?: string;
15
+ contentClassName?: string;
16
+ }
17
+ /**
18
+ * Renders an info icon that opens the message on click or tap, unlike a hover tooltip, so it
19
+ * works on touch devices. Escape, an outside click or the close button dismiss it.
20
+ * @param props - Message, optional labels, side and class names.
21
+ * @returns The trigger with its popover.
22
+ */
23
+ export declare function InfoPopover({ message, labels, side, className, contentClassName }: InfoPopoverProps): import("react").JSX.Element;
@@ -0,0 +1,158 @@
1
+ "use client";
2
+ import { c as _c } from "react/compiler-runtime";
3
+ /** Icon button that toggles a short explanatory message, for inline help next to labels. */
4
+ import { useState } from "react";
5
+ import { Info, X } from "lucide-react";
6
+ import { cn } from "../lib/utils.js";
7
+ import { Button } from "./button.js";
8
+ import { Popover, PopoverContent, PopoverTrigger } from "./popover.js";
9
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
+ const INFO_POPOVER_DEFAULT_LABELS = {
11
+ close: "Cerrar ayuda",
12
+ trigger: "Más información"
13
+ };
14
+ /**
15
+ * Renders an info icon that opens the message on click or tap, unlike a hover tooltip, so it
16
+ * works on touch devices. Escape, an outside click or the close button dismiss it.
17
+ * @param props - Message, optional labels, side and class names.
18
+ * @returns The trigger with its popover.
19
+ */
20
+ export function InfoPopover(t0) {
21
+ const $ = _c(25);
22
+ const { message, labels, side: t1, className, contentClassName } = t0;
23
+ const side = t1 === undefined ? "top" : t1;
24
+ let t2;
25
+ if ($[0] !== labels) {
26
+ t2 = {
27
+ ...INFO_POPOVER_DEFAULT_LABELS,
28
+ ...labels
29
+ };
30
+ $[0] = labels;
31
+ $[1] = t2;
32
+ } else {
33
+ t2 = $[1];
34
+ }
35
+ const resolvedLabels = t2;
36
+ const [isOpen, setIsOpen] = useState(false);
37
+ const t3 = resolvedLabels.trigger;
38
+ let t4;
39
+ if ($[2] !== className) {
40
+ t4 = cn("text-muted-foreground hover:text-foreground aria-expanded:text-foreground", className);
41
+ $[2] = className;
42
+ $[3] = t4;
43
+ } else {
44
+ t4 = $[3];
45
+ }
46
+ let t5;
47
+ if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
48
+ t5 = /* @__PURE__ */ _jsx(Info, {
49
+ "aria-hidden": "true",
50
+ className: "size-[0.95rem]"
51
+ });
52
+ $[4] = t5;
53
+ } else {
54
+ t5 = $[4];
55
+ }
56
+ let t6;
57
+ if ($[5] !== resolvedLabels.trigger || $[6] !== t4) {
58
+ t6 = /* @__PURE__ */ _jsx(PopoverTrigger, {
59
+ asChild: true,
60
+ children: /* @__PURE__ */ _jsx(Button, {
61
+ "data-slot": "info-popover-trigger",
62
+ "aria-label": t3,
63
+ className: t4,
64
+ size: "icon-xs",
65
+ type: "button",
66
+ variant: "ghost",
67
+ children: t5
68
+ })
69
+ });
70
+ $[5] = resolvedLabels.trigger;
71
+ $[6] = t4;
72
+ $[7] = t6;
73
+ } else {
74
+ t6 = $[7];
75
+ }
76
+ let t7;
77
+ if ($[8] !== contentClassName) {
78
+ t7 = cn("relative w-[min(20rem,calc(100vw-2rem))] py-3.5 pr-10 pl-3.5", contentClassName);
79
+ $[8] = contentClassName;
80
+ $[9] = t7;
81
+ } else {
82
+ t7 = $[9];
83
+ }
84
+ let t8;
85
+ let t9;
86
+ if ($[10] === Symbol.for("react.memo_cache_sentinel")) {
87
+ t8 = () => setIsOpen(false);
88
+ t9 = /* @__PURE__ */ _jsx(X, {
89
+ "aria-hidden": "true",
90
+ className: "size-[0.95rem]"
91
+ });
92
+ $[10] = t8;
93
+ $[11] = t9;
94
+ } else {
95
+ t8 = $[10];
96
+ t9 = $[11];
97
+ }
98
+ let t10;
99
+ if ($[12] !== resolvedLabels.close) {
100
+ t10 = /* @__PURE__ */ _jsx(Button, {
101
+ "aria-label": resolvedLabels.close,
102
+ className: "absolute top-2 right-2",
103
+ size: "icon-xs",
104
+ type: "button",
105
+ variant: "ghost",
106
+ onClick: t8,
107
+ children: t9
108
+ });
109
+ $[12] = resolvedLabels.close;
110
+ $[13] = t10;
111
+ } else {
112
+ t10 = $[13];
113
+ }
114
+ let t11;
115
+ if ($[14] !== message) {
116
+ t11 = /* @__PURE__ */ _jsx("div", {
117
+ className: "m-0 text-sm leading-normal text-foreground",
118
+ children: message
119
+ });
120
+ $[14] = message;
121
+ $[15] = t11;
122
+ } else {
123
+ t11 = $[15];
124
+ }
125
+ let t12;
126
+ if ($[16] !== side || $[17] !== t10 || $[18] !== t11 || $[19] !== t7) {
127
+ t12 = /* @__PURE__ */ _jsxs(PopoverContent, {
128
+ "data-slot": "info-popover-content",
129
+ side,
130
+ sideOffset: 10,
131
+ collisionPadding: 16,
132
+ className: t7,
133
+ children: [t10, t11]
134
+ });
135
+ $[16] = side;
136
+ $[17] = t10;
137
+ $[18] = t11;
138
+ $[19] = t7;
139
+ $[20] = t12;
140
+ } else {
141
+ t12 = $[20];
142
+ }
143
+ let t13;
144
+ if ($[21] !== isOpen || $[22] !== t12 || $[23] !== t6) {
145
+ t13 = /* @__PURE__ */ _jsxs(Popover, {
146
+ open: isOpen,
147
+ onOpenChange: setIsOpen,
148
+ children: [t6, t12]
149
+ });
150
+ $[21] = isOpen;
151
+ $[22] = t12;
152
+ $[23] = t6;
153
+ $[24] = t13;
154
+ } else {
155
+ t13 = $[24];
156
+ }
157
+ return t13;
158
+ }
@@ -23,7 +23,7 @@ function InputGroup(t0) {
23
23
  }
24
24
  let t1;
25
25
  if ($[3] !== className) {
26
- t1 = cn("group/input-group relative flex h-8 w-full min-w-0 items-center rounded-lg border border-input transition-colors outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto dark:bg-input/30 dark:has-disabled:bg-input/80 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5", className);
26
+ t1 = cn("group/input-group relative flex h-8 w-full min-w-0 items-center rounded-lg border border-input transition-[color,background-color,border-color,box-shadow] outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-disabled:bg-input/50 has-disabled:opacity-50 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto dark:bg-input/30 dark:has-disabled:bg-input/80 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5", className);
27
27
  $[3] = className;
28
28
  $[4] = t1;
29
29
  } else {
@@ -9,7 +9,7 @@ function Input({ className, type, ...props }) {
9
9
  children: /* @__PURE__ */ _jsx(InputPrimitive, {
10
10
  type,
11
11
  "data-slot": "input",
12
- className: cn("h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40", className),
12
+ className: cn("h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-[color,background-color,border-color,box-shadow] outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40", className),
13
13
  ...props
14
14
  })
15
15
  });
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Month calendar toolbar: previous/next month navigation with an animated title, a "today"
3
+ * shortcut with an optional clock label, an optional view toggle and slots for actions and filters.
4
+ */
5
+ import { type ComponentType, type ReactNode, type SVGProps } from "react";
6
+ import type { MonthTransitionDirection } from "../hooks/use-month-transition-direction.js";
7
+ export interface MonthCalendarHeaderLabels {
8
+ previousMonth: string;
9
+ nextMonth: string;
10
+ today: string;
11
+ viewMode: string;
12
+ }
13
+ /** One option of the view toggle, such as list or calendar. */
14
+ export interface MonthCalendarViewOption<TView extends string> {
15
+ value: TView;
16
+ label: string;
17
+ icon: ComponentType<SVGProps<SVGSVGElement>>;
18
+ }
19
+ /** Navigation target: a URL rendered as a link, or a callback rendered as a button. */
20
+ export type MonthCalendarNavigation = {
21
+ href: string;
22
+ onSelect?: never;
23
+ } | {
24
+ href?: never;
25
+ onSelect: () => void;
26
+ };
27
+ export interface MonthCalendarHeaderProps<TView extends string> {
28
+ /** Visible `YYYY-MM` month. */
29
+ month: string;
30
+ previousMonth: MonthCalendarNavigation;
31
+ nextMonth: MonthCalendarNavigation;
32
+ today?: MonthCalendarNavigation;
33
+ /** Title of the month. Defaults to the localized month and year, such as "Mayo de 2026". */
34
+ title?: ReactNode;
35
+ titleAs?: "h1" | "h2";
36
+ /** Side the title slides in from after a month change (see `useMonthTransitionDirection`). */
37
+ monthTransitionDirection?: MonthTransitionDirection;
38
+ /** Clock label next to the "today" shortcut, or null before hydration. */
39
+ timeLabel?: string | null;
40
+ viewOptions?: readonly MonthCalendarViewOption<TView>[];
41
+ view?: TView;
42
+ onViewChange?: (view: TView) => void;
43
+ /** Glide the active view marker; turn off while the view still follows the viewport. */
44
+ shouldAnimateView?: boolean;
45
+ actions?: ReactNode;
46
+ /** Rendered as the last row, such as filter chips. */
47
+ filters?: ReactNode;
48
+ locale?: string;
49
+ labels?: Partial<MonthCalendarHeaderLabels>;
50
+ className?: string;
51
+ }
52
+ /**
53
+ * Formats a month key as a capitalized month and year.
54
+ * @param month - `YYYY-MM` month key.
55
+ * @param locale - BCP 47 locale.
56
+ * @returns For example "Mayo de 2026".
57
+ */
58
+ export declare function formatMonthTitle(month: string, locale?: string): string;
59
+ /**
60
+ * Renders the header of a month calendar. The title is keyed by month so it replays its entrance,
61
+ * and the view marker glides between the toggle buttons.
62
+ * @param props - Month, navigation targets, optional view toggle, actions and labels.
63
+ * @returns The calendar header.
64
+ */
65
+ export declare function MonthCalendarHeader<TView extends string>({ month, previousMonth, nextMonth, today, title, titleAs: TitleElement, monthTransitionDirection, timeLabel, viewOptions, view, onViewChange, shouldAnimateView, actions, filters, locale, labels, className, }: MonthCalendarHeaderProps<TView>): import("react").JSX.Element;
@@ -0,0 +1,337 @@
1
+ "use client";
2
+ import { c as _c } from "react/compiler-runtime";
3
+ /**
4
+ * Month calendar toolbar: previous/next month navigation with an animated title, a "today"
5
+ * shortcut with an optional clock label, an optional view toggle and slots for actions and filters.
6
+ */
7
+ import { useId } from "react";
8
+ import { motion } from "motion/react";
9
+ import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
10
+ import { usePrefersReducedMotion } from "../hooks/use-prefers-reduced-motion.js";
11
+ import { cn } from "../lib/utils.js";
12
+ import { SPRING_LAYOUT } from "../motion/tokens.js";
13
+ import { Button } from "./button.js";
14
+ import { Link } from "./link.js";
15
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
16
+ const DEFAULT_LOCALE = "es-AR";
17
+ const MONTH_KEY_FIRST_DAY_SUFFIX = "-01T12:00:00Z";
18
+ /** Moves the view marker without animation (for example while the view follows the viewport). */
19
+ const INSTANT_TRANSITION = { duration: 0 };
20
+ const MONTH_CALENDAR_HEADER_DEFAULT_LABELS = {
21
+ previousMonth: "Mes anterior",
22
+ nextMonth: "Mes siguiente",
23
+ today: "Hoy",
24
+ viewMode: "Vista"
25
+ };
26
+ /**
27
+ * Formats a month key as a capitalized month and year.
28
+ * @param month - `YYYY-MM` month key.
29
+ * @param locale - BCP 47 locale.
30
+ * @returns For example "Mayo de 2026".
31
+ */
32
+ export function formatMonthTitle(month, locale = DEFAULT_LOCALE) {
33
+ const monthTitle = new Intl.DateTimeFormat(locale, {
34
+ month: "long",
35
+ year: "numeric",
36
+ timeZone: "UTC"
37
+ }).format(new Date(month + MONTH_KEY_FIRST_DAY_SUFFIX));
38
+ return monthTitle.charAt(0).toLocaleUpperCase(locale) + monthTitle.slice(1);
39
+ }
40
+ const PILL_LINK_CLASS_NAME = "inline-flex items-center justify-center rounded-full border border-border bg-background font-semibold text-foreground no-underline [-webkit-tap-highlight-color:transparent] transition-[border-color,background-color,scale] hover:border-[color-mix(in_oklab,var(--foreground)_24%,var(--border))] hover:bg-accent active:scale-[0.96] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring";
41
+ /**
42
+ * Renders a navigation target as a router link or a button.
43
+ * @param props - Target, class name, accessible name and content.
44
+ * @returns The control.
45
+ */
46
+ function NavigationControl(t0) {
47
+ const $ = _c(12);
48
+ const { navigation, className, ariaLabel, children } = t0;
49
+ if (navigation.href !== undefined) {
50
+ let t1;
51
+ if ($[0] !== ariaLabel || $[1] !== children || $[2] !== className || $[3] !== navigation.href) {
52
+ t1 = /* @__PURE__ */ _jsx(Link, {
53
+ "aria-label": ariaLabel,
54
+ className,
55
+ href: navigation.href,
56
+ children
57
+ });
58
+ $[0] = ariaLabel;
59
+ $[1] = children;
60
+ $[2] = className;
61
+ $[3] = navigation.href;
62
+ $[4] = t1;
63
+ } else {
64
+ t1 = $[4];
65
+ }
66
+ return t1;
67
+ }
68
+ let t1;
69
+ if ($[5] !== className) {
70
+ t1 = cn("cursor-pointer", className);
71
+ $[5] = className;
72
+ $[6] = t1;
73
+ } else {
74
+ t1 = $[6];
75
+ }
76
+ let t2;
77
+ if ($[7] !== ariaLabel || $[8] !== children || $[9] !== navigation.onSelect || $[10] !== t1) {
78
+ t2 = /* @__PURE__ */ _jsx("button", {
79
+ "aria-label": ariaLabel,
80
+ className: t1,
81
+ type: "button",
82
+ onClick: navigation.onSelect,
83
+ children
84
+ });
85
+ $[7] = ariaLabel;
86
+ $[8] = children;
87
+ $[9] = navigation.onSelect;
88
+ $[10] = t1;
89
+ $[11] = t2;
90
+ } else {
91
+ t2 = $[11];
92
+ }
93
+ return t2;
94
+ }
95
+ /**
96
+ * Renders the header of a month calendar. The title is keyed by month so it replays its entrance,
97
+ * and the view marker glides between the toggle buttons.
98
+ * @param props - Month, navigation targets, optional view toggle, actions and labels.
99
+ * @returns The calendar header.
100
+ */
101
+ export function MonthCalendarHeader(t0) {
102
+ const $ = _c(47);
103
+ const { month, previousMonth, nextMonth, today, title, titleAs: t1, monthTransitionDirection, timeLabel, viewOptions, view, onViewChange, shouldAnimateView: t2, actions, filters, locale: t3, labels, className } = t0;
104
+ const TitleElement = t1 === undefined ? "h1" : t1;
105
+ const shouldAnimateView = t2 === undefined ? true : t2;
106
+ const locale = t3 === undefined ? DEFAULT_LOCALE : t3;
107
+ let t4;
108
+ if ($[0] !== labels) {
109
+ t4 = {
110
+ ...MONTH_CALENDAR_HEADER_DEFAULT_LABELS,
111
+ ...labels
112
+ };
113
+ $[0] = labels;
114
+ $[1] = t4;
115
+ } else {
116
+ t4 = $[1];
117
+ }
118
+ const resolvedLabels = t4;
119
+ const shouldReduceMotion = usePrefersReducedMotion();
120
+ const viewIndicatorLayoutId = useId();
121
+ const hasToolbar = Boolean(today || timeLabel || viewOptions && viewOptions.length > 0 || actions);
122
+ const viewIndicatorTransition = shouldAnimateView && !shouldReduceMotion ? SPRING_LAYOUT : INSTANT_TRANSITION;
123
+ let t5;
124
+ if ($[2] !== className) {
125
+ t5 = cn("grid grid-cols-[minmax(0,1fr)] items-center gap-x-6 gap-y-3 border-b border-border/75 pb-4 lg:grid-cols-[auto_minmax(0,1fr)]", className);
126
+ $[2] = className;
127
+ $[3] = t5;
128
+ } else {
129
+ t5 = $[3];
130
+ }
131
+ const t6 = resolvedLabels.previousMonth;
132
+ let t7;
133
+ if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
134
+ t7 = cn(PILL_LINK_CLASS_NAME, "group/previous size-9 flex-none");
135
+ $[4] = t7;
136
+ } else {
137
+ t7 = $[4];
138
+ }
139
+ let t8;
140
+ if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
141
+ t8 = /* @__PURE__ */ _jsx(ChevronLeftIcon, {
142
+ "aria-hidden": true,
143
+ className: "size-[1.1rem] transition-transform [@media(hover:hover)_and_(pointer:fine)]:group-hover/previous:-translate-x-0.5"
144
+ });
145
+ $[5] = t8;
146
+ } else {
147
+ t8 = $[5];
148
+ }
149
+ let t9;
150
+ if ($[6] !== previousMonth || $[7] !== resolvedLabels.previousMonth) {
151
+ t9 = /* @__PURE__ */ _jsx(NavigationControl, {
152
+ ariaLabel: t6,
153
+ className: t7,
154
+ navigation: previousMonth,
155
+ children: t8
156
+ });
157
+ $[6] = previousMonth;
158
+ $[7] = resolvedLabels.previousMonth;
159
+ $[8] = t9;
160
+ } else {
161
+ t9 = $[8];
162
+ }
163
+ let t10;
164
+ if ($[9] === Symbol.for("react.memo_cache_sentinel")) {
165
+ t10 = cn("m-0 min-w-0 text-center font-display text-[clamp(1.6rem,4vw,2.25rem)] leading-[1.1] font-semibold text-foreground [overflow-wrap:anywhere] lg:px-1 lg:text-left", "data-[month-transition=next]:[--beez-month-enter-offset:10px] data-[month-transition=previous]:[--beez-month-enter-offset:-10px]", "data-[month-transition=next]:animate-[beez-month-title-enter_280ms_cubic-bezier(0.16,1,0.3,1)_both] data-[month-transition=previous]:animate-[beez-month-title-enter_280ms_cubic-bezier(0.16,1,0.3,1)_both]");
166
+ $[9] = t10;
167
+ } else {
168
+ t10 = $[9];
169
+ }
170
+ let t11;
171
+ if ($[10] !== locale || $[11] !== month || $[12] !== title) {
172
+ t11 = title ?? formatMonthTitle(month, locale);
173
+ $[10] = locale;
174
+ $[11] = month;
175
+ $[12] = title;
176
+ $[13] = t11;
177
+ } else {
178
+ t11 = $[13];
179
+ }
180
+ let t12;
181
+ if ($[14] !== TitleElement || $[15] !== month || $[16] !== monthTransitionDirection || $[17] !== t11) {
182
+ t12 = /* @__PURE__ */ _jsx(TitleElement, {
183
+ className: t10,
184
+ "data-month-transition": monthTransitionDirection,
185
+ children: t11
186
+ }, month);
187
+ $[14] = TitleElement;
188
+ $[15] = month;
189
+ $[16] = monthTransitionDirection;
190
+ $[17] = t11;
191
+ $[18] = t12;
192
+ } else {
193
+ t12 = $[18];
194
+ }
195
+ const t13 = resolvedLabels.nextMonth;
196
+ let t14;
197
+ if ($[19] === Symbol.for("react.memo_cache_sentinel")) {
198
+ t14 = cn(PILL_LINK_CLASS_NAME, "group/next size-9 flex-none");
199
+ $[19] = t14;
200
+ } else {
201
+ t14 = $[19];
202
+ }
203
+ let t15;
204
+ if ($[20] === Symbol.for("react.memo_cache_sentinel")) {
205
+ t15 = /* @__PURE__ */ _jsx(ChevronRightIcon, {
206
+ "aria-hidden": true,
207
+ className: "size-[1.1rem] transition-transform [@media(hover:hover)_and_(pointer:fine)]:group-hover/next:translate-x-0.5"
208
+ });
209
+ $[20] = t15;
210
+ } else {
211
+ t15 = $[20];
212
+ }
213
+ let t16;
214
+ if ($[21] !== nextMonth || $[22] !== resolvedLabels.nextMonth) {
215
+ t16 = /* @__PURE__ */ _jsx(NavigationControl, {
216
+ ariaLabel: t13,
217
+ className: t14,
218
+ navigation: nextMonth,
219
+ children: t15
220
+ });
221
+ $[21] = nextMonth;
222
+ $[22] = resolvedLabels.nextMonth;
223
+ $[23] = t16;
224
+ } else {
225
+ t16 = $[23];
226
+ }
227
+ let t17;
228
+ if ($[24] !== t12 || $[25] !== t16 || $[26] !== t9) {
229
+ t17 = /* @__PURE__ */ _jsxs("div", {
230
+ className: "grid min-w-0 grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-2 lg:grid-cols-[auto_auto_auto]",
231
+ children: [
232
+ t9,
233
+ t12,
234
+ t16
235
+ ]
236
+ });
237
+ $[24] = t12;
238
+ $[25] = t16;
239
+ $[26] = t9;
240
+ $[27] = t17;
241
+ } else {
242
+ t17 = $[27];
243
+ }
244
+ let t18;
245
+ if ($[28] !== actions || $[29] !== hasToolbar || $[30] !== onViewChange || $[31] !== resolvedLabels.today || $[32] !== resolvedLabels.viewMode || $[33] !== timeLabel || $[34] !== today || $[35] !== view || $[36] !== viewIndicatorLayoutId || $[37] !== viewIndicatorTransition || $[38] !== viewOptions) {
246
+ t18 = hasToolbar ? /* @__PURE__ */ _jsxs("div", {
247
+ className: "flex min-w-0 flex-wrap items-center justify-between gap-3 lg:justify-end",
248
+ children: [today || timeLabel ? /* @__PURE__ */ _jsxs("div", {
249
+ className: "flex min-w-0 flex-wrap items-center gap-[0.6rem]",
250
+ children: [today ? /* @__PURE__ */ _jsx(NavigationControl, {
251
+ className: cn(PILL_LINK_CLASS_NAME, "min-h-9 px-[0.9rem] text-[0.9rem]"),
252
+ navigation: today,
253
+ children: resolvedLabels.today
254
+ }) : null, timeLabel ? /* @__PURE__ */ _jsx("p", {
255
+ className: "m-0 text-[0.85rem] text-muted-foreground tabular-nums",
256
+ children: timeLabel
257
+ }) : null]
258
+ }) : null, /* @__PURE__ */ _jsxs("div", {
259
+ className: "flex min-w-0 flex-wrap items-center gap-[0.6rem]",
260
+ children: [viewOptions && viewOptions.length > 0 ? /* @__PURE__ */ _jsx("div", {
261
+ "aria-label": resolvedLabels.viewMode,
262
+ className: "isolate inline-flex gap-1 rounded-xl border border-border p-[0.2rem]",
263
+ role: "group",
264
+ children: viewOptions.map((option) => {
265
+ const isActive = view === option.value;
266
+ const ViewIcon = option.icon;
267
+ return /* @__PURE__ */ _jsxs("span", {
268
+ className: "relative inline-flex",
269
+ children: [isActive ? /* @__PURE__ */ _jsx(motion.span, {
270
+ "aria-hidden": true,
271
+ className: "pointer-events-none absolute inset-0 -z-10 rounded-(--radius) bg-secondary",
272
+ layoutId: viewIndicatorLayoutId,
273
+ transition: viewIndicatorTransition
274
+ }) : null, /* @__PURE__ */ _jsxs(Button, {
275
+ "aria-pressed": isActive,
276
+ className: cn(isActive && "bg-transparent text-secondary-foreground hover:bg-transparent"),
277
+ size: "icon",
278
+ type: "button",
279
+ variant: "ghost",
280
+ onClick: () => onViewChange?.(option.value),
281
+ children: [/* @__PURE__ */ _jsx(ViewIcon, { "aria-hidden": true }), /* @__PURE__ */ _jsx("span", {
282
+ className: "sr-only",
283
+ children: option.label
284
+ })]
285
+ })]
286
+ }, option.value);
287
+ })
288
+ }) : null, actions]
289
+ })]
290
+ }) : null;
291
+ $[28] = actions;
292
+ $[29] = hasToolbar;
293
+ $[30] = onViewChange;
294
+ $[31] = resolvedLabels.today;
295
+ $[32] = resolvedLabels.viewMode;
296
+ $[33] = timeLabel;
297
+ $[34] = today;
298
+ $[35] = view;
299
+ $[36] = viewIndicatorLayoutId;
300
+ $[37] = viewIndicatorTransition;
301
+ $[38] = viewOptions;
302
+ $[39] = t18;
303
+ } else {
304
+ t18 = $[39];
305
+ }
306
+ let t19;
307
+ if ($[40] !== filters) {
308
+ t19 = filters ? /* @__PURE__ */ _jsx("div", {
309
+ className: "col-span-full min-w-0",
310
+ children: filters
311
+ }) : null;
312
+ $[40] = filters;
313
+ $[41] = t19;
314
+ } else {
315
+ t19 = $[41];
316
+ }
317
+ let t20;
318
+ if ($[42] !== t17 || $[43] !== t18 || $[44] !== t19 || $[45] !== t5) {
319
+ t20 = /* @__PURE__ */ _jsxs("header", {
320
+ "data-slot": "month-calendar-header",
321
+ className: t5,
322
+ children: [
323
+ t17,
324
+ t18,
325
+ t19
326
+ ]
327
+ });
328
+ $[42] = t17;
329
+ $[43] = t18;
330
+ $[44] = t19;
331
+ $[45] = t5;
332
+ $[46] = t20;
333
+ } else {
334
+ t20 = $[46];
335
+ }
336
+ return t20;
337
+ }