@octaviaflow/core 3.1.0-beta.75 → 3.1.0-beta.77

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 (40) hide show
  1. package/dist/chunk-CVU6QDOL.js +1696 -0
  2. package/dist/chunk-CVU6QDOL.js.map +1 -0
  3. package/dist/chunk-PZIWTCI6.js +1690 -0
  4. package/dist/chunk-PZIWTCI6.js.map +1 -0
  5. package/dist/chunk-REEBXURQ.js +1646 -0
  6. package/dist/chunk-REEBXURQ.js.map +1 -0
  7. package/dist/components/Avatar/Avatar.d.ts +17 -3
  8. package/dist/components/Avatar/Avatar.d.ts.map +1 -1
  9. package/dist/components/Avatar/index.d.ts +1 -1
  10. package/dist/components/Avatar/index.d.ts.map +1 -1
  11. package/dist/components/BlogCard/BlogCard.d.ts +1 -1
  12. package/dist/components/BlogCard/BlogCard.d.ts.map +1 -1
  13. package/dist/components/KanbanCard/KanbanCard.d.ts +1 -1
  14. package/dist/components/KanbanCard/KanbanCard.d.ts.map +1 -1
  15. package/dist/components/MembersSettings/MembersSettings.d.ts.map +1 -1
  16. package/dist/components/OTPInput/OTPInput.d.ts.map +1 -1
  17. package/dist/components/OnboardingFlow/OnboardingFlow.d.ts +90 -0
  18. package/dist/components/OnboardingFlow/OnboardingFlow.d.ts.map +1 -0
  19. package/dist/components/OnboardingFlow/index.d.ts +2 -0
  20. package/dist/components/OnboardingFlow/index.d.ts.map +1 -0
  21. package/dist/components/OrganizationSettings/OrganizationSettings.d.ts.map +1 -1
  22. package/dist/components/SettingsLayout/SettingsLayout.d.ts +1 -1
  23. package/dist/components/SettingsLayout/SettingsLayout.d.ts.map +1 -1
  24. package/dist/components/TemplateCard/TemplateCard.d.ts +1 -1
  25. package/dist/components/TemplateCard/TemplateCard.d.ts.map +1 -1
  26. package/dist/components/TestimonialCard/TestimonialCard.d.ts +1 -1
  27. package/dist/components/TestimonialCard/TestimonialCard.d.ts.map +1 -1
  28. package/dist/components/UserCard/UserCard.d.ts +1 -1
  29. package/dist/components/UserCard/UserCard.d.ts.map +1 -1
  30. package/dist/index.cjs +4793 -4481
  31. package/dist/index.cjs.map +1 -1
  32. package/dist/index.d.ts +3 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +3685 -3355
  35. package/dist/index.js.map +1 -1
  36. package/dist/marketing.cjs +228 -260
  37. package/dist/marketing.cjs.map +1 -1
  38. package/dist/marketing.js +1 -1
  39. package/dist/styles.css +1 -1
  40. package/package.json +1 -1
