@giddaa-housing/ui 2.1.0 → 3.0.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 (70) hide show
  1. package/css/generated/shared.css +1 -1
  2. package/css/theme.css +24 -0
  3. package/dist/accordion.js +1 -1
  4. package/dist/badge.js +1 -0
  5. package/dist/breadcrumb.js +1 -0
  6. package/dist/{button-BZIeyUw6.d.ts → button-DW5OijoR.d.ts} +1 -1
  7. package/dist/button-group.d.ts +1 -1
  8. package/dist/button-group.js +3 -2
  9. package/dist/button.d.ts +1 -1
  10. package/dist/button.js +5 -3
  11. package/dist/call-card.d.ts +131 -0
  12. package/dist/call-card.js +300 -0
  13. package/dist/card.js +1 -0
  14. package/dist/carousel.js +12 -9
  15. package/dist/chat.d.ts +208 -0
  16. package/dist/chat.js +528 -0
  17. package/dist/checkbox.js +1 -1
  18. package/dist/combobox.js +1 -1
  19. package/dist/cta.d.ts +1 -1
  20. package/dist/cta.js +1 -0
  21. package/dist/data-table.d.ts +1 -1
  22. package/dist/details-card.js +1 -0
  23. package/dist/dialog.js +2 -2
  24. package/dist/dropdown-menu.js +1 -1
  25. package/dist/editorial-card.js +1 -0
  26. package/dist/file-upload.js +3 -3
  27. package/dist/footer.js +1 -0
  28. package/dist/infinite-scroll.js +1 -1
  29. package/dist/infotip.js +1 -1
  30. package/dist/input-group.d.ts +1 -1
  31. package/dist/input.js +1 -0
  32. package/dist/item.js +1 -0
  33. package/dist/kpi-card.js +1 -0
  34. package/dist/media-player.js +15 -8
  35. package/dist/message.d.ts +263 -0
  36. package/dist/message.js +524 -0
  37. package/dist/multi-step-form.js +10 -6
  38. package/dist/number-input.js +1 -0
  39. package/dist/page-header.d.ts +6 -6
  40. package/dist/page-header.js +21 -7
  41. package/dist/pagination.d.ts +1 -1
  42. package/dist/password-input.js +1 -0
  43. package/dist/popover.js +1 -1
  44. package/dist/price-tag.js +1 -1
  45. package/dist/property-listing-card.d.ts +1 -1
  46. package/dist/property-listing-card.js +1 -0
  47. package/dist/purchase-option-card.js +1 -0
  48. package/dist/radio-group.js +1 -1
  49. package/dist/review-block.js +1 -0
  50. package/dist/rich-text-editor.js +3 -3
  51. package/dist/search-input.js +1 -0
  52. package/dist/select.js +1 -1
  53. package/dist/sheet.js +1 -1
  54. package/dist/sidebar.d.ts +1 -1
  55. package/dist/sidebar.js +5 -5
  56. package/dist/skeleton.js +1 -1
  57. package/dist/slider.js +3 -2
  58. package/dist/stepper.js +1 -0
  59. package/dist/styles.css +1870 -162
  60. package/dist/switch.js +3 -3
  61. package/dist/table-card.js +1 -0
  62. package/dist/tabs.js +2 -2
  63. package/dist/testimonial-block.js +1 -0
  64. package/dist/textarea.js +1 -0
  65. package/dist/time-picker.js +85 -7
  66. package/dist/toast.js +1 -1
  67. package/dist/tooltip.d.ts +2 -1
  68. package/dist/tooltip.js +9 -2
  69. package/dist/use-reduced-motion-BDvOK14x.js +17 -0
  70. package/package.json +13 -1
