@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
package/dist/chat.js ADDED
@@ -0,0 +1,528 @@
1
+ "use client";
2
+ import { t as cn } from "./cn-BI_4DMBf.js";
3
+ import { Button } from "./button.js";
4
+ import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "./input-group.js";
5
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "./dropdown-menu.js";
6
+ import { FileText, Image, Plus, Send, Video } from "lucide-react";
7
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
+ import * as React from "react";
9
+ //#region src/chat.tsx
10
+ /**
11
+ * The chat shell — a column of header, body and footer.
12
+ *
13
+ * **It sets no height on purpose.** The consumer decides: `h-[708px]`, `h-full`,
14
+ * `h-dvh`. Note that `ChatBody` can only scroll inside a bounded height — give
15
+ * this element one, or the body grows with its content and the page scrolls
16
+ * instead.
17
+ *
18
+ * Header and footer sit outside the scrolling region, so they never move and
19
+ * never need measuring. Anything that should pin *within* the scroll — tabs, a
20
+ * filter row — belongs in `ChatBodyBar`.
21
+ */
22
+ function Chat({ className, ...props }) {
23
+ return /* @__PURE__ */ jsx("section", {
24
+ "data-slot": "chat",
25
+ className: cn("relative flex min-h-0 flex-col overflow-hidden rounded-[40px] border border-line-subtle bg-surface", className),
26
+ ...props
27
+ });
28
+ }
29
+ /**
30
+ * The fixed header. Composes anything — avatar, title, actions, a sentiment bar.
31
+ *
32
+ * Its own height is free: it can wrap, gain a row, or lose one, and nothing
33
+ * downstream cares. Only the body scrolls.
34
+ */
35
+ function ChatHeader({ className, ...props }) {
36
+ return /* @__PURE__ */ jsx("header", {
37
+ "data-slot": "chat-header",
38
+ className: cn("flex min-h-20 shrink-0 items-center gap-3 border-b border-line-subtle bg-canvas px-3.5", className),
39
+ ...props
40
+ });
41
+ }
42
+ /** The header's primary line — who the conversation is with. */
43
+ function ChatHeaderTitle({ className, ...props }) {
44
+ return /* @__PURE__ */ jsx("h2", {
45
+ "data-slot": "chat-header-title",
46
+ className: cn("truncate text-gdt-md leading-[22px] font-semibold text-fg-primary", className),
47
+ ...props
48
+ });
49
+ }
50
+ /** The header's secondary line — timezone, status, last seen. */
51
+ function ChatHeaderDescription({ className, ...props }) {
52
+ return /* @__PURE__ */ jsx("p", {
53
+ "data-slot": "chat-header-description",
54
+ className: cn("truncate text-gdt-xs leading-[18px] text-fg-secondary", className),
55
+ ...props
56
+ });
57
+ }
58
+ /** Avatar and contact copy. Fills the space between navigation and actions. */
59
+ function ChatHeaderIdentity({ className, ...props }) {
60
+ return /* @__PURE__ */ jsx("div", {
61
+ "data-slot": "chat-header-identity",
62
+ className: cn("flex min-w-0 flex-1 items-center gap-3", className),
63
+ ...props
64
+ });
65
+ }
66
+ /** Stacks the contact name and presence line at the Figma one-pixel gap. */
67
+ function ChatHeaderText({ className, ...props }) {
68
+ return /* @__PURE__ */ jsx("div", {
69
+ "data-slot": "chat-header-text",
70
+ className: cn("flex min-w-0 flex-col gap-px", className),
71
+ ...props
72
+ });
73
+ }
74
+ /** Trailing icon actions such as video and voice calling. */
75
+ function ChatHeaderActions({ className, ...props }) {
76
+ return /* @__PURE__ */ jsx("div", {
77
+ "data-slot": "chat-header-actions",
78
+ className: cn("flex shrink-0 items-center gap-3", className),
79
+ ...props
80
+ });
81
+ }
82
+ /** A 24px header affordance. The caller supplies the icon and behaviour. */
83
+ function ChatHeaderAction({ className, size = "icon-xs", variant = "ghost", ...props }) {
84
+ return /* @__PURE__ */ jsx(Button, {
85
+ "data-slot": "chat-header-action",
86
+ className: cn("text-fg-primary [&_svg:not([class*='size-'])]:size-6", className),
87
+ size,
88
+ type: "button",
89
+ variant,
90
+ ...props
91
+ });
92
+ }
93
+ /**
94
+ * The fixed footer. Wraps `ChatInput`, and owns only the chrome around it —
95
+ * the rule and the surface. The composer owns its internal bar padding.
96
+ */
97
+ function ChatFooter({ className, ...props }) {
98
+ return /* @__PURE__ */ jsx("footer", {
99
+ "data-slot": "chat-footer",
100
+ className: cn("shrink-0 border-t border-line-subtle bg-canvas", className),
101
+ ...props
102
+ });
103
+ }
104
+ /**
105
+ * Distance from the bottom, in px, that still counts as being at the bottom.
106
+ *
107
+ * Big enough to absorb sub-pixel layout rounding, small enough that it never
108
+ * feels sticky.
109
+ */
110
+ const SCROLL_EDGE_THRESHOLD = 8;
111
+ /** Movement below this is layout jitter, not deliberate reader input. */
112
+ const SUBPIXEL_EPSILON = .5;
113
+ const ChatBodyContext = React.createContext(null);
114
+ function assignRef(ref, value) {
115
+ if (typeof ref === "function") {
116
+ ref(value);
117
+ return;
118
+ }
119
+ if (ref) ref.current = value;
120
+ }
121
+ function getStableAnchor(element, content) {
122
+ const stable = element.closest("[data-scroll-anchor],[data-message-id]");
123
+ if (!stable || !content.contains(stable)) return { element };
124
+ const scrollAnchor = stable.getAttribute("data-scroll-anchor");
125
+ if (scrollAnchor !== null) return {
126
+ element: stable,
127
+ attribute: "data-scroll-anchor",
128
+ value: scrollAnchor
129
+ };
130
+ return {
131
+ element: stable,
132
+ attribute: "data-message-id",
133
+ value: stable.getAttribute("data-message-id") ?? void 0
134
+ };
135
+ }
136
+ function findAnchorByAttribute(content, attribute, value) {
137
+ for (const element of content.querySelectorAll(`[${attribute}]`)) if (element.getAttribute(attribute) === value) return element;
138
+ return null;
139
+ }
140
+ /**
141
+ * Read the body's scroll state — for a "jump to bottom" or "N new messages"
142
+ * affordance. Must be called inside `<ChatBody>`.
143
+ */
144
+ function useChatBody() {
145
+ const context = React.useContext(ChatBodyContext);
146
+ if (!context) throw new Error("useChatBody must be called inside <ChatBody>.");
147
+ return context;
148
+ }
149
+ /**
150
+ * The scrolling region of a chat. Composes anything — messages, a table, an
151
+ * empty state. It owns scrolling and nothing else.
152
+ *
153
+ * Two behaviours, and they are the whole point:
154
+ *
155
+ * **Following.** New content keeps the bottom in view — but only while the
156
+ * reader is already there. Any deliberate scroll upward hands control over
157
+ * immediately; returning to the bottom hands it back. It never yanks.
158
+ *
159
+ * **Prepend.** When older messages load in above, the row you were reading
160
+ * stays exactly where it was. This works by remembering an element and its
161
+ * offset from the viewport, then putting it back after the layout changes —
162
+ * which holds no matter where the content grew.
163
+ *
164
+ * Both are driven by one `ResizeObserver` on the content, so they also cover an
165
+ * image finishing its load or a bubble reflowing — things a dependency on a
166
+ * message array cannot see. It fires once on observe, which is what opens the
167
+ * view at the latest message.
168
+ *
169
+ * Children may use `sticky top-0` (see `ChatBodyBar`) — this is their scroll
170
+ * container.
171
+ */
172
+ function ChatBody({ className, children, onScroll, ref, ...props }) {
173
+ const viewportRef = React.useRef(null);
174
+ const contentRef = React.useRef(null);
175
+ const [isPinned, setIsPinned] = React.useState(true);
176
+ const setViewportRef = React.useCallback((node) => {
177
+ viewportRef.current = node;
178
+ assignRef(ref, node);
179
+ }, [ref]);
180
+ const followingRef = React.useRef(true);
181
+ const lastScrollTopRef = React.useRef(0);
182
+ const anchorRef = React.useRef(null);
183
+ const scrollToBottom = React.useCallback((behavior = "smooth") => {
184
+ const viewport = viewportRef.current;
185
+ if (!viewport) return;
186
+ followingRef.current = true;
187
+ setIsPinned(true);
188
+ viewport.scrollTo({
189
+ top: viewport.scrollHeight - viewport.clientHeight,
190
+ behavior
191
+ });
192
+ }, []);
193
+ /**
194
+ * Remember the element under the middle of the viewport, and how far below
195
+ * the viewport's top edge it sits.
196
+ *
197
+ * The middle rather than the top on purpose: `ChatBodyBar` is sticky at the
198
+ * top, so probing there would anchor to a bar that never moves and defeat
199
+ * the whole mechanism.
200
+ */
201
+ const captureAnchor = React.useCallback(() => {
202
+ const viewport = viewportRef.current;
203
+ const content = contentRef.current;
204
+ if (!viewport || !content || followingRef.current) {
205
+ anchorRef.current = null;
206
+ return;
207
+ }
208
+ const bounds = viewport.getBoundingClientRect();
209
+ const element = document.elementFromPoint(bounds.left + bounds.width / 2, bounds.top + bounds.height / 2);
210
+ if (!element || !viewport.contains(element)) {
211
+ anchorRef.current = null;
212
+ return;
213
+ }
214
+ const anchor = getStableAnchor(element, content);
215
+ anchorRef.current = {
216
+ ...anchor,
217
+ top: anchor.element.getBoundingClientRect().top - bounds.top
218
+ };
219
+ }, []);
220
+ /** Put the remembered element back where it was. */
221
+ const restoreAnchor = React.useCallback(() => {
222
+ const viewport = viewportRef.current;
223
+ const content = contentRef.current;
224
+ const anchor = anchorRef.current;
225
+ if (!viewport || !content || !anchor) return;
226
+ let element = anchor.element;
227
+ if (!element.isConnected && anchor.attribute && anchor.value !== void 0) element = findAnchorByAttribute(content, anchor.attribute, anchor.value) ?? element;
228
+ if (!element.isConnected || !content.contains(element)) return;
229
+ const viewportTop = viewport.getBoundingClientRect().top;
230
+ const delta = element.getBoundingClientRect().top - viewportTop - anchor.top;
231
+ if (Math.abs(delta) <= SUBPIXEL_EPSILON) return;
232
+ viewport.scrollTop += delta;
233
+ anchor.element = element;
234
+ anchor.top = element.getBoundingClientRect().top - viewportTop;
235
+ }, []);
236
+ const handleScroll = React.useCallback((event) => {
237
+ onScroll?.(event);
238
+ if (event.defaultPrevented) return;
239
+ const viewport = event.currentTarget;
240
+ const previous = lastScrollTopRef.current;
241
+ const current = viewport.scrollTop;
242
+ lastScrollTopRef.current = current;
243
+ if (viewport.scrollHeight - current - viewport.clientHeight <= SCROLL_EDGE_THRESHOLD) followingRef.current = true;
244
+ else if (current < previous - SUBPIXEL_EPSILON) followingRef.current = false;
245
+ captureAnchor();
246
+ setIsPinned(followingRef.current);
247
+ }, [captureAnchor, onScroll]);
248
+ React.useLayoutEffect(() => {
249
+ scrollToBottom("instant");
250
+ }, [scrollToBottom]);
251
+ React.useEffect(() => {
252
+ const content = contentRef.current;
253
+ if (!content) return;
254
+ const observer = new ResizeObserver(() => {
255
+ if (followingRef.current) scrollToBottom("instant");
256
+ else restoreAnchor();
257
+ });
258
+ observer.observe(content);
259
+ return () => observer.disconnect();
260
+ }, [scrollToBottom, restoreAnchor]);
261
+ const context = React.useMemo(() => ({
262
+ isPinned,
263
+ scrollToBottom
264
+ }), [isPinned, scrollToBottom]);
265
+ return /* @__PURE__ */ jsx(ChatBodyContext.Provider, {
266
+ value: context,
267
+ children: /* @__PURE__ */ jsx("div", {
268
+ ...props,
269
+ ref: setViewportRef,
270
+ "data-slot": "chat-body",
271
+ "data-pinned": isPinned || void 0,
272
+ className: cn("min-h-0 flex-1 overflow-y-auto overscroll-contain bg-surface px-3.5 py-3 [background-image:radial-gradient(circle,color-mix(in_srgb,var(--color-line)_55%,transparent)_0.75px,transparent_0.75px)] [background-position:0_0] [background-size:24px_24px] [overflow-anchor:none]", className),
273
+ onScroll: handleScroll,
274
+ children: /* @__PURE__ */ jsx("div", {
275
+ ref: contentRef,
276
+ "data-slot": "chat-body-content",
277
+ children
278
+ })
279
+ })
280
+ });
281
+ }
282
+ /**
283
+ * A bar pinned to the top of the scrolling region — tabs, a filter row, a date
284
+ * header. Content scrolls behind it.
285
+ *
286
+ * It needs an opaque background to hide what passes under it; `bg-canvas`
287
+ * matches the panel surface and can be overridden.
288
+ */
289
+ function ChatBodyBar({ className, ...props }) {
290
+ return /* @__PURE__ */ jsx("div", {
291
+ "data-slot": "chat-body-bar",
292
+ className: cn("sticky top-0 z-10 bg-canvas", className),
293
+ ...props
294
+ });
295
+ }
296
+ /**
297
+ * Bounds one dated group so its sticky divider is pushed away by the next
298
+ * section instead of stacking with it.
299
+ */
300
+ function ChatDateSection({ className, ...props }) {
301
+ return /* @__PURE__ */ jsx("section", {
302
+ "data-slot": "chat-date-section",
303
+ className: cn("relative flex min-w-0 flex-col gap-3 [&+&]:mt-5", className),
304
+ ...props
305
+ });
306
+ }
307
+ /** Sticky date pill for the messages in its containing `ChatDateSection`. */
308
+ function ChatDateDivider({ className, ...props }) {
309
+ return /* @__PURE__ */ jsx("time", {
310
+ "data-slot": "chat-date-divider",
311
+ className: cn("sticky top-3 z-10 self-center rounded-[14px] bg-canvas px-3 py-1.25 text-gdt-sm font-bold text-fg-secondary shadow-1", className),
312
+ ...props
313
+ });
314
+ }
315
+ /**
316
+ * The default menu — File, Video, Image. Spread it to *extend* rather than
317
+ * replace: `items={[...defaultChatInputAttachItems, { kind: "document", … }]}`.
318
+ */
319
+ const defaultChatInputAttachItems = [
320
+ {
321
+ kind: "file",
322
+ label: "File",
323
+ icon: /* @__PURE__ */ jsx(FileText, {}),
324
+ accept: "*/*"
325
+ },
326
+ {
327
+ kind: "video",
328
+ label: "Video",
329
+ icon: /* @__PURE__ */ jsx(Video, {}),
330
+ accept: "video/*"
331
+ },
332
+ {
333
+ kind: "image",
334
+ label: "Image",
335
+ icon: /* @__PURE__ */ jsx(Image, {}),
336
+ accept: "image/*"
337
+ }
338
+ ];
339
+ const ChatInputContext = React.createContext(null);
340
+ function useChatInputContext() {
341
+ const context = React.useContext(ChatInputContext);
342
+ if (!context) throw new Error("ChatInput parts must be rendered inside <ChatInput>.");
343
+ return context;
344
+ }
345
+ /**
346
+ * The chat composer. A real `<form>`, so Enter and the send button are one
347
+ * submit path rather than two.
348
+ *
349
+ * Compose the parts inside it — the layout is yours, the behaviour is ours.
350
+ */
351
+ function ChatInput({ value, defaultValue, onValueChange, onSend, clearOnSend = true, disabled = false, className, children, ...props }) {
352
+ const [uncontrolled, setUncontrolled] = React.useState(defaultValue ?? "");
353
+ const isControlled = value !== void 0;
354
+ const text = isControlled ? value : uncontrolled;
355
+ const canSend = onSend !== void 0;
356
+ const setValue = React.useCallback((next) => {
357
+ if (!isControlled) setUncontrolled(next);
358
+ onValueChange?.(next);
359
+ }, [isControlled, onValueChange]);
360
+ const submit = React.useCallback(() => {
361
+ const trimmed = text.trim();
362
+ if (!trimmed || disabled || !onSend) return;
363
+ const accepted = onSend(trimmed);
364
+ if (clearOnSend && accepted !== false) setValue("");
365
+ }, [
366
+ text,
367
+ disabled,
368
+ onSend,
369
+ clearOnSend,
370
+ setValue
371
+ ]);
372
+ const context = React.useMemo(() => ({
373
+ value: text,
374
+ setValue,
375
+ disabled,
376
+ canSend,
377
+ submit
378
+ }), [
379
+ text,
380
+ setValue,
381
+ disabled,
382
+ canSend,
383
+ submit
384
+ ]);
385
+ return /* @__PURE__ */ jsx(ChatInputContext.Provider, {
386
+ value: context,
387
+ children: /* @__PURE__ */ jsx("form", {
388
+ "data-slot": "chat-input",
389
+ "data-disabled": disabled || void 0,
390
+ className: cn("flex min-h-19 w-full min-w-0 items-center gap-2.5 bg-canvas px-3.5", className),
391
+ ...props,
392
+ onSubmit: (event) => {
393
+ event.preventDefault();
394
+ submit();
395
+ },
396
+ children
397
+ })
398
+ });
399
+ }
400
+ /** The `+` button. Opens a dropdown of file kinds, each opening a filtered picker. */
401
+ function ChatInputAttach({ onAttach, items = defaultChatInputAttachItems, className, contentClassName, disabled: disabledProp = false, "aria-label": ariaLabel = "Add attachment", children, size = "icon-xs", variant = "ghost", ...props }) {
402
+ const { disabled } = useChatInputContext();
403
+ const fileRef = React.useRef(null);
404
+ const kindRef = React.useRef("");
405
+ const [open, setOpen] = React.useState(false);
406
+ const hasActionableItem = items.some((item) => item.onSelect !== void 0 || onAttach !== void 0);
407
+ const isDisabled = disabled || disabledProp || !hasActionableItem;
408
+ const handleSelect = (item) => {
409
+ setOpen(false);
410
+ if (item.onSelect) {
411
+ item.onSelect();
412
+ return;
413
+ }
414
+ const input = fileRef.current;
415
+ if (!input || onAttach === void 0) return;
416
+ kindRef.current = item.kind;
417
+ input.accept = item.accept ?? "*/*";
418
+ input.value = "";
419
+ input.click();
420
+ };
421
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("input", {
422
+ ref: fileRef,
423
+ className: "hidden",
424
+ disabled: isDisabled,
425
+ multiple: true,
426
+ onChange: (event) => {
427
+ const files = Array.from(event.target.files ?? []);
428
+ if (files.length > 0) onAttach?.(files, kindRef.current);
429
+ },
430
+ tabIndex: -1,
431
+ type: "file"
432
+ }), /* @__PURE__ */ jsxs(DropdownMenu, {
433
+ onOpenChange: (nextOpen) => setOpen(isDisabled ? false : nextOpen),
434
+ open: isDisabled ? false : open,
435
+ children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
436
+ disabled: isDisabled,
437
+ render: /* @__PURE__ */ jsx(Button, {
438
+ ...props,
439
+ "aria-label": ariaLabel,
440
+ "data-slot": "chat-input-attach",
441
+ className: cn("shrink-0 text-fg-secondary data-popup-open:bg-action-tertiary [&_svg:not([class*='size-'])]:size-6", className),
442
+ disabled: isDisabled,
443
+ size,
444
+ type: "button",
445
+ variant
446
+ }),
447
+ children: children ?? /* @__PURE__ */ jsx(Plus, {})
448
+ }), /* @__PURE__ */ jsx(DropdownMenuContent, {
449
+ align: "start",
450
+ className: cn("w-auto min-w-28 rounded-lg bg-surface-overlay shadow-2", contentClassName),
451
+ side: "top",
452
+ sideOffset: 8,
453
+ children: items.map((item) => /* @__PURE__ */ jsxs(DropdownMenuItem, {
454
+ className: "gap-2.5 px-2.5 py-2 text-gdt-sm [&_svg:not([class*='size-'])]:size-6",
455
+ onClick: () => handleSelect(item),
456
+ children: [item.icon, item.label]
457
+ }, item.kind))
458
+ })]
459
+ })] });
460
+ }
461
+ /**
462
+ * The single-line message field.
463
+ *
464
+ * Enter sends. The `isComposing` guard stops Enter from sending while an IME
465
+ * candidate is being confirmed.
466
+ */
467
+ function ChatInputField({ className, children, disabled: disabledProp = false, onKeyDown, type = "text", ...props }) {
468
+ const { value, setValue, disabled, canSend, submit } = useChatInputContext();
469
+ const isDisabled = disabled || disabledProp || !canSend;
470
+ return /* @__PURE__ */ jsxs(InputGroup, {
471
+ "data-slot": "chat-input-field",
472
+ className: "h-11 rounded-[22px] border-[1.5px] border-transparent bg-surface-raised focus-within:border-line-focus focus-within:shadow-[0_0_0_3px_var(--color-line-focus)] focus-within:inset-ring-0",
473
+ children: [/* @__PURE__ */ jsx(InputGroupInput, {
474
+ ...props,
475
+ className: cn("px-4 text-gdt-sm", className),
476
+ disabled: isDisabled,
477
+ onChange: (event) => setValue(event.target.value),
478
+ onKeyDown: (event) => {
479
+ onKeyDown?.(event);
480
+ if (event.defaultPrevented) return;
481
+ if (event.key !== "Enter" || event.nativeEvent.isComposing) return;
482
+ event.preventDefault();
483
+ submit();
484
+ },
485
+ type,
486
+ value
487
+ }), children]
488
+ });
489
+ }
490
+ /**
491
+ * An accessible button inside the field — emoji, mic, whatever. The caller
492
+ * owns the action, while the component owns the input-group placement.
493
+ */
494
+ function ChatInputAction({ align = "inline-start", addonClassName, className, disabled: disabledProp = false, size = "icon-xs", ...props }) {
495
+ const { disabled, canSend } = useChatInputContext();
496
+ return /* @__PURE__ */ jsx(InputGroupAddon, {
497
+ "data-slot": "chat-input-action",
498
+ align,
499
+ className: cn("p-0 has-[>button]:m-0", align === "inline-start" ? "pl-4" : "pr-4", addonClassName),
500
+ children: /* @__PURE__ */ jsx(InputGroupButton, {
501
+ ...props,
502
+ className: cn("text-fg-secondary [&_svg:not([class*='size-'])]:size-6", className),
503
+ disabled: disabled || disabledProp || !canSend,
504
+ size,
505
+ type: "button"
506
+ })
507
+ });
508
+ }
509
+ /** Submit button. Disabled while empty — there is nothing to send. */
510
+ function ChatInputSend({ className, disabled: disabledProp = false, "aria-label": ariaLabel = "Send message", size = "icon", variant = "primary", ...props }) {
511
+ const { value, disabled, canSend } = useChatInputContext();
512
+ const unavailable = disabled || disabledProp || !canSend;
513
+ const empty = value.trim().length === 0;
514
+ return /* @__PURE__ */ jsx(Button, {
515
+ ...props,
516
+ "aria-label": ariaLabel,
517
+ "data-slot": "chat-input-send",
518
+ "data-empty": empty && !unavailable ? true : void 0,
519
+ className: cn("size-11 shrink-0 rounded-full disabled:bg-action-tertiary disabled:text-fg-caption-placeholder disabled:opacity-100 [&_svg:not([class*='size-'])]:size-5.5", className),
520
+ disabled: unavailable || empty,
521
+ size,
522
+ type: "submit",
523
+ variant,
524
+ children: /* @__PURE__ */ jsx(Send, {})
525
+ });
526
+ }
527
+ //#endregion
528
+ export { Chat, ChatBody, ChatBodyBar, ChatDateDivider, ChatDateSection, ChatFooter, ChatHeader, ChatHeaderAction, ChatHeaderActions, ChatHeaderDescription, ChatHeaderIdentity, ChatHeaderText, ChatHeaderTitle, ChatInput, ChatInputAction, ChatInputAttach, ChatInputField, ChatInputSend, defaultChatInputAttachItems, useChatBody };
package/dist/checkbox.js CHANGED
@@ -7,7 +7,7 @@ import { cva } from "class-variance-authority";
7
7
  import { Checkbox as Checkbox$1 } from "@base-ui/react/checkbox";