@@ -0,0 +1,1690 @@
1
+ import {
2
+ Slot,
3
+ resolveAccessibleName
4
+ } from "./chunk-CRDYAV5R.js";
5
+ import {
6
+ cn
7
+ } from "./chunk-ZAUUGK2Y.js";
8
+
9
+ // src/components/Card/Card.tsx
10
+ import { motion, useReducedMotion } from "framer-motion";
11
+ import {
12
+ forwardRef
13
+ } from "react";
14
+ import { jsx } from "react/jsx-runtime";
15
+ var Card = forwardRef(function Card2({
16
+ variant = "default",
17
+ padding = "md",
18
+ radius = "md",
19
+ hoverable = false,
20
+ disabled = false,
21
+ loading = false,
22
+ asChild = false,
23
+ onClick,
24
+ onKeyDown,
25
+ className,
26
+ children,
27
+ ...rest
28
+ }, ref) {
29
+ const reducedMotion = useReducedMotion();
30
+ const isInteractive = Boolean(onClick) && !disabled && !loading;
31
+ const handleKeyDown = (e) => {
32
+ onKeyDown?.(e);
33
+ if (e.defaultPrevented) return;
34
+ if (!isInteractive) return;
35
+ if ((e.key === "Enter" || e.key === " ") && !e.repeat) {
36
+ e.preventDefault();
37
+ onClick?.(e);
38
+ }
39
+ };
40
+ const rootClassName = cn(
41
+ "ods-card",
42
+ `ods-card--${variant}`,
43
+ `ods-card--pad-${padding}`,
44
+ `ods-card--radius-${radius}`,
45
+ (hoverable || isInteractive) && !loading && !disabled && "ods-card--hoverable",
46
+ isInteractive && "ods-card--clickable",
47
+ disabled && "ods-card--disabled",
48
+ loading && "ods-card--loading",
49
+ className
50
+ );
51
+ if (asChild) {
52
+ return /* @__PURE__ */ jsx(
53
+ Slot,
54
+ {
55
+ ...rest,
56
+ ref,
57
+ className: rootClassName,
58
+ onClick: isInteractive ? onClick : void 0,
59
+ onKeyDown: isInteractive ? handleKeyDown : onKeyDown,
60
+ "aria-disabled": disabled || void 0,
61
+ children
62
+ }
63
+ );
64
+ }
65
+ if (loading) {
66
+ return /* @__PURE__ */ jsx(
67
+ "div",
68
+ {
69
+ ...rest,
70
+ ref,
71
+ className: rootClassName,
72
+ "aria-busy": "true",
73
+ "aria-label": "Loading",
74
+ children: /* @__PURE__ */ jsx("div", { className: "ods-card__skeleton", "data-ods-animate": "shimmer" })
75
+ }
76
+ );
77
+ }
78
+ return /* @__PURE__ */ jsx(
79
+ motion.div,
80
+ {
81
+ ...rest,
82
+ ref,
83
+ onClick: isInteractive ? onClick : void 0,
84
+ onKeyDown: isInteractive ? handleKeyDown : onKeyDown,
85
+ className: rootClassName,
86
+ whileHover: (hoverable || isInteractive) && !reducedMotion ? { y: -2, transition: { duration: 0.15 } } : void 0,
87
+ whileTap: isInteractive && !reducedMotion ? { scale: 0.99 } : void 0,
88
+ role: isInteractive ? "button" : rest.role,
89
+ tabIndex: isInteractive ? rest.tabIndex ?? 0 : rest.tabIndex,
90
+ "aria-disabled": disabled || void 0,
91
+ children
92
+ }
93
+ );
94
+ });
95
+ Card.displayName = "Card";
96
+ var CardHeader = forwardRef(
97
+ function CardHeader2({ className, children, ...rest }, ref) {
98
+ return /* @__PURE__ */ jsx("div", { ...rest, ref, className: cn("ods-card__header", className), children });
99
+ }
100
+ );
101
+ CardHeader.displayName = "Card.Header";
102
+ var CardBody = forwardRef(
103
+ function CardBody2({ className, children, ...rest }, ref) {
104
+ return /* @__PURE__ */ jsx("div", { ...rest, ref, className: cn("ods-card__body", className), children });
105
+ }
106
+ );
107
+ CardBody.displayName = "Card.Body";
108
+ var CardFooter = forwardRef(
109
+ function CardFooter2({ className, children, ...rest }, ref) {
110
+ return /* @__PURE__ */ jsx("div", { ...rest, ref, className: cn("ods-card__footer", className), children });
111
+ }
112
+ );
113
+ CardFooter.displayName = "Card.Footer";
114
+ var CardTitle = forwardRef(
115
+ function CardTitle2({ as: Comp = "h3", className, children, ...rest }, ref) {
116
+ return /* @__PURE__ */ jsx(
117
+ Comp,
118
+ {
119
+ ...rest,
120
+ ref,
121
+ className: cn("ods-card__title", className),
122
+ children
123
+ }
124
+ );
125
+ }
126
+ );
127
+ CardTitle.displayName = "Card.Title";
128
+ var CardDescription = forwardRef(function CardDescription2({ className, children, ...rest }, ref) {
129
+ return /* @__PURE__ */ jsx("p", { ...rest, ref, className: cn("ods-card__description", className), children });
130
+ });
131
+ CardDescription.displayName = "Card.Description";
132
+ Card.Header = CardHeader;
133
+ Card.Body = CardBody;
134
+ Card.Footer = CardFooter;
135
+ Card.Title = CardTitle;
136
+ Card.Description = CardDescription;
137
+
138
+ // src/components/Avatar/Avatar.tsx
139
+ import {
140
+ forwardRef as forwardRef2,
141
+ useCallback,
142
+ useMemo,
143
+ useState
144
+ } from "react";
145
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
146
+ var PALETTE_SIZE = 12;
147
+ function djb2(str) {
148
+ let h = 5381;
149
+ for (let i = 0; i < str.length; i++) h = (h << 5) + h + str.charCodeAt(i);
150
+ return h >>> 0;
151
+ }
152
+ function paletteIndexFor(seed) {
153
+ return djb2(seed) % PALETTE_SIZE;
154
+ }
155
+ function normalizeInitials(raw) {
156
+ const trimmed = raw.trim();
157
+ if (!trimmed) return "";
158
+ const tokens = trimmed.split(/\s+/).filter(Boolean);
159
+ if (tokens.length >= 2) {
160
+ return (tokens[0].charAt(0) + tokens[1].charAt(0)).toUpperCase();
161
+ }
162
+ return trimmed.slice(0, 2).toUpperCase();
163
+ }
164
+ var STATUS_DEFAULT_LABEL = {
165
+ online: "Online",
166
+ offline: "Offline",
167
+ busy: "Busy",
168
+ away: "Away"
169
+ };
170
+ var Avatar = forwardRef2(function Avatar2({
171
+ src,
172
+ alt,
173
+ initials,
174
+ icon,
175
+ fallback,
176
+ seed,
177
+ palette,
178
+ size = "md",
179
+ shape = "circle",
180
+ status,
181
+ statusLabel,
182
+ onClick,
183
+ imgLoading = "lazy",
184
+ referrerPolicy,
185
+ className,
186
+ ...rest
187
+ }, ref) {
188
+ const [imgFailed, setImgFailed] = useState(false);
189
+ const handleImgError = useCallback(() => setImgFailed(true), []);
190
+ const showImage = Boolean(src) && !imgFailed;
191
+ const normalizedInitials = useMemo(
192
+ () => initials ? normalizeInitials(initials) : "",
193
+ [initials]
194
+ );
195
+ const paletteIdx = useMemo(() => {
196
+ if (palette !== void 0) {
197
+ return (Math.trunc(palette) % PALETTE_SIZE + PALETTE_SIZE) % PALETTE_SIZE;
198
+ }
199
+ const resolved = seed ?? alt ?? initials ?? "?";
200
+ return paletteIndexFor(resolved);
201
+ }, [palette, seed, alt, initials]);
202
+ const baseName = alt || normalizedInitials || "Avatar";
203
+ const announcedStatus = status ? statusLabel ?? STATUS_DEFAULT_LABEL[status] : "";
204
+ const accessibleName = announcedStatus ? `${baseName}, ${announcedStatus}` : baseName;
205
+ const isClickable = Boolean(onClick);
206
+ const handleKeyDown = (e) => {
207
+ if (!isClickable) return;
208
+ if ((e.key === "Enter" || e.key === " ") && !e.repeat) {
209
+ e.preventDefault();
210
+ onClick?.();
211
+ }
212
+ };
213
+ const rootClassName = cn(
214
+ "ods-avatar",
215
+ `ods-avatar--${size}`,
216
+ `ods-avatar--${shape}`,
217
+ isClickable && "ods-avatar--clickable",
218
+ className
219
+ );
220
+ return /* @__PURE__ */ jsxs(
221
+ "div",
222
+ {
223
+ ...rest,
224
+ ref,
225
+ className: rootClassName,
226
+ role: isClickable ? "button" : "img",
227
+ "aria-label": accessibleName,
228
+ tabIndex: isClickable ? 0 : void 0,
229
+ onClick,
230
+ onKeyDown: isClickable ? handleKeyDown : void 0,
231
+ "data-status": status,
232
+ children: [
233
+ /* @__PURE__ */ jsx2("span", { className: "ods-avatar__inner", "data-palette": paletteIdx, children: showImage ? /* @__PURE__ */ jsx2(
234
+ "img",
235
+ {
236
+ src,
237
+ alt: "",
238
+ className: "ods-avatar__image",
239
+ loading: imgLoading,
240
+ decoding: "async",
241
+ referrerPolicy,
242
+ onError: handleImgError,
243
+ draggable: false
244
+ }
245
+ ) : icon ? /* @__PURE__ */ jsx2("span", { className: "ods-avatar__icon", "aria-hidden": "true", children: icon }) : normalizedInitials ? /* @__PURE__ */ jsx2("span", { className: "ods-avatar__initials", "aria-hidden": "true", children: normalizedInitials }) : /* @__PURE__ */ jsx2("span", { className: "ods-avatar__initials", "aria-hidden": "true", children: fallback ?? "?" }) }),
246
+ status && // Decorative — the textual status is folded into the wrapper's
247
+ // aria-label above. `aria-hidden` keeps AT from double-announcing.
248
+ /* @__PURE__ */ jsx2(
249
+ "span",
250
+ {
251
+ className: cn("ods-avatar__status", `ods-avatar__status--${status}`),
252
+ "aria-hidden": "true"
253
+ }
254
+ )
255
+ ]
256
+ }
257
+ );
258
+ });
259
+ Avatar.displayName = "Avatar";
260
+ var AvatarStack = forwardRef2(
261
+ function AvatarStack2({
262
+ children,
263
+ max,
264
+ size = "md",
265
+ renderOverflow,
266
+ direction = "ltr",
267
+ className,
268
+ ...rest
269
+ }, ref) {
270
+ const arr = Array.isArray(children) ? children : [children];
271
+ const visible = max && arr.length > max ? arr.slice(0, max) : arr;
272
+ const overflow = max && arr.length > max ? arr.length - max : 0;
273
+ return /* @__PURE__ */ jsxs(
274
+ "div",
275
+ {
276
+ ...rest,
277
+ ref,
278
+ className: cn(
279
+ "ods-avatar-stack",
280
+ `ods-avatar-stack--${direction}`,
281
+ className
282
+ ),
283
+ children: [
284
+ visible,
285
+ overflow > 0 && (renderOverflow ? renderOverflow(overflow) : /* @__PURE__ */ jsx2(
286
+ "div",
287
+ {
288
+ className: cn(
289
+ "ods-avatar",
290
+ `ods-avatar--${size}`,
291
+ "ods-avatar--circle",
292
+ "ods-avatar-stack__overflow"
293
+ ),
294
+ role: "img",
295
+ "aria-label": `${overflow} more`,
296
+ children: /* @__PURE__ */ jsx2("span", { className: "ods-avatar__inner", children: /* @__PURE__ */ jsxs("span", { className: "ods-avatar__initials", "aria-hidden": "true", children: [
297
+ "+",
298
+ overflow
299
+ ] }) })
300
+ }
301
+ ))
302
+ ]
303
+ }
304
+ );
305
+ }
306
+ );
307
+ AvatarStack.displayName = "AvatarStack";
308
+
309
+ // src/components/BlogCard/BlogCard.tsx
310
+ import {
311
+ forwardRef as forwardRef3,
312
+ useId
313
+ } from "react";
314
+ import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
315
+ var BlogCard = forwardRef3(
316
+ function BlogCard2({
317
+ cover,
318
+ coverAspect = "16/9",
319
+ category,
320
+ tags,
321
+ meta,
322
+ readingTime,
323
+ title,
324
+ titleAs = "h3",
325
+ description,
326
+ author,
327
+ footer,
328
+ radius = "md",
329
+ orientation = "vertical",
330
+ href,
331
+ onClick,
332
+ id: providedId,
333
+ className,
334
+ ...rest
335
+ }, ref) {
336
+ const reactId = useId();
337
+ const baseId = providedId ?? `ods-blog-card-${reactId}`;
338
+ const titleId = `${baseId}-title`;
339
+ const descId = description ? `${baseId}-desc` : void 0;
340
+ const tagList = tags ?? (category ? [category] : []);
341
+ const body = /* @__PURE__ */ jsxs2(Fragment, { children: [
342
+ cover && /* @__PURE__ */ jsx3(
343
+ "div",
344
+ {
345
+ className: cn(
346
+ "ods-blog-card__cover",
347
+ `ods-blog-card__cover--aspect-${coverAspect.replace("/", "-")}`
348
+ ),
349
+ "aria-hidden": "true",
350
+ children: cover
351
+ }
352
+ ),
353
+ /* @__PURE__ */ jsxs2("div", { className: "ods-blog-card__body", children: [
354
+ (tagList.length > 0 || meta || readingTime) && /* @__PURE__ */ jsxs2("div", { className: "ods-blog-card__meta", children: [
355
+ tagList.length > 0 && /* @__PURE__ */ jsx3("span", { className: "ods-blog-card__tags", children: tagList.map((t, i) => /* @__PURE__ */ jsx3("span", { className: "ods-blog-card__tag", children: t }, i)) }),
356
+ meta && /* @__PURE__ */ jsx3("span", { className: "ods-blog-card__date", children: meta }),
357
+ readingTime && /* @__PURE__ */ jsx3("span", { className: "ods-blog-card__reading", children: readingTime })
358
+ ] }),
359
+ /* @__PURE__ */ jsx3(
360
+ Card.Title,
361
+ {
362
+ as: titleAs,
363
+ id: titleId,
364
+ className: "ods-blog-card__title",
365
+ children: title
366
+ }
367
+ ),
368
+ description && /* @__PURE__ */ jsx3(
369
+ Card.Description,
370
+ {
371
+ id: descId,
372
+ className: "ods-blog-card__desc",
373
+ children: description
374
+ }
375
+ ),
376
+ (author || footer) && /* @__PURE__ */ jsxs2("div", { className: "ods-blog-card__footer", children: [
377
+ author && /* @__PURE__ */ jsxs2("div", { className: "ods-blog-card__author", children: [
378
+ author.avatar && /* @__PURE__ */ jsx3(
379
+ Avatar,
380
+ {
381
+ size: "sm",
382
+ src: author.avatar.src,
383
+ initials: author.avatar.initials,
384
+ seed: author.avatar.seed,
385
+ palette: author.avatar.palette,
386
+ alt: author.avatar.alt ?? (typeof author.name === "string" ? author.name : void 0)
387
+ }
388
+ ),
389
+ /* @__PURE__ */ jsxs2("div", { className: "ods-blog-card__author-text", children: [
390
+ /* @__PURE__ */ jsx3("span", { className: "ods-blog-card__author-name", children: author.name }),
391
+ author.byline && /* @__PURE__ */ jsx3("span", { className: "ods-blog-card__author-byline", children: author.byline })
392
+ ] })
393
+ ] }),
394
+ footer && /* @__PURE__ */ jsx3("div", { className: "ods-blog-card__action", children: footer })
395
+ ] })
396
+ ] })
397
+ ] });
398
+ if (href) {
399
+ return /* @__PURE__ */ jsx3(
400
+ Card,
401
+ {
402
+ asChild: true,
403
+ radius,
404
+ padding: "none",
405
+ className: cn(
406
+ "ods-blog-card",
407
+ `ods-blog-card--${orientation}`,
408
+ "ods-blog-card--interactive",
409
+ className
410
+ ),
411
+ "aria-labelledby": titleId,
412
+ "aria-describedby": descId,
413
+ children: /* @__PURE__ */ jsx3(
414
+ "a",
415
+ {
416
+ ...rest,
417
+ ref,
418
+ href,
419
+ id: baseId,
420
+ onClick,
421
+ children: body
422
+ }
423
+ )
424
+ }
425
+ );
426
+ }
427
+ return /* @__PURE__ */ jsx3(
428
+ Card,
429
+ {
430
+ ...rest,
431
+ ref,
432
+ id: baseId,
433
+ radius,
434
+ padding: "none",
435
+ onClick,
436
+ className: cn(
437
+ "ods-blog-card",
438
+ `ods-blog-card--${orientation}`,
439
+ onClick && "ods-blog-card--interactive",
440
+ className
441
+ ),
442
+ "aria-labelledby": titleId,
443
+ "aria-describedby": descId,
444
+ children: body
445
+ }
446
+ );
447
+ }
448
+ );
449
+ BlogCard.displayName = "BlogCard";
450
+
451
+ // src/hooks/useTextareaCommands.ts
452
+ import {
453
+ useCallback as useCallback2,
454
+ useEffect,
455
+ useMemo as useMemo2,
456
+ useRef,
457
+ useState as useState2
458
+ } from "react";
459
+ function findOpenTrigger(value, caret, trigger) {
460
+ let i = caret - 1;
461
+ while (i >= 0) {
462
+ const ch = value[i];
463
+ if (ch === "\n" || ch === " " || ch === " ") return null;
464
+ if (value.startsWith(trigger, i)) {
465
+ const before = i === 0 ? "" : value[i - 1];
466
+ if (i === 0 || before === " " || before === "\n" || before === " ") {
467
+ return {
468
+ triggerIndex: i,
469
+ query: value.slice(i + trigger.length, caret)
470
+ };
471
+ }
472
+ return null;
473
+ }
474
+ i--;
475
+ }
476
+ return null;
477
+ }
478
+ function getCaretRect(textarea, caret) {
479
+ if (typeof document === "undefined") return null;
480
+ const mirror = document.createElement("div");
481
+ const styles = window.getComputedStyle(textarea);
482
+ for (const prop of [
483
+ "boxSizing",
484
+ "width",
485
+ "fontFamily",
486
+ "fontSize",
487
+ "fontWeight",
488
+ "fontStyle",
489
+ "letterSpacing",
490
+ "lineHeight",
491
+ "paddingTop",
492
+ "paddingRight",
493
+ "paddingBottom",
494
+ "paddingLeft",
495
+ "borderTopWidth",
496
+ "borderRightWidth",
497
+ "borderBottomWidth",
498
+ "borderLeftWidth",
499
+ "textTransform",
500
+ "wordSpacing",
501
+ "tabSize",
502
+ "whiteSpace"
503
+ ]) {
504
+ mirror.style[prop] = styles[prop];
505
+ }
506
+ mirror.style.position = "absolute";
507
+ mirror.style.visibility = "hidden";
508
+ mirror.style.overflow = "hidden";
509
+ mirror.style.top = "0";
510
+ mirror.style.left = "0";
511
+ mirror.style.whiteSpace = "pre-wrap";
512
+ mirror.style.wordWrap = "break-word";
513
+ mirror.textContent = textarea.value.slice(0, caret);
514
+ const marker = document.createElement("span");
515
+ marker.textContent = "\u200B";
516
+ mirror.appendChild(marker);
517
+ document.body.appendChild(mirror);
518
+ const taRect = textarea.getBoundingClientRect();
519
+ const markerRect = marker.getBoundingClientRect();
520
+ const mirrorRect = mirror.getBoundingClientRect();
521
+ const x = taRect.left + (markerRect.left - mirrorRect.left) - textarea.scrollLeft;
522
+ const y = taRect.top + (markerRect.top - mirrorRect.top) - textarea.scrollTop;
523
+ const out = new DOMRect(x, y, 0, markerRect.height || 16);
524
+ document.body.removeChild(mirror);
525
+ return out;
526
+ }
527
+ function useTextareaCommands({
528
+ textareaRef,
529
+ value,
530
+ onChange,
531
+ commands,
532
+ trigger = "/",
533
+ openOnEmptyTrigger = true,
534
+ maxItems = 8
535
+ }) {
536
+ const [isOpen, setIsOpen] = useState2(false);
537
+ const [query, setQuery] = useState2("");
538
+ const [activeIndex, setActiveIndex] = useState2(0);
539
+ const [triggerIndex, setTriggerIndex] = useState2(null);
540
+ const [caretRect, setCaretRect] = useState2(null);
541
+ const dismissedAtRef = useRef(
542
+ null
543
+ );
544
+ const items = useMemo2(() => {
545
+ const q = query.trim().toLowerCase();
546
+ if (!q) return commands.slice(0, maxItems);
547
+ return commands.filter((c) => {
548
+ const hay = [
549
+ typeof c.label === "string" ? c.label : "",
550
+ typeof c.description === "string" ? c.description : "",
551
+ c.id,
552
+ ...c.keywords ?? []
553
+ ].join(" ").toLowerCase();
554
+ return hay.includes(q);
555
+ }).slice(0, maxItems);
556
+ }, [commands, query, maxItems]);
557
+ useEffect(() => {
558
+ const ta = textareaRef.current;
559
+ if (!ta) return;
560
+ const caret = ta.selectionStart ?? 0;
561
+ const dismissedAt = dismissedAtRef.current;
562
+ if (dismissedAt && dismissedAt.value === value && dismissedAt.caret === caret) {
563
+ return;
564
+ }
565
+ const found = findOpenTrigger(value, caret, trigger);
566
+ if (!found) {
567
+ if (isOpen) setIsOpen(false);
568
+ setTriggerIndex(null);
569
+ return;
570
+ }
571
+ if (dismissedAt) dismissedAtRef.current = null;
572
+ if (found.query.length > 0 || openOnEmptyTrigger) {
573
+ setQuery(found.query);
574
+ setTriggerIndex(found.triggerIndex);
575
+ setIsOpen(true);
576
+ setActiveIndex(0);
577
+ setCaretRect(getCaretRect(ta, caret));
578
+ }
579
+ }, [value, trigger, openOnEmptyTrigger, isOpen, textareaRef]);
580
+ const commit = useCallback2(
581
+ (cmd) => {
582
+ const ta = textareaRef.current;
583
+ if (!ta || triggerIndex == null) return;
584
+ const insertText = typeof cmd.insert === "function" ? cmd.insert(query) : cmd.insert;
585
+ const caret = ta.selectionStart ?? value.length;
586
+ const before = value.slice(0, triggerIndex);
587
+ const after = value.slice(caret);
588
+ const next = `${before}${insertText}${after}`;
589
+ dismissedAtRef.current = {
590
+ value,
591
+ caret
592
+ };
593
+ onChange(next);
594
+ setIsOpen(false);
595
+ setTriggerIndex(null);
596
+ requestAnimationFrame(() => {
597
+ const pos = before.length + insertText.length;
598
+ ta.focus();
599
+ ta.setSelectionRange(pos, pos);
600
+ });
601
+ },
602
+ [textareaRef, triggerIndex, query, value, onChange]
603
+ );
604
+ const dismiss = useCallback2(() => {
605
+ const ta = textareaRef.current;
606
+ dismissedAtRef.current = {
607
+ value,
608
+ caret: ta?.selectionStart ?? value.length
609
+ };
610
+ setIsOpen(false);
611
+ setTriggerIndex(null);
612
+ }, [textareaRef, value]);
613
+ const onKeyDown = useCallback2(
614
+ (e) => {
615
+ if (!isOpen) return false;
616
+ if (e.key === "ArrowDown") {
617
+ e.preventDefault();
618
+ setActiveIndex((i) => Math.min(items.length - 1, i + 1));
619
+ return true;
620
+ }
621
+ if (e.key === "ArrowUp") {
622
+ e.preventDefault();
623
+ setActiveIndex((i) => Math.max(0, i - 1));
624
+ return true;
625
+ }
626
+ if (e.key === "Enter" || e.key === "Tab") {
627
+ const pick = items[activeIndex];
628
+ if (pick) {
629
+ e.preventDefault();
630
+ commit(pick);
631
+ return true;
632
+ }
633
+ }
634
+ if (e.key === "Escape") {
635
+ e.preventDefault();
636
+ dismiss();
637
+ return true;
638
+ }
639
+ return false;
640
+ },
641
+ [isOpen, items, activeIndex, commit, dismiss]
642
+ );
643
+ return {
644
+ isOpen,
645
+ query,
646
+ activeIndex,
647
+ items,
648
+ onKeyDown,
649
+ dismiss,
650
+ commit,
651
+ caretRect
652
+ };
653
+ }
654
+
655
+ // src/hooks/useTextareaSelection.ts
656
+ import { useCallback as useCallback3, useEffect as useEffect2, useState as useState3 } from "react";
657
+ function getSelectionRect(textarea, start, end) {
658
+ if (typeof document === "undefined") return null;
659
+ if (start === end) return null;
660
+ const mirror = document.createElement("div");
661
+ const styles = window.getComputedStyle(textarea);
662
+ for (const prop of [
663
+ "boxSizing",
664
+ "width",
665
+ "fontFamily",
666
+ "fontSize",
667
+ "fontWeight",
668
+ "fontStyle",
669
+ "letterSpacing",
670
+ "lineHeight",
671
+ "paddingTop",
672
+ "paddingRight",
673
+ "paddingBottom",
674
+ "paddingLeft",
675
+ "borderTopWidth",
676
+ "borderRightWidth",
677
+ "borderBottomWidth",
678
+ "borderLeftWidth",
679
+ "textTransform",
680
+ "wordSpacing",
681
+ "tabSize",
682
+ "whiteSpace"
683
+ ]) {
684
+ mirror.style[prop] = styles[prop];
685
+ }
686
+ mirror.style.position = "absolute";
687
+ mirror.style.visibility = "hidden";
688
+ mirror.style.overflow = "hidden";
689
+ mirror.style.top = "0";
690
+ mirror.style.left = "0";
691
+ mirror.style.whiteSpace = "pre-wrap";
692
+ mirror.style.wordWrap = "break-word";
693
+ const pre = document.createTextNode(textarea.value.slice(0, start));
694
+ const range = document.createElement("span");
695
+ range.textContent = textarea.value.slice(start, end) || "\u200B";
696
+ const post = document.createTextNode(textarea.value.slice(end));
697
+ mirror.appendChild(pre);
698
+ mirror.appendChild(range);
699
+ mirror.appendChild(post);
700
+ document.body.appendChild(mirror);
701
+ const taRect = textarea.getBoundingClientRect();
702
+ const rangeRect = range.getBoundingClientRect();
703
+ const mirrorRect = mirror.getBoundingClientRect();
704
+ const out = new DOMRect(
705
+ taRect.left + (rangeRect.left - mirrorRect.left) - textarea.scrollLeft,
706
+ taRect.top + (rangeRect.top - mirrorRect.top) - textarea.scrollTop,
707
+ rangeRect.width,
708
+ rangeRect.height
709
+ );
710
+ document.body.removeChild(mirror);
711
+ return out;
712
+ }
713
+ function useTextareaSelection({
714
+ textareaRef,
715
+ value,
716
+ onChange
717
+ }) {
718
+ const [start, setStart] = useState3(0);
719
+ const [end, setEnd] = useState3(0);
720
+ const [rect, setRect] = useState3(null);
721
+ useEffect2(() => {
722
+ if (typeof document === "undefined") return;
723
+ const handler = () => {
724
+ const ta = textareaRef.current;
725
+ if (!ta || document.activeElement !== ta) {
726
+ setStart(0);
727
+ setEnd(0);
728
+ setRect(null);
729
+ return;
730
+ }
731
+ const s = ta.selectionStart ?? 0;
732
+ const e = ta.selectionEnd ?? 0;
733
+ setStart(s);
734
+ setEnd(e);
735
+ setRect(s !== e ? getSelectionRect(ta, s, e) : null);
736
+ };
737
+ document.addEventListener("selectionchange", handler);
738
+ return () => document.removeEventListener("selectionchange", handler);
739
+ }, [textareaRef]);
740
+ const active = start !== end;
741
+ const text = active ? value.slice(start, end) : "";
742
+ const writeToClipboard = useCallback3(
743
+ async (str) => {
744
+ try {
745
+ if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
746
+ await navigator.clipboard.writeText(str);
747
+ return true;
748
+ }
749
+ } catch {
750
+ }
751
+ try {
752
+ const tmp = document.createElement("textarea");
753
+ tmp.value = str;
754
+ tmp.style.position = "fixed";
755
+ tmp.style.left = "-9999px";
756
+ document.body.appendChild(tmp);
757
+ tmp.select();
758
+ const ok = document.execCommand("copy");
759
+ document.body.removeChild(tmp);
760
+ return ok;
761
+ } catch {
762
+ return false;
763
+ }
764
+ },
765
+ []
766
+ );
767
+ const copy = useCallback3(async () => {
768
+ if (!active) return false;
769
+ return writeToClipboard(text);
770
+ }, [active, text, writeToClipboard]);
771
+ const replace = useCallback3(
772
+ (str) => {
773
+ const ta = textareaRef.current;
774
+ if (!ta) return;
775
+ const next = value.slice(0, start) + str + value.slice(end);
776
+ onChange(next);
777
+ requestAnimationFrame(() => {
778
+ ta.focus();
779
+ const caret = start + str.length;
780
+ ta.setSelectionRange(caret, caret);
781
+ });
782
+ },
783
+ [textareaRef, value, start, end, onChange]
784
+ );
785
+ const cut = useCallback3(async () => {
786
+ if (!active) return false;
787
+ const ok = await writeToClipboard(text);
788
+ if (ok) replace("");
789
+ return ok;
790
+ }, [active, text, writeToClipboard, replace]);
791
+ const wrap = useCallback3(
792
+ (open, close) => {
793
+ const ta = textareaRef.current;
794
+ if (!ta) return;
795
+ const closing = close ?? open;
796
+ const before = value.slice(0, start);
797
+ const sel = value.slice(start, end);
798
+ const after = value.slice(end);
799
+ const next = `${before}${open}${sel}${closing}${after}`;
800
+ onChange(next);
801
+ requestAnimationFrame(() => {
802
+ ta.focus();
803
+ const newStart = start;
804
+ const newEnd = end + open.length + closing.length;
805
+ ta.setSelectionRange(newStart, newEnd);
806
+ });
807
+ },
808
+ [textareaRef, value, start, end, onChange]
809
+ );
810
+ return {
811
+ active,
812
+ text,
813
+ start,
814
+ end,
815
+ rect,
816
+ copy,
817
+ cut,
818
+ wrap,
819
+ replace
820
+ };
821
+ }
822
+
823
+ // src/hooks/useTextareaTools.tsx
824
+ import {
825
+ CodeIcon,
826
+ HeadingIcon,
827
+ ListBulletedIcon,
828
+ ListNumberedIcon,
829
+ QuotesIcon,
830
+ RedoIcon,
831
+ TextBoldIcon,
832
+ TextItalicIcon,
833
+ TextLinkIcon,
834
+ TextStrikethroughIcon,
835
+ UndoIcon
836
+ } from "@octaviaflow/icons";
837
+ import { useCallback as useCallback4, useMemo as useMemo3, useRef as useRef2 } from "react";
838
+ import { jsx as jsx4 } from "react/jsx-runtime";
839
+ function useTextareaTools({
840
+ textareaRef,
841
+ value,
842
+ onChange,
843
+ tools
844
+ }) {
845
+ const valueRef = useRef2(value);
846
+ valueRef.current = value;
847
+ const helpers = useMemo3(() => {
848
+ const getTa = () => textareaRef.current;
849
+ const getSelection = () => {
850
+ const ta = getTa();
851
+ const s = ta?.selectionStart ?? 0;
852
+ const e = ta?.selectionEnd ?? 0;
853
+ return {
854
+ text: valueRef.current.slice(s, e),
855
+ start: s,
856
+ end: e,
857
+ active: s !== e
858
+ };
859
+ };
860
+ const setValue = (next) => onChange(next);
861
+ const replace = (text) => {
862
+ const ta = getTa();
863
+ if (!ta) return;
864
+ const { start, end } = getSelection();
865
+ const next = valueRef.current.slice(0, start) + text + valueRef.current.slice(end);
866
+ onChange(next);
867
+ requestAnimationFrame(() => {
868
+ ta.focus();
869
+ const caret = start + text.length;
870
+ ta.setSelectionRange(caret, caret);
871
+ });
872
+ };
873
+ const insert = (text) => replace(text);
874
+ const wrap = (open, close) => {
875
+ const ta = getTa();
876
+ if (!ta) return;
877
+ const closing = close ?? open;
878
+ const { start, end } = getSelection();
879
+ const sel = valueRef.current.slice(start, end);
880
+ const next = valueRef.current.slice(0, start) + open + sel + closing + valueRef.current.slice(end);
881
+ onChange(next);
882
+ requestAnimationFrame(() => {
883
+ ta.focus();
884
+ if (start === end) {
885
+ const caret = start + open.length;
886
+ ta.setSelectionRange(caret, caret);
887
+ } else {
888
+ ta.setSelectionRange(start, end + open.length + closing.length);
889
+ }
890
+ });
891
+ };
892
+ const prependLines = (prefix) => {
893
+ const ta = getTa();
894
+ if (!ta) return;
895
+ const { start, end } = getSelection();
896
+ const v = valueRef.current;
897
+ const lineStart = v.lastIndexOf("\n", start - 1) + 1;
898
+ const lineEnd = (() => {
899
+ const idx = v.indexOf("\n", end);
900
+ return idx === -1 ? v.length : idx;
901
+ })();
902
+ const block = v.slice(lineStart, lineEnd);
903
+ const updated = block.split("\n").map((line) => `${prefix}${line}`).join("\n");
904
+ const next = v.slice(0, lineStart) + updated + v.slice(lineEnd);
905
+ onChange(next);
906
+ requestAnimationFrame(() => {
907
+ ta.focus();
908
+ ta.setSelectionRange(
909
+ lineStart,
910
+ lineStart + updated.length
911
+ );
912
+ });
913
+ };
914
+ const undo = () => {
915
+ try {
916
+ getTa()?.focus();
917
+ document.execCommand("undo");
918
+ } catch {
919
+ }
920
+ };
921
+ const redo = () => {
922
+ try {
923
+ getTa()?.focus();
924
+ document.execCommand("redo");
925
+ } catch {
926
+ }
927
+ };
928
+ return {
929
+ get value() {
930
+ return valueRef.current;
931
+ },
932
+ get selection() {
933
+ return getSelection();
934
+ },
935
+ setValue,
936
+ replace,
937
+ insert,
938
+ wrap,
939
+ prependLines,
940
+ undo,
941
+ redo,
942
+ getTextarea: getTa
943
+ };
944
+ }, [textareaRef, onChange]);
945
+ const visibleTools = useMemo3(
946
+ () => tools.filter((t) => t.isVisible ? t.isVisible(helpers) : true),
947
+ [tools, helpers]
948
+ );
949
+ const runTool = useCallback4(
950
+ async (id) => {
951
+ const tool = tools.find((t) => t.id === id);
952
+ if (!tool || tool.kind === "divider") return;
953
+ await tool.onAction?.(helpers);
954
+ },
955
+ [tools, helpers]
956
+ );
957
+ return { tools: visibleTools, runTool, helpers };
958
+ }
959
+ var wrapTool = (id, open, close, label, icon, title) => ({
960
+ id,
961
+ label,
962
+ icon,
963
+ title,
964
+ onAction: (h) => h.wrap(open, close)
965
+ });
966
+ var prependLineTool = (id, prefix, label, icon, title) => ({
967
+ id,
968
+ label,
969
+ icon,
970
+ title,
971
+ onAction: (h) => h.prependLines(prefix)
972
+ });
973
+ var textareaTools = {
974
+ bold: () => wrapTool("bold", "**", "**", "Bold", /* @__PURE__ */ jsx4(TextBoldIcon, {}), "Bold (\u2318B)"),
975
+ italic: () => wrapTool("italic", "*", "*", "Italic", /* @__PURE__ */ jsx4(TextItalicIcon, {}), "Italic (\u2318I)"),
976
+ strikethrough: () => wrapTool(
977
+ "strike",
978
+ "~~",
979
+ "~~",
980
+ "Strikethrough",
981
+ /* @__PURE__ */ jsx4(TextStrikethroughIcon, {}),
982
+ "Strikethrough"
983
+ ),
984
+ code: () => wrapTool("code", "`", "`", "Code", /* @__PURE__ */ jsx4(CodeIcon, {}), "Inline code"),
985
+ codeBlock: () => ({
986
+ id: "code-block",
987
+ label: "Code block",
988
+ icon: /* @__PURE__ */ jsx4(CodeIcon, {}),
989
+ title: "Code block",
990
+ onAction: (h) => h.wrap("```\n", "\n```")
991
+ }),
992
+ link: () => ({
993
+ id: "link",
994
+ label: "Link",
995
+ icon: /* @__PURE__ */ jsx4(TextLinkIcon, {}),
996
+ title: "Insert link",
997
+ onAction: (h) => {
998
+ const sel = h.selection;
999
+ const text = sel.active ? sel.text : "link text";
1000
+ h.replace(`[${text}](https://)`);
1001
+ }
1002
+ }),
1003
+ /** Heading. Defaults to H2. Pass a `level` (1–6) to override. */
1004
+ heading: (level = 2) => prependLineTool(
1005
+ `heading-${level}`,
1006
+ `${"#".repeat(level)} `,
1007
+ `H${level}`,
1008
+ /* @__PURE__ */ jsx4(HeadingIcon, {}),
1009
+ `Heading ${level}`
1010
+ ),
1011
+ bulletList: () => prependLineTool(
1012
+ "ul",
1013
+ "- ",
1014
+ "Bullet list",
1015
+ /* @__PURE__ */ jsx4(ListBulletedIcon, {}),
1016
+ "Bullet list"
1017
+ ),
1018
+ numberedList: () => prependLineTool(
1019
+ "ol",
1020
+ "1. ",
1021
+ "Numbered list",
1022
+ /* @__PURE__ */ jsx4(ListNumberedIcon, {}),
1023
+ "Numbered list"
1024
+ ),
1025
+ quote: () => prependLineTool(
1026
+ "quote",
1027
+ "> ",
1028
+ "Quote",
1029
+ /* @__PURE__ */ jsx4(QuotesIcon, {}),
1030
+ "Quote"
1031
+ ),
1032
+ undo: () => ({
1033
+ id: "undo",
1034
+ label: "Undo",
1035
+ icon: /* @__PURE__ */ jsx4(UndoIcon, {}),
1036
+ title: "Undo (\u2318Z)",
1037
+ onAction: (h) => h.undo()
1038
+ }),
1039
+ redo: () => ({
1040
+ id: "redo",
1041
+ label: "Redo",
1042
+ icon: /* @__PURE__ */ jsx4(RedoIcon, {}),
1043
+ title: "Redo (\u2318\u21E7Z)",
1044
+ onAction: (h) => h.redo()
1045
+ }),
1046
+ divider: (id = "divider") => ({
1047
+ id: `${id}-${Math.random().toString(36).slice(2, 8)}`,
1048
+ label: "",
1049
+ kind: "divider"
1050
+ })
1051
+ };
1052
+
1053
+ // src/components/Textarea/Textarea.tsx
1054
+ import {
1055
+ forwardRef as forwardRef4,
1056
+ useEffect as useEffect3,
1057
+ useId as useId2,
1058
+ useRef as useRef3,
1059
+ useState as useState4
1060
+ } from "react";
1061
+ import { useTextField } from "react-aria";
1062
+ import { createPortal } from "react-dom";
1063
+ import { CopyIcon, CutIcon } from "@octaviaflow/icons";
1064
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1065
+ var Textarea = forwardRef4(
1066
+ function Textarea2({
1067
+ label,
1068
+ error = false,
1069
+ errorMessage,
1070
+ helperText,
1071
+ resize = "vertical",
1072
+ autoResize = false,
1073
+ minRows,
1074
+ maxRows,
1075
+ maxHeight,
1076
+ maxWidth,
1077
+ minHeight,
1078
+ minWidth,
1079
+ maxLength,
1080
+ size = "md",
1081
+ rows = 3,
1082
+ disabled = false,
1083
+ className,
1084
+ style: consumerStyle,
1085
+ defaultValue,
1086
+ value,
1087
+ onChange,
1088
+ id: providedId,
1089
+ commands,
1090
+ commandTrigger = "/",
1091
+ selectionToolbar = false,
1092
+ selectionActions,
1093
+ tools,
1094
+ toolbarPosition = "top",
1095
+ ...props
1096
+ }, forwardedRef) {
1097
+ const innerRef = useRef3(null);
1098
+ const setRef = (node) => {
1099
+ innerRef.current = node;
1100
+ if (typeof forwardedRef === "function") forwardedRef(node);
1101
+ else if (forwardedRef)
1102
+ forwardedRef.current = node;
1103
+ };
1104
+ const reactId = useId2();
1105
+ const baseId = providedId ?? `ods-textarea-${reactId}`;
1106
+ const hintId = error && errorMessage || helperText ? `${baseId}-hint` : void 0;
1107
+ const [charCount, setCharCount] = useState4(
1108
+ () => String(value ?? defaultValue ?? "").length
1109
+ );
1110
+ const ariaNameProps = resolveAccessibleName({
1111
+ label: typeof label === "string" ? label : void 0,
1112
+ ariaLabel: props["aria-label"],
1113
+ ariaLabelledby: props["aria-labelledby"],
1114
+ componentName: "Textarea"
1115
+ });
1116
+ const { labelProps, inputProps } = useTextField(
1117
+ {
1118
+ label: typeof label === "string" ? label : void 0,
1119
+ ...ariaNameProps,
1120
+ isDisabled: disabled,
1121
+ errorMessage: typeof errorMessage === "string" ? errorMessage : void 0,
1122
+ validationState: error ? "invalid" : void 0,
1123
+ inputElementType: "textarea",
1124
+ value,
1125
+ defaultValue
1126
+ },
1127
+ innerRef
1128
+ );
1129
+ const describedBy = [
1130
+ props["aria-describedby"],
1131
+ hintId
1132
+ ].filter(Boolean).join(" ") || void 0;
1133
+ const handleChange = (e) => {
1134
+ setCharCount(e.target.value.length);
1135
+ onChange?.(e);
1136
+ };
1137
+ const [liveValue, setLiveValue] = useState4(
1138
+ String(value ?? defaultValue ?? "")
1139
+ );
1140
+ useEffect3(() => {
1141
+ if (value != null) setLiveValue(String(value));
1142
+ }, [value]);
1143
+ const handleChangeWithMirror = (e) => {
1144
+ setLiveValue(e.target.value);
1145
+ handleChange(e);
1146
+ };
1147
+ const setLiveAndCommit = (next) => {
1148
+ setLiveValue(next);
1149
+ if (innerRef.current) innerRef.current.value = next;
1150
+ onChange?.({
1151
+ target: innerRef.current,
1152
+ currentTarget: innerRef.current
1153
+ });
1154
+ setCharCount(next.length);
1155
+ };
1156
+ const cmdPalette = useTextareaCommands({
1157
+ textareaRef: innerRef,
1158
+ value: liveValue,
1159
+ onChange: setLiveAndCommit,
1160
+ commands: commands ?? [],
1161
+ trigger: commandTrigger
1162
+ });
1163
+ const cmdEnabled = (commands?.length ?? 0) > 0;
1164
+ const sel = useTextareaSelection({
1165
+ textareaRef: innerRef,
1166
+ value: liveValue,
1167
+ onChange: setLiveAndCommit
1168
+ });
1169
+ const toolbar = useTextareaTools({
1170
+ textareaRef: innerRef,
1171
+ value: liveValue,
1172
+ onChange: setLiveAndCommit,
1173
+ tools: tools ?? []
1174
+ });
1175
+ const toolbarEnabled = (tools?.length ?? 0) > 0;
1176
+ const handleKeyDownWithCommands = (e) => {
1177
+ if (cmdEnabled) {
1178
+ const consumed = cmdPalette.onKeyDown(e);
1179
+ if (consumed) return;
1180
+ }
1181
+ props.onKeyDown?.(e);
1182
+ };
1183
+ useEffect3(() => {
1184
+ if (!autoResize) return;
1185
+ const el = innerRef.current;
1186
+ if (!el) return;
1187
+ const computed = window.getComputedStyle(el);
1188
+ const fontSize = parseFloat(computed.fontSize) || 16;
1189
+ const lhRaw = computed.lineHeight;
1190
+ const lh = lhRaw === "normal" ? fontSize * 1.2 : lhRaw.endsWith("px") ? parseFloat(lhRaw) : parseFloat(lhRaw) * fontSize;
1191
+ const padding = parseFloat(computed.paddingTop) + parseFloat(computed.paddingBottom);
1192
+ const rowCap = maxRows != null && Number.isFinite(maxRows) ? lh * maxRows + padding : Number.POSITIVE_INFINITY;
1193
+ const pixelCap = typeof maxHeight === "number" ? maxHeight : Number.POSITIVE_INFINITY;
1194
+ const effectiveMax = Math.min(rowCap, pixelCap);
1195
+ const rowFloor = lh * (minRows ?? rows) + padding;
1196
+ el.style.height = "auto";
1197
+ const target = Math.max(
1198
+ rowFloor,
1199
+ Math.min(effectiveMax, el.scrollHeight)
1200
+ );
1201
+ el.style.height = `${target}px`;
1202
+ el.style.overflowY = el.scrollHeight > effectiveMax ? "auto" : "hidden";
1203
+ }, [autoResize, value, defaultValue, minRows, maxRows, maxHeight, rows]);
1204
+ const resolvedResize = typeof resize === "boolean" ? resize ? "vertical" : "none" : resize;
1205
+ const toCss = (v) => v == null ? void 0 : typeof v === "number" ? `${v}px` : v;
1206
+ const fieldStyle = {
1207
+ maxHeight: toCss(maxHeight),
1208
+ maxWidth: toCss(maxWidth),
1209
+ minHeight: toCss(minHeight),
1210
+ minWidth: toCss(minWidth),
1211
+ resize: resolvedResize
1212
+ };
1213
+ return /* @__PURE__ */ jsxs3(
1214
+ "div",
1215
+ {
1216
+ className: cn(
1217
+ "ods-textarea",
1218
+ `ods-textarea--${size}`,
1219
+ `ods-textarea--resize-${resolvedResize}`,
1220
+ autoResize && "ods-textarea--auto-resize",
1221
+ error && "ods-textarea--error",
1222
+ disabled && "ods-textarea--disabled",
1223
+ className
1224
+ ),
1225
+ style: consumerStyle,
1226
+ children: [
1227
+ label && /* @__PURE__ */ jsx5(
1228
+ "label",
1229
+ {
1230
+ ...labelProps,
1231
+ htmlFor: baseId,
1232
+ className: "ods-textarea__label",
1233
+ children: label
1234
+ }
1235
+ ),
1236
+ toolbarEnabled && toolbarPosition === "top" && /* @__PURE__ */ jsx5(
1237
+ TextareaToolbar,
1238
+ {
1239
+ tools: toolbar.tools,
1240
+ onRun: toolbar.runTool
1241
+ }
1242
+ ),
1243
+ /* @__PURE__ */ jsxs3("div", { className: "ods-textarea__wrapper", children: [
1244
+ /* @__PURE__ */ jsx5(
1245
+ "textarea",
1246
+ {
1247
+ ...props,
1248
+ ...inputProps,
1249
+ ref: setRef,
1250
+ id: baseId,
1251
+ rows: autoResize ? minRows ?? rows : rows,
1252
+ disabled,
1253
+ maxLength,
1254
+ defaultValue,
1255
+ value,
1256
+ onChange: handleChangeWithMirror,
1257
+ onKeyDown: handleKeyDownWithCommands,
1258
+ className: "ods-textarea__field",
1259
+ style: fieldStyle,
1260
+ "aria-invalid": error || void 0,
1261
+ "aria-describedby": describedBy,
1262
+ "aria-autocomplete": cmdEnabled ? "list" : void 0,
1263
+ "aria-expanded": cmdEnabled ? cmdPalette.isOpen : void 0,
1264
+ "aria-controls": cmdEnabled && cmdPalette.isOpen ? `${baseId}-cmd-list` : void 0,
1265
+ "aria-activedescendant": cmdEnabled && cmdPalette.isOpen && cmdPalette.items[cmdPalette.activeIndex] ? `${baseId}-cmd-${cmdPalette.items[cmdPalette.activeIndex].id}` : void 0
1266
+ }
1267
+ ),
1268
+ cmdEnabled && cmdPalette.isOpen && cmdPalette.items.length > 0 && typeof document !== "undefined" && createPortal(
1269
+ /* @__PURE__ */ jsx5(
1270
+ "ul",
1271
+ {
1272
+ id: `${baseId}-cmd-list`,
1273
+ role: "listbox",
1274
+ "aria-label": "Inline commands",
1275
+ className: "ods-textarea__cmd-popover",
1276
+ style: {
1277
+ position: "fixed",
1278
+ top: (cmdPalette.caretRect?.bottom ?? 0) + 6,
1279
+ left: cmdPalette.caretRect?.left ?? 0
1280
+ },
1281
+ children: cmdPalette.items.map((c, i) => /* @__PURE__ */ jsxs3(
1282
+ "li",
1283
+ {
1284
+ id: `${baseId}-cmd-${c.id}`,
1285
+ role: "option",
1286
+ "aria-selected": i === cmdPalette.activeIndex,
1287
+ className: cn(
1288
+ "ods-textarea__cmd-item",
1289
+ i === cmdPalette.activeIndex && "ods-textarea__cmd-item--active"
1290
+ ),
1291
+ onMouseDown: (e) => {
1292
+ e.preventDefault();
1293
+ cmdPalette.commit(c);
1294
+ },
1295
+ onMouseEnter: () => {
1296
+ },
1297
+ children: [
1298
+ c.icon && /* @__PURE__ */ jsx5("span", { className: "ods-textarea__cmd-icon", children: c.icon }),
1299
+ /* @__PURE__ */ jsxs3("span", { className: "ods-textarea__cmd-text", children: [
1300
+ /* @__PURE__ */ jsx5("span", { className: "ods-textarea__cmd-label", children: c.label }),
1301
+ c.description && /* @__PURE__ */ jsx5("span", { className: "ods-textarea__cmd-desc", children: c.description })
1302
+ ] })
1303
+ ]
1304
+ },
1305
+ c.id
1306
+ ))
1307
+ }
1308
+ ),
1309
+ document.body
1310
+ ),
1311
+ selectionToolbar && sel.active && sel.rect && typeof document !== "undefined" && createPortal(
1312
+ /* @__PURE__ */ jsxs3(
1313
+ "div",
1314
+ {
1315
+ role: "toolbar",
1316
+ "aria-label": "Selection actions",
1317
+ className: "ods-textarea__sel-toolbar",
1318
+ style: {
1319
+ position: "fixed",
1320
+ top: sel.rect.top - 36,
1321
+ left: sel.rect.left
1322
+ },
1323
+ onMouseDown: (e) => e.preventDefault(),
1324
+ children: [
1325
+ /* @__PURE__ */ jsx5(
1326
+ "button",
1327
+ {
1328
+ type: "button",
1329
+ className: "ods-textarea__sel-btn",
1330
+ onClick: () => sel.copy(),
1331
+ "aria-label": "Copy selection",
1332
+ title: "Copy",
1333
+ children: /* @__PURE__ */ jsx5(CopyIcon, { size: "xs" })
1334
+ }
1335
+ ),
1336
+ /* @__PURE__ */ jsx5(
1337
+ "button",
1338
+ {
1339
+ type: "button",
1340
+ className: "ods-textarea__sel-btn",
1341
+ onClick: () => sel.cut(),
1342
+ "aria-label": "Cut selection",
1343
+ title: "Cut",
1344
+ children: /* @__PURE__ */ jsx5(CutIcon, { size: "xs" })
1345
+ }
1346
+ ),
1347
+ selectionActions?.map((a) => /* @__PURE__ */ jsxs3(
1348
+ "button",
1349
+ {
1350
+ type: "button",
1351
+ className: "ods-textarea__sel-btn",
1352
+ onClick: () => a.onAction(sel.text, {
1353
+ replace: sel.replace,
1354
+ wrap: sel.wrap,
1355
+ copy: sel.copy,
1356
+ cut: sel.cut
1357
+ }),
1358
+ title: typeof a.label === "string" ? a.label : void 0,
1359
+ children: [
1360
+ a.icon,
1361
+ a.label && /* @__PURE__ */ jsx5("span", { className: "ods-textarea__sel-btn-label", children: a.label })
1362
+ ]
1363
+ },
1364
+ a.id
1365
+ ))
1366
+ ]
1367
+ }
1368
+ ),
1369
+ document.body
1370
+ ),
1371
+ maxLength != null && /* @__PURE__ */ jsxs3(
1372
+ "span",
1373
+ {
1374
+ className: "ods-textarea__count",
1375
+ "aria-live": charCount > maxLength ? "polite" : "off",
1376
+ children: [
1377
+ charCount,
1378
+ "/",
1379
+ maxLength
1380
+ ]
1381
+ }
1382
+ )
1383
+ ] }),
1384
+ toolbarEnabled && toolbarPosition === "bottom" && /* @__PURE__ */ jsx5(
1385
+ TextareaToolbar,
1386
+ {
1387
+ tools: toolbar.tools,
1388
+ onRun: toolbar.runTool
1389
+ }
1390
+ ),
1391
+ error && errorMessage ? /* @__PURE__ */ jsx5(
1392
+ "div",
1393
+ {
1394
+ id: hintId,
1395
+ className: "ods-textarea__error-message",
1396
+ role: "alert",
1397
+ children: errorMessage
1398
+ }
1399
+ ) : helperText ? /* @__PURE__ */ jsx5("div", { id: hintId, className: "ods-textarea__hint", children: helperText }) : null
1400
+ ]
1401
+ }
1402
+ );
1403
+ }
1404
+ );
1405
+ Textarea.displayName = "Textarea";
1406
+ function TextareaToolbar({
1407
+ tools,
1408
+ onRun
1409
+ }) {
1410
+ return /* @__PURE__ */ jsx5(
1411
+ "div",
1412
+ {
1413
+ role: "toolbar",
1414
+ "aria-label": "Formatting",
1415
+ className: "ods-textarea__toolbar",
1416
+ onMouseDown: (e) => {
1417
+ if (e.target.closest(".ods-textarea__toolbar-btn")) {
1418
+ e.preventDefault();
1419
+ }
1420
+ },
1421
+ children: tools.map((t) => {
1422
+ if (t.kind === "divider") {
1423
+ return /* @__PURE__ */ jsx5(
1424
+ "span",
1425
+ {
1426
+ className: "ods-textarea__toolbar-divider",
1427
+ "aria-hidden": "true"
1428
+ },
1429
+ t.id
1430
+ );
1431
+ }
1432
+ return /* @__PURE__ */ jsx5(
1433
+ "button",
1434
+ {
1435
+ type: "button",
1436
+ className: "ods-textarea__toolbar-btn",
1437
+ title: t.title ?? (typeof t.label === "string" ? t.label : void 0),
1438
+ "aria-label": typeof t.label === "string" ? t.label : t.id,
1439
+ onClick: () => onRun(t.id),
1440
+ children: t.icon ? /* @__PURE__ */ jsx5("span", { className: "ods-textarea__toolbar-icon", children: t.icon }) : /* @__PURE__ */ jsx5("span", { className: "ods-textarea__toolbar-text", children: t.label })
1441
+ },
1442
+ t.id
1443
+ );
1444
+ })
1445
+ }
1446
+ );
1447
+ }
1448
+
1449
+ // src/components/CountUp/CountUp.tsx
1450
+ import {
1451
+ forwardRef as forwardRef5,
1452
+ useEffect as useEffect4,
1453
+ useRef as useRef4,
1454
+ useState as useState5
1455
+ } from "react";
1456
+ import { jsxs as jsxs4 } from "react/jsx-runtime";
1457
+ var easeLinear = (t) => t;
1458
+ var easeOutCubic = (t) => 1 - Math.pow(1 - t, 3);
1459
+ var easeOutQuart = (t) => 1 - Math.pow(1 - t, 4);
1460
+ var easeOutExpo = (t) => t === 1 ? 1 : 1 - Math.pow(2, -10 * t);
1461
+ var CountUp = forwardRef5(
1462
+ function CountUp2({
1463
+ value,
1464
+ from = 0,
1465
+ duration = 800,
1466
+ easing = easeOutCubic,
1467
+ decimals = 0,
1468
+ formatValue,
1469
+ prefix,
1470
+ suffix,
1471
+ delay = 0,
1472
+ disableAnimation = false,
1473
+ onComplete,
1474
+ className,
1475
+ ...rest
1476
+ }, ref) {
1477
+ const [n, setN] = useState5(disableAnimation ? value : from);
1478
+ const lastRendered = useRef4(disableAnimation ? value : from);
1479
+ useEffect4(() => {
1480
+ const reduced = typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
1481
+ if (disableAnimation || reduced) {
1482
+ setN(value);
1483
+ lastRendered.current = value;
1484
+ onComplete?.();
1485
+ return;
1486
+ }
1487
+ const startValue = lastRendered.current;
1488
+ const delta = value - startValue;
1489
+ if (delta === 0) return;
1490
+ let raf = null;
1491
+ let timeoutId = null;
1492
+ let startTime = null;
1493
+ let cancelled = false;
1494
+ const tick = (ts) => {
1495
+ if (cancelled) return;
1496
+ if (startTime === null) startTime = ts;
1497
+ const t = Math.min(1, (ts - startTime) / Math.max(1, duration));
1498
+ const eased = easing(t);
1499
+ const current = startValue + delta * eased;
1500
+ setN(current);
1501
+ lastRendered.current = current;
1502
+ if (t < 1) {
1503
+ raf = requestAnimationFrame(tick);
1504
+ } else {
1505
+ lastRendered.current = value;
1506
+ setN(value);
1507
+ onComplete?.();
1508
+ }
1509
+ };
1510
+ const start = () => {
1511
+ raf = requestAnimationFrame(tick);
1512
+ };
1513
+ if (delay > 0) {
1514
+ timeoutId = window.setTimeout(start, delay);
1515
+ } else {
1516
+ start();
1517
+ }
1518
+ return () => {
1519
+ cancelled = true;
1520
+ if (raf !== null) cancelAnimationFrame(raf);
1521
+ if (timeoutId !== null) window.clearTimeout(timeoutId);
1522
+ };
1523
+ }, [value, duration, delay, disableAnimation]);
1524
+ const display = formatValue ? formatValue(n) : n.toFixed(decimals);
1525
+ return /* @__PURE__ */ jsxs4(
1526
+ "span",
1527
+ {
1528
+ ...rest,
1529
+ ref,
1530
+ className: cn("ods-count-up", className),
1531
+ "aria-live": "polite",
1532
+ "aria-atomic": "true",
1533
+ children: [
1534
+ prefix,
1535
+ display,
1536
+ suffix
1537
+ ]
1538
+ }
1539
+ );
1540
+ }
1541
+ );
1542
+ CountUp.displayName = "CountUp";
1543
+
1544
+ // src/components/TestimonialCard/TestimonialCard.tsx
1545
+ import {
1546
+ QuotesIcon as QuotesIcon2,
1547
+ StarFilledIcon,
1548
+ StarIcon
1549
+ } from "@octaviaflow/icons";
1550
+ import {
1551
+ forwardRef as forwardRef6,
1552
+ useId as useId3
1553
+ } from "react";
1554
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1555
+ function isAuthorObject(v) {
1556
+ return typeof v === "object" && v !== null && !("type" in v) && // exclude React elements
1557
+ "name" in v;
1558
+ }
1559
+ var TestimonialCard = forwardRef6(function TestimonialCard2({
1560
+ quote,
1561
+ author,
1562
+ role: legacyRole,
1563
+ avatar: legacyAvatar,
1564
+ initial: legacyInitial,
1565
+ rating,
1566
+ logo,
1567
+ size = "md",
1568
+ radius = "md",
1569
+ id: providedId,
1570
+ className,
1571
+ ...rest
1572
+ }, ref) {
1573
+ const reactId = useId3();
1574
+ const baseId = providedId ?? `ods-testimonial-${reactId}`;
1575
+ const quoteId = `${baseId}-quote`;
1576
+ const authorId = `${baseId}-author`;
1577
+ const resolved = isAuthorObject(author) ? author : {
1578
+ name: author,
1579
+ role: legacyRole,
1580
+ avatar: legacyAvatar ? void 0 : {
1581
+ initials: typeof legacyInitial === "string" ? legacyInitial : typeof author === "string" ? author.charAt(0).toUpperCase() : void 0
1582
+ }
1583
+ };
1584
+ const renderAvatar = () => {
1585
+ if (!isAuthorObject(author) && legacyAvatar) {
1586
+ return /* @__PURE__ */ jsx6(
1587
+ "span",
1588
+ {
1589
+ className: "ods-testimonial__avatar ods-testimonial__avatar--legacy",
1590
+ "aria-hidden": "true",
1591
+ children: legacyAvatar
1592
+ }
1593
+ );
1594
+ }
1595
+ return /* @__PURE__ */ jsx6(
1596
+ Avatar,
1597
+ {
1598
+ size: size === "sm" ? "xs" : size === "lg" ? "md" : "sm",
1599
+ src: resolved.avatar?.src,
1600
+ initials: resolved.avatar?.initials,
1601
+ seed: resolved.avatar?.seed,
1602
+ palette: resolved.avatar?.palette,
1603
+ alt: resolved.avatar?.alt ?? (typeof resolved.name === "string" ? resolved.name : void 0),
1604
+ className: "ods-testimonial__avatar"
1605
+ }
1606
+ );
1607
+ };
1608
+ return /* @__PURE__ */ jsxs5(
1609
+ Card,
1610
+ {
1611
+ ...rest,
1612
+ ref,
1613
+ id: baseId,
1614
+ radius,
1615
+ padding: "none",
1616
+ variant: "subtle",
1617
+ className: cn(
1618
+ "ods-testimonial",
1619
+ `ods-testimonial--${size}`,
1620
+ className
1621
+ ),
1622
+ "aria-labelledby": authorId,
1623
+ "aria-describedby": quoteId,
1624
+ children: [
1625
+ /* @__PURE__ */ jsxs5("div", { className: "ods-testimonial__head", children: [
1626
+ /* @__PURE__ */ jsx6("span", { className: "ods-testimonial__quote", "aria-hidden": "true", children: /* @__PURE__ */ jsx6(QuotesIcon2, { size: "md" }) }),
1627
+ logo && /* @__PURE__ */ jsx6("span", { className: "ods-testimonial__logo", "aria-hidden": "true", children: logo })
1628
+ ] }),
1629
+ /* @__PURE__ */ jsx6("blockquote", { id: quoteId, className: "ods-testimonial__text", children: quote }),
1630
+ typeof rating === "number" && /* @__PURE__ */ jsx6(
1631
+ "div",
1632
+ {
1633
+ className: "ods-testimonial__rating",
1634
+ role: "img",
1635
+ "aria-label": `${rating} out of 5 stars`,
1636
+ children: Array.from({ length: 5 }).map(
1637
+ (_, i) => i < Math.round(rating) ? /* @__PURE__ */ jsx6(
1638
+ StarFilledIcon,
1639
+ {
1640
+ size: "xs",
1641
+ className: "ods-testimonial__star ods-testimonial__star--on"
1642
+ },
1643
+ i
1644
+ ) : /* @__PURE__ */ jsx6(
1645
+ StarIcon,
1646
+ {
1647
+ size: "xs",
1648
+ className: "ods-testimonial__star"
1649
+ },
1650
+ i
1651
+ )
1652
+ )
1653
+ }
1654
+ ),
1655
+ /* @__PURE__ */ jsxs5("figcaption", { className: "ods-testimonial__author", id: authorId, children: [
1656
+ renderAvatar(),
1657
+ /* @__PURE__ */ jsxs5("span", { className: "ods-testimonial__author-info", children: [
1658
+ /* @__PURE__ */ jsx6("span", { className: "ods-testimonial__author-name", children: resolved.name }),
1659
+ resolved.role && /* @__PURE__ */ jsx6("span", { className: "ods-testimonial__author-role", children: resolved.role })
1660
+ ] })
1661
+ ] })
1662
+ ]
1663
+ }
1664
+ );
1665
+ });
1666
+ TestimonialCard.displayName = "TestimonialCard";
1667
+
1668
+ export {
1669
+ Card,
1670
+ CardHeader,
1671
+ CardBody,
1672
+ CardFooter,
1673
+ CardTitle,
1674
+ CardDescription,
1675
+ Avatar,
1676
+ AvatarStack,
1677
+ BlogCard,
1678
+ useTextareaCommands,
1679
+ useTextareaSelection,
1680
+ useTextareaTools,
1681
+ textareaTools,
1682
+ Textarea,
1683
+ easeLinear,
1684
+ easeOutCubic,
1685
+ easeOutQuart,
1686
+ easeOutExpo,
1687
+ CountUp,
1688
+ TestimonialCard
1689
+ };
1690
+ //# sourceMappingURL=chunk-PZIWTCI6.js.map