@@ -0,0 +1,524 @@
1
+ "use client";
2
+ import { t as cn } from "./cn-BI_4DMBf.js";
3
+ import { CheckCheck } from "lucide-react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { mergeProps } from "@base-ui/react/merge-props";
6
+ import { useRender } from "@base-ui/react/use-render";
7
+ import { cva } from "class-variance-authority";
8
+ import * as React from "react";
9
+ //#region src/message.tsx
10
+ const MessageContext = React.createContext(null);
11
+ function useMessageContext() {
12
+ const context = React.useContext(MessageContext);
13
+ if (!context) throw new Error("Message parts must be rendered inside <Message>.");
14
+ return context;
15
+ }
16
+ function toDateTimeAttribute(value) {
17
+ return value instanceof Date ? value.toISOString() : value;
18
+ }
19
+ function formatTimestamp(value) {
20
+ if (typeof value === "string") return value;
21
+ return value.toLocaleTimeString([], {
22
+ hour: "2-digit",
23
+ minute: "2-digit",
24
+ hour12: false
25
+ });
26
+ }
27
+ /** Styles the bubble itself — the surface inside `Message`, not `Message`'s root. */
28
+ const messageVariants = cva("relative flex w-fit min-w-0 flex-col shadow-1 *:data-[slot=message-media]:max-w-full", {
29
+ variants: {
30
+ direction: {
31
+ sent: "items-end bg-surface-brand text-fg-on-brand",
32
+ received: "items-start bg-surface-raised text-fg-primary"
33
+ },
34
+ size: {
35
+ sm: "max-w-52.5 gap-gdt-xs rounded-[14px] px-2.5 py-gdt-sm",
36
+ md: "max-w-67.5 gap-1.25 rounded-xl px-gdt-md py-2.5",
37
+ lg: "max-w-82.5 gap-1.5 rounded-[20px] px-3.5 py-gdt-md",
38
+ xl: "max-w-100 gap-gdt-sm rounded-2xl px-4.5 py-3.5"
39
+ }
40
+ },
41
+ compoundVariants: [
42
+ {
43
+ direction: "sent",
44
+ size: "sm",
45
+ class: "rounded-br-sm"
46
+ },
47
+ {
48
+ direction: "sent",
49
+ size: "md",
50
+ class: "rounded-br-[5px]"
51
+ },
52
+ {
53
+ direction: "sent",
54
+ size: "lg",
55
+ class: "rounded-br-[6px]"
56
+ },
57
+ {
58
+ direction: "sent",
59
+ size: "xl",
60
+ class: "rounded-br-md"
61
+ },
62
+ {
63
+ direction: "received",
64
+ size: "sm",
65
+ class: "rounded-bl-sm"
66
+ },
67
+ {
68
+ direction: "received",
69
+ size: "md",
70
+ class: "rounded-bl-[5px]"
71
+ },
72
+ {
73
+ direction: "received",
74
+ size: "lg",
75
+ class: "rounded-bl-[6px]"
76
+ },
77
+ {
78
+ direction: "received",
79
+ size: "xl",
80
+ class: "rounded-bl-md"
81
+ }
82
+ ],
83
+ defaultVariants: {
84
+ direction: "sent",
85
+ size: "md"
86
+ }
87
+ });
88
+ /** Vertical stack of messages. Owns inter-message spacing only. */
89
+ function MessageGroup({ className, ...props }) {
90
+ return /* @__PURE__ */ jsx("div", {
91
+ "data-slot": "message-group",
92
+ className: cn("flex min-w-0 flex-col gap-1", "[&>[data-direction=received]+[data-direction=sent]]:mt-1.5 [&>[data-direction=sent]+[data-direction=received]]:mt-1.5", className),
93
+ ...props
94
+ });
95
+ }
96
+ /**
97
+ * The bubble shell. Owns direction, size, surface, tail corner, and the meta row.
98
+ * Knows nothing about content type — it reacts to whichever slots are present,
99
+ * so attachments drop in without changing this component.
100
+ *
101
+ * The root is a column: the bubble, then an optional footer beneath it. That
102
+ * split exists only so `footer` has somewhere to live outside the surface —
103
+ * without a footer the column collapses to the bubble alone.
104
+ */
105
+ function Message({ direction = "sent", size = "md", showTimestamp = true, showReadReceipt = true, timestamp, timestampDateTime, status = "read", footer, className, children, ...props }) {
106
+ const receiptVisible = direction === "sent" && showReadReceipt;
107
+ const timestampVisible = showTimestamp && timestamp !== void 0;
108
+ const footerVisible = direction === "sent" && footer !== void 0 && footer !== null && footer !== false;
109
+ const context = {
110
+ direction,
111
+ size,
112
+ status,
113
+ timestamp,
114
+ timestampDateTime,
115
+ showTimestamp: timestampVisible,
116
+ showReadReceipt: receiptVisible
117
+ };
118
+ return /* @__PURE__ */ jsx(MessageContext.Provider, {
119
+ value: context,
120
+ children: /* @__PURE__ */ jsxs("div", {
121
+ "data-slot": "message",
122
+ "data-direction": direction,
123
+ "data-size": size,
124
+ "data-direction-size": `${direction}-${size}`,
125
+ className: cn("group/message flex min-w-0 flex-col gap-gdt-xs", direction === "sent" ? "items-end self-end" : "items-start self-start", className),
126
+ ...props,
127
+ children: [/* @__PURE__ */ jsxs("div", {
128
+ "data-slot": "message-bubble",
129
+ className: messageVariants({
130
+ direction,
131
+ size
132
+ }),
133
+ children: [children, timestampVisible || receiptVisible ? /* @__PURE__ */ jsxs(MessageMeta, { children: [timestampVisible ? /* @__PURE__ */ jsx(MessageTimestamp, {}) : null, receiptVisible ? /* @__PURE__ */ jsx(MessageReadReceipt, {}) : null] }) : null]
134
+ }), footerVisible ? /* @__PURE__ */ jsx(MessageFooter, { children: footer }) : null]
135
+ })
136
+ });
137
+ }
138
+ /**
139
+ * Quiet attribution beneath the bubble — "Answered by AI", "Edited", "Sent via
140
+ * SMS". Rendered by the shell from its `footer` prop; exported for callers who
141
+ * compose their own.
142
+ *
143
+ * It owns layout and register only. What it says, and whether that is
144
+ * uppercased, is the caller's — this is a slot, not a label.
145
+ */
146
+ function MessageFooter({ className, ...props }) {
147
+ return /* @__PURE__ */ jsx("div", {
148
+ "data-slot": "message-footer",
149
+ className: cn("flex min-w-0 max-w-full items-end gap-2.5 font-medium text-fg-secondary", "[&_svg:not([class*='size-'])]:size-5", "group-data-[size=sm]/message:text-gdt-subtext", "group-data-[size=md]/message:text-gdt-subtext", "group-data-[size=lg]/message:text-gdt-xs", "group-data-[size=xl]/message:text-gdt-sm", className),
150
+ ...props
151
+ });
152
+ }
153
+ /**
154
+ * The content region for prose. Attachment slots are sibling alternatives.
155
+ * Polymorphic via `render` — pass `render={<p />}` for paragraphs.
156
+ */
157
+ function MessageContent({ className, render, ...props }) {
158
+ return useRender({
159
+ defaultTagName: "div",
160
+ props: mergeProps({ className: cn("min-w-0 max-w-full wrap-break-word", "group-data-[size=sm]/message:text-gdt-xs", "group-data-[size=md]/message:text-gdt-sm", "group-data-[size=lg]/message:text-gdt-md", "group-data-[size=xl]/message:text-gdt-lg", className) }, props),
161
+ render,
162
+ state: { slot: "message-content" }
163
+ });
164
+ }
165
+ /** The meta row (timestamp + receipt). Not rendered when both are absent. */
166
+ function MessageMeta({ className, ...props }) {
167
+ return /* @__PURE__ */ jsx("div", {
168
+ "data-slot": "message-meta",
169
+ className: cn("flex shrink-0 items-center", "group-data-[size=sm]/message:gap-0.75", "group-data-[size=md]/message:gap-gdt-xs", "group-data-[size=lg]/message:gap-1.25", "group-data-[size=xl]/message:gap-1.75", "group-data-[direction=received]/message:text-fg-secondary", className),
170
+ ...props
171
+ });
172
+ }
173
+ /** Timestamp. Date values get an ISO `dateTime`; display strings require an explicit one. */
174
+ function MessageTimestamp({ className, children, dateTime, ...props }) {
175
+ const { timestamp, timestampDateTime, showTimestamp } = useMessageContext();
176
+ if (!showTimestamp || timestamp === void 0) return null;
177
+ return /* @__PURE__ */ jsx("time", {
178
+ "data-slot": "message-timestamp",
179
+ dateTime: dateTime ?? (timestampDateTime ? toDateTimeAttribute(timestampDateTime) : timestamp instanceof Date ? timestamp.toISOString() : void 0),
180
+ className: cn("whitespace-nowrap", "group-data-[size=sm]/message:text-gdt-subtext", "group-data-[size=md]/message:text-gdt-subtext", "group-data-[size=lg]/message:text-gdt-xs", "group-data-[size=xl]/message:text-gdt-sm", className),
181
+ ...props,
182
+ children: children ?? formatTimestamp(timestamp)
183
+ });
184
+ }
185
+ /**
186
+ * Delivery state indicator. Sent-only; returns nothing when the receipt is
187
+ * hidden or the message is received.
188
+ *
189
+ * `failed` renders text rather than a tick, so the message is legible instead of
190
+ * encoded in an icon nobody recognises.
191
+ *
192
+ * The tick is 14px at every size — the design pins it across the whole ladder.
193
+ */
194
+ function MessageReadReceipt({ className, ...props }) {
195
+ const { direction, status, showReadReceipt } = useMessageContext();
196
+ if (!showReadReceipt || direction !== "sent") return null;
197
+ if (status === "failed") return /* @__PURE__ */ jsx("span", {
198
+ "data-slot": "message-read-receipt",
199
+ "data-status": status,
200
+ className: cn("shrink-0 whitespace-nowrap text-status-danger", "group-data-[size=sm]/message:text-gdt-subtext", "group-data-[size=md]/message:text-gdt-subtext", "group-data-[size=lg]/message:text-gdt-xs", "group-data-[size=xl]/message:text-gdt-sm", className),
201
+ ...props,
202
+ children: "Failed to send"
203
+ });
204
+ return /* @__PURE__ */ jsx("span", {
205
+ "data-slot": "message-read-receipt",
206
+ "data-status": status,
207
+ role: "img",
208
+ "aria-label": status === "read" ? "Read" : "Delivered",
209
+ className: cn("flex shrink-0 items-center", className),
210
+ ...props,
211
+ children: /* @__PURE__ */ jsx(CheckCheck, {
212
+ className: "size-3.5",
213
+ "aria-hidden": true
214
+ })
215
+ });
216
+ }
217
+ /**
218
+ * The media box — the content region for `image` and `video`.
219
+ *
220
+ * A direct child of `Message`, replacing `MessageContent` rather than nesting
221
+ * inside it: Figma's bubble is `[content, meta]` with no wrapper.
222
+ *
223
+ * Owns the fixed per-size dimensions and radius, `bg-surface-overlay`, and the
224
+ * clip. Reads `size` from the shell via `group-data-*` — no spine props of its own.
225
+ */
226
+ function MessageMedia({ className, ...props }) {
227
+ return /* @__PURE__ */ jsx("div", {
228
+ "data-slot": "message-media",
229
+ className: cn("relative shrink-0 overflow-hidden bg-surface-overlay", "group-data-[size=sm]/message:h-25 group-data-[size=sm]/message:w-37.5 group-data-[size=sm]/message:rounded-[10px]", "group-data-[size=md]/message:h-32.5 group-data-[size=md]/message:w-50 group-data-[size=md]/message:rounded-lg", "group-data-[size=lg]/message:h-41.25 group-data-[size=lg]/message:w-65 group-data-[size=lg]/message:rounded-xl", "group-data-[size=xl]/message:h-50 group-data-[size=xl]/message:w-[320px] group-data-[size=xl]/message:rounded-[20px]", className),
230
+ ...props
231
+ });
232
+ }
233
+ /**
234
+ * The image itself. Fills the box and crops via `object-cover`.
235
+ * Polymorphic via `render` for custom loaders.
236
+ */
237
+ function MessageMediaImage({ className, render, ...props }) {
238
+ return useRender({
239
+ defaultTagName: "img",
240
+ props: mergeProps({ className: cn("size-full object-cover", className) }, props),
241
+ render,
242
+ state: { slot: "message-media-image" }
243
+ });
244
+ }
245
+ /**
246
+ * The reserved-slot visual — a centred, decorative icon at
247
+ * `text-fg-caption-placeholder`. Children are the icon.
248
+ */
249
+ function MessageMediaPlaceholder({ className, ...props }) {
250
+ return /* @__PURE__ */ jsx("div", {
251
+ "data-slot": "message-media-placeholder",
252
+ "aria-hidden": true,
253
+ className: cn("flex size-full items-center justify-center text-fg-caption-placeholder", "group-data-[size=sm]/message:[&_svg:not([class*='size-'])]:size-10.5", "group-data-[size=md]/message:[&_svg:not([class*='size-'])]:size-13.75", "group-data-[size=lg]/message:[&_svg:not([class*='size-'])]:size-17.25", "group-data-[size=xl]/message:[&_svg:not([class*='size-'])]:size-21", className),
254
+ ...props
255
+ });
256
+ }
257
+ /**
258
+ * The play control — a centred brand circle over the media.
259
+ *
260
+ * A real `<button>`, so focus and Enter/Space work without extra wiring.
261
+ * Labelled "Play video" by default; pass `aria-label` to override.
262
+ *
263
+ * The icon is nudged right for optical centring.
264
+ */
265
+ function MessageMediaPlay({ className, type, "aria-label": ariaLabel = "Play video", ...props }) {
266
+ return /* @__PURE__ */ jsx("button", {
267
+ ...props,
268
+ type: type ?? "button",
269
+ "data-slot": "message-media-play",
270
+ "aria-label": ariaLabel,
271
+ className: cn("absolute inset-0 m-auto flex items-center justify-center rounded-full bg-surface-brand text-fg-on-brand outline-none focus-visible:ring-2 focus-visible:ring-line-focus focus-visible:ring-offset-2", "[&_svg]:translate-x-[1.9px]", "group-data-[size=sm]/message:size-[33px] group-data-[size=sm]/message:[&_svg:not([class*='size-'])]:size-[15px]", "group-data-[size=md]/message:size-9.5 group-data-[size=md]/message:[&_svg:not([class*='size-'])]:size-4.25", "group-data-[size=lg]/message:size-12 group-data-[size=lg]/message:[&_svg:not([class*='size-'])]:size-5.5", "group-data-[size=xl]/message:size-14.5 group-data-[size=xl]/message:[&_svg:not([class*='size-'])]:size-6.75", className)
272
+ });
273
+ }
274
+ /**
275
+ * The duration badge, inset from the bottom-right of the media box.
276
+ *
277
+ * Children are the already-formatted string — the app owns the value, the
278
+ * component owns the presentation.
279
+ */
280
+ function MessageMediaDuration({ className, ...props }) {
281
+ return /* @__PURE__ */ jsx("div", {
282
+ "data-slot": "message-media-duration",
283
+ className: cn("absolute right-gdt-sm bottom-gdt-sm flex items-center whitespace-nowrap rounded-[6px] bg-surface-raised px-1.5 py-0.5 text-fg-primary", "group-data-[size=sm]/message:text-gdt-subtext", "group-data-[size=md]/message:text-gdt-subtext", "group-data-[size=lg]/message:text-gdt-xs", "group-data-[size=xl]/message:text-gdt-sm", className),
284
+ ...props
285
+ });
286
+ }
287
+ /**
288
+ * The file row — icon chip, filename column, download control.
289
+ *
290
+ * A direct child of `Message`, in the same slot `MessageContent`, `MessageMedia`
291
+ * or `MessageVoice` would occupy. Reads `size` from the shell via `group-data-*`.
292
+ */
293
+ function MessageFile({ className, ...props }) {
294
+ return /* @__PURE__ */ jsx("div", {
295
+ "data-slot": "message-file",
296
+ className: cn("flex items-center", "group-data-[size=sm]/message:gap-gdt-sm", "group-data-[size=md]/message:gap-2.25", "group-data-[size=lg]/message:gap-2.5", "group-data-[size=xl]/message:gap-3", className),
297
+ ...props
298
+ });
299
+ }
300
+ /**
301
+ * The file-type icon chip. Children are the icon.
302
+ *
303
+ * `bg-fg-on-brand` is a foreground token used as a background, for the same
304
+ * reason as `MessageVoicePlay`: it preserves the intended contrast in dark
305
+ * mode.
306
+ */
307
+ function MessageFileIcon({ className, ...props }) {
308
+ return /* @__PURE__ */ jsx("div", {
309
+ "data-slot": "message-file-icon",
310
+ className: cn("flex shrink-0 items-center justify-center bg-fg-on-brand text-fg-brand", "group-data-[direction=received]/message:bg-surface-brand group-data-[direction=received]/message:text-fg-on-brand", "group-data-[size=sm]/message:size-[32px] group-data-[size=sm]/message:rounded-[7px]", "group-data-[size=md]/message:size-9.5 group-data-[size=md]/message:rounded-md", "group-data-[size=lg]/message:size-11.5 group-data-[size=lg]/message:rounded-[10px]", "group-data-[size=xl]/message:size-13.5 group-data-[size=xl]/message:rounded-lg", "group-data-[size=sm]/message:[&_svg:not([class*='size-'])]:size-4.5", "group-data-[size=md]/message:[&_svg:not([class*='size-'])]:size-5.25", "group-data-[size=lg]/message:[&_svg:not([class*='size-'])]:size-6.25", "group-data-[size=xl]/message:[&_svg:not([class*='size-'])]:size-7.5", className),
311
+ ...props
312
+ });
313
+ }
314
+ /**
315
+ * The filename column. `min-w-0` so its children can truncate against the
316
+ * shell's per-size `max-w` rather than pushing the bubble wider.
317
+ */
318
+ function MessageFileInfo({ className, ...props }) {
319
+ return /* @__PURE__ */ jsx("div", {
320
+ "data-slot": "message-file-info",
321
+ className: cn("flex min-w-0 flex-col gap-0.5 text-fg-on-brand", "group-data-[direction=received]/message:text-fg-primary", className),
322
+ ...props
323
+ });
324
+ }
325
+ /** The filename. Follows the body type ladder, semibold. Truncates. */
326
+ function MessageFileName({ className, ...props }) {
327
+ return /* @__PURE__ */ jsx("p", {
328
+ "data-slot": "message-file-name",
329
+ className: cn("truncate font-semibold", "group-data-[size=sm]/message:text-gdt-xs", "group-data-[size=md]/message:text-gdt-sm", "group-data-[size=lg]/message:text-gdt-md", "group-data-[size=xl]/message:text-gdt-lg", className),
330
+ ...props
331
+ });
332
+ }
333
+ /**
334
+ * The secondary line — size and type, e.g. "248 KB · PDF". Follows the
335
+ * timestamp type ladder. The app formats it; the component only presents it.
336
+ *
337
+ * Received messages use secondary text; sent messages inherit on-brand text.
338
+ */
339
+ function MessageFileDetail({ className, ...props }) {
340
+ return /* @__PURE__ */ jsx("p", {
341
+ "data-slot": "message-file-detail",
342
+ className: cn("truncate", "group-data-[direction=received]/message:text-fg-secondary", "group-data-[size=sm]/message:text-gdt-subtext", "group-data-[size=md]/message:text-gdt-subtext", "group-data-[size=lg]/message:text-gdt-xs", "group-data-[size=xl]/message:text-gdt-sm", className),
343
+ ...props
344
+ });
345
+ }
346
+ /**
347
+ * The download control. Defaults to a `<button>`; pass `render={<a download />}`
348
+ * for a direct link. Labelled "Download file" by default.
349
+ */
350
+ function MessageFileAction({ className, render, type, ...props }) {
351
+ return useRender({
352
+ defaultTagName: "button",
353
+ props: mergeProps({
354
+ "aria-label": "Download file",
355
+ type: render ? type : type ?? "button",
356
+ className: cn("flex size-6 shrink-0 items-center justify-center rounded-sm text-fg-on-brand outline-none focus-visible:ring-2 focus-visible:ring-line-focus", "group-data-[direction=received]/message:text-fg-primary", "group-data-[size=sm]/message:-mx-1 group-data-[size=sm]/message:[&_svg]:size-[16px]", "group-data-[size=md]/message:-mx-0.75 group-data-[size=md]/message:[&_svg]:size-4.5", "group-data-[size=lg]/message:-mx-0.5 group-data-[size=lg]/message:[&_svg]:size-5", "group-data-[size=xl]/message:[&_svg]:size-[24px]", className)
357
+ }, props),
358
+ render,
359
+ state: { slot: "message-file-action" }
360
+ });
361
+ }
362
+ /** Decorative fallback waveform used when no caller-supplied waveform exists. */
363
+ const WAVEFORM_VIEWBOX_WIDTH = 58;
364
+ const WAVEFORM_VIEWBOX_HEIGHT = 22;
365
+ const WAVEFORM_BAR_WIDTH = 2;
366
+ const WAVEFORM_BAR_PITCH = 4;
367
+ const WAVEFORM_BARS = [
368
+ 6,
369
+ 10,
370
+ 16,
371
+ 11,
372
+ 20,
373
+ 13,
374
+ 8,
375
+ 17,
376
+ 22,
377
+ 11,
378
+ 15,
379
+ 9,
380
+ 14,
381
+ 19,
382
+ 10
383
+ ].map((height, index) => ({
384
+ height,
385
+ id: `waveform-bar-${index}`,
386
+ x: index * WAVEFORM_BAR_PITCH,
387
+ y: (WAVEFORM_VIEWBOX_HEIGHT - height) / 2
388
+ }));
389
+ function WaveformGlyph() {
390
+ return /* @__PURE__ */ jsx("svg", {
391
+ "aria-hidden": true,
392
+ className: "size-full",
393
+ fill: "currentColor",
394
+ preserveAspectRatio: "none",
395
+ role: "presentation",
396
+ viewBox: `0 0 ${WAVEFORM_VIEWBOX_WIDTH} ${WAVEFORM_VIEWBOX_HEIGHT}`,
397
+ children: WAVEFORM_BARS.map((bar) => /* @__PURE__ */ jsx("rect", {
398
+ height: bar.height,
399
+ rx: 1,
400
+ width: WAVEFORM_BAR_WIDTH,
401
+ x: bar.x,
402
+ y: bar.y
403
+ }, bar.id))
404
+ });
405
+ }
406
+ /**
407
+ * The voice-note row — play control alongside a waveform.
408
+ *
409
+ * A direct child of `Message`, in the same slot `MessageContent` or
410
+ * `MessageMedia` would occupy. Reads `size` from the shell via `group-data-*`.
411
+ */
412
+ function MessageVoice({ className, ...props }) {
413
+ return /* @__PURE__ */ jsx("div", {
414
+ "data-slot": "message-voice",
415
+ className: cn("flex items-center", "group-data-[size=sm]/message:gap-1.5", "group-data-[size=md]/message:gap-1.75", "group-data-[size=lg]/message:gap-gdt-sm", "group-data-[size=xl]/message:gap-2.5", className),
416
+ ...props
417
+ });
418
+ }
419
+ /**
420
+ * The play control. A real `<button>`, so focus and Enter/Space work without
421
+ * extra wiring. Labelled "Play voice note" by default; pass `aria-label` to
422
+ * override.
423
+ *
424
+ * Direction controls the visual size and contrast treatment.
425
+ */
426
+ function MessageVoicePlay({ className, type, "aria-label": ariaLabel = "Play voice note", ...props }) {
427
+ return /* @__PURE__ */ jsx("button", {
428
+ ...props,
429
+ type: type ?? "button",
430
+ "data-slot": "message-voice-play",
431
+ "aria-label": ariaLabel,
432
+ className: cn("flex shrink-0 items-center justify-center rounded-full outline-none focus-visible:ring-2 focus-visible:ring-line-focus focus-visible:ring-offset-2", "size-7 bg-fg-on-brand text-fg-brand", "[&_svg]:translate-x-[1.4px]", "[&_svg:not([class*='size-'])]:size-[12px]", "group-data-[direction=received]/message:bg-surface-brand group-data-[direction=received]/message:text-fg-on-brand", "group-data-[direction-size=received-sm]/message:size-7", "group-data-[direction-size=received-md]/message:size-8", "group-data-[direction-size=received-lg]/message:size-10", "group-data-[direction-size=received-xl]/message:size-12", "group-data-[direction=received]/message:[&_svg]:translate-x-[1.6px]", "group-data-[direction-size=received-sm]/message:[&_svg:not([class*='size-'])]:size-3", "group-data-[direction-size=received-md]/message:[&_svg:not([class*='size-'])]:size-3.25", "group-data-[direction-size=received-lg]/message:[&_svg:not([class*='size-'])]:size-4.25", "group-data-[direction-size=received-xl]/message:[&_svg:not([class*='size-'])]:size-5", className)
433
+ });
434
+ }
435
+ /**
436
+ * The waveform. Renders the built-in glyph unless children are supplied.
437
+ *
438
+ * Colour rides on `currentColor`, so the direction flip costs nothing.
439
+ */
440
+ function MessageVoiceWaveform({ className, children, ...props }) {
441
+ return /* @__PURE__ */ jsx("div", {
442
+ "data-slot": "message-voice-waveform",
443
+ className: cn("shrink-0 text-fg-on-brand", "group-data-[direction=received]/message:text-fg-brand", "group-data-[size=sm]/message:h-4.5 group-data-[size=sm]/message:w-14.5", "group-data-[size=md]/message:h-5.5 group-data-[size=md]/message:w-14.5", "group-data-[size=lg]/message:h-6.25 group-data-[size=lg]/message:w-18.25", "group-data-[size=xl]/message:h-7.5 group-data-[size=xl]/message:w-21.75", className),
444
+ ...props,
445
+ children: children ?? /* @__PURE__ */ jsx(WaveformGlyph, {})
446
+ });
447
+ }
448
+ /**
449
+ * The clip's length. Children are the already-formatted string — the app owns
450
+ * the value, the component owns the presentation.
451
+ *
452
+ * Follows the timestamp type ladder, and drops to `text-fg-primary` when
453
+ * received rather than inheriting the bubble's `text-fg-on-brand`.
454
+ */
455
+ function MessageVoiceDuration({ className, ...props }) {
456
+ return /* @__PURE__ */ jsx("p", {
457
+ "data-slot": "message-voice-duration",
458
+ className: cn("shrink-0 whitespace-nowrap", "group-data-[direction=received]/message:text-fg-primary", "group-data-[size=sm]/message:text-gdt-subtext", "group-data-[size=md]/message:text-gdt-subtext", "group-data-[size=lg]/message:text-gdt-xs", "group-data-[size=xl]/message:text-gdt-sm", className),
459
+ ...props
460
+ });
461
+ }
462
+ /** Variant styling for full-width notices and centred date-divider pills. */
463
+ const markerVariants = cva("group/marker relative flex min-h-gdt-lg items-center gap-gdt-xs text-left text-fg-secondary [&_svg:not([class*='size-'])]:size-gdt-lg [a]:underline [a]:underline-offset-2 [a]:hover:text-fg-primary", {
464
+ variants: {
465
+ variant: {
466
+ default: "w-full",
467
+ pill: "mx-auto w-fit rounded-full bg-surface-raised px-gdt-sm py-0.5"
468
+ },
469
+ size: {
470
+ sm: "text-gdt-subtext",
471
+ md: "text-gdt-subtext",
472
+ lg: "text-gdt-xs",
473
+ xl: "text-gdt-sm"
474
+ }
475
+ },
476
+ defaultVariants: {
477
+ variant: "default",
478
+ size: "md"
479
+ }
480
+ });
481
+ /**
482
+ * A break in the conversation — a date divider, an unread line, a system notice.
483
+ *
484
+ * Deliberately takes no `direction`. A marker is not from anyone; it spans the
485
+ * conversation. Exposing `direction` would be a prop that does nothing in every
486
+ * state.
487
+ *
488
+ * Takes no role by default, because the same component serves three different
489
+ * meanings. Use `render` to give it real semantics where they exist — e.g.
490
+ * `render={<h3 />}` for a date heading, or a `role="separator"` for an unread line.
491
+ */
492
+ function Marker({ className, variant = "default", size = "md", render, ...props }) {
493
+ return useRender({
494
+ defaultTagName: "div",
495
+ props: mergeProps({ className: cn(markerVariants({
496
+ variant,
497
+ size
498
+ }), className) }, props),
499
+ render,
500
+ state: {
501
+ slot: "marker",
502
+ variant
503
+ }
504
+ });
505
+ }
506
+ /** Decorative icon slot. Always hidden from assistive tech — the content carries the meaning. */
507
+ function MarkerIcon({ className, ...props }) {
508
+ return /* @__PURE__ */ jsx("span", {
509
+ "data-slot": "marker-icon",
510
+ "aria-hidden": "true",
511
+ className: cn("size-gdt-lg shrink-0 [&_svg:not([class*='size-'])]:size-gdt-lg", className),
512
+ ...props
513
+ });
514
+ }
515
+ /** The marker's text. Wraps in a full-width row; stays on one line inside a pill. */
516
+ function MarkerContent({ className, ...props }) {
517
+ return /* @__PURE__ */ jsx("span", {
518
+ "data-slot": "marker-content",
519
+ className: cn("min-w-0 wrap-break-word", "group-data-[variant=pill]/marker:whitespace-nowrap", "*:[a]:underline *:[a]:underline-offset-2 *:[a]:hover:text-fg-primary", className),
520
+ ...props
521
+ });
522
+ }
523
+ //#endregion
524
+ export { Marker, MarkerContent, MarkerIcon, Message, MessageContent, MessageFile, MessageFileAction, MessageFileDetail, MessageFileIcon, MessageFileInfo, MessageFileName, MessageFooter, MessageGroup, MessageMedia, MessageMediaDuration, MessageMediaImage, MessageMediaPlaceholder, MessageMediaPlay, MessageMeta, MessageReadReceipt, MessageTimestamp, MessageVoice, MessageVoiceDuration, MessageVoicePlay, MessageVoiceWaveform, markerVariants, messageVariants };
@@ -170,18 +170,22 @@ function MultiStepFormIndicator({ type = "stepper", progressValue, showNextLabel
170
170
  "aria-hidden": "true",
171
171
  className: cn("h-0.5 flex-1 rounded-full bg-line", index === 0 && "bg-transparent", leftComplete && "bg-status-success")
172
172
  }),