8
8
  import { CheckboxGroup } from "@base-ui/react/checkbox-group";
9
9
  //#region src/checkbox.tsx
10
- const checkboxVariants = cva("peer group/checkbox relative flex shrink-0 items-center justify-center rounded-sm border bg-canvas text-transparent outline-none transition-[background-color,border-color,box-shadow,color] after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-line-focus focus-visible:shadow-[0px_0px_0px_2px_var(--color-line-focus)] aria-invalid:border-line-danger aria-invalid:ring-3 aria-invalid:ring-line-danger/20 aria-invalid:focus-visible:border-line-danger aria-invalid:focus-visible:shadow-[0px_0px_0px_2px_var(--color-line-danger)] border-line data-unchecked:hover:border-line-focus data-unchecked:hover:bg-surface-brand-subtle data-checked:border-action-primary data-checked:bg-action-primary data-checked:text-fg-on-brand data-disabled:cursor-not-allowed data-disabled:border-line-subtle data-disabled:bg-surface data-disabled:text-fg-caption-placeholder size-5 [--checkbox-icon-size:0.75rem] group-data-[size=sm]/field:size-4 group-data-[size=sm]/field:[--checkbox-icon-size:0.625rem] group-data-[size=md]/field:size-5 group-data-[size=md]/field:[--checkbox-icon-size:0.75rem] group-data-[size=lg]/field:size-6 group-data-[size=lg]/field:[--checkbox-icon-size:0.875rem]", { variants: { size: {
10
+ const checkboxVariants = cva("peer group/checkbox relative flex shrink-0 items-center justify-center rounded-sm border bg-canvas text-transparent outline-none transition-[background-color,border-color,box-shadow,color,scale] duration-[var(--duration-motion-press)] ease-gdt-out active:scale-[0.97] motion-reduce:active:scale-100 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-line-focus focus-visible:shadow-[0px_0px_0px_2px_var(--color-line-focus)] aria-invalid:border-line-danger aria-invalid:ring-3 aria-invalid:ring-line-danger/20 aria-invalid:focus-visible:border-line-danger aria-invalid:focus-visible:shadow-[0px_0px_0px_2px_var(--color-line-danger)] border-line data-unchecked:hover:border-line-focus data-unchecked:hover:bg-surface-brand-subtle data-checked:border-action-primary data-checked:bg-action-primary data-checked:text-fg-on-brand data-disabled:cursor-not-allowed data-disabled:border-line-subtle data-disabled:bg-surface data-disabled:text-fg-caption-placeholder data-disabled:active:scale-100 size-5 [--checkbox-icon-size:0.75rem] group-data-[size=sm]/field:size-4 group-data-[size=sm]/field:[--checkbox-icon-size:0.625rem] group-data-[size=md]/field:size-5 group-data-[size=md]/field:[--checkbox-icon-size:0.75rem] group-data-[size=lg]/field:size-6 group-data-[size=lg]/field:[--checkbox-icon-size:0.875rem]", { variants: { size: {
11
11
  sm: "size-4 [--checkbox-icon-size:0.625rem]",
12
12
  md: "size-5 [--checkbox-icon-size:0.75rem]",
13
13
  lg: "size-6 [--checkbox-icon-size:0.875rem]"
package/dist/combobox.js CHANGED
@@ -40,7 +40,7 @@ const comboboxInputVariants = cva("w-auto has-data-popup-open:rounded-2xl has-da
40
40
  } },
41
41
  defaultVariants: { size: "md" }
42
42
  });
43
- const comboboxContentVariants = cva("relative isolate z-50 max-h-(--available-height) w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width))] origin-(--transform-origin) overflow-hidden border border-line-subtle bg-canvas text-fg-primary shadow-(--elevation-e1-shadow) duration-100 data-[chips=true]:min-w-(--anchor-width) data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 *:data-[slot=input-group]:mb-1 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", {
43
+ const comboboxContentVariants = cva("relative isolate z-50 max-h-(--available-height) w-(--anchor-width) max-w-(--available-width) min-w-[calc(var(--anchor-width))] origin-(--transform-origin) overflow-hidden border border-line-subtle bg-canvas text-fg-primary shadow-(--elevation-e1-shadow) transition-[opacity,scale,translate] duration-[var(--duration-motion-popup)] ease-gdt-out data-[chips=true]:min-w-(--anchor-width) data-starting-style:opacity-0 data-starting-style:scale-95 data-ending-style:opacity-0 data-ending-style:scale-95 data-[side=bottom]:data-starting-style:translate-y-2 data-[side=bottom]:data-ending-style:translate-y-2 data-[side=top]:data-starting-style:-translate-y-2 data-[side=top]:data-ending-style:-translate-y-2 data-[side=left]:data-starting-style:translate-x-2 data-[side=left]:data-ending-style:translate-x-2 data-[side=right]:data-starting-style:-translate-x-2 data-[side=right]:data-ending-style:-translate-x-2 data-[side=inline-end]:data-starting-style:-translate-x-2 data-[side=inline-end]:data-ending-style:-translate-x-2 data-[side=inline-start]:data-starting-style:translate-x-2 data-[side=inline-start]:data-ending-style:translate-x-2 motion-reduce:data-starting-style:scale-100 motion-reduce:data-ending-style:scale-100 motion-reduce:data-[side=bottom]:data-starting-style:translate-y-0 motion-reduce:data-[side=bottom]:data-ending-style:translate-y-0 motion-reduce:data-[side=top]:data-starting-style:translate-y-0 motion-reduce:data-[side=top]:data-ending-style:translate-y-0 motion-reduce:data-[side=left]:data-starting-style:translate-x-0 motion-reduce:data-[side=left]:data-ending-style:translate-x-0 motion-reduce:data-[side=right]:data-starting-style:translate-x-0 motion-reduce:data-[side=right]:data-ending-style:translate-x-0 motion-reduce:data-[side=inline-end]:data-starting-style:translate-x-0 motion-reduce:data-[side=inline-end]:data-ending-style:translate-x-0 motion-reduce:data-[side=inline-start]:data-starting-style:translate-x-0 motion-reduce:data-[side=inline-start]:data-ending-style:translate-x-0 *:data-[slot=input-group]:mb-1", {
44
44
  variants: { size: {
45
45
  sm: "rounded-lg p-1",
46
46
  md: "rounded-xl p-1.5",
package/dist/cta.d.ts CHANGED
@@ -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 { useRender } from "@base-ui/react/use-render";
3
3
  import { VariantProps } from "class-variance-authority";
4
4
  import * as React from "react";
package/dist/cta.js CHANGED
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { t as cn } from "./cn-BI_4DMBf.js";
2
3
  import { Button, ButtonLink } from "./button.js";
3
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -1,4 +1,4 @@
1
- import { t as Button } from "./button-BZIeyUw6.js";
1
+ import { t as Button } from "./button-DW5OijoR.js";
2
2
  import { Empty } from "./empty-state.js";
3
3
  import { FilterApplyButtonProps, FilterContentProps, FilterProps, FilterResetButtonProps } from "./filter.js";
4
4
  import { Pagination } from "./pagination.js";
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { t as cn } from "./cn-BI_4DMBf.js";
2
3
  import { jsx } from "react/jsx-runtime";
3
4
  import { mergeProps } from "@base-ui/react/merge-props";
package/dist/dialog.js CHANGED
@@ -38,11 +38,11 @@ function DialogOverlay({ className, ...props }) {
38
38
  forceRender: true,
39
39
  "data-slot": "dialog-overlay",
40
40
  "data-nested": nested || void 0,
41
- className: cn("fixed inset-0 z-50 transition-opacity duration-150 data-closed:opacity-0 data-open:opacity-100", nested ? "bg-black/25" : "bg-black/55 supports-backdrop-filter:backdrop-blur-xl", className),
41
+ className: cn("fixed inset-0 z-50 transition-opacity duration-150 ease-gdt-out data-closed:opacity-0 data-open:opacity-100", nested ? "bg-black/25" : "bg-black/55 supports-backdrop-filter:backdrop-blur-xl", className),
42
42
  ...props
43
43
  });
44
44
  }
45
- const dialogContentVariants = cva("group/dialog-content fixed top-1/2 left-1/2 z-50 flex max-h-[calc(100dvh-5rem)] w-full max-w-[calc(100vw-2rem)] -translate-x-1/2 -translate-y-1/2 flex-col gap-4 overflow-visible rounded-2xl border border-line-subtle bg-surface-overlay bg-clip-padding p-(--dialog-padding) text-fg-primary shadow-(--elevation-e2-shadow) outline-none [--dialog-padding:1.25rem] transition-[opacity,transform] duration-200 ease-in-out data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 origin-center data-[nested-dialog-open]:scale-[0.96]", {
45
+ const dialogContentVariants = cva("group/dialog-content fixed top-1/2 left-1/2 z-50 flex max-h-[calc(100dvh-5rem)] w-full max-w-[calc(100vw-2rem)] -translate-x-1/2 -translate-y-1/2 flex-col gap-4 overflow-visible rounded-2xl border border-line-subtle bg-surface-overlay bg-clip-padding p-(--dialog-padding) text-fg-primary shadow-(--elevation-e2-shadow) outline-none [--dialog-padding:1.25rem] origin-center transition-[opacity,scale] duration-[var(--duration-motion-popup)] ease-gdt-out data-starting-style:opacity-0 data-starting-style:scale-95 data-ending-style:opacity-0 data-ending-style:scale-95 motion-reduce:data-starting-style:scale-100 motion-reduce:data-ending-style:scale-100 data-[nested-dialog-open]:scale-[0.96]", {
46
46
  variants: { size: {
47
47
  sm: "sm:w-90 sm:[--dialog-padding:1.5rem]",
48
48
  md: "sm:w-105 sm:[--dialog-padding:1.5rem]",
@@ -28,7 +28,7 @@ function DropdownMenuTrigger({ ...props }) {
28
28
  ...props
29
29
  });
30
30
  }
31
- const dropdownMenuContentVariants = cva("relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto border border-line-subtle bg-canvas text-fg-primary shadow-(--elevation-e1-shadow) duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:overflow-hidden data-closed:fade-out-0 data-closed:zoom-out-95", {
31
+ const dropdownMenuContentVariants = cva("relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto border border-line-subtle bg-canvas text-fg-primary shadow-(--elevation-e1-shadow) transition-[opacity,scale,translate] duration-[var(--duration-motion-popup)] ease-gdt-out outline-none data-starting-style:opacity-0 data-starting-style:scale-95 data-ending-style:opacity-0 data-ending-style:scale-95 data-closed:overflow-hidden data-[side=bottom]:data-starting-style:translate-y-2 data-[side=bottom]:data-ending-style:translate-y-2 data-[side=top]:data-starting-style:-translate-y-2 data-[side=top]:data-ending-style:-translate-y-2 data-[side=left]:data-starting-style:translate-x-2 data-[side=left]:data-ending-style:translate-x-2 data-[side=right]:data-starting-style:-translate-x-2 data-[side=right]:data-ending-style:-translate-x-2 data-[side=inline-end]:data-starting-style:-translate-x-2 data-[side=inline-end]:data-ending-style:-translate-x-2 data-[side=inline-start]:data-starting-style:translate-x-2 data-[side=inline-start]:data-ending-style:translate-x-2 motion-reduce:data-starting-style:scale-100 motion-reduce:data-ending-style:scale-100 motion-reduce:data-[side=bottom]:data-starting-style:translate-y-0 motion-reduce:data-[side=bottom]:data-ending-style:translate-y-0 motion-reduce:data-[side=top]:data-starting-style:translate-y-0 motion-reduce:data-[side=top]:data-ending-style:translate-y-0 motion-reduce:data-[side=left]:data-starting-style:translate-x-0 motion-reduce:data-[side=left]:data-ending-style:translate-x-0 motion-reduce:data-[side=right]:data-starting-style:translate-x-0 motion-reduce:data-[side=right]:data-ending-style:translate-x-0 motion-reduce:data-[side=inline-end]:data-starting-style:translate-x-0 motion-reduce:data-[side=inline-end]:data-ending-style:translate-x-0 motion-reduce:data-[side=inline-start]:data-starting-style:translate-x-0 motion-reduce:data-[side=inline-start]:data-ending-style:translate-x-0", {
32
32
  variants: { size: {
33
33
  sm: "rounded-lg p-1",
34
34
  md: "rounded-xl p-1.5",
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { t as cn } from "./cn-BI_4DMBf.js";
2
3
  import { Avatar, AvatarFallback, AvatarImage } from "./avatar.js";
3
4
  import { buttonVariants } from "./button.js";
@@ -27,7 +27,7 @@ function FileUpload({ className, state = "uploading", value, "aria-busy": ariaBu
27
27
  "data-state": state,
28
28
  "data-value": progress,
29
29
  "aria-busy": ariaBusy ?? (state === "uploading" || void 0),
30
- className: cn("group/file-upload relative flex min-w-0 items-start gap-3 rounded-xl border border-line bg-surface p-3", "data-[state=error]:border-line-danger data-[state=error]:bg-surface-danger-subtle", className),
30
+ className: cn("group/file-upload relative flex min-w-0 items-start gap-3 rounded-xl border border-line bg-surface p-3 transition-[background-color,border-color,color] duration-[var(--duration-motion-press)] ease-gdt-out", "data-[state=error]:border-line-danger data-[state=error]:bg-surface-danger-subtle", className),
31
31
  ...props
32
32
  })
33
33
  });
@@ -64,7 +64,7 @@ function FileUploadPreviewAction({ className, ...props }) {
64
64
  function FileUploadIcon({ className, children, ...props }) {
65
65
  return /* @__PURE__ */ jsx("div", {
66
66
  "data-slot": "file-upload-icon",
67
- className: cn("flex size-11 shrink-0 items-center justify-center rounded-lg bg-surface-brand-subtle text-fg-brand", "group-data-[state=error]/file-upload:bg-surface-danger-subtle group-data-[state=error]/file-upload:text-status-danger", className),
67
+ className: cn("flex size-11 shrink-0 items-center justify-center rounded-lg bg-surface-brand-subtle text-fg-brand transition-[background-color,border-color,color] duration-[var(--duration-motion-press)] ease-gdt-out", "group-data-[state=error]/file-upload:bg-surface-danger-subtle group-data-[state=error]/file-upload:text-status-danger", className),
68
68
  ...props,
69
69
  children: children ?? /* @__PURE__ */ jsx(File, {
70
70
  "aria-hidden": "true",
@@ -112,7 +112,7 @@ function FileUploadStatus({ className, children, pendingLabel = "Waiting to uplo
112
112
  return /* @__PURE__ */ jsx("output", {
113
113
  "data-slot": "file-upload-status",
114
114
  "aria-live": "polite",
115
- className: cn("text-gdt-xs leading-snug text-fg-secondary", "group-data-[state=complete]/file-upload:text-status-success group-data-[state=error]/file-upload:text-status-danger", className),
115
+ className: cn("text-gdt-xs leading-snug text-fg-secondary transition-[background-color,border-color,color] duration-[var(--duration-motion-press)] ease-gdt-out", "group-data-[state=complete]/file-upload:text-status-success group-data-[state=error]/file-upload:text-status-danger", className),
116
116
  ...props,
117
117
  children: content
118
118
  });
package/dist/footer.js CHANGED
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { t as cn } from "./cn-BI_4DMBf.js";
2
3
  import { InputGroup, InputGroupButton, InputGroupInput } from "./input-group.js";
3
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -74,7 +74,7 @@ function DefaultLoader({ label }) {
74
74
  return /* @__PURE__ */ jsxs("output", {
75
75
  "aria-live": "polite",
76
76
  className: "flex items-center justify-center gap-2 px-4 py-6 text-fg-secondary text-gdt-sm",
77
- children: [/* @__PURE__ */ jsx(Loader2, { className: "animate-spin" }), /* @__PURE__ */ jsx("span", { children: label })]
77
+ children: [/* @__PURE__ */ jsx(Loader2, { className: "animate-spin motion-reduce:animate-none" }), /* @__PURE__ */ jsx("span", { children: label })]
78
78
  });
79
79
  }
80
80
  function InfiniteScrollLoading({ children }) {