173
- /* @__PURE__ */ jsx("button", {
173
+ /* @__PURE__ */ jsxs("button", {
174
174
  type: "button",
175
175
  "aria-current": state === "active" ? "step" : void 0,
176
176
  disabled: isValidating,
177
- className: cn("flex size-10 shrink-0 items-center justify-center rounded-full border text-gdt-sm font-bold transition-[background-color,border-color,color,box-shadow] focus-visible:border-line-focus focus-visible:shadow-[0_0_0_2px_var(--color-line-focus)] focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50", state === "complete" && "border-status-success bg-status-success text-fg-on-brand", state === "active" && "border-status-info bg-status-info text-fg-on-brand", state === "upcoming" && "border-line bg-canvas text-fg-caption-placeholder hover:border-line-strong hover:text-fg-primary"),
177
+ className: cn("relative flex size-10 shrink-0 items-center justify-center rounded-full border text-gdt-sm font-bold transition-[background-color,border-color,color,box-shadow] focus-visible:border-line-focus focus-visible:shadow-[0_0_0_2px_var(--color-line-focus)] focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50", state === "complete" && "border-status-success bg-status-success text-fg-on-brand", state === "active" && "border-status-info bg-status-info text-fg-on-brand", state === "upcoming" && "border-line bg-canvas text-fg-caption-placeholder hover:border-line-strong hover:text-fg-primary"),
178
178
  onClick: () => {
179
179
  goTo(step.value);
180
180
  },
181
- children: state === "complete" ? /* @__PURE__ */ jsx(CheckIcon, {
182
- className: "size-4.5",
183
- strokeWidth: 2.5
184
- }) : index + 1
181
+ children: [/* @__PURE__ */ jsx("span", {
182
+ className: "transition-opacity duration-[var(--duration-motion-press)] ease-gdt-out group-data-[state=complete]/multi-step-form-step:opacity-0",
183
+ children: index + 1
184
+ }), /* @__PURE__ */ jsx(CheckIcon, {
185
+ "aria-hidden": "true",
186
+ strokeWidth: 2.5,
187
+ className: "absolute inset-0 m-auto size-4.5 scale-95 opacity-0 transition-[opacity,scale] duration-[var(--duration-motion-press)] ease-gdt-out motion-reduce:scale-100 group-data-[state=complete]/multi-step-form-step:scale-100 group-data-[state=complete]/multi-step-form-step:opacity-100"
188
+ })]
185
189
  }),
186
190
  /* @__PURE__ */ jsx("span", {
187
191
  "aria-hidden": "true",
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { t as cn } from "./cn-BI_4DMBf.js";
2
3
  import { SizeProvider, useComponentSize } from "./size-context.js";
3
4
  import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "./input-group.js";
@@ -1,12 +1,12 @@
1
1
  //#region src/page-header.d.ts
2
- declare function DashboardHeader({ border, className, ...props }: React.ComponentProps<"div"> & {
3
- border?: boolean;
4
- }): import("react").JSX.Element;
5
- declare function DashboardHeaderGroup({ className, orientation, ...props }: React.ComponentProps<"div"> & {
6
- orientation?: "horizontal" | "vertical";
2
+ declare function DashboardHeader({ hideBorder, className, ...props }: React.ComponentProps<"div"> & {
3
+ hideBorder?: boolean;
7
4
  }): import("react").JSX.Element;
5
+ declare function DashboardHeaderNavigation({ className, ...props }: React.ComponentProps<"div">): import("react").JSX.Element;
6
+ declare function DashboardHeaderMain({ className, ...props }: React.ComponentProps<"div">): import("react").JSX.Element;
7
+ declare function DashboardHeaderGroup({ className, ...props }: React.ComponentProps<"div">): import("react").JSX.Element;
8
8
  declare function DashboardHeaderTitle({ className, ...props }: React.ComponentProps<"h1">): import("react").JSX.Element;
9
9
  declare function DashboardHeaderDescription({ className, ...props }: React.ComponentProps<"p">): import("react").JSX.Element;
10
10
  declare function DashboardHeaderActions({ className, ...props }: React.ComponentProps<"div">): import("react").JSX.Element;
11
11
  //#endregion
12
- export { DashboardHeader, DashboardHeaderActions, DashboardHeaderDescription, DashboardHeaderGroup, DashboardHeaderTitle };
12
+ export { DashboardHeader, DashboardHeaderActions, DashboardHeaderDescription, DashboardHeaderGroup, DashboardHeaderMain, DashboardHeaderNavigation, DashboardHeaderTitle };
@@ -1,31 +1,45 @@
1
1
  import { t as cn } from "./cn-BI_4DMBf.js";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  //#region src/page-header.tsx
4
- function DashboardHeader({ border, className, ...props }) {
4
+ function DashboardHeader({ hideBorder = false, className, ...props }) {
5
5
  return /* @__PURE__ */ jsx("div", {
6
6
  "data-slot": "dashboard-header",
7
- className: cn("flex flex-col gap-4", className),
7
+ className: cn("flex flex-col gap-6", !hideBorder && "border-b border-line-subtle", className),
8
8
  ...props
9
9
  });
10
10
  }
11
- function DashboardHeaderGroup({ className, orientation = "vertical", ...props }) {
11
+ function DashboardHeaderNavigation({ className, ...props }) {
12
+ return /* @__PURE__ */ jsx("div", {
13
+ "data-slot": "dashboard-header-navigation",
14
+ className: cn("flex min-w-0 flex-wrap items-center gap-4", className),
15
+ ...props
16
+ });
17
+ }
18
+ function DashboardHeaderMain({ className, ...props }) {
19
+ return /* @__PURE__ */ jsx("div", {
20
+ "data-slot": "dashboard-header-main",
21
+ className: cn("flex flex-col gap-4 md:flex-row md:items-center md:justify-between md:gap-6", className),
22
+ ...props
23
+ });
24
+ }
25
+ function DashboardHeaderGroup({ className, ...props }) {
12
26
  return /* @__PURE__ */ jsx("div", {
13
27
  "data-slot": "dashboard-header-group",
14
- className: cn("flex", orientation === "horizontal" ? "gap-4 md:gap-6 md:flex-row md:items-center" : "flex-col gap-2", className),
28
+ className: cn("flex flex-col gap-2", className),
15
29
  ...props
16
30
  });
17
31
  }
18
32
  function DashboardHeaderTitle({ className, ...props }) {
19
33
  return /* @__PURE__ */ jsx("h1", {
20
34
  "data-slot": "dashboard-header-title",
21
- className: cn("text-gdt-h3", className),
35
+ className: cn("text-fg-primary text-gdt-h3", className),
22
36
  ...props
23
37
  });
24
38
  }
25
39
  function DashboardHeaderDescription({ className, ...props }) {
26
40
  return /* @__PURE__ */ jsx("p", {
27
41
  "data-slot": "dashboard-header-description",
28
- className: cn("text-gdt-sm text-fg-secondary", className),
42
+ className: cn("text-fg-secondary text-gdt-sm", className),
29
43
  ...props
30
44
  });
31
45
  }
@@ -37,4 +51,4 @@ function DashboardHeaderActions({ className, ...props }) {
37
51
  });
38
52
  }
39
53
  //#endregion
40
- export { DashboardHeader, DashboardHeaderActions, DashboardHeaderDescription, DashboardHeaderGroup, DashboardHeaderTitle };
54
+ export { DashboardHeader, DashboardHeaderActions, DashboardHeaderDescription, DashboardHeaderGroup, DashboardHeaderMain, DashboardHeaderNavigation, DashboardHeaderTitle };
@@ -1,4 +1,4 @@
1
- import { n as ButtonLink, t as Button } from "./button-BZIeyUw6.js";
1
+ import { n as ButtonLink, t as Button } from "./button-DW5OijoR.js";
2
2
  import * as React from "react";
3
3
  //#region src/pagination.d.ts
4
4
  declare function Pagination({ className, ...props }: React.ComponentProps<"nav">): React.JSX.Element;
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { t as cn } from "./cn-BI_4DMBf.js";
2
3
  import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "./input-group.js";
3
4
  import { Eye, EyeOff } from "lucide-react";