@octaviaflow/core 3.1.0-beta.70 → 3.1.0-beta.71

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.
@@ -129,7 +129,7 @@ function BackedBy({
129
129
  }
130
130
 
131
131
  // src/marketing/components/BetaAccessForm/BetaAccessForm.tsx
132
- var import_react17 = require("react");
132
+ var import_react21 = require("react");
133
133
 
134
134
  // src/components/Button/Button.tsx
135
135
  var import_framer_motion = require("framer-motion");
@@ -2309,1710 +2309,1755 @@ var Select = (0, import_react16.forwardRef)(function Select2({
2309
2309
  });
2310
2310
  Select.displayName = "Select";
2311
2311
 
2312
- // src/marketing/components/WorkflowShowcase/showcaseEngine.ts
2313
- var SETTLED_PHASES = [
2314
- "done",
2315
- "signup",
2316
- "creating",
2317
- "welcome"
2318
- ];
2319
- var WORKFLOW_SHOWCASE_DEFAULT_CONNECTORS = [
2320
- { name: "Storefront", role: "source", aliases: ["store", "shop orders"] },
2321
- { name: "Payments", role: "source", aliases: ["payouts"] },
2322
- { name: "Orders DB", role: "source", aliases: ["orders database"] },
2323
- { name: "CRM", role: "source", aliases: ["crm leads"] },
2324
- { name: "Warehouse", role: "destination" },
2325
- { name: "Lakehouse", role: "destination" },
2326
- { name: "Campaigns", role: "action", aliases: ["campaign"] },
2327
- { name: "Team Chat", role: "notify", aliases: ["chat", "alert the team"] }
2328
- ];
2329
- var WORKFLOW_SHOWCASE_DEFAULT_EXAMPLES = [
2330
- {
2331
- id: "hourly-sync",
2332
- label: "Storefront orders \u2192 Warehouse, hourly",
2333
- prompt: "Sync Storefront orders to Warehouse every hour, alert sales in Team Chat"
2334
- },
2335
- {
2336
- id: "daily-load",
2337
- label: "Payments payouts \u2192 Lakehouse, daily",
2338
- prompt: "Load Payments payouts into Lakehouse daily"
2339
- },
2340
- {
2341
- id: "realtime-stream",
2342
- label: "Orders DB changes \u2192 Warehouse + Team Chat",
2343
- prompt: "Stream Orders DB changes to Warehouse in real-time and notify Team Chat"
2344
- }
2345
- ];
2346
- var WORKFLOW_SHOWCASE_DEFAULT_METRICS = [
2347
- { id: "throughput", value: 2341, buildingValue: 842, unit: "rows/min", label: "THROUGHPUT" },
2348
- {
2349
- id: "latency",
2350
- value: 0.8,
2351
- buildingValue: 1.1,
2352
- decimals: 1,
2353
- unit: "s latency",
2354
- label: "SYNC SPEED"
2355
- },
2356
- { id: "cost", value: 43, suffix: "%", label: "SAVED VS BASELINE", tone: "success" }
2357
- ];
2358
- var WORKFLOW_SHOWCASE_SCHEDULES = ["AUTO", "HOURLY", "DAILY", "REAL-TIME"];
2359
- var WORKFLOW_SHOWCASE_TRIGGERS = ["SCHEDULE", "WEBHOOK", "APP EVENT", "MANUAL"];
2360
- var WORKFLOW_SHOWCASE_DEFAULT_TILES = [
2361
- "ELT",
2362
- "CDC",
2363
- "LLM",
2364
- "RAG",
2365
- "HEAL",
2366
- "COST",
2367
- "IDP",
2368
- "VEC"
2369
- ];
2370
- function escapeRegExp(value) {
2371
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2372
- }
2373
- function mentionsOf(catalog) {
2374
- const out = [];
2375
- for (const connector of catalog) {
2376
- out.push({ connector, pattern: connector.name });
2377
- for (const alias of connector.aliases ?? []) out.push({ connector, pattern: alias });
2312
+ // src/components/Textarea/Textarea.tsx
2313
+ var import_react20 = require("react");
2314
+ var import_react_aria4 = require("react-aria");
2315
+ var import_react_dom3 = require("react-dom");
2316
+ var import_icons4 = require("@octaviaflow/icons");
2317
+
2318
+ // src/hooks/useTextareaCommands.ts
2319
+ var import_react17 = require("react");
2320
+ function findOpenTrigger(value, caret, trigger) {
2321
+ let i = caret - 1;
2322
+ while (i >= 0) {
2323
+ const ch = value[i];
2324
+ if (ch === "\n" || ch === " " || ch === " ") return null;
2325
+ if (value.startsWith(trigger, i)) {
2326
+ const before = i === 0 ? "" : value[i - 1];
2327
+ if (i === 0 || before === " " || before === "\n" || before === " ") {
2328
+ return {
2329
+ triggerIndex: i,
2330
+ query: value.slice(i + trigger.length, caret)
2331
+ };
2332
+ }
2333
+ return null;
2334
+ }
2335
+ i--;
2378
2336
  }
2379
- return out.sort((a, b) => b.pattern.length - a.pattern.length);
2380
- }
2381
- function detectConnectors(prompt, catalog) {
2382
- const lower = prompt.toLowerCase();
2383
- return catalog.filter((c) => [c.name, ...c.aliases ?? []].some((p) => lower.includes(p.toLowerCase()))).map((c) => c.name);
2384
- }
2385
- function segmentPrompt(prompt, catalog) {
2386
- if (!prompt) return [];
2387
- const mentions = mentionsOf(catalog);
2388
- if (!mentions.length) return [{ text: prompt }];
2389
- const re = new RegExp(`(${mentions.map((m) => escapeRegExp(m.pattern)).join("|")})`, "gi");
2390
- const byPattern = new Map(mentions.map((m) => [m.pattern.toLowerCase(), m.connector]));
2391
- return prompt.split(re).filter((part) => part !== "").map((part) => {
2392
- const connector = byPattern.get(part.toLowerCase());
2393
- return connector ? { text: part, connector } : { text: part };
2394
- });
2395
- }
2396
- function scheduleFromPrompt(prompt) {
2397
- const t = prompt.toLowerCase();
2398
- if (/stream|real.?time|cdc/.test(t)) return "REAL-TIME";
2399
- if (/hour/.test(t)) return "HOURLY";
2400
- if (/dail|every day|nightly/.test(t)) return "DAILY";
2401
2337
  return null;
2402
2338
  }
2403
- var RIGHT_ORDER = {
2404
- source: -1,
2405
- destination: 0,
2406
- action: 1,
2407
- notify: 2
2408
- };
2409
- function resolvePlan(selected, detected, catalog) {
2410
- const byName = new Map(catalog.map((c) => [c.name, c]));
2411
- let names = [.../* @__PURE__ */ new Set([...selected, ...detected])].filter((n) => byName.has(n));
2412
- if (!names.length) {
2413
- const fallback = [
2414
- catalog.find((c) => c.role === "source"),
2415
- catalog.find((c) => c.role === "destination"),
2416
- catalog.find((c) => c.role === "notify")
2417
- ].filter((c) => Boolean(c));
2418
- names = fallback.map((c) => c.name);
2419
- }
2420
- const has = (predicate) => names.some((n) => {
2421
- const c = byName.get(n);
2422
- return Boolean(c && predicate(c));
2423
- });
2424
- if (!has((c) => c.role === "source")) {
2425
- const source = catalog.find((c) => c.role === "source");
2426
- if (source) names = [source.name, ...names];
2427
- }
2428
- if (!has((c) => c.role !== "source")) {
2429
- const sink = catalog.find((c) => c.role === "notify") ?? catalog.find((c) => c.role !== "source");
2430
- if (sink) names = [...names, sink.name];
2339
+ function getCaretRect(textarea, caret) {
2340
+ if (typeof document === "undefined") return null;
2341
+ const mirror = document.createElement("div");
2342
+ const styles = window.getComputedStyle(textarea);
2343
+ for (const prop of [
2344
+ "boxSizing",
2345
+ "width",
2346
+ "fontFamily",
2347
+ "fontSize",
2348
+ "fontWeight",
2349
+ "fontStyle",
2350
+ "letterSpacing",
2351
+ "lineHeight",
2352
+ "paddingTop",
2353
+ "paddingRight",
2354
+ "paddingBottom",
2355
+ "paddingLeft",
2356
+ "borderTopWidth",
2357
+ "borderRightWidth",
2358
+ "borderBottomWidth",
2359
+ "borderLeftWidth",
2360
+ "textTransform",
2361
+ "wordSpacing",
2362
+ "tabSize",
2363
+ "whiteSpace"
2364
+ ]) {
2365
+ mirror.style[prop] = styles[prop];
2431
2366
  }
2432
- const picked = catalog.filter((c) => names.includes(c.name));
2433
- const left = picked.filter((c) => c.role === "source");
2434
- const right = picked.filter((c) => c.role !== "source").sort((a, b) => RIGHT_ORDER[a.role] - RIGHT_ORDER[b.role]);
2435
- const all = [...left, ...right];
2436
- return { left, right, all, totalSteps: all.length + 1 };
2437
- }
2438
- function stepVerb(role) {
2439
- if (role === "notify") return "alert";
2440
- if (role === "action") return "trigger";
2441
- return "load";
2442
- }
2443
- function assembleSteps(plan) {
2444
- return [
2445
- ...plan.left.map((c) => `${c.name.toLowerCase()} extract`),
2446
- "schema map \xB7 14 fields",
2447
- ...plan.right.map((c) => `${c.name.toLowerCase()} ${stepVerb(c.role)}`)
2448
- ];
2367
+ mirror.style.position = "absolute";
2368
+ mirror.style.visibility = "hidden";
2369
+ mirror.style.overflow = "hidden";
2370
+ mirror.style.top = "0";
2371
+ mirror.style.left = "0";
2372
+ mirror.style.whiteSpace = "pre-wrap";
2373
+ mirror.style.wordWrap = "break-word";
2374
+ mirror.textContent = textarea.value.slice(0, caret);
2375
+ const marker = document.createElement("span");
2376
+ marker.textContent = "\u200B";
2377
+ mirror.appendChild(marker);
2378
+ document.body.appendChild(mirror);
2379
+ const taRect = textarea.getBoundingClientRect();
2380
+ const markerRect = marker.getBoundingClientRect();
2381
+ const mirrorRect = mirror.getBoundingClientRect();
2382
+ const x = taRect.left + (markerRect.left - mirrorRect.left) - textarea.scrollLeft;
2383
+ const y = taRect.top + (markerRect.top - mirrorRect.top) - textarea.scrollTop;
2384
+ const out = new DOMRect(x, y, 0, markerRect.height || 16);
2385
+ document.body.removeChild(mirror);
2386
+ return out;
2449
2387
  }
2450
- function buildActivity(args) {
2451
- const { phase, shown, plan, selected, status } = args;
2452
- const settled = SETTLED_PHASES.includes(phase);
2453
- const lines = [];
2454
- if (phase === "idle") {
2455
- if (!selected.length) {
2456
- lines.push({ id: "wait", text: "\u25B8 waiting for instructions\u2026", tone: "muted" });
2457
- lines.push({ id: "hint", text: "\xB7 type a request or pick apps", tone: "faint" });
2458
- }
2459
- for (const name of selected.slice(-6)) {
2460
- lines.push({ id: `sel-${name}`, text: `+ ${name.toLowerCase()} connected`, tone: "plain" });
2388
+ function useTextareaCommands({
2389
+ textareaRef,
2390
+ value,
2391
+ onChange,
2392
+ commands,
2393
+ trigger = "/",
2394
+ openOnEmptyTrigger = true,
2395
+ maxItems = 8
2396
+ }) {
2397
+ const [isOpen, setIsOpen] = (0, import_react17.useState)(false);
2398
+ const [query, setQuery] = (0, import_react17.useState)("");
2399
+ const [activeIndex, setActiveIndex] = (0, import_react17.useState)(0);
2400
+ const [triggerIndex, setTriggerIndex] = (0, import_react17.useState)(null);
2401
+ const [caretRect, setCaretRect] = (0, import_react17.useState)(null);
2402
+ const dismissedAtRef = (0, import_react17.useRef)(
2403
+ null
2404
+ );
2405
+ const items = (0, import_react17.useMemo)(() => {
2406
+ const q = query.trim().toLowerCase();
2407
+ if (!q) return commands.slice(0, maxItems);
2408
+ return commands.filter((c) => {
2409
+ const hay = [
2410
+ typeof c.label === "string" ? c.label : "",
2411
+ typeof c.description === "string" ? c.description : "",
2412
+ c.id,
2413
+ ...c.keywords ?? []
2414
+ ].join(" ").toLowerCase();
2415
+ return hay.includes(q);
2416
+ }).slice(0, maxItems);
2417
+ }, [commands, query, maxItems]);
2418
+ (0, import_react17.useEffect)(() => {
2419
+ const ta = textareaRef.current;
2420
+ if (!ta) return;
2421
+ const caret = ta.selectionStart ?? 0;
2422
+ const dismissedAt = dismissedAtRef.current;
2423
+ if (dismissedAt && dismissedAt.value === value && dismissedAt.caret === caret) {
2424
+ return;
2461
2425
  }
2462
- return lines;
2463
- }
2464
- if (phase === "analyzing") {
2465
- for (const name of selected.slice(-4)) {
2466
- lines.push({ id: `sel-${name}`, text: `+ ${name.toLowerCase()} connected`, tone: "muted" });
2426
+ const found = findOpenTrigger(value, caret, trigger);
2427
+ if (!found) {
2428
+ if (isOpen) setIsOpen(false);
2429
+ setTriggerIndex(null);
2430
+ return;
2467
2431
  }
2468
- lines.push({ id: "status", text: `\u27F3 ${status}`, tone: "active" });
2469
- return lines;
2470
- }
2471
- assembleSteps(plan).forEach((step, i) => {
2472
- if (settled || shown > i) lines.push({ id: `step-${i}`, text: `\u2713 ${step}`, tone: "done" });
2473
- else if (shown === i) lines.push({ id: `step-${i}`, text: `\u27F3 ${step}\u2026`, tone: "active" });
2474
- else lines.push({ id: `step-${i}`, text: `\xB7 ${step} queued`, tone: "muted" });
2475
- });
2476
- if (settled) lines.push({ id: "errors", text: "\u2713 error handling attached", tone: "done" });
2477
- return lines.slice(-8);
2478
- }
2479
- function slugify(value) {
2480
- return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "your-team";
2481
- }
2482
- function isValidEmail(value) {
2483
- return /^\S+@\S+\.\S+$/.test(value.trim());
2484
- }
2485
-
2486
- // src/marketing/components/BetaAccessForm/BetaAccessForm.tsx
2487
- var import_jsx_runtime7 = require("react/jsx-runtime");
2488
- function BetaAccessForm({
2489
- title = "Request early access",
2490
- subtitle = "CURRENTLY IN PRIVATE BETA \u2014 WE REVIEW ACCESS REQUESTS WEEKLY",
2491
- fields = "email",
2492
- appearance = "card",
2493
- teamSizes,
2494
- plans,
2495
- defaultPlan,
2496
- submitLabel = "Request access \u2192",
2497
- footnote = "NO CREDIT CARD \xB7 SSO & SELF-HOSTING AVAILABLE",
2498
- onSubmit,
2499
- successTitle = "Request received.",
2500
- successBody = "We review access requests weekly \u2014 you'll hear from us by email.",
2501
- disabled = false,
2502
- className
2503
- }) {
2504
- const uid = (0, import_react17.useId)();
2505
- const [values, setValues] = (0, import_react17.useState)({
2506
- email: "",
2507
- name: "",
2508
- company: "",
2509
- teamSize: "",
2510
- plan: defaultPlan ?? ""
2511
- });
2512
- const [phase, setPhase] = (0, import_react17.useState)("idle");
2513
- const [error, setError] = (0, import_react17.useState)(null);
2514
- const handleSubmit = async (event) => {
2515
- event.preventDefault();
2516
- if (phase === "submitting") return;
2517
- if (!isValidEmail(values.email)) {
2518
- setError("Please enter a valid work email.");
2519
- return;
2520
- }
2521
- if (fields === "full" && !values.name?.trim()) {
2522
- setError("Please enter your name.");
2523
- return;
2524
- }
2525
- if (fields === "full" && teamSizes?.length && !values.teamSize) {
2526
- setError("Please select your team size.");
2527
- return;
2528
- }
2529
- setError(null);
2530
- setPhase("submitting");
2531
- try {
2532
- await onSubmit?.(values);
2533
- setPhase("success");
2534
- } catch {
2535
- setError("Something went wrong \u2014 please try again.");
2536
- setPhase("idle");
2432
+ if (dismissedAt) dismissedAtRef.current = null;
2433
+ if (found.query.length > 0 || openOnEmptyTrigger) {
2434
+ setQuery(found.query);
2435
+ setTriggerIndex(found.triggerIndex);
2436
+ setIsOpen(true);
2437
+ setActiveIndex(0);
2438
+ setCaretRect(getCaretRect(ta, caret));
2537
2439
  }
2538
- };
2539
- const inline = appearance === "inline";
2540
- if (phase === "success") {
2541
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2542
- "div",
2543
- {
2544
- className: cn(
2545
- "ods-mkt-access",
2546
- "ods-mkt-access--success",
2547
- inline && "ods-mkt-access--inline",
2548
- className
2549
- ),
2550
- role: "status",
2551
- children: [
2552
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("p", { className: "ods-mkt-access__success-mark", children: [
2553
- "\u2713 ",
2554
- successTitle
2555
- ] }),
2556
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "ods-mkt-access__success-body", children: successBody })
2557
- ]
2440
+ }, [value, trigger, openOnEmptyTrigger, isOpen, textareaRef]);
2441
+ const commit = (0, import_react17.useCallback)(
2442
+ (cmd) => {
2443
+ const ta = textareaRef.current;
2444
+ if (!ta || triggerIndex == null) return;
2445
+ const insertText = typeof cmd.insert === "function" ? cmd.insert(query) : cmd.insert;
2446
+ const caret = ta.selectionStart ?? value.length;
2447
+ const before = value.slice(0, triggerIndex);
2448
+ const after = value.slice(caret);
2449
+ const next = `${before}${insertText}${after}`;
2450
+ dismissedAtRef.current = {
2451
+ value,
2452
+ caret
2453
+ };
2454
+ onChange(next);
2455
+ setIsOpen(false);
2456
+ setTriggerIndex(null);
2457
+ requestAnimationFrame(() => {
2458
+ const pos = before.length + insertText.length;
2459
+ ta.focus();
2460
+ ta.setSelectionRange(pos, pos);
2461
+ });
2462
+ },
2463
+ [textareaRef, triggerIndex, query, value, onChange]
2464
+ );
2465
+ const dismiss = (0, import_react17.useCallback)(() => {
2466
+ const ta = textareaRef.current;
2467
+ dismissedAtRef.current = {
2468
+ value,
2469
+ caret: ta?.selectionStart ?? value.length
2470
+ };
2471
+ setIsOpen(false);
2472
+ setTriggerIndex(null);
2473
+ }, [textareaRef, value]);
2474
+ const onKeyDown = (0, import_react17.useCallback)(
2475
+ (e) => {
2476
+ if (!isOpen) return false;
2477
+ if (e.key === "ArrowDown") {
2478
+ e.preventDefault();
2479
+ setActiveIndex((i) => Math.min(items.length - 1, i + 1));
2480
+ return true;
2558
2481
  }
2559
- );
2560
- }
2561
- const busy = disabled || phase === "submitting";
2562
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2563
- "form",
2564
- {
2565
- className: cn("ods-mkt-access", inline && "ods-mkt-access--inline", className),
2566
- onSubmit: handleSubmit,
2567
- noValidate: true,
2568
- children: [
2569
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "ods-mkt-access__head", children: [
2570
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h3", { className: "ods-mkt-access__title", children: title }),
2571
- subtitle && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "ods-mkt-access__subtitle", children: subtitle })
2572
- ] }),
2573
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
2574
- "div",
2575
- {
2576
- className: cn(
2577
- "ods-mkt-access__fields",
2578
- fields === "full" && "ods-mkt-access__fields--full"
2579
- ),
2580
- children: [
2581
- fields === "full" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "ods-mkt-access__field", children: [
2582
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("label", { className: "ods-mkt-access__label", htmlFor: `${uid}-name`, children: "Full name" }),
2583
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2584
- Input,
2585
- {
2586
- id: `${uid}-name`,
2587
- autoComplete: "name",
2588
- placeholder: "Ada Lovelace",
2589
- value: values.name ?? "",
2590
- onChange: (e) => setValues((v) => ({ ...v, name: e.target.value })),
2591
- disabled: busy
2592
- }
2593
- )
2594
- ] }),
2595
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "ods-mkt-access__field", children: [
2596
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("label", { className: "ods-mkt-access__label", htmlFor: `${uid}-email`, children: "Work email" }),
2597
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2598
- Input,
2599
- {
2600
- id: `${uid}-email`,
2601
- type: "email",
2602
- autoComplete: "email",
2603
- placeholder: "ada@company.com",
2604
- value: values.email,
2605
- onChange: (e) => setValues((v) => ({ ...v, email: e.target.value })),
2606
- disabled: busy
2607
- }
2608
- )
2609
- ] }),
2610
- fields === "full" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "ods-mkt-access__field", children: [
2611
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("label", { className: "ods-mkt-access__label", htmlFor: `${uid}-company`, children: "Company" }),
2612
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2613
- Input,
2614
- {
2615
- id: `${uid}-company`,
2616
- autoComplete: "organization",
2617
- placeholder: "Acme Inc.",
2618
- value: values.company ?? "",
2619
- onChange: (e) => setValues((v) => ({ ...v, company: e.target.value })),
2620
- disabled: busy
2621
- }
2622
- )
2623
- ] }),
2624
- fields === "full" && teamSizes && teamSizes.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "ods-mkt-access__field", children: [
2625
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ods-mkt-access__label", id: `${uid}-teamsize-label`, children: "Team size" }),
2626
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2627
- Select,
2628
- {
2629
- "aria-labelledby": `${uid}-teamsize-label`,
2630
- placeholder: "Select team size\u2026",
2631
- options: teamSizes.map((size) => ({ value: size, label: size })),
2632
- value: values.teamSize ?? "",
2633
- onChange: (teamSize) => setValues((v) => ({ ...v, teamSize })),
2634
- disabled: busy
2635
- }
2636
- )
2637
- ] }),
2638
- fields === "full" && plans && plans.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "ods-mkt-access__field", children: [
2639
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ods-mkt-access__label", id: `${uid}-plan-label`, children: "Plan (optional)" }),
2640
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2641
- Select,
2642
- {
2643
- "aria-labelledby": `${uid}-plan-label`,
2644
- placeholder: "Not sure yet",
2645
- options: plans.map((plan) => ({ value: plan, label: plan })),
2646
- value: values.plan ?? "",
2647
- onChange: (plan) => setValues((v) => ({ ...v, plan })),
2648
- disabled: busy
2649
- }
2650
- )
2651
- ] }),
2652
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2653
- Button,
2654
- {
2655
- type: "submit",
2656
- variant: "primary",
2657
- loading: phase === "submitting",
2658
- disabled,
2659
- children: submitLabel
2660
- }
2661
- )
2662
- ]
2663
- }
2664
- ),
2665
- error && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "ods-mkt-access__error", role: "alert", children: error }),
2666
- footnote && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "ods-mkt-access__footnote", children: footnote })
2667
- ]
2668
- }
2482
+ if (e.key === "ArrowUp") {
2483
+ e.preventDefault();
2484
+ setActiveIndex((i) => Math.max(0, i - 1));
2485
+ return true;
2486
+ }
2487
+ if (e.key === "Enter" || e.key === "Tab") {
2488
+ const pick = items[activeIndex];
2489
+ if (pick) {
2490
+ e.preventDefault();
2491
+ commit(pick);
2492
+ return true;
2493
+ }
2494
+ }
2495
+ if (e.key === "Escape") {
2496
+ e.preventDefault();
2497
+ dismiss();
2498
+ return true;
2499
+ }
2500
+ return false;
2501
+ },
2502
+ [isOpen, items, activeIndex, commit, dismiss]
2669
2503
  );
2504
+ return {
2505
+ isOpen,
2506
+ query,
2507
+ activeIndex,
2508
+ items,
2509
+ onKeyDown,
2510
+ dismiss,
2511
+ commit,
2512
+ caretRect
2513
+ };
2670
2514
  }
2671
2515
 
2672
- // src/components/BlogCard/BlogCard.tsx
2673
- var import_react20 = require("react");
2674
-
2675
- // src/components/Avatar/Avatar.tsx
2516
+ // src/hooks/useTextareaSelection.ts
2676
2517
  var import_react18 = require("react");
2677
- var import_jsx_runtime8 = require("react/jsx-runtime");
2678
- var PALETTE_SIZE = 12;
2679
- function djb2(str) {
2680
- let h = 5381;
2681
- for (let i = 0; i < str.length; i++) h = (h << 5) + h + str.charCodeAt(i);
2682
- return h >>> 0;
2683
- }
2684
- function paletteIndexFor(seed) {
2685
- return djb2(seed) % PALETTE_SIZE;
2686
- }
2687
- function normalizeInitials(raw) {
2688
- const trimmed = raw.trim();
2689
- if (!trimmed) return "";
2690
- const tokens = trimmed.split(/\s+/).filter(Boolean);
2691
- if (tokens.length >= 2) {
2692
- return (tokens[0].charAt(0) + tokens[1].charAt(0)).toUpperCase();
2518
+ function getSelectionRect(textarea, start, end) {
2519
+ if (typeof document === "undefined") return null;
2520
+ if (start === end) return null;
2521
+ const mirror = document.createElement("div");
2522
+ const styles = window.getComputedStyle(textarea);
2523
+ for (const prop of [
2524
+ "boxSizing",
2525
+ "width",
2526
+ "fontFamily",
2527
+ "fontSize",
2528
+ "fontWeight",
2529
+ "fontStyle",
2530
+ "letterSpacing",
2531
+ "lineHeight",
2532
+ "paddingTop",
2533
+ "paddingRight",
2534
+ "paddingBottom",
2535
+ "paddingLeft",
2536
+ "borderTopWidth",
2537
+ "borderRightWidth",
2538
+ "borderBottomWidth",
2539
+ "borderLeftWidth",
2540
+ "textTransform",
2541
+ "wordSpacing",
2542
+ "tabSize",
2543
+ "whiteSpace"
2544
+ ]) {
2545
+ mirror.style[prop] = styles[prop];
2693
2546
  }
2694
- return trimmed.slice(0, 2).toUpperCase();
2547
+ mirror.style.position = "absolute";
2548
+ mirror.style.visibility = "hidden";
2549
+ mirror.style.overflow = "hidden";
2550
+ mirror.style.top = "0";
2551
+ mirror.style.left = "0";
2552
+ mirror.style.whiteSpace = "pre-wrap";
2553
+ mirror.style.wordWrap = "break-word";
2554
+ const pre = document.createTextNode(textarea.value.slice(0, start));
2555
+ const range = document.createElement("span");
2556
+ range.textContent = textarea.value.slice(start, end) || "\u200B";
2557
+ const post = document.createTextNode(textarea.value.slice(end));
2558
+ mirror.appendChild(pre);
2559
+ mirror.appendChild(range);
2560
+ mirror.appendChild(post);
2561
+ document.body.appendChild(mirror);
2562
+ const taRect = textarea.getBoundingClientRect();
2563
+ const rangeRect = range.getBoundingClientRect();
2564
+ const mirrorRect = mirror.getBoundingClientRect();
2565
+ const out = new DOMRect(
2566
+ taRect.left + (rangeRect.left - mirrorRect.left) - textarea.scrollLeft,
2567
+ taRect.top + (rangeRect.top - mirrorRect.top) - textarea.scrollTop,
2568
+ rangeRect.width,
2569
+ rangeRect.height
2570
+ );
2571
+ document.body.removeChild(mirror);
2572
+ return out;
2695
2573
  }
2696
- var STATUS_DEFAULT_LABEL = {
2697
- online: "Online",
2698
- offline: "Offline",
2699
- busy: "Busy",
2700
- away: "Away"
2701
- };
2702
- var Avatar = (0, import_react18.forwardRef)(function Avatar2({
2703
- src,
2704
- alt,
2705
- initials,
2706
- icon,
2707
- fallback,
2708
- seed,
2709
- size = "md",
2710
- shape = "circle",
2711
- status,
2712
- statusLabel,
2713
- onClick,
2714
- imgLoading = "lazy",
2715
- referrerPolicy,
2716
- className,
2717
- ...rest
2718
- }, ref) {
2719
- const [imgFailed, setImgFailed] = (0, import_react18.useState)(false);
2720
- const handleImgError = (0, import_react18.useCallback)(() => setImgFailed(true), []);
2721
- const showImage = Boolean(src) && !imgFailed;
2722
- const normalizedInitials = (0, import_react18.useMemo)(
2723
- () => initials ? normalizeInitials(initials) : "",
2724
- [initials]
2574
+ function useTextareaSelection({
2575
+ textareaRef,
2576
+ value,
2577
+ onChange
2578
+ }) {
2579
+ const [start, setStart] = (0, import_react18.useState)(0);
2580
+ const [end, setEnd] = (0, import_react18.useState)(0);
2581
+ const [rect, setRect] = (0, import_react18.useState)(null);
2582
+ (0, import_react18.useEffect)(() => {
2583
+ if (typeof document === "undefined") return;
2584
+ const handler = () => {
2585
+ const ta = textareaRef.current;
2586
+ if (!ta || document.activeElement !== ta) {
2587
+ setStart(0);
2588
+ setEnd(0);
2589
+ setRect(null);
2590
+ return;
2591
+ }
2592
+ const s = ta.selectionStart ?? 0;
2593
+ const e = ta.selectionEnd ?? 0;
2594
+ setStart(s);
2595
+ setEnd(e);
2596
+ setRect(s !== e ? getSelectionRect(ta, s, e) : null);
2597
+ };
2598
+ document.addEventListener("selectionchange", handler);
2599
+ return () => document.removeEventListener("selectionchange", handler);
2600
+ }, [textareaRef]);
2601
+ const active = start !== end;
2602
+ const text = active ? value.slice(start, end) : "";
2603
+ const writeToClipboard = (0, import_react18.useCallback)(
2604
+ async (str) => {
2605
+ try {
2606
+ if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
2607
+ await navigator.clipboard.writeText(str);
2608
+ return true;
2609
+ }
2610
+ } catch {
2611
+ }
2612
+ try {
2613
+ const tmp = document.createElement("textarea");
2614
+ tmp.value = str;
2615
+ tmp.style.position = "fixed";
2616
+ tmp.style.left = "-9999px";
2617
+ document.body.appendChild(tmp);
2618
+ tmp.select();
2619
+ const ok = document.execCommand("copy");
2620
+ document.body.removeChild(tmp);
2621
+ return ok;
2622
+ } catch {
2623
+ return false;
2624
+ }
2625
+ },
2626
+ []
2725
2627
  );
2726
- const paletteIdx = (0, import_react18.useMemo)(() => {
2727
- const resolved = seed ?? initials ?? alt ?? "?";
2728
- return paletteIndexFor(resolved);
2729
- }, [seed, initials, alt]);
2730
- const baseName = alt || normalizedInitials || "Avatar";
2731
- const announcedStatus = status ? statusLabel ?? STATUS_DEFAULT_LABEL[status] : "";
2732
- const accessibleName = announcedStatus ? `${baseName}, ${announcedStatus}` : baseName;
2733
- const isClickable = Boolean(onClick);
2734
- const handleKeyDown = (e) => {
2735
- if (!isClickable) return;
2736
- if ((e.key === "Enter" || e.key === " ") && !e.repeat) {
2737
- e.preventDefault();
2738
- onClick?.();
2739
- }
2740
- };
2741
- const rootClassName = cn(
2742
- "ods-avatar",
2743
- `ods-avatar--${size}`,
2744
- `ods-avatar--${shape}`,
2745
- isClickable && "ods-avatar--clickable",
2746
- className
2628
+ const copy = (0, import_react18.useCallback)(async () => {
2629
+ if (!active) return false;
2630
+ return writeToClipboard(text);
2631
+ }, [active, text, writeToClipboard]);
2632
+ const replace = (0, import_react18.useCallback)(
2633
+ (str) => {
2634
+ const ta = textareaRef.current;
2635
+ if (!ta) return;
2636
+ const next = value.slice(0, start) + str + value.slice(end);
2637
+ onChange(next);
2638
+ requestAnimationFrame(() => {
2639
+ ta.focus();
2640
+ const caret = start + str.length;
2641
+ ta.setSelectionRange(caret, caret);
2642
+ });
2643
+ },
2644
+ [textareaRef, value, start, end, onChange]
2747
2645
  );
2748
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2749
- "div",
2750
- {
2751
- ...rest,
2752
- ref,
2753
- className: rootClassName,
2754
- role: isClickable ? "button" : "img",
2755
- "aria-label": accessibleName,
2756
- tabIndex: isClickable ? 0 : void 0,
2757
- onClick,
2758
- onKeyDown: isClickable ? handleKeyDown : void 0,
2759
- "data-status": status,
2760
- children: [
2761
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "ods-avatar__inner", "data-palette": paletteIdx, children: showImage ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2762
- "img",
2763
- {
2764
- src,
2765
- alt: "",
2766
- className: "ods-avatar__image",
2767
- loading: imgLoading,
2768
- decoding: "async",
2769
- referrerPolicy,
2770
- onError: handleImgError,
2771
- draggable: false
2772
- }
2773
- ) : icon ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "ods-avatar__icon", "aria-hidden": "true", children: icon }) : normalizedInitials ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "ods-avatar__initials", "aria-hidden": "true", children: normalizedInitials }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "ods-avatar__initials", "aria-hidden": "true", children: fallback ?? "?" }) }),
2774
- status && // Decorative — the textual status is folded into the wrapper's
2775
- // aria-label above. `aria-hidden` keeps AT from double-announcing.
2776
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2777
- "span",
2778
- {
2779
- className: cn("ods-avatar__status", `ods-avatar__status--${status}`),
2780
- "aria-hidden": "true"
2781
- }
2782
- )
2783
- ]
2784
- }
2646
+ const cut = (0, import_react18.useCallback)(async () => {
2647
+ if (!active) return false;
2648
+ const ok = await writeToClipboard(text);
2649
+ if (ok) replace("");
2650
+ return ok;
2651
+ }, [active, text, writeToClipboard, replace]);
2652
+ const wrap = (0, import_react18.useCallback)(
2653
+ (open, close) => {
2654
+ const ta = textareaRef.current;
2655
+ if (!ta) return;
2656
+ const closing = close ?? open;
2657
+ const before = value.slice(0, start);
2658
+ const sel = value.slice(start, end);
2659
+ const after = value.slice(end);
2660
+ const next = `${before}${open}${sel}${closing}${after}`;
2661
+ onChange(next);
2662
+ requestAnimationFrame(() => {
2663
+ ta.focus();
2664
+ const newStart = start;
2665
+ const newEnd = end + open.length + closing.length;
2666
+ ta.setSelectionRange(newStart, newEnd);
2667
+ });
2668
+ },
2669
+ [textareaRef, value, start, end, onChange]
2785
2670
  );
2786
- });
2787
- Avatar.displayName = "Avatar";
2788
- var AvatarStack = (0, import_react18.forwardRef)(
2789
- function AvatarStack2({
2790
- children,
2791
- max,
2792
- size = "md",
2793
- renderOverflow,
2794
- direction = "ltr",
2795
- className,
2796
- ...rest
2797
- }, ref) {
2798
- const arr = Array.isArray(children) ? children : [children];
2799
- const visible = max && arr.length > max ? arr.slice(0, max) : arr;
2800
- const overflow = max && arr.length > max ? arr.length - max : 0;
2801
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2802
- "div",
2803
- {
2804
- ...rest,
2805
- ref,
2806
- className: cn(
2807
- "ods-avatar-stack",
2808
- `ods-avatar-stack--${direction}`,
2809
- className
2810
- ),
2811
- children: [
2812
- visible,
2813
- overflow > 0 && (renderOverflow ? renderOverflow(overflow) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2814
- "div",
2815
- {
2816
- className: cn(
2817
- "ods-avatar",
2818
- `ods-avatar--${size}`,
2819
- "ods-avatar--circle",
2820
- "ods-avatar-stack__overflow"
2821
- ),
2822
- role: "img",
2823
- "aria-label": `${overflow} more`,
2824
- children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "ods-avatar__inner", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "ods-avatar__initials", "aria-hidden": "true", children: [
2825
- "+",
2826
- overflow
2827
- ] }) })
2828
- }
2829
- ))
2830
- ]
2831
- }
2832
- );
2833
- }
2834
- );
2835
- AvatarStack.displayName = "AvatarStack";
2671
+ return {
2672
+ active,
2673
+ text,
2674
+ start,
2675
+ end,
2676
+ rect,
2677
+ copy,
2678
+ cut,
2679
+ wrap,
2680
+ replace
2681
+ };
2682
+ }
2836
2683
 
2837
- // src/components/Card/Card.tsx
2838
- var import_framer_motion4 = require("framer-motion");
2684
+ // src/hooks/useTextareaTools.tsx
2685
+ var import_icons3 = require("@octaviaflow/icons");
2839
2686
  var import_react19 = require("react");
2840
- var import_jsx_runtime9 = require("react/jsx-runtime");
2841
- var Card = (0, import_react19.forwardRef)(function Card2({
2842
- variant = "default",
2843
- padding = "md",
2844
- radius = "md",
2845
- hoverable = false,
2846
- disabled = false,
2847
- loading = false,
2848
- asChild = false,
2849
- onClick,
2850
- onKeyDown,
2851
- className,
2852
- children,
2853
- ...rest
2854
- }, ref) {
2855
- const reducedMotion = (0, import_framer_motion4.useReducedMotion)();
2856
- const isInteractive = Boolean(onClick) && !disabled && !loading;
2857
- const handleKeyDown = (e) => {
2858
- onKeyDown?.(e);
2859
- if (e.defaultPrevented) return;
2860
- if (!isInteractive) return;
2861
- if ((e.key === "Enter" || e.key === " ") && !e.repeat) {
2862
- e.preventDefault();
2863
- onClick?.(e);
2864
- }
2865
- };
2866
- const rootClassName = cn(
2867
- "ods-card",
2868
- `ods-card--${variant}`,
2869
- `ods-card--pad-${padding}`,
2870
- `ods-card--radius-${radius}`,
2871
- (hoverable || isInteractive) && !loading && !disabled && "ods-card--hoverable",
2872
- isInteractive && "ods-card--clickable",
2873
- disabled && "ods-card--disabled",
2874
- loading && "ods-card--loading",
2875
- className
2876
- );
2877
- if (asChild) {
2878
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2879
- Slot,
2880
- {
2881
- ...rest,
2882
- ref,
2883
- className: rootClassName,
2884
- onClick: isInteractive ? onClick : void 0,
2885
- onKeyDown: isInteractive ? handleKeyDown : onKeyDown,
2886
- "aria-disabled": disabled || void 0,
2887
- children
2687
+ var import_jsx_runtime7 = require("react/jsx-runtime");
2688
+ function useTextareaTools({
2689
+ textareaRef,
2690
+ value,
2691
+ onChange,
2692
+ tools
2693
+ }) {
2694
+ const valueRef = (0, import_react19.useRef)(value);
2695
+ valueRef.current = value;
2696
+ const helpers = (0, import_react19.useMemo)(() => {
2697
+ const getTa = () => textareaRef.current;
2698
+ const getSelection = () => {
2699
+ const ta = getTa();
2700
+ const s = ta?.selectionStart ?? 0;
2701
+ const e = ta?.selectionEnd ?? 0;
2702
+ return {
2703
+ text: valueRef.current.slice(s, e),
2704
+ start: s,
2705
+ end: e,
2706
+ active: s !== e
2707
+ };
2708
+ };
2709
+ const setValue = (next) => onChange(next);
2710
+ const replace = (text) => {
2711
+ const ta = getTa();
2712
+ if (!ta) return;
2713
+ const { start, end } = getSelection();
2714
+ const next = valueRef.current.slice(0, start) + text + valueRef.current.slice(end);
2715
+ onChange(next);
2716
+ requestAnimationFrame(() => {
2717
+ ta.focus();
2718
+ const caret = start + text.length;
2719
+ ta.setSelectionRange(caret, caret);
2720
+ });
2721
+ };
2722
+ const insert = (text) => replace(text);
2723
+ const wrap = (open, close) => {
2724
+ const ta = getTa();
2725
+ if (!ta) return;
2726
+ const closing = close ?? open;
2727
+ const { start, end } = getSelection();
2728
+ const sel = valueRef.current.slice(start, end);
2729
+ const next = valueRef.current.slice(0, start) + open + sel + closing + valueRef.current.slice(end);
2730
+ onChange(next);
2731
+ requestAnimationFrame(() => {
2732
+ ta.focus();
2733
+ if (start === end) {
2734
+ const caret = start + open.length;
2735
+ ta.setSelectionRange(caret, caret);
2736
+ } else {
2737
+ ta.setSelectionRange(start, end + open.length + closing.length);
2738
+ }
2739
+ });
2740
+ };
2741
+ const prependLines = (prefix) => {
2742
+ const ta = getTa();
2743
+ if (!ta) return;
2744
+ const { start, end } = getSelection();
2745
+ const v = valueRef.current;
2746
+ const lineStart = v.lastIndexOf("\n", start - 1) + 1;
2747
+ const lineEnd = (() => {
2748
+ const idx = v.indexOf("\n", end);
2749
+ return idx === -1 ? v.length : idx;
2750
+ })();
2751
+ const block = v.slice(lineStart, lineEnd);
2752
+ const updated = block.split("\n").map((line) => `${prefix}${line}`).join("\n");
2753
+ const next = v.slice(0, lineStart) + updated + v.slice(lineEnd);
2754
+ onChange(next);
2755
+ requestAnimationFrame(() => {
2756
+ ta.focus();
2757
+ ta.setSelectionRange(
2758
+ lineStart,
2759
+ lineStart + updated.length
2760
+ );
2761
+ });
2762
+ };
2763
+ const undo = () => {
2764
+ try {
2765
+ getTa()?.focus();
2766
+ document.execCommand("undo");
2767
+ } catch {
2888
2768
  }
2889
- );
2890
- }
2891
- if (loading) {
2892
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2893
- "div",
2894
- {
2895
- ...rest,
2896
- ref,
2897
- className: rootClassName,
2898
- "aria-busy": "true",
2899
- "aria-label": "Loading",
2900
- children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "ods-card__skeleton", "data-ods-animate": "shimmer" })
2769
+ };
2770
+ const redo = () => {
2771
+ try {
2772
+ getTa()?.focus();
2773
+ document.execCommand("redo");
2774
+ } catch {
2901
2775
  }
2902
- );
2903
- }
2904
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2905
- import_framer_motion4.motion.div,
2906
- {
2907
- ...rest,
2908
- ref,
2909
- onClick: isInteractive ? onClick : void 0,
2910
- onKeyDown: isInteractive ? handleKeyDown : onKeyDown,
2911
- className: rootClassName,
2912
- whileHover: (hoverable || isInteractive) && !reducedMotion ? { y: -2, transition: { duration: 0.15 } } : void 0,
2913
- whileTap: isInteractive && !reducedMotion ? { scale: 0.99 } : void 0,
2914
- role: isInteractive ? "button" : rest.role,
2915
- tabIndex: isInteractive ? rest.tabIndex ?? 0 : rest.tabIndex,
2916
- "aria-disabled": disabled || void 0,
2917
- children
2918
- }
2776
+ };
2777
+ return {
2778
+ get value() {
2779
+ return valueRef.current;
2780
+ },
2781
+ get selection() {
2782
+ return getSelection();
2783
+ },
2784
+ setValue,
2785
+ replace,
2786
+ insert,
2787
+ wrap,
2788
+ prependLines,
2789
+ undo,
2790
+ redo,
2791
+ getTextarea: getTa
2792
+ };
2793
+ }, [textareaRef, onChange]);
2794
+ const visibleTools = (0, import_react19.useMemo)(
2795
+ () => tools.filter((t) => t.isVisible ? t.isVisible(helpers) : true),
2796
+ [tools, helpers]
2919
2797
  );
2920
- });
2921
- Card.displayName = "Card";
2922
- var CardHeader = (0, import_react19.forwardRef)(
2923
- function CardHeader2({ className, children, ...rest }, ref) {
2924
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { ...rest, ref, className: cn("ods-card__header", className), children });
2925
- }
2926
- );
2927
- CardHeader.displayName = "Card.Header";
2928
- var CardBody = (0, import_react19.forwardRef)(
2929
- function CardBody2({ className, children, ...rest }, ref) {
2930
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { ...rest, ref, className: cn("ods-card__body", className), children });
2931
- }
2932
- );
2933
- CardBody.displayName = "Card.Body";
2934
- var CardFooter = (0, import_react19.forwardRef)(
2935
- function CardFooter2({ className, children, ...rest }, ref) {
2936
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { ...rest, ref, className: cn("ods-card__footer", className), children });
2937
- }
2938
- );
2939
- CardFooter.displayName = "Card.Footer";
2940
- var CardTitle = (0, import_react19.forwardRef)(
2941
- function CardTitle2({ as: Comp = "h3", className, children, ...rest }, ref) {
2942
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2943
- Comp,
2944
- {
2945
- ...rest,
2946
- ref,
2947
- className: cn("ods-card__title", className),
2948
- children
2949
- }
2950
- );
2951
- }
2952
- );
2953
- CardTitle.displayName = "Card.Title";
2954
- var CardDescription = (0, import_react19.forwardRef)(function CardDescription2({ className, children, ...rest }, ref) {
2955
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { ...rest, ref, className: cn("ods-card__description", className), children });
2956
- });
2957
- CardDescription.displayName = "Card.Description";
2958
- Card.Header = CardHeader;
2959
- Card.Body = CardBody;
2960
- Card.Footer = CardFooter;
2961
- Card.Title = CardTitle;
2962
- Card.Description = CardDescription;
2798
+ const runTool = (0, import_react19.useCallback)(
2799
+ async (id) => {
2800
+ const tool = tools.find((t) => t.id === id);
2801
+ if (!tool || tool.kind === "divider") return;
2802
+ await tool.onAction?.(helpers);
2803
+ },
2804
+ [tools, helpers]
2805
+ );
2806
+ return { tools: visibleTools, runTool, helpers };
2807
+ }
2963
2808
 
2964
- // src/components/BlogCard/BlogCard.tsx
2965
- var import_jsx_runtime10 = require("react/jsx-runtime");
2966
- var BlogCard = (0, import_react20.forwardRef)(
2967
- function BlogCard2({
2968
- cover,
2969
- coverAspect = "16/9",
2970
- category,
2971
- tags,
2972
- meta,
2973
- readingTime,
2974
- title,
2975
- titleAs = "h3",
2976
- description,
2977
- author,
2978
- footer,
2979
- radius = "md",
2980
- orientation = "vertical",
2981
- href,
2982
- onClick,
2983
- id: providedId,
2809
+ // src/components/Textarea/Textarea.tsx
2810
+ var import_jsx_runtime8 = require("react/jsx-runtime");
2811
+ var Textarea = (0, import_react20.forwardRef)(
2812
+ function Textarea2({
2813
+ label,
2814
+ error = false,
2815
+ errorMessage,
2816
+ helperText,
2817
+ resize = "vertical",
2818
+ autoResize = false,
2819
+ minRows,
2820
+ maxRows,
2821
+ maxHeight,
2822
+ maxWidth,
2823
+ minHeight,
2824
+ minWidth,
2825
+ maxLength,
2826
+ size = "md",
2827
+ rows = 3,
2828
+ disabled = false,
2984
2829
  className,
2985
- ...rest
2986
- }, ref) {
2830
+ style: consumerStyle,
2831
+ defaultValue,
2832
+ value,
2833
+ onChange,
2834
+ id: providedId,
2835
+ commands,
2836
+ commandTrigger = "/",
2837
+ selectionToolbar = false,
2838
+ selectionActions,
2839
+ tools,
2840
+ toolbarPosition = "top",
2841
+ ...props
2842
+ }, forwardedRef) {
2843
+ const innerRef = (0, import_react20.useRef)(null);
2844
+ const setRef = (node) => {
2845
+ innerRef.current = node;
2846
+ if (typeof forwardedRef === "function") forwardedRef(node);
2847
+ else if (forwardedRef)
2848
+ forwardedRef.current = node;
2849
+ };
2987
2850
  const reactId = (0, import_react20.useId)();
2988
- const baseId = providedId ?? `ods-blog-card-${reactId}`;
2989
- const titleId = `${baseId}-title`;
2990
- const descId = description ? `${baseId}-desc` : void 0;
2991
- const tagList = tags ?? (category ? [category] : []);
2992
- const body = /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
2993
- cover && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2994
- "div",
2995
- {
2996
- className: cn(
2997
- "ods-blog-card__cover",
2998
- `ods-blog-card__cover--aspect-${coverAspect.replace("/", "-")}`
2999
- ),
3000
- "aria-hidden": "true",
3001
- children: cover
3002
- }
3003
- ),
3004
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ods-blog-card__body", children: [
3005
- (tagList.length > 0 || meta || readingTime) && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ods-blog-card__meta", children: [
3006
- tagList.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ods-blog-card__tags", children: tagList.map((t, i) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ods-blog-card__tag", children: t }, i)) }),
3007
- meta && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ods-blog-card__date", children: meta }),
3008
- readingTime && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ods-blog-card__reading", children: readingTime })
3009
- ] }),
3010
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3011
- Card.Title,
3012
- {
3013
- as: titleAs,
3014
- id: titleId,
3015
- className: "ods-blog-card__title",
3016
- children: title
3017
- }
3018
- ),
3019
- description && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3020
- Card.Description,
3021
- {
3022
- id: descId,
3023
- className: "ods-blog-card__desc",
3024
- children: description
3025
- }
3026
- ),
3027
- (author || footer) && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ods-blog-card__footer", children: [
3028
- author && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ods-blog-card__author", children: [
3029
- author.avatar && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3030
- Avatar,
3031
- {
3032
- size: "sm",
3033
- src: author.avatar.src,
3034
- initials: author.avatar.initials,
3035
- alt: author.avatar.alt ?? (typeof author.name === "string" ? author.name : void 0)
3036
- }
3037
- ),
3038
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "ods-blog-card__author-text", children: [
3039
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ods-blog-card__author-name", children: author.name }),
3040
- author.byline && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ods-blog-card__author-byline", children: author.byline })
3041
- ] })
3042
- ] }),
3043
- footer && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "ods-blog-card__action", children: footer })
3044
- ] })
3045
- ] })
3046
- ] });
3047
- if (href) {
3048
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3049
- Card,
3050
- {
3051
- asChild: true,
3052
- radius,
3053
- padding: "none",
3054
- className: cn(
3055
- "ods-blog-card",
3056
- `ods-blog-card--${orientation}`,
3057
- "ods-blog-card--interactive",
3058
- className
3059
- ),
3060
- "aria-labelledby": titleId,
3061
- "aria-describedby": descId,
3062
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3063
- "a",
3064
- {
3065
- ...rest,
3066
- ref,
3067
- href,
3068
- id: baseId,
3069
- onClick,
3070
- children: body
3071
- }
3072
- )
3073
- }
2851
+ const baseId = providedId ?? `ods-textarea-${reactId}`;
2852
+ const hintId = error && errorMessage || helperText ? `${baseId}-hint` : void 0;
2853
+ const [charCount, setCharCount] = (0, import_react20.useState)(
2854
+ () => String(value ?? defaultValue ?? "").length
2855
+ );
2856
+ const ariaNameProps = resolveAccessibleName({
2857
+ label: typeof label === "string" ? label : void 0,
2858
+ ariaLabel: props["aria-label"],
2859
+ ariaLabelledby: props["aria-labelledby"],
2860
+ componentName: "Textarea"
2861
+ });
2862
+ const { labelProps, inputProps } = (0, import_react_aria4.useTextField)(
2863
+ {
2864
+ label: typeof label === "string" ? label : void 0,
2865
+ ...ariaNameProps,
2866
+ isDisabled: disabled,
2867
+ errorMessage: typeof errorMessage === "string" ? errorMessage : void 0,
2868
+ validationState: error ? "invalid" : void 0,
2869
+ inputElementType: "textarea",
2870
+ value,
2871
+ defaultValue
2872
+ },
2873
+ innerRef
2874
+ );
2875
+ const describedBy = [
2876
+ props["aria-describedby"],
2877
+ hintId
2878
+ ].filter(Boolean).join(" ") || void 0;
2879
+ const handleChange = (e) => {
2880
+ setCharCount(e.target.value.length);
2881
+ onChange?.(e);
2882
+ };
2883
+ const [liveValue, setLiveValue] = (0, import_react20.useState)(
2884
+ String(value ?? defaultValue ?? "")
2885
+ );
2886
+ (0, import_react20.useEffect)(() => {
2887
+ if (value != null) setLiveValue(String(value));
2888
+ }, [value]);
2889
+ const handleChangeWithMirror = (e) => {
2890
+ setLiveValue(e.target.value);
2891
+ handleChange(e);
2892
+ };
2893
+ const setLiveAndCommit = (next) => {
2894
+ setLiveValue(next);
2895
+ if (innerRef.current) innerRef.current.value = next;
2896
+ onChange?.({
2897
+ target: innerRef.current,
2898
+ currentTarget: innerRef.current
2899
+ });
2900
+ setCharCount(next.length);
2901
+ };
2902
+ const cmdPalette = useTextareaCommands({
2903
+ textareaRef: innerRef,
2904
+ value: liveValue,
2905
+ onChange: setLiveAndCommit,
2906
+ commands: commands ?? [],
2907
+ trigger: commandTrigger
2908
+ });
2909
+ const cmdEnabled = (commands?.length ?? 0) > 0;
2910
+ const sel = useTextareaSelection({
2911
+ textareaRef: innerRef,
2912
+ value: liveValue,
2913
+ onChange: setLiveAndCommit
2914
+ });
2915
+ const toolbar = useTextareaTools({
2916
+ textareaRef: innerRef,
2917
+ value: liveValue,
2918
+ onChange: setLiveAndCommit,
2919
+ tools: tools ?? []
2920
+ });
2921
+ const toolbarEnabled = (tools?.length ?? 0) > 0;
2922
+ const handleKeyDownWithCommands = (e) => {
2923
+ if (cmdEnabled) {
2924
+ const consumed = cmdPalette.onKeyDown(e);
2925
+ if (consumed) return;
2926
+ }
2927
+ props.onKeyDown?.(e);
2928
+ };
2929
+ (0, import_react20.useEffect)(() => {
2930
+ if (!autoResize) return;
2931
+ const el = innerRef.current;
2932
+ if (!el) return;
2933
+ const computed = window.getComputedStyle(el);
2934
+ const fontSize = parseFloat(computed.fontSize) || 16;
2935
+ const lhRaw = computed.lineHeight;
2936
+ const lh = lhRaw === "normal" ? fontSize * 1.2 : lhRaw.endsWith("px") ? parseFloat(lhRaw) : parseFloat(lhRaw) * fontSize;
2937
+ const padding = parseFloat(computed.paddingTop) + parseFloat(computed.paddingBottom);
2938
+ const rowCap = maxRows != null && Number.isFinite(maxRows) ? lh * maxRows + padding : Number.POSITIVE_INFINITY;
2939
+ const pixelCap = typeof maxHeight === "number" ? maxHeight : Number.POSITIVE_INFINITY;
2940
+ const effectiveMax = Math.min(rowCap, pixelCap);
2941
+ const rowFloor = lh * (minRows ?? rows) + padding;
2942
+ el.style.height = "auto";
2943
+ const target = Math.max(
2944
+ rowFloor,
2945
+ Math.min(effectiveMax, el.scrollHeight)
3074
2946
  );
3075
- }
3076
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3077
- Card,
2947
+ el.style.height = `${target}px`;
2948
+ el.style.overflowY = el.scrollHeight > effectiveMax ? "auto" : "hidden";
2949
+ }, [autoResize, value, defaultValue, minRows, maxRows, maxHeight, rows]);
2950
+ const resolvedResize = typeof resize === "boolean" ? resize ? "vertical" : "none" : resize;
2951
+ const toCss = (v) => v == null ? void 0 : typeof v === "number" ? `${v}px` : v;
2952
+ const fieldStyle = {
2953
+ maxHeight: toCss(maxHeight),
2954
+ maxWidth: toCss(maxWidth),
2955
+ minHeight: toCss(minHeight),
2956
+ minWidth: toCss(minWidth),
2957
+ resize: resolvedResize
2958
+ };
2959
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
2960
+ "div",
3078
2961
  {
3079
- ...rest,
3080
- ref,
3081
- id: baseId,
3082
- radius,
3083
- padding: "none",
3084
- onClick,
3085
2962
  className: cn(
3086
- "ods-blog-card",
3087
- `ods-blog-card--${orientation}`,
3088
- onClick && "ods-blog-card--interactive",
2963
+ "ods-textarea",
2964
+ `ods-textarea--${size}`,
2965
+ `ods-textarea--resize-${resolvedResize}`,
2966
+ autoResize && "ods-textarea--auto-resize",
2967
+ error && "ods-textarea--error",
2968
+ disabled && "ods-textarea--disabled",
3089
2969
  className
3090
2970
  ),
3091
- "aria-labelledby": titleId,
3092
- "aria-describedby": descId,
3093
- children: body
3094
- }
3095
- );
3096
- }
3097
- );
3098
- BlogCard.displayName = "BlogCard";
3099
-
3100
- // src/marketing/components/BlogGrid/BlogGrid.tsx
3101
- var import_jsx_runtime11 = require("react/jsx-runtime");
3102
- function BlogGrid({
3103
- posts,
3104
- columns = 3,
3105
- viewAllHref,
3106
- viewAllLabel = "View all posts \u2192",
3107
- className,
3108
- ...rest
2971
+ style: consumerStyle,
2972
+ children: [
2973
+ label && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2974
+ "label",
2975
+ {
2976
+ ...labelProps,
2977
+ htmlFor: baseId,
2978
+ className: "ods-textarea__label",
2979
+ children: label
2980
+ }
2981
+ ),
2982
+ toolbarEnabled && toolbarPosition === "top" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2983
+ TextareaToolbar,
2984
+ {
2985
+ tools: toolbar.tools,
2986
+ onRun: toolbar.runTool
2987
+ }
2988
+ ),
2989
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "ods-textarea__wrapper", children: [
2990
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2991
+ "textarea",
2992
+ {
2993
+ ...props,
2994
+ ...inputProps,
2995
+ ref: setRef,
2996
+ id: baseId,
2997
+ rows: autoResize ? minRows ?? rows : rows,
2998
+ disabled,
2999
+ maxLength,
3000
+ defaultValue,
3001
+ value,
3002
+ onChange: handleChangeWithMirror,
3003
+ onKeyDown: handleKeyDownWithCommands,
3004
+ className: "ods-textarea__field",
3005
+ style: fieldStyle,
3006
+ "aria-invalid": error || void 0,
3007
+ "aria-describedby": describedBy,
3008
+ "aria-autocomplete": cmdEnabled ? "list" : void 0,
3009
+ "aria-expanded": cmdEnabled ? cmdPalette.isOpen : void 0,
3010
+ "aria-controls": cmdEnabled && cmdPalette.isOpen ? `${baseId}-cmd-list` : void 0,
3011
+ "aria-activedescendant": cmdEnabled && cmdPalette.isOpen && cmdPalette.items[cmdPalette.activeIndex] ? `${baseId}-cmd-${cmdPalette.items[cmdPalette.activeIndex].id}` : void 0
3012
+ }
3013
+ ),
3014
+ cmdEnabled && cmdPalette.isOpen && cmdPalette.items.length > 0 && typeof document !== "undefined" && (0, import_react_dom3.createPortal)(
3015
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3016
+ "ul",
3017
+ {
3018
+ id: `${baseId}-cmd-list`,
3019
+ role: "listbox",
3020
+ "aria-label": "Inline commands",
3021
+ className: "ods-textarea__cmd-popover",
3022
+ style: {
3023
+ position: "fixed",
3024
+ top: (cmdPalette.caretRect?.bottom ?? 0) + 6,
3025
+ left: cmdPalette.caretRect?.left ?? 0
3026
+ },
3027
+ children: cmdPalette.items.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3028
+ "li",
3029
+ {
3030
+ id: `${baseId}-cmd-${c.id}`,
3031
+ role: "option",
3032
+ "aria-selected": i === cmdPalette.activeIndex,
3033
+ className: cn(
3034
+ "ods-textarea__cmd-item",
3035
+ i === cmdPalette.activeIndex && "ods-textarea__cmd-item--active"
3036
+ ),
3037
+ onMouseDown: (e) => {
3038
+ e.preventDefault();
3039
+ cmdPalette.commit(c);
3040
+ },
3041
+ onMouseEnter: () => {
3042
+ },
3043
+ children: [
3044
+ c.icon && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "ods-textarea__cmd-icon", children: c.icon }),
3045
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "ods-textarea__cmd-text", children: [
3046
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "ods-textarea__cmd-label", children: c.label }),
3047
+ c.description && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "ods-textarea__cmd-desc", children: c.description })
3048
+ ] })
3049
+ ]
3050
+ },
3051
+ c.id
3052
+ ))
3053
+ }
3054
+ ),
3055
+ document.body
3056
+ ),
3057
+ selectionToolbar && sel.active && sel.rect && typeof document !== "undefined" && (0, import_react_dom3.createPortal)(
3058
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3059
+ "div",
3060
+ {
3061
+ role: "toolbar",
3062
+ "aria-label": "Selection actions",
3063
+ className: "ods-textarea__sel-toolbar",
3064
+ style: {
3065
+ position: "fixed",
3066
+ top: sel.rect.top - 36,
3067
+ left: sel.rect.left
3068
+ },
3069
+ onMouseDown: (e) => e.preventDefault(),
3070
+ children: [
3071
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3072
+ "button",
3073
+ {
3074
+ type: "button",
3075
+ className: "ods-textarea__sel-btn",
3076
+ onClick: () => sel.copy(),
3077
+ "aria-label": "Copy selection",
3078
+ title: "Copy",
3079
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_icons4.CopyIcon, { size: "xs" })
3080
+ }
3081
+ ),
3082
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3083
+ "button",
3084
+ {
3085
+ type: "button",
3086
+ className: "ods-textarea__sel-btn",
3087
+ onClick: () => sel.cut(),
3088
+ "aria-label": "Cut selection",
3089
+ title: "Cut",
3090
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_icons4.CutIcon, { size: "xs" })
3091
+ }
3092
+ ),
3093
+ selectionActions?.map((a) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3094
+ "button",
3095
+ {
3096
+ type: "button",
3097
+ className: "ods-textarea__sel-btn",
3098
+ onClick: () => a.onAction(sel.text, {
3099
+ replace: sel.replace,
3100
+ wrap: sel.wrap,
3101
+ copy: sel.copy,
3102
+ cut: sel.cut
3103
+ }),
3104
+ title: typeof a.label === "string" ? a.label : void 0,
3105
+ children: [
3106
+ a.icon,
3107
+ a.label && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "ods-textarea__sel-btn-label", children: a.label })
3108
+ ]
3109
+ },
3110
+ a.id
3111
+ ))
3112
+ ]
3113
+ }
3114
+ ),
3115
+ document.body
3116
+ ),
3117
+ maxLength != null && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
3118
+ "span",
3119
+ {
3120
+ className: "ods-textarea__count",
3121
+ "aria-live": charCount > maxLength ? "polite" : "off",
3122
+ children: [
3123
+ charCount,
3124
+ "/",
3125
+ maxLength
3126
+ ]
3127
+ }
3128
+ )
3129
+ ] }),
3130
+ toolbarEnabled && toolbarPosition === "bottom" && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3131
+ TextareaToolbar,
3132
+ {
3133
+ tools: toolbar.tools,
3134
+ onRun: toolbar.runTool
3135
+ }
3136
+ ),
3137
+ error && errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3138
+ "div",
3139
+ {
3140
+ id: hintId,
3141
+ className: "ods-textarea__error-message",
3142
+ role: "alert",
3143
+ children: errorMessage
3144
+ }
3145
+ ) : helperText ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { id: hintId, className: "ods-textarea__hint", children: helperText }) : null
3146
+ ]
3147
+ }
3148
+ );
3149
+ }
3150
+ );
3151
+ Textarea.displayName = "Textarea";
3152
+ function TextareaToolbar({
3153
+ tools,
3154
+ onRun
3109
3155
  }) {
3110
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { ...rest, className: cn("ods-mkt-blog", className), children: [
3111
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: cn("ods-mkt-blog__grid", `ods-mkt-blog__grid--${columns}`), children: posts.map((post) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3112
- BlogCard,
3113
- {
3114
- title: post.title,
3115
- description: post.excerpt,
3116
- href: post.href,
3117
- meta: post.date,
3118
- readingTime: post.readTime,
3119
- tags: post.tags,
3120
- cover: post.cover,
3121
- author: post.author
3156
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3157
+ "div",
3158
+ {
3159
+ role: "toolbar",
3160
+ "aria-label": "Formatting",
3161
+ className: "ods-textarea__toolbar",
3162
+ onMouseDown: (e) => {
3163
+ if (e.target.closest(".ods-textarea__toolbar-btn")) {
3164
+ e.preventDefault();
3165
+ }
3122
3166
  },
3123
- post.id
3124
- )) }),
3125
- viewAllHref && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "ods-mkt-blog__more", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("a", { className: "ods-mkt-blog__more-link", href: viewAllHref, children: viewAllLabel }) })
3126
- ] });
3127
- }
3128
-
3129
- // src/marketing/components/ContactForm/ContactForm.tsx
3130
- var import_react25 = require("react");
3131
-
3132
- // src/components/Textarea/Textarea.tsx
3133
- var import_react24 = require("react");
3134
- var import_react_aria4 = require("react-aria");
3135
- var import_react_dom3 = require("react-dom");
3136
- var import_icons4 = require("@octaviaflow/icons");
3137
-
3138
- // src/hooks/useTextareaCommands.ts
3139
- var import_react21 = require("react");
3140
- function findOpenTrigger(value, caret, trigger) {
3141
- let i = caret - 1;
3142
- while (i >= 0) {
3143
- const ch = value[i];
3144
- if (ch === "\n" || ch === " " || ch === " ") return null;
3145
- if (value.startsWith(trigger, i)) {
3146
- const before = i === 0 ? "" : value[i - 1];
3147
- if (i === 0 || before === " " || before === "\n" || before === " ") {
3148
- return {
3149
- triggerIndex: i,
3150
- query: value.slice(i + trigger.length, caret)
3151
- };
3152
- }
3153
- return null;
3167
+ children: tools.map((t) => {
3168
+ if (t.kind === "divider") {
3169
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3170
+ "span",
3171
+ {
3172
+ className: "ods-textarea__toolbar-divider",
3173
+ "aria-hidden": "true"
3174
+ },
3175
+ t.id
3176
+ );
3177
+ }
3178
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
3179
+ "button",
3180
+ {
3181
+ type: "button",
3182
+ className: "ods-textarea__toolbar-btn",
3183
+ title: t.title ?? (typeof t.label === "string" ? t.label : void 0),
3184
+ "aria-label": typeof t.label === "string" ? t.label : t.id,
3185
+ onClick: () => onRun(t.id),
3186
+ children: t.icon ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "ods-textarea__toolbar-icon", children: t.icon }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "ods-textarea__toolbar-text", children: t.label })
3187
+ },
3188
+ t.id
3189
+ );
3190
+ })
3154
3191
  }
3155
- i--;
3156
- }
3157
- return null;
3192
+ );
3158
3193
  }
3159
- function getCaretRect(textarea, caret) {
3160
- if (typeof document === "undefined") return null;
3161
- const mirror = document.createElement("div");
3162
- const styles = window.getComputedStyle(textarea);
3163
- for (const prop of [
3164
- "boxSizing",
3165
- "width",
3166
- "fontFamily",
3167
- "fontSize",
3168
- "fontWeight",
3169
- "fontStyle",
3170
- "letterSpacing",
3171
- "lineHeight",
3172
- "paddingTop",
3173
- "paddingRight",
3174
- "paddingBottom",
3175
- "paddingLeft",
3176
- "borderTopWidth",
3177
- "borderRightWidth",
3178
- "borderBottomWidth",
3179
- "borderLeftWidth",
3180
- "textTransform",
3181
- "wordSpacing",
3182
- "tabSize",
3183
- "whiteSpace"
3184
- ]) {
3185
- mirror.style[prop] = styles[prop];
3194
+
3195
+ // src/marketing/components/WorkflowShowcase/showcaseEngine.ts
3196
+ var SETTLED_PHASES = [
3197
+ "done",
3198
+ "signup",
3199
+ "creating",
3200
+ "welcome"
3201
+ ];
3202
+ var WORKFLOW_SHOWCASE_DEFAULT_CONNECTORS = [
3203
+ { name: "Storefront", role: "source", aliases: ["store", "shop orders"] },
3204
+ { name: "Payments", role: "source", aliases: ["payouts"] },
3205
+ { name: "Orders DB", role: "source", aliases: ["orders database"] },
3206
+ { name: "CRM", role: "source", aliases: ["crm leads"] },
3207
+ { name: "Warehouse", role: "destination" },
3208
+ { name: "Lakehouse", role: "destination" },
3209
+ { name: "Campaigns", role: "action", aliases: ["campaign"] },
3210
+ { name: "Team Chat", role: "notify", aliases: ["chat", "alert the team"] }
3211
+ ];
3212
+ var WORKFLOW_SHOWCASE_DEFAULT_EXAMPLES = [
3213
+ {
3214
+ id: "hourly-sync",
3215
+ label: "Storefront orders \u2192 Warehouse, hourly",
3216
+ prompt: "Sync Storefront orders to Warehouse every hour, alert sales in Team Chat"
3217
+ },
3218
+ {
3219
+ id: "daily-load",
3220
+ label: "Payments payouts \u2192 Lakehouse, daily",
3221
+ prompt: "Load Payments payouts into Lakehouse daily"
3222
+ },
3223
+ {
3224
+ id: "realtime-stream",
3225
+ label: "Orders DB changes \u2192 Warehouse + Team Chat",
3226
+ prompt: "Stream Orders DB changes to Warehouse in real-time and notify Team Chat"
3186
3227
  }
3187
- mirror.style.position = "absolute";
3188
- mirror.style.visibility = "hidden";
3189
- mirror.style.overflow = "hidden";
3190
- mirror.style.top = "0";
3191
- mirror.style.left = "0";
3192
- mirror.style.whiteSpace = "pre-wrap";
3193
- mirror.style.wordWrap = "break-word";
3194
- mirror.textContent = textarea.value.slice(0, caret);
3195
- const marker = document.createElement("span");
3196
- marker.textContent = "\u200B";
3197
- mirror.appendChild(marker);
3198
- document.body.appendChild(mirror);
3199
- const taRect = textarea.getBoundingClientRect();
3200
- const markerRect = marker.getBoundingClientRect();
3201
- const mirrorRect = mirror.getBoundingClientRect();
3202
- const x = taRect.left + (markerRect.left - mirrorRect.left) - textarea.scrollLeft;
3203
- const y = taRect.top + (markerRect.top - mirrorRect.top) - textarea.scrollTop;
3204
- const out = new DOMRect(x, y, 0, markerRect.height || 16);
3205
- document.body.removeChild(mirror);
3206
- return out;
3228
+ ];
3229
+ var WORKFLOW_SHOWCASE_DEFAULT_METRICS = [
3230
+ { id: "throughput", value: 2341, buildingValue: 842, unit: "rows/min", label: "THROUGHPUT" },
3231
+ {
3232
+ id: "latency",
3233
+ value: 0.8,
3234
+ buildingValue: 1.1,
3235
+ decimals: 1,
3236
+ unit: "s latency",
3237
+ label: "SYNC SPEED"
3238
+ },
3239
+ { id: "cost", value: 43, suffix: "%", label: "SAVED VS BASELINE", tone: "success" }
3240
+ ];
3241
+ var WORKFLOW_SHOWCASE_SCHEDULES = ["AUTO", "HOURLY", "DAILY", "REAL-TIME"];
3242
+ var WORKFLOW_SHOWCASE_TRIGGERS = ["SCHEDULE", "WEBHOOK", "APP EVENT", "MANUAL"];
3243
+ var WORKFLOW_SHOWCASE_DEFAULT_TILES = [
3244
+ "ELT",
3245
+ "CDC",
3246
+ "LLM",
3247
+ "RAG",
3248
+ "HEAL",
3249
+ "COST",
3250
+ "IDP",
3251
+ "VEC"
3252
+ ];
3253
+ function escapeRegExp(value) {
3254
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3207
3255
  }
3208
- function useTextareaCommands({
3209
- textareaRef,
3210
- value,
3211
- onChange,
3212
- commands,
3213
- trigger = "/",
3214
- openOnEmptyTrigger = true,
3215
- maxItems = 8
3216
- }) {
3217
- const [isOpen, setIsOpen] = (0, import_react21.useState)(false);
3218
- const [query, setQuery] = (0, import_react21.useState)("");
3219
- const [activeIndex, setActiveIndex] = (0, import_react21.useState)(0);
3220
- const [triggerIndex, setTriggerIndex] = (0, import_react21.useState)(null);
3221
- const [caretRect, setCaretRect] = (0, import_react21.useState)(null);
3222
- const dismissedAtRef = (0, import_react21.useRef)(
3223
- null
3224
- );
3225
- const items = (0, import_react21.useMemo)(() => {
3226
- const q = query.trim().toLowerCase();
3227
- if (!q) return commands.slice(0, maxItems);
3228
- return commands.filter((c) => {
3229
- const hay = [
3230
- typeof c.label === "string" ? c.label : "",
3231
- typeof c.description === "string" ? c.description : "",
3232
- c.id,
3233
- ...c.keywords ?? []
3234
- ].join(" ").toLowerCase();
3235
- return hay.includes(q);
3236
- }).slice(0, maxItems);
3237
- }, [commands, query, maxItems]);
3238
- (0, import_react21.useEffect)(() => {
3239
- const ta = textareaRef.current;
3240
- if (!ta) return;
3241
- const caret = ta.selectionStart ?? 0;
3242
- const dismissedAt = dismissedAtRef.current;
3243
- if (dismissedAt && dismissedAt.value === value && dismissedAt.caret === caret) {
3244
- return;
3256
+ function mentionsOf(catalog) {
3257
+ const out = [];
3258
+ for (const connector of catalog) {
3259
+ out.push({ connector, pattern: connector.name });
3260
+ for (const alias of connector.aliases ?? []) out.push({ connector, pattern: alias });
3261
+ }
3262
+ return out.sort((a, b) => b.pattern.length - a.pattern.length);
3263
+ }
3264
+ function detectConnectors(prompt, catalog) {
3265
+ const lower = prompt.toLowerCase();
3266
+ return catalog.filter((c) => [c.name, ...c.aliases ?? []].some((p) => lower.includes(p.toLowerCase()))).map((c) => c.name);
3267
+ }
3268
+ function segmentPrompt(prompt, catalog) {
3269
+ if (!prompt) return [];
3270
+ const mentions = mentionsOf(catalog);
3271
+ if (!mentions.length) return [{ text: prompt }];
3272
+ const re = new RegExp(`(${mentions.map((m) => escapeRegExp(m.pattern)).join("|")})`, "gi");
3273
+ const byPattern = new Map(mentions.map((m) => [m.pattern.toLowerCase(), m.connector]));
3274
+ return prompt.split(re).filter((part) => part !== "").map((part) => {
3275
+ const connector = byPattern.get(part.toLowerCase());
3276
+ return connector ? { text: part, connector } : { text: part };
3277
+ });
3278
+ }
3279
+ function scheduleFromPrompt(prompt) {
3280
+ const t = prompt.toLowerCase();
3281
+ if (/stream|real.?time|cdc/.test(t)) return "REAL-TIME";
3282
+ if (/hour/.test(t)) return "HOURLY";
3283
+ if (/dail|every day|nightly/.test(t)) return "DAILY";
3284
+ return null;
3285
+ }
3286
+ var RIGHT_ORDER = {
3287
+ source: -1,
3288
+ destination: 0,
3289
+ action: 1,
3290
+ notify: 2
3291
+ };
3292
+ function resolvePlan(selected, detected, catalog) {
3293
+ const byName = new Map(catalog.map((c) => [c.name, c]));
3294
+ let names = [.../* @__PURE__ */ new Set([...selected, ...detected])].filter((n) => byName.has(n));
3295
+ if (!names.length) {
3296
+ const fallback = [
3297
+ catalog.find((c) => c.role === "source"),
3298
+ catalog.find((c) => c.role === "destination"),
3299
+ catalog.find((c) => c.role === "notify")
3300
+ ].filter((c) => Boolean(c));
3301
+ names = fallback.map((c) => c.name);
3302
+ }
3303
+ const has = (predicate) => names.some((n) => {
3304
+ const c = byName.get(n);
3305
+ return Boolean(c && predicate(c));
3306
+ });
3307
+ if (!has((c) => c.role === "source")) {
3308
+ const source = catalog.find((c) => c.role === "source");
3309
+ if (source) names = [source.name, ...names];
3310
+ }
3311
+ if (!has((c) => c.role !== "source")) {
3312
+ const sink = catalog.find((c) => c.role === "notify") ?? catalog.find((c) => c.role !== "source");
3313
+ if (sink) names = [...names, sink.name];
3314
+ }
3315
+ const picked = catalog.filter((c) => names.includes(c.name));
3316
+ const left = picked.filter((c) => c.role === "source");
3317
+ const right = picked.filter((c) => c.role !== "source").sort((a, b) => RIGHT_ORDER[a.role] - RIGHT_ORDER[b.role]);
3318
+ const all = [...left, ...right];
3319
+ return { left, right, all, totalSteps: all.length + 1 };
3320
+ }
3321
+ function stepVerb(role) {
3322
+ if (role === "notify") return "alert";
3323
+ if (role === "action") return "trigger";
3324
+ return "load";
3325
+ }
3326
+ function assembleSteps(plan) {
3327
+ return [
3328
+ ...plan.left.map((c) => `${c.name.toLowerCase()} extract`),
3329
+ "schema map \xB7 14 fields",
3330
+ ...plan.right.map((c) => `${c.name.toLowerCase()} ${stepVerb(c.role)}`)
3331
+ ];
3332
+ }
3333
+ function buildActivity(args) {
3334
+ const { phase, shown, plan, selected, status } = args;
3335
+ const settled = SETTLED_PHASES.includes(phase);
3336
+ const lines = [];
3337
+ if (phase === "idle") {
3338
+ if (!selected.length) {
3339
+ lines.push({ id: "wait", text: "\u25B8 waiting for instructions\u2026", tone: "muted" });
3340
+ lines.push({ id: "hint", text: "\xB7 type a request or pick apps", tone: "faint" });
3245
3341
  }
3246
- const found = findOpenTrigger(value, caret, trigger);
3247
- if (!found) {
3248
- if (isOpen) setIsOpen(false);
3249
- setTriggerIndex(null);
3250
- return;
3342
+ for (const name of selected.slice(-6)) {
3343
+ lines.push({ id: `sel-${name}`, text: `+ ${name.toLowerCase()} connected`, tone: "plain" });
3251
3344
  }
3252
- if (dismissedAt) dismissedAtRef.current = null;
3253
- if (found.query.length > 0 || openOnEmptyTrigger) {
3254
- setQuery(found.query);
3255
- setTriggerIndex(found.triggerIndex);
3256
- setIsOpen(true);
3257
- setActiveIndex(0);
3258
- setCaretRect(getCaretRect(ta, caret));
3345
+ return lines;
3346
+ }
3347
+ if (phase === "analyzing") {
3348
+ for (const name of selected.slice(-4)) {
3349
+ lines.push({ id: `sel-${name}`, text: `+ ${name.toLowerCase()} connected`, tone: "muted" });
3259
3350
  }
3260
- }, [value, trigger, openOnEmptyTrigger, isOpen, textareaRef]);
3261
- const commit = (0, import_react21.useCallback)(
3262
- (cmd) => {
3263
- const ta = textareaRef.current;
3264
- if (!ta || triggerIndex == null) return;
3265
- const insertText = typeof cmd.insert === "function" ? cmd.insert(query) : cmd.insert;
3266
- const caret = ta.selectionStart ?? value.length;
3267
- const before = value.slice(0, triggerIndex);
3268
- const after = value.slice(caret);
3269
- const next = `${before}${insertText}${after}`;
3270
- dismissedAtRef.current = {
3271
- value,
3272
- caret
3273
- };
3274
- onChange(next);
3275
- setIsOpen(false);
3276
- setTriggerIndex(null);
3277
- requestAnimationFrame(() => {
3278
- const pos = before.length + insertText.length;
3279
- ta.focus();
3280
- ta.setSelectionRange(pos, pos);
3281
- });
3282
- },
3283
- [textareaRef, triggerIndex, query, value, onChange]
3284
- );
3285
- const dismiss = (0, import_react21.useCallback)(() => {
3286
- const ta = textareaRef.current;
3287
- dismissedAtRef.current = {
3288
- value,
3289
- caret: ta?.selectionStart ?? value.length
3290
- };
3291
- setIsOpen(false);
3292
- setTriggerIndex(null);
3293
- }, [textareaRef, value]);
3294
- const onKeyDown = (0, import_react21.useCallback)(
3295
- (e) => {
3296
- if (!isOpen) return false;
3297
- if (e.key === "ArrowDown") {
3298
- e.preventDefault();
3299
- setActiveIndex((i) => Math.min(items.length - 1, i + 1));
3300
- return true;
3301
- }
3302
- if (e.key === "ArrowUp") {
3303
- e.preventDefault();
3304
- setActiveIndex((i) => Math.max(0, i - 1));
3305
- return true;
3306
- }
3307
- if (e.key === "Enter" || e.key === "Tab") {
3308
- const pick = items[activeIndex];
3309
- if (pick) {
3310
- e.preventDefault();
3311
- commit(pick);
3312
- return true;
3313
- }
3314
- }
3315
- if (e.key === "Escape") {
3316
- e.preventDefault();
3317
- dismiss();
3318
- return true;
3319
- }
3320
- return false;
3321
- },
3322
- [isOpen, items, activeIndex, commit, dismiss]
3323
- );
3324
- return {
3325
- isOpen,
3326
- query,
3327
- activeIndex,
3328
- items,
3329
- onKeyDown,
3330
- dismiss,
3331
- commit,
3332
- caretRect
3333
- };
3334
- }
3335
-
3336
- // src/hooks/useTextareaSelection.ts
3337
- var import_react22 = require("react");
3338
- function getSelectionRect(textarea, start, end) {
3339
- if (typeof document === "undefined") return null;
3340
- if (start === end) return null;
3341
- const mirror = document.createElement("div");
3342
- const styles = window.getComputedStyle(textarea);
3343
- for (const prop of [
3344
- "boxSizing",
3345
- "width",
3346
- "fontFamily",
3347
- "fontSize",
3348
- "fontWeight",
3349
- "fontStyle",
3350
- "letterSpacing",
3351
- "lineHeight",
3352
- "paddingTop",
3353
- "paddingRight",
3354
- "paddingBottom",
3355
- "paddingLeft",
3356
- "borderTopWidth",
3357
- "borderRightWidth",
3358
- "borderBottomWidth",
3359
- "borderLeftWidth",
3360
- "textTransform",
3361
- "wordSpacing",
3362
- "tabSize",
3363
- "whiteSpace"
3364
- ]) {
3365
- mirror.style[prop] = styles[prop];
3351
+ lines.push({ id: "status", text: `\u27F3 ${status}`, tone: "active" });
3352
+ return lines;
3366
3353
  }
3367
- mirror.style.position = "absolute";
3368
- mirror.style.visibility = "hidden";
3369
- mirror.style.overflow = "hidden";
3370
- mirror.style.top = "0";
3371
- mirror.style.left = "0";
3372
- mirror.style.whiteSpace = "pre-wrap";
3373
- mirror.style.wordWrap = "break-word";
3374
- const pre = document.createTextNode(textarea.value.slice(0, start));
3375
- const range = document.createElement("span");
3376
- range.textContent = textarea.value.slice(start, end) || "\u200B";
3377
- const post = document.createTextNode(textarea.value.slice(end));
3378
- mirror.appendChild(pre);
3379
- mirror.appendChild(range);
3380
- mirror.appendChild(post);
3381
- document.body.appendChild(mirror);
3382
- const taRect = textarea.getBoundingClientRect();
3383
- const rangeRect = range.getBoundingClientRect();
3384
- const mirrorRect = mirror.getBoundingClientRect();
3385
- const out = new DOMRect(
3386
- taRect.left + (rangeRect.left - mirrorRect.left) - textarea.scrollLeft,
3387
- taRect.top + (rangeRect.top - mirrorRect.top) - textarea.scrollTop,
3388
- rangeRect.width,
3389
- rangeRect.height
3390
- );
3391
- document.body.removeChild(mirror);
3392
- return out;
3354
+ assembleSteps(plan).forEach((step, i) => {
3355
+ if (settled || shown > i) lines.push({ id: `step-${i}`, text: `\u2713 ${step}`, tone: "done" });
3356
+ else if (shown === i) lines.push({ id: `step-${i}`, text: `\u27F3 ${step}\u2026`, tone: "active" });
3357
+ else lines.push({ id: `step-${i}`, text: `\xB7 ${step} queued`, tone: "muted" });
3358
+ });
3359
+ if (settled) lines.push({ id: "errors", text: "\u2713 error handling attached", tone: "done" });
3360
+ return lines.slice(-8);
3393
3361
  }
3394
- function useTextareaSelection({
3395
- textareaRef,
3396
- value,
3397
- onChange
3398
- }) {
3399
- const [start, setStart] = (0, import_react22.useState)(0);
3400
- const [end, setEnd] = (0, import_react22.useState)(0);
3401
- const [rect, setRect] = (0, import_react22.useState)(null);
3402
- (0, import_react22.useEffect)(() => {
3403
- if (typeof document === "undefined") return;
3404
- const handler = () => {
3405
- const ta = textareaRef.current;
3406
- if (!ta || document.activeElement !== ta) {
3407
- setStart(0);
3408
- setEnd(0);
3409
- setRect(null);
3410
- return;
3411
- }
3412
- const s = ta.selectionStart ?? 0;
3413
- const e = ta.selectionEnd ?? 0;
3414
- setStart(s);
3415
- setEnd(e);
3416
- setRect(s !== e ? getSelectionRect(ta, s, e) : null);
3417
- };
3418
- document.addEventListener("selectionchange", handler);
3419
- return () => document.removeEventListener("selectionchange", handler);
3420
- }, [textareaRef]);
3421
- const active = start !== end;
3422
- const text = active ? value.slice(start, end) : "";
3423
- const writeToClipboard = (0, import_react22.useCallback)(
3424
- async (str) => {
3425
- try {
3426
- if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
3427
- await navigator.clipboard.writeText(str);
3428
- return true;
3429
- }
3430
- } catch {
3431
- }
3432
- try {
3433
- const tmp = document.createElement("textarea");
3434
- tmp.value = str;
3435
- tmp.style.position = "fixed";
3436
- tmp.style.left = "-9999px";
3437
- document.body.appendChild(tmp);
3438
- tmp.select();
3439
- const ok = document.execCommand("copy");
3440
- document.body.removeChild(tmp);
3441
- return ok;
3442
- } catch {
3443
- return false;
3444
- }
3445
- },
3446
- []
3447
- );
3448
- const copy = (0, import_react22.useCallback)(async () => {
3449
- if (!active) return false;
3450
- return writeToClipboard(text);
3451
- }, [active, text, writeToClipboard]);
3452
- const replace = (0, import_react22.useCallback)(
3453
- (str) => {
3454
- const ta = textareaRef.current;
3455
- if (!ta) return;
3456
- const next = value.slice(0, start) + str + value.slice(end);
3457
- onChange(next);
3458
- requestAnimationFrame(() => {
3459
- ta.focus();
3460
- const caret = start + str.length;
3461
- ta.setSelectionRange(caret, caret);
3462
- });
3463
- },
3464
- [textareaRef, value, start, end, onChange]
3465
- );
3466
- const cut = (0, import_react22.useCallback)(async () => {
3467
- if (!active) return false;
3468
- const ok = await writeToClipboard(text);
3469
- if (ok) replace("");
3470
- return ok;
3471
- }, [active, text, writeToClipboard, replace]);
3472
- const wrap = (0, import_react22.useCallback)(
3473
- (open, close) => {
3474
- const ta = textareaRef.current;
3475
- if (!ta) return;
3476
- const closing = close ?? open;
3477
- const before = value.slice(0, start);
3478
- const sel = value.slice(start, end);
3479
- const after = value.slice(end);
3480
- const next = `${before}${open}${sel}${closing}${after}`;
3481
- onChange(next);
3482
- requestAnimationFrame(() => {
3483
- ta.focus();
3484
- const newStart = start;
3485
- const newEnd = end + open.length + closing.length;
3486
- ta.setSelectionRange(newStart, newEnd);
3487
- });
3488
- },
3489
- [textareaRef, value, start, end, onChange]
3490
- );
3491
- return {
3492
- active,
3493
- text,
3494
- start,
3495
- end,
3496
- rect,
3497
- copy,
3498
- cut,
3499
- wrap,
3500
- replace
3501
- };
3362
+ function slugify(value) {
3363
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "your-team";
3364
+ }
3365
+ function isValidEmail(value) {
3366
+ return /^\S+@\S+\.\S+$/.test(value.trim());
3502
3367
  }
3503
3368
 
3504
- // src/hooks/useTextareaTools.tsx
3505
- var import_icons3 = require("@octaviaflow/icons");
3506
- var import_react23 = require("react");
3507
- var import_jsx_runtime12 = require("react/jsx-runtime");
3508
- function useTextareaTools({
3509
- textareaRef,
3510
- value,
3511
- onChange,
3512
- tools
3369
+ // src/marketing/components/BetaAccessForm/BetaAccessForm.tsx
3370
+ var import_jsx_runtime9 = require("react/jsx-runtime");
3371
+ function BetaAccessForm({
3372
+ title = "Request early access",
3373
+ subtitle = "CURRENTLY IN PRIVATE BETA \u2014 WE REVIEW ACCESS REQUESTS WEEKLY",
3374
+ fields = "email",
3375
+ appearance = "card",
3376
+ teamSizes,
3377
+ plans,
3378
+ defaultPlan,
3379
+ useCases,
3380
+ showDetails = false,
3381
+ detailsLabel = "Anything else we should know?",
3382
+ detailsPlaceholder = "Current stack, timelines, must-have connectors\u2026",
3383
+ submitLabel = "Request access \u2192",
3384
+ footnote = "NO CREDIT CARD \xB7 SSO & SELF-HOSTING AVAILABLE",
3385
+ onSubmit,
3386
+ successTitle = "Request received.",
3387
+ successBody = "We review access requests weekly \u2014 you'll hear from us by email.",
3388
+ disabled = false,
3389
+ className
3513
3390
  }) {
3514
- const valueRef = (0, import_react23.useRef)(value);
3515
- valueRef.current = value;
3516
- const helpers = (0, import_react23.useMemo)(() => {
3517
- const getTa = () => textareaRef.current;
3518
- const getSelection = () => {
3519
- const ta = getTa();
3520
- const s = ta?.selectionStart ?? 0;
3521
- const e = ta?.selectionEnd ?? 0;
3522
- return {
3523
- text: valueRef.current.slice(s, e),
3524
- start: s,
3525
- end: e,
3526
- active: s !== e
3527
- };
3528
- };
3529
- const setValue = (next) => onChange(next);
3530
- const replace = (text) => {
3531
- const ta = getTa();
3532
- if (!ta) return;
3533
- const { start, end } = getSelection();
3534
- const next = valueRef.current.slice(0, start) + text + valueRef.current.slice(end);
3535
- onChange(next);
3536
- requestAnimationFrame(() => {
3537
- ta.focus();
3538
- const caret = start + text.length;
3539
- ta.setSelectionRange(caret, caret);
3540
- });
3541
- };
3542
- const insert = (text) => replace(text);
3543
- const wrap = (open, close) => {
3544
- const ta = getTa();
3545
- if (!ta) return;
3546
- const closing = close ?? open;
3547
- const { start, end } = getSelection();
3548
- const sel = valueRef.current.slice(start, end);
3549
- const next = valueRef.current.slice(0, start) + open + sel + closing + valueRef.current.slice(end);
3550
- onChange(next);
3551
- requestAnimationFrame(() => {
3552
- ta.focus();
3553
- if (start === end) {
3554
- const caret = start + open.length;
3555
- ta.setSelectionRange(caret, caret);
3556
- } else {
3557
- ta.setSelectionRange(start, end + open.length + closing.length);
3558
- }
3559
- });
3560
- };
3561
- const prependLines = (prefix) => {
3562
- const ta = getTa();
3563
- if (!ta) return;
3564
- const { start, end } = getSelection();
3565
- const v = valueRef.current;
3566
- const lineStart = v.lastIndexOf("\n", start - 1) + 1;
3567
- const lineEnd = (() => {
3568
- const idx = v.indexOf("\n", end);
3569
- return idx === -1 ? v.length : idx;
3570
- })();
3571
- const block = v.slice(lineStart, lineEnd);
3572
- const updated = block.split("\n").map((line) => `${prefix}${line}`).join("\n");
3573
- const next = v.slice(0, lineStart) + updated + v.slice(lineEnd);
3574
- onChange(next);
3575
- requestAnimationFrame(() => {
3576
- ta.focus();
3577
- ta.setSelectionRange(
3578
- lineStart,
3579
- lineStart + updated.length
3580
- );
3581
- });
3582
- };
3583
- const undo = () => {
3584
- try {
3585
- getTa()?.focus();
3586
- document.execCommand("undo");
3587
- } catch {
3588
- }
3589
- };
3590
- const redo = () => {
3591
- try {
3592
- getTa()?.focus();
3593
- document.execCommand("redo");
3594
- } catch {
3391
+ const uid = (0, import_react21.useId)();
3392
+ const [values, setValues] = (0, import_react21.useState)({
3393
+ email: "",
3394
+ name: "",
3395
+ company: "",
3396
+ teamSize: "",
3397
+ plan: defaultPlan ?? "",
3398
+ useCase: "",
3399
+ details: ""
3400
+ });
3401
+ const [phase, setPhase] = (0, import_react21.useState)("idle");
3402
+ const [error, setError] = (0, import_react21.useState)(null);
3403
+ const handleSubmit = async (event) => {
3404
+ event.preventDefault();
3405
+ if (phase === "submitting") return;
3406
+ if (!isValidEmail(values.email)) {
3407
+ setError("Please enter a valid work email.");
3408
+ return;
3409
+ }
3410
+ if (fields === "full" && !values.name?.trim()) {
3411
+ setError("Please enter your name.");
3412
+ return;
3413
+ }
3414
+ if (fields === "full" && teamSizes?.length && !values.teamSize) {
3415
+ setError("Please select your team size.");
3416
+ return;
3417
+ }
3418
+ if (fields === "full" && useCases?.length && !values.useCase) {
3419
+ setError("Please select your primary use case.");
3420
+ return;
3421
+ }
3422
+ setError(null);
3423
+ setPhase("submitting");
3424
+ try {
3425
+ await onSubmit?.(values);
3426
+ setPhase("success");
3427
+ } catch {
3428
+ setError("Something went wrong \u2014 please try again.");
3429
+ setPhase("idle");
3430
+ }
3431
+ };
3432
+ const inline = appearance === "inline";
3433
+ if (phase === "success") {
3434
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3435
+ "div",
3436
+ {
3437
+ className: cn(
3438
+ "ods-mkt-access",
3439
+ "ods-mkt-access--success",
3440
+ inline && "ods-mkt-access--inline",
3441
+ className
3442
+ ),
3443
+ role: "status",
3444
+ children: [
3445
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("p", { className: "ods-mkt-access__success-mark", children: [
3446
+ "\u2713 ",
3447
+ successTitle
3448
+ ] }),
3449
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "ods-mkt-access__success-body", children: successBody })
3450
+ ]
3595
3451
  }
3596
- };
3597
- return {
3598
- get value() {
3599
- return valueRef.current;
3600
- },
3601
- get selection() {
3602
- return getSelection();
3603
- },
3604
- setValue,
3605
- replace,
3606
- insert,
3607
- wrap,
3608
- prependLines,
3609
- undo,
3610
- redo,
3611
- getTextarea: getTa
3612
- };
3613
- }, [textareaRef, onChange]);
3614
- const visibleTools = (0, import_react23.useMemo)(
3615
- () => tools.filter((t) => t.isVisible ? t.isVisible(helpers) : true),
3616
- [tools, helpers]
3617
- );
3618
- const runTool = (0, import_react23.useCallback)(
3619
- async (id) => {
3620
- const tool = tools.find((t) => t.id === id);
3621
- if (!tool || tool.kind === "divider") return;
3622
- await tool.onAction?.(helpers);
3623
- },
3624
- [tools, helpers]
3452
+ );
3453
+ }
3454
+ const busy = disabled || phase === "submitting";
3455
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3456
+ "form",
3457
+ {
3458
+ className: cn("ods-mkt-access", inline && "ods-mkt-access--inline", className),
3459
+ onSubmit: handleSubmit,
3460
+ noValidate: true,
3461
+ children: [
3462
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-mkt-access__head", children: [
3463
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h3", { className: "ods-mkt-access__title", children: title }),
3464
+ subtitle && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "ods-mkt-access__subtitle", children: subtitle })
3465
+ ] }),
3466
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3467
+ "div",
3468
+ {
3469
+ className: cn(
3470
+ "ods-mkt-access__fields",
3471
+ fields === "full" && "ods-mkt-access__fields--full"
3472
+ ),
3473
+ children: [
3474
+ fields === "full" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-mkt-access__field", children: [
3475
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("label", { className: "ods-mkt-access__label", htmlFor: `${uid}-name`, children: "Full name" }),
3476
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3477
+ Input,
3478
+ {
3479
+ id: `${uid}-name`,
3480
+ autoComplete: "name",
3481
+ placeholder: "Ada Lovelace",
3482
+ value: values.name ?? "",
3483
+ onChange: (e) => setValues((v) => ({ ...v, name: e.target.value })),
3484
+ disabled: busy
3485
+ }
3486
+ )
3487
+ ] }),
3488
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-mkt-access__field", children: [
3489
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("label", { className: "ods-mkt-access__label", htmlFor: `${uid}-email`, children: "Work email" }),
3490
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3491
+ Input,
3492
+ {
3493
+ id: `${uid}-email`,
3494
+ type: "email",
3495
+ autoComplete: "email",
3496
+ placeholder: "ada@company.com",
3497
+ value: values.email,
3498
+ onChange: (e) => setValues((v) => ({ ...v, email: e.target.value })),
3499
+ disabled: busy
3500
+ }
3501
+ )
3502
+ ] }),
3503
+ fields === "full" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-mkt-access__field", children: [
3504
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("label", { className: "ods-mkt-access__label", htmlFor: `${uid}-company`, children: "Company" }),
3505
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3506
+ Input,
3507
+ {
3508
+ id: `${uid}-company`,
3509
+ autoComplete: "organization",
3510
+ placeholder: "Acme Inc.",
3511
+ value: values.company ?? "",
3512
+ onChange: (e) => setValues((v) => ({ ...v, company: e.target.value })),
3513
+ disabled: busy
3514
+ }
3515
+ )
3516
+ ] }),
3517
+ fields === "full" && teamSizes && teamSizes.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-mkt-access__field", children: [
3518
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ods-mkt-access__label", id: `${uid}-teamsize-label`, children: "Team size" }),
3519
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3520
+ Select,
3521
+ {
3522
+ "aria-labelledby": `${uid}-teamsize-label`,
3523
+ placeholder: "Select team size\u2026",
3524
+ options: teamSizes.map((size) => ({ value: size, label: size })),
3525
+ value: values.teamSize ?? "",
3526
+ onChange: (teamSize) => setValues((v) => ({ ...v, teamSize })),
3527
+ disabled: busy
3528
+ }
3529
+ )
3530
+ ] }),
3531
+ fields === "full" && plans && plans.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-mkt-access__field", children: [
3532
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ods-mkt-access__label", id: `${uid}-plan-label`, children: "Plan (optional)" }),
3533
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3534
+ Select,
3535
+ {
3536
+ "aria-labelledby": `${uid}-plan-label`,
3537
+ placeholder: "Not sure yet",
3538
+ options: plans.map((plan) => ({ value: plan, label: plan })),
3539
+ value: values.plan ?? "",
3540
+ onChange: (plan) => setValues((v) => ({ ...v, plan })),
3541
+ disabled: busy
3542
+ }
3543
+ )
3544
+ ] }),
3545
+ fields === "full" && useCases && useCases.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-mkt-access__field", children: [
3546
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ods-mkt-access__label", id: `${uid}-usecase-label`, children: "Primary use case" }),
3547
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3548
+ Select,
3549
+ {
3550
+ "aria-labelledby": `${uid}-usecase-label`,
3551
+ placeholder: "Select a use case\u2026",
3552
+ options: useCases.map((useCase) => ({ value: useCase, label: useCase })),
3553
+ value: values.useCase ?? "",
3554
+ onChange: (useCase) => setValues((v) => ({ ...v, useCase })),
3555
+ disabled: busy
3556
+ }
3557
+ )
3558
+ ] }),
3559
+ fields === "full" && showDetails && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-mkt-access__field ods-mkt-access__field--wide", children: [
3560
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3561
+ "label",
3562
+ {
3563
+ className: "ods-mkt-access__label",
3564
+ id: `${uid}-details-label`,
3565
+ htmlFor: `${uid}-details`,
3566
+ children: detailsLabel
3567
+ }
3568
+ ),
3569
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3570
+ Textarea,
3571
+ {
3572
+ id: `${uid}-details`,
3573
+ "aria-labelledby": `${uid}-details-label`,
3574
+ placeholder: detailsPlaceholder,
3575
+ rows: 3,
3576
+ value: values.details ?? "",
3577
+ onChange: (e) => setValues((v) => ({ ...v, details: e.target.value })),
3578
+ disabled: busy
3579
+ }
3580
+ )
3581
+ ] }),
3582
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3583
+ Button,
3584
+ {
3585
+ type: "submit",
3586
+ variant: "primary",
3587
+ loading: phase === "submitting",
3588
+ disabled,
3589
+ children: submitLabel
3590
+ }
3591
+ )
3592
+ ]
3593
+ }
3594
+ ),
3595
+ error && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "ods-mkt-access__error", role: "alert", children: error }),
3596
+ footnote && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "ods-mkt-access__footnote", children: footnote })
3597
+ ]
3598
+ }
3625
3599
  );
3626
- return { tools: visibleTools, runTool, helpers };
3627
3600
  }
3628
3601
 
3629
- // src/components/Textarea/Textarea.tsx
3630
- var import_jsx_runtime13 = require("react/jsx-runtime");
3631
- var Textarea = (0, import_react24.forwardRef)(
3632
- function Textarea2({
3633
- label,
3634
- error = false,
3635
- errorMessage,
3636
- helperText,
3637
- resize = "vertical",
3638
- autoResize = false,
3639
- minRows,
3640
- maxRows,
3641
- maxHeight,
3642
- maxWidth,
3643
- minHeight,
3644
- minWidth,
3645
- maxLength,
3646
- size = "md",
3647
- rows = 3,
3648
- disabled = false,
3649
- className,
3650
- style: consumerStyle,
3651
- defaultValue,
3652
- value,
3653
- onChange,
3654
- id: providedId,
3655
- commands,
3656
- commandTrigger = "/",
3657
- selectionToolbar = false,
3658
- selectionActions,
3659
- tools,
3660
- toolbarPosition = "top",
3661
- ...props
3662
- }, forwardedRef) {
3663
- const innerRef = (0, import_react24.useRef)(null);
3664
- const setRef = (node) => {
3665
- innerRef.current = node;
3666
- if (typeof forwardedRef === "function") forwardedRef(node);
3667
- else if (forwardedRef)
3668
- forwardedRef.current = node;
3669
- };
3670
- const reactId = (0, import_react24.useId)();
3671
- const baseId = providedId ?? `ods-textarea-${reactId}`;
3672
- const hintId = error && errorMessage || helperText ? `${baseId}-hint` : void 0;
3673
- const [charCount, setCharCount] = (0, import_react24.useState)(
3674
- () => String(value ?? defaultValue ?? "").length
3675
- );
3676
- const ariaNameProps = resolveAccessibleName({
3677
- label: typeof label === "string" ? label : void 0,
3678
- ariaLabel: props["aria-label"],
3679
- ariaLabelledby: props["aria-labelledby"],
3680
- componentName: "Textarea"
3681
- });
3682
- const { labelProps, inputProps } = (0, import_react_aria4.useTextField)(
3683
- {
3684
- label: typeof label === "string" ? label : void 0,
3685
- ...ariaNameProps,
3686
- isDisabled: disabled,
3687
- errorMessage: typeof errorMessage === "string" ? errorMessage : void 0,
3688
- validationState: error ? "invalid" : void 0,
3689
- inputElementType: "textarea",
3690
- value,
3691
- defaultValue
3692
- },
3693
- innerRef
3694
- );
3695
- const describedBy = [
3696
- props["aria-describedby"],
3697
- hintId
3698
- ].filter(Boolean).join(" ") || void 0;
3699
- const handleChange = (e) => {
3700
- setCharCount(e.target.value.length);
3701
- onChange?.(e);
3702
- };
3703
- const [liveValue, setLiveValue] = (0, import_react24.useState)(
3704
- String(value ?? defaultValue ?? "")
3705
- );
3706
- (0, import_react24.useEffect)(() => {
3707
- if (value != null) setLiveValue(String(value));
3708
- }, [value]);
3709
- const handleChangeWithMirror = (e) => {
3710
- setLiveValue(e.target.value);
3711
- handleChange(e);
3712
- };
3713
- const setLiveAndCommit = (next) => {
3714
- setLiveValue(next);
3715
- if (innerRef.current) innerRef.current.value = next;
3716
- onChange?.({
3717
- target: innerRef.current,
3718
- currentTarget: innerRef.current
3719
- });
3720
- setCharCount(next.length);
3721
- };
3722
- const cmdPalette = useTextareaCommands({
3723
- textareaRef: innerRef,
3724
- value: liveValue,
3725
- onChange: setLiveAndCommit,
3726
- commands: commands ?? [],
3727
- trigger: commandTrigger
3728
- });
3729
- const cmdEnabled = (commands?.length ?? 0) > 0;
3730
- const sel = useTextareaSelection({
3731
- textareaRef: innerRef,
3732
- value: liveValue,
3733
- onChange: setLiveAndCommit
3734
- });
3735
- const toolbar = useTextareaTools({
3736
- textareaRef: innerRef,
3737
- value: liveValue,
3738
- onChange: setLiveAndCommit,
3739
- tools: tools ?? []
3740
- });
3741
- const toolbarEnabled = (tools?.length ?? 0) > 0;
3742
- const handleKeyDownWithCommands = (e) => {
3743
- if (cmdEnabled) {
3744
- const consumed = cmdPalette.onKeyDown(e);
3745
- if (consumed) return;
3746
- }
3747
- props.onKeyDown?.(e);
3748
- };
3749
- (0, import_react24.useEffect)(() => {
3750
- if (!autoResize) return;
3751
- const el = innerRef.current;
3752
- if (!el) return;
3753
- const computed = window.getComputedStyle(el);
3754
- const fontSize = parseFloat(computed.fontSize) || 16;
3755
- const lhRaw = computed.lineHeight;
3756
- const lh = lhRaw === "normal" ? fontSize * 1.2 : lhRaw.endsWith("px") ? parseFloat(lhRaw) : parseFloat(lhRaw) * fontSize;
3757
- const padding = parseFloat(computed.paddingTop) + parseFloat(computed.paddingBottom);
3758
- const rowCap = maxRows != null && Number.isFinite(maxRows) ? lh * maxRows + padding : Number.POSITIVE_INFINITY;
3759
- const pixelCap = typeof maxHeight === "number" ? maxHeight : Number.POSITIVE_INFINITY;
3760
- const effectiveMax = Math.min(rowCap, pixelCap);
3761
- const rowFloor = lh * (minRows ?? rows) + padding;
3762
- el.style.height = "auto";
3763
- const target = Math.max(
3764
- rowFloor,
3765
- Math.min(effectiveMax, el.scrollHeight)
3766
- );
3767
- el.style.height = `${target}px`;
3768
- el.style.overflowY = el.scrollHeight > effectiveMax ? "auto" : "hidden";
3769
- }, [autoResize, value, defaultValue, minRows, maxRows, maxHeight, rows]);
3770
- const resolvedResize = typeof resize === "boolean" ? resize ? "vertical" : "none" : resize;
3771
- const toCss = (v) => v == null ? void 0 : typeof v === "number" ? `${v}px` : v;
3772
- const fieldStyle = {
3773
- maxHeight: toCss(maxHeight),
3774
- maxWidth: toCss(maxWidth),
3775
- minHeight: toCss(minHeight),
3776
- minWidth: toCss(minWidth),
3777
- resize: resolvedResize
3778
- };
3779
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3602
+ // src/components/BlogCard/BlogCard.tsx
3603
+ var import_react24 = require("react");
3604
+
3605
+ // src/components/Avatar/Avatar.tsx
3606
+ var import_react22 = require("react");
3607
+ var import_jsx_runtime10 = require("react/jsx-runtime");
3608
+ var PALETTE_SIZE = 12;
3609
+ function djb2(str) {
3610
+ let h = 5381;
3611
+ for (let i = 0; i < str.length; i++) h = (h << 5) + h + str.charCodeAt(i);
3612
+ return h >>> 0;
3613
+ }
3614
+ function paletteIndexFor(seed) {
3615
+ return djb2(seed) % PALETTE_SIZE;
3616
+ }
3617
+ function normalizeInitials(raw) {
3618
+ const trimmed = raw.trim();
3619
+ if (!trimmed) return "";
3620
+ const tokens = trimmed.split(/\s+/).filter(Boolean);
3621
+ if (tokens.length >= 2) {
3622
+ return (tokens[0].charAt(0) + tokens[1].charAt(0)).toUpperCase();
3623
+ }
3624
+ return trimmed.slice(0, 2).toUpperCase();
3625
+ }
3626
+ var STATUS_DEFAULT_LABEL = {
3627
+ online: "Online",
3628
+ offline: "Offline",
3629
+ busy: "Busy",
3630
+ away: "Away"
3631
+ };
3632
+ var Avatar = (0, import_react22.forwardRef)(function Avatar2({
3633
+ src,
3634
+ alt,
3635
+ initials,
3636
+ icon,
3637
+ fallback,
3638
+ seed,
3639
+ size = "md",
3640
+ shape = "circle",
3641
+ status,
3642
+ statusLabel,
3643
+ onClick,
3644
+ imgLoading = "lazy",
3645
+ referrerPolicy,
3646
+ className,
3647
+ ...rest
3648
+ }, ref) {
3649
+ const [imgFailed, setImgFailed] = (0, import_react22.useState)(false);
3650
+ const handleImgError = (0, import_react22.useCallback)(() => setImgFailed(true), []);
3651
+ const showImage = Boolean(src) && !imgFailed;
3652
+ const normalizedInitials = (0, import_react22.useMemo)(
3653
+ () => initials ? normalizeInitials(initials) : "",
3654
+ [initials]
3655
+ );
3656
+ const paletteIdx = (0, import_react22.useMemo)(() => {
3657
+ const resolved = seed ?? initials ?? alt ?? "?";
3658
+ return paletteIndexFor(resolved);
3659
+ }, [seed, initials, alt]);
3660
+ const baseName = alt || normalizedInitials || "Avatar";
3661
+ const announcedStatus = status ? statusLabel ?? STATUS_DEFAULT_LABEL[status] : "";
3662
+ const accessibleName = announcedStatus ? `${baseName}, ${announcedStatus}` : baseName;
3663
+ const isClickable = Boolean(onClick);
3664
+ const handleKeyDown = (e) => {
3665
+ if (!isClickable) return;
3666
+ if ((e.key === "Enter" || e.key === " ") && !e.repeat) {
3667
+ e.preventDefault();
3668
+ onClick?.();
3669
+ }
3670
+ };
3671
+ const rootClassName = cn(
3672
+ "ods-avatar",
3673
+ `ods-avatar--${size}`,
3674
+ `ods-avatar--${shape}`,
3675
+ isClickable && "ods-avatar--clickable",
3676
+ className
3677
+ );
3678
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3679
+ "div",
3680
+ {
3681
+ ...rest,
3682
+ ref,
3683
+ className: rootClassName,
3684
+ role: isClickable ? "button" : "img",
3685
+ "aria-label": accessibleName,
3686
+ tabIndex: isClickable ? 0 : void 0,
3687
+ onClick,
3688
+ onKeyDown: isClickable ? handleKeyDown : void 0,
3689
+ "data-status": status,
3690
+ children: [
3691
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ods-avatar__inner", "data-palette": paletteIdx, children: showImage ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3692
+ "img",
3693
+ {
3694
+ src,
3695
+ alt: "",
3696
+ className: "ods-avatar__image",
3697
+ loading: imgLoading,
3698
+ decoding: "async",
3699
+ referrerPolicy,
3700
+ onError: handleImgError,
3701
+ draggable: false
3702
+ }
3703
+ ) : icon ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ods-avatar__icon", "aria-hidden": "true", children: icon }) : normalizedInitials ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ods-avatar__initials", "aria-hidden": "true", children: normalizedInitials }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ods-avatar__initials", "aria-hidden": "true", children: fallback ?? "?" }) }),
3704
+ status && // Decorative — the textual status is folded into the wrapper's
3705
+ // aria-label above. `aria-hidden` keeps AT from double-announcing.
3706
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3707
+ "span",
3708
+ {
3709
+ className: cn("ods-avatar__status", `ods-avatar__status--${status}`),
3710
+ "aria-hidden": "true"
3711
+ }
3712
+ )
3713
+ ]
3714
+ }
3715
+ );
3716
+ });
3717
+ Avatar.displayName = "Avatar";
3718
+ var AvatarStack = (0, import_react22.forwardRef)(
3719
+ function AvatarStack2({
3720
+ children,
3721
+ max,
3722
+ size = "md",
3723
+ renderOverflow,
3724
+ direction = "ltr",
3725
+ className,
3726
+ ...rest
3727
+ }, ref) {
3728
+ const arr = Array.isArray(children) ? children : [children];
3729
+ const visible = max && arr.length > max ? arr.slice(0, max) : arr;
3730
+ const overflow = max && arr.length > max ? arr.length - max : 0;
3731
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3780
3732
  "div",
3781
3733
  {
3734
+ ...rest,
3735
+ ref,
3782
3736
  className: cn(
3783
- "ods-textarea",
3784
- `ods-textarea--${size}`,
3785
- `ods-textarea--resize-${resolvedResize}`,
3786
- autoResize && "ods-textarea--auto-resize",
3787
- error && "ods-textarea--error",
3788
- disabled && "ods-textarea--disabled",
3737
+ "ods-avatar-stack",
3738
+ `ods-avatar-stack--${direction}`,
3789
3739
  className
3790
3740
  ),
3791
- style: consumerStyle,
3792
3741
  children: [
3793
- label && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3794
- "label",
3795
- {
3796
- ...labelProps,
3797
- htmlFor: baseId,
3798
- className: "ods-textarea__label",
3799
- children: label
3800
- }
3801
- ),
3802
- toolbarEnabled && toolbarPosition === "top" && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3803
- TextareaToolbar,
3742
+ visible,
3743
+ overflow > 0 && (renderOverflow ? renderOverflow(overflow) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3744
+ "div",
3804
3745
  {
3805
- tools: toolbar.tools,
3806
- onRun: toolbar.runTool
3807
- }
3808
- ),
3809
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "ods-textarea__wrapper", children: [
3810
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3811
- "textarea",
3812
- {
3813
- ...props,
3814
- ...inputProps,
3815
- ref: setRef,
3816
- id: baseId,
3817
- rows: autoResize ? minRows ?? rows : rows,
3818
- disabled,
3819
- maxLength,
3820
- defaultValue,
3821
- value,
3822
- onChange: handleChangeWithMirror,
3823
- onKeyDown: handleKeyDownWithCommands,
3824
- className: "ods-textarea__field",
3825
- style: fieldStyle,
3826
- "aria-invalid": error || void 0,
3827
- "aria-describedby": describedBy,
3828
- "aria-autocomplete": cmdEnabled ? "list" : void 0,
3829
- "aria-expanded": cmdEnabled ? cmdPalette.isOpen : void 0,
3830
- "aria-controls": cmdEnabled && cmdPalette.isOpen ? `${baseId}-cmd-list` : void 0,
3831
- "aria-activedescendant": cmdEnabled && cmdPalette.isOpen && cmdPalette.items[cmdPalette.activeIndex] ? `${baseId}-cmd-${cmdPalette.items[cmdPalette.activeIndex].id}` : void 0
3832
- }
3833
- ),
3834
- cmdEnabled && cmdPalette.isOpen && cmdPalette.items.length > 0 && typeof document !== "undefined" && (0, import_react_dom3.createPortal)(
3835
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3836
- "ul",
3837
- {
3838
- id: `${baseId}-cmd-list`,
3839
- role: "listbox",
3840
- "aria-label": "Inline commands",
3841
- className: "ods-textarea__cmd-popover",
3842
- style: {
3843
- position: "fixed",
3844
- top: (cmdPalette.caretRect?.bottom ?? 0) + 6,
3845
- left: cmdPalette.caretRect?.left ?? 0
3846
- },
3847
- children: cmdPalette.items.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3848
- "li",
3849
- {
3850
- id: `${baseId}-cmd-${c.id}`,
3851
- role: "option",
3852
- "aria-selected": i === cmdPalette.activeIndex,
3853
- className: cn(
3854
- "ods-textarea__cmd-item",
3855
- i === cmdPalette.activeIndex && "ods-textarea__cmd-item--active"
3856
- ),
3857
- onMouseDown: (e) => {
3858
- e.preventDefault();
3859
- cmdPalette.commit(c);
3860
- },
3861
- onMouseEnter: () => {
3862
- },
3863
- children: [
3864
- c.icon && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "ods-textarea__cmd-icon", children: c.icon }),
3865
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "ods-textarea__cmd-text", children: [
3866
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "ods-textarea__cmd-label", children: c.label }),
3867
- c.description && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "ods-textarea__cmd-desc", children: c.description })
3868
- ] })
3869
- ]
3870
- },
3871
- c.id
3872
- ))
3873
- }
3874
- ),
3875
- document.body
3876
- ),
3877
- selectionToolbar && sel.active && sel.rect && typeof document !== "undefined" && (0, import_react_dom3.createPortal)(
3878
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3879
- "div",
3880
- {
3881
- role: "toolbar",
3882
- "aria-label": "Selection actions",
3883
- className: "ods-textarea__sel-toolbar",
3884
- style: {
3885
- position: "fixed",
3886
- top: sel.rect.top - 36,
3887
- left: sel.rect.left
3888
- },
3889
- onMouseDown: (e) => e.preventDefault(),
3890
- children: [
3891
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3892
- "button",
3893
- {
3894
- type: "button",
3895
- className: "ods-textarea__sel-btn",
3896
- onClick: () => sel.copy(),
3897
- "aria-label": "Copy selection",
3898
- title: "Copy",
3899
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_icons4.CopyIcon, { size: "xs" })
3900
- }
3901
- ),
3902
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3903
- "button",
3904
- {
3905
- type: "button",
3906
- className: "ods-textarea__sel-btn",
3907
- onClick: () => sel.cut(),
3908
- "aria-label": "Cut selection",
3909
- title: "Cut",
3910
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_icons4.CutIcon, { size: "xs" })
3911
- }
3912
- ),
3913
- selectionActions?.map((a) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3914
- "button",
3915
- {
3916
- type: "button",
3917
- className: "ods-textarea__sel-btn",
3918
- onClick: () => a.onAction(sel.text, {
3919
- replace: sel.replace,
3920
- wrap: sel.wrap,
3921
- copy: sel.copy,
3922
- cut: sel.cut
3923
- }),
3924
- title: typeof a.label === "string" ? a.label : void 0,
3925
- children: [
3926
- a.icon,
3927
- a.label && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "ods-textarea__sel-btn-label", children: a.label })
3928
- ]
3929
- },
3930
- a.id
3931
- ))
3932
- ]
3933
- }
3746
+ className: cn(
3747
+ "ods-avatar",
3748
+ `ods-avatar--${size}`,
3749
+ "ods-avatar--circle",
3750
+ "ods-avatar-stack__overflow"
3934
3751
  ),
3935
- document.body
3936
- ),
3937
- maxLength != null && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3938
- "span",
3752
+ role: "img",
3753
+ "aria-label": `${overflow} more`,
3754
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ods-avatar__inner", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "ods-avatar__initials", "aria-hidden": "true", children: [
3755
+ "+",
3756
+ overflow
3757
+ ] }) })
3758
+ }
3759
+ ))
3760
+ ]
3761
+ }
3762
+ );
3763
+ }
3764
+ );
3765
+ AvatarStack.displayName = "AvatarStack";
3766
+
3767
+ // src/components/Card/Card.tsx
3768
+ var import_framer_motion4 = require("framer-motion");
3769
+ var import_react23 = require("react");
3770
+ var import_jsx_runtime11 = require("react/jsx-runtime");
3771
+ var Card = (0, import_react23.forwardRef)(function Card2({
3772
+ variant = "default",
3773
+ padding = "md",
3774
+ radius = "md",
3775
+ hoverable = false,
3776
+ disabled = false,
3777
+ loading = false,
3778
+ asChild = false,
3779
+ onClick,
3780
+ onKeyDown,
3781
+ className,
3782
+ children,
3783
+ ...rest
3784
+ }, ref) {
3785
+ const reducedMotion = (0, import_framer_motion4.useReducedMotion)();
3786
+ const isInteractive = Boolean(onClick) && !disabled && !loading;
3787
+ const handleKeyDown = (e) => {
3788
+ onKeyDown?.(e);
3789
+ if (e.defaultPrevented) return;
3790
+ if (!isInteractive) return;
3791
+ if ((e.key === "Enter" || e.key === " ") && !e.repeat) {
3792
+ e.preventDefault();
3793
+ onClick?.(e);
3794
+ }
3795
+ };
3796
+ const rootClassName = cn(
3797
+ "ods-card",
3798
+ `ods-card--${variant}`,
3799
+ `ods-card--pad-${padding}`,
3800
+ `ods-card--radius-${radius}`,
3801
+ (hoverable || isInteractive) && !loading && !disabled && "ods-card--hoverable",
3802
+ isInteractive && "ods-card--clickable",
3803
+ disabled && "ods-card--disabled",
3804
+ loading && "ods-card--loading",
3805
+ className
3806
+ );
3807
+ if (asChild) {
3808
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3809
+ Slot,
3810
+ {
3811
+ ...rest,
3812
+ ref,
3813
+ className: rootClassName,
3814
+ onClick: isInteractive ? onClick : void 0,
3815
+ onKeyDown: isInteractive ? handleKeyDown : onKeyDown,
3816
+ "aria-disabled": disabled || void 0,
3817
+ children
3818
+ }
3819
+ );
3820
+ }
3821
+ if (loading) {
3822
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3823
+ "div",
3824
+ {
3825
+ ...rest,
3826
+ ref,
3827
+ className: rootClassName,
3828
+ "aria-busy": "true",
3829
+ "aria-label": "Loading",
3830
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "ods-card__skeleton", "data-ods-animate": "shimmer" })
3831
+ }
3832
+ );
3833
+ }
3834
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3835
+ import_framer_motion4.motion.div,
3836
+ {
3837
+ ...rest,
3838
+ ref,
3839
+ onClick: isInteractive ? onClick : void 0,
3840
+ onKeyDown: isInteractive ? handleKeyDown : onKeyDown,
3841
+ className: rootClassName,
3842
+ whileHover: (hoverable || isInteractive) && !reducedMotion ? { y: -2, transition: { duration: 0.15 } } : void 0,
3843
+ whileTap: isInteractive && !reducedMotion ? { scale: 0.99 } : void 0,
3844
+ role: isInteractive ? "button" : rest.role,
3845
+ tabIndex: isInteractive ? rest.tabIndex ?? 0 : rest.tabIndex,
3846
+ "aria-disabled": disabled || void 0,
3847
+ children
3848
+ }
3849
+ );
3850
+ });
3851
+ Card.displayName = "Card";
3852
+ var CardHeader = (0, import_react23.forwardRef)(
3853
+ function CardHeader2({ className, children, ...rest }, ref) {
3854
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { ...rest, ref, className: cn("ods-card__header", className), children });
3855
+ }
3856
+ );
3857
+ CardHeader.displayName = "Card.Header";
3858
+ var CardBody = (0, import_react23.forwardRef)(
3859
+ function CardBody2({ className, children, ...rest }, ref) {
3860
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { ...rest, ref, className: cn("ods-card__body", className), children });
3861
+ }
3862
+ );
3863
+ CardBody.displayName = "Card.Body";
3864
+ var CardFooter = (0, import_react23.forwardRef)(
3865
+ function CardFooter2({ className, children, ...rest }, ref) {
3866
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { ...rest, ref, className: cn("ods-card__footer", className), children });
3867
+ }
3868
+ );
3869
+ CardFooter.displayName = "Card.Footer";
3870
+ var CardTitle = (0, import_react23.forwardRef)(
3871
+ function CardTitle2({ as: Comp = "h3", className, children, ...rest }, ref) {
3872
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3873
+ Comp,
3874
+ {
3875
+ ...rest,
3876
+ ref,
3877
+ className: cn("ods-card__title", className),
3878
+ children
3879
+ }
3880
+ );
3881
+ }
3882
+ );
3883
+ CardTitle.displayName = "Card.Title";
3884
+ var CardDescription = (0, import_react23.forwardRef)(function CardDescription2({ className, children, ...rest }, ref) {
3885
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { ...rest, ref, className: cn("ods-card__description", className), children });
3886
+ });
3887
+ CardDescription.displayName = "Card.Description";
3888
+ Card.Header = CardHeader;
3889
+ Card.Body = CardBody;
3890
+ Card.Footer = CardFooter;
3891
+ Card.Title = CardTitle;
3892
+ Card.Description = CardDescription;
3893
+
3894
+ // src/components/BlogCard/BlogCard.tsx
3895
+ var import_jsx_runtime12 = require("react/jsx-runtime");
3896
+ var BlogCard = (0, import_react24.forwardRef)(
3897
+ function BlogCard2({
3898
+ cover,
3899
+ coverAspect = "16/9",
3900
+ category,
3901
+ tags,
3902
+ meta,
3903
+ readingTime,
3904
+ title,
3905
+ titleAs = "h3",
3906
+ description,
3907
+ author,
3908
+ footer,
3909
+ radius = "md",
3910
+ orientation = "vertical",
3911
+ href,
3912
+ onClick,
3913
+ id: providedId,
3914
+ className,
3915
+ ...rest
3916
+ }, ref) {
3917
+ const reactId = (0, import_react24.useId)();
3918
+ const baseId = providedId ?? `ods-blog-card-${reactId}`;
3919
+ const titleId = `${baseId}-title`;
3920
+ const descId = description ? `${baseId}-desc` : void 0;
3921
+ const tagList = tags ?? (category ? [category] : []);
3922
+ const body = /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
3923
+ cover && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3924
+ "div",
3925
+ {
3926
+ className: cn(
3927
+ "ods-blog-card__cover",
3928
+ `ods-blog-card__cover--aspect-${coverAspect.replace("/", "-")}`
3929
+ ),
3930
+ "aria-hidden": "true",
3931
+ children: cover
3932
+ }
3933
+ ),
3934
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "ods-blog-card__body", children: [
3935
+ (tagList.length > 0 || meta || readingTime) && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "ods-blog-card__meta", children: [
3936
+ tagList.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "ods-blog-card__tags", children: tagList.map((t, i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "ods-blog-card__tag", children: t }, i)) }),
3937
+ meta && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "ods-blog-card__date", children: meta }),
3938
+ readingTime && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "ods-blog-card__reading", children: readingTime })
3939
+ ] }),
3940
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3941
+ Card.Title,
3942
+ {
3943
+ as: titleAs,
3944
+ id: titleId,
3945
+ className: "ods-blog-card__title",
3946
+ children: title
3947
+ }
3948
+ ),
3949
+ description && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3950
+ Card.Description,
3951
+ {
3952
+ id: descId,
3953
+ className: "ods-blog-card__desc",
3954
+ children: description
3955
+ }
3956
+ ),
3957
+ (author || footer) && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "ods-blog-card__footer", children: [
3958
+ author && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "ods-blog-card__author", children: [
3959
+ author.avatar && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3960
+ Avatar,
3939
3961
  {
3940
- className: "ods-textarea__count",
3941
- "aria-live": charCount > maxLength ? "polite" : "off",
3942
- children: [
3943
- charCount,
3944
- "/",
3945
- maxLength
3946
- ]
3962
+ size: "sm",
3963
+ src: author.avatar.src,
3964
+ initials: author.avatar.initials,
3965
+ alt: author.avatar.alt ?? (typeof author.name === "string" ? author.name : void 0)
3947
3966
  }
3948
- )
3967
+ ),
3968
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "ods-blog-card__author-text", children: [
3969
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "ods-blog-card__author-name", children: author.name }),
3970
+ author.byline && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "ods-blog-card__author-byline", children: author.byline })
3971
+ ] })
3949
3972
  ] }),
3950
- toolbarEnabled && toolbarPosition === "bottom" && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3951
- TextareaToolbar,
3952
- {
3953
- tools: toolbar.tools,
3954
- onRun: toolbar.runTool
3955
- }
3973
+ footer && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "ods-blog-card__action", children: footer })
3974
+ ] })
3975
+ ] })
3976
+ ] });
3977
+ if (href) {
3978
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3979
+ Card,
3980
+ {
3981
+ asChild: true,
3982
+ radius,
3983
+ padding: "none",
3984
+ className: cn(
3985
+ "ods-blog-card",
3986
+ `ods-blog-card--${orientation}`,
3987
+ "ods-blog-card--interactive",
3988
+ className
3956
3989
  ),
3957
- error && errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3958
- "div",
3990
+ "aria-labelledby": titleId,
3991
+ "aria-describedby": descId,
3992
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3993
+ "a",
3959
3994
  {
3960
- id: hintId,
3961
- className: "ods-textarea__error-message",
3962
- role: "alert",
3963
- children: errorMessage
3995
+ ...rest,
3996
+ ref,
3997
+ href,
3998
+ id: baseId,
3999
+ onClick,
4000
+ children: body
3964
4001
  }
3965
- ) : helperText ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { id: hintId, className: "ods-textarea__hint", children: helperText }) : null
3966
- ]
4002
+ )
4003
+ }
4004
+ );
4005
+ }
4006
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4007
+ Card,
4008
+ {
4009
+ ...rest,
4010
+ ref,
4011
+ id: baseId,
4012
+ radius,
4013
+ padding: "none",
4014
+ onClick,
4015
+ className: cn(
4016
+ "ods-blog-card",
4017
+ `ods-blog-card--${orientation}`,
4018
+ onClick && "ods-blog-card--interactive",
4019
+ className
4020
+ ),
4021
+ "aria-labelledby": titleId,
4022
+ "aria-describedby": descId,
4023
+ children: body
3967
4024
  }
3968
4025
  );
3969
4026
  }
3970
4027
  );
3971
- Textarea.displayName = "Textarea";
3972
- function TextareaToolbar({
3973
- tools,
3974
- onRun
4028
+ BlogCard.displayName = "BlogCard";
4029
+
4030
+ // src/marketing/components/BlogGrid/BlogGrid.tsx
4031
+ var import_jsx_runtime13 = require("react/jsx-runtime");
4032
+ function BlogGrid({
4033
+ posts,
4034
+ columns = 3,
4035
+ viewAllHref,
4036
+ viewAllLabel = "View all posts \u2192",
4037
+ className,
4038
+ ...rest
3975
4039
  }) {
3976
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3977
- "div",
3978
- {
3979
- role: "toolbar",
3980
- "aria-label": "Formatting",
3981
- className: "ods-textarea__toolbar",
3982
- onMouseDown: (e) => {
3983
- if (e.target.closest(".ods-textarea__toolbar-btn")) {
3984
- e.preventDefault();
3985
- }
4040
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { ...rest, className: cn("ods-mkt-blog", className), children: [
4041
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: cn("ods-mkt-blog__grid", `ods-mkt-blog__grid--${columns}`), children: posts.map((post) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
4042
+ BlogCard,
4043
+ {
4044
+ title: post.title,
4045
+ description: post.excerpt,
4046
+ href: post.href,
4047
+ meta: post.date,
4048
+ readingTime: post.readTime,
4049
+ tags: post.tags,
4050
+ cover: post.cover,
4051
+ author: post.author
3986
4052
  },
3987
- children: tools.map((t) => {
3988
- if (t.kind === "divider") {
3989
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3990
- "span",
3991
- {
3992
- className: "ods-textarea__toolbar-divider",
3993
- "aria-hidden": "true"
3994
- },
3995
- t.id
3996
- );
3997
- }
3998
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3999
- "button",
4000
- {
4001
- type: "button",
4002
- className: "ods-textarea__toolbar-btn",
4003
- title: t.title ?? (typeof t.label === "string" ? t.label : void 0),
4004
- "aria-label": typeof t.label === "string" ? t.label : t.id,
4005
- onClick: () => onRun(t.id),
4006
- children: t.icon ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "ods-textarea__toolbar-icon", children: t.icon }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "ods-textarea__toolbar-text", children: t.label })
4007
- },
4008
- t.id
4009
- );
4010
- })
4011
- }
4012
- );
4053
+ post.id
4054
+ )) }),
4055
+ viewAllHref && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "ods-mkt-blog__more", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("a", { className: "ods-mkt-blog__more-link", href: viewAllHref, children: viewAllLabel }) })
4056
+ ] });
4013
4057
  }
4014
4058
 
4015
4059
  // src/marketing/components/ContactForm/ContactForm.tsx
4060
+ var import_react25 = require("react");
4016
4061
  var import_jsx_runtime14 = require("react/jsx-runtime");
4017
4062
  function ContactForm({
4018
4063
  title = "Talk to us",
@@ -4973,7 +5018,24 @@ function MarketingHero({
4973
5018
  }
4974
5019
 
4975
5020
  // src/marketing/components/MarketingNav/MarketingNav.tsx
5021
+ var import_react29 = require("react");
4976
5022
  var import_jsx_runtime27 = require("react/jsx-runtime");
5023
+ function BurgerGlyph({ open }) {
5024
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
5025
+ "svg",
5026
+ {
5027
+ viewBox: "0 0 24 24",
5028
+ width: "20",
5029
+ height: "20",
5030
+ fill: "none",
5031
+ stroke: "currentColor",
5032
+ strokeWidth: "2",
5033
+ strokeLinecap: "round",
5034
+ "aria-hidden": "true",
5035
+ children: open ? /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("path", { d: "M6 6l12 12M18 6 6 18" }) : /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("path", { d: "M4 7h16M4 12h16M4 17h16" })
5036
+ }
5037
+ );
5038
+ }
4977
5039
  function MarketingNav({
4978
5040
  brand,
4979
5041
  brandHref = "/",
@@ -4983,6 +5045,7 @@ function MarketingNav({
4983
5045
  className,
4984
5046
  ...rest
4985
5047
  }) {
5048
+ const [mobileOpen, setMobileOpen] = (0, import_react29.useState)(false);
4986
5049
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("header", { ...rest, className: cn("ods-mkt-nav", sticky && "ods-mkt-nav--sticky", className), children: [
4987
5050
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("a", { className: "ods-mkt-nav__brand", href: brandHref, children: brand ?? /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(import_jsx_runtime27.Fragment, { children: [
4988
5051
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(BrandMark, {}),
@@ -4998,7 +5061,35 @@ function MarketingNav({
4998
5061
  },
4999
5062
  link.href
5000
5063
  )) }),
5001
- actions && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "ods-mkt-nav__actions", children: actions })
5064
+ actions && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "ods-mkt-nav__actions", children: actions }),
5065
+ (links.length > 0 || actions) && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
5066
+ "button",
5067
+ {
5068
+ type: "button",
5069
+ className: "ods-mkt-nav__toggle",
5070
+ "aria-expanded": mobileOpen,
5071
+ "aria-label": "Toggle menu",
5072
+ onClick: () => setMobileOpen((open) => !open),
5073
+ children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(BurgerGlyph, { open: mobileOpen })
5074
+ }
5075
+ ),
5076
+ mobileOpen && /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("nav", { className: "ods-mkt-nav__mobile", "aria-label": "Main", children: [
5077
+ links.map((link) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
5078
+ "a",
5079
+ {
5080
+ href: link.href,
5081
+ className: cn(
5082
+ "ods-mkt-nav__mobile-link",
5083
+ link.active && "ods-mkt-nav__mobile-link--active"
5084
+ ),
5085
+ "aria-current": link.active ? "page" : void 0,
5086
+ onClick: () => setMobileOpen(false),
5087
+ children: link.label
5088
+ },
5089
+ link.href
5090
+ )),
5091
+ actions && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "ods-mkt-nav__mobile-actions", children: actions })
5092
+ ] })
5002
5093
  ] });
5003
5094
  }
5004
5095
 
@@ -5122,10 +5213,10 @@ function PricingTiers({ tiers, note, className, ...rest }) {
5122
5213
  var import_framer_motion7 = require("framer-motion");
5123
5214
 
5124
5215
  // src/components/CountUp/CountUp.tsx
5125
- var import_react29 = require("react");
5216
+ var import_react30 = require("react");
5126
5217
  var import_jsx_runtime31 = require("react/jsx-runtime");
5127
5218
  var easeOutCubic = (t) => 1 - Math.pow(1 - t, 3);
5128
- var CountUp = (0, import_react29.forwardRef)(
5219
+ var CountUp = (0, import_react30.forwardRef)(
5129
5220
  function CountUp2({
5130
5221
  value,
5131
5222
  from = 0,
@@ -5141,9 +5232,9 @@ var CountUp = (0, import_react29.forwardRef)(
5141
5232
  className,
5142
5233
  ...rest
5143
5234
  }, ref) {
5144
- const [n, setN] = (0, import_react29.useState)(disableAnimation ? value : from);
5145
- const lastRendered = (0, import_react29.useRef)(disableAnimation ? value : from);
5146
- (0, import_react29.useEffect)(() => {
5235
+ const [n, setN] = (0, import_react30.useState)(disableAnimation ? value : from);
5236
+ const lastRendered = (0, import_react30.useRef)(disableAnimation ? value : from);
5237
+ (0, import_react30.useEffect)(() => {
5147
5238
  const reduced = typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
5148
5239
  if (disableAnimation || reduced) {
5149
5240
  setN(value);
@@ -5241,13 +5332,13 @@ function StatBand({ items, orientation = "column", className, ...rest }) {
5241
5332
 
5242
5333
  // src/components/TestimonialCard/TestimonialCard.tsx
5243
5334
  var import_icons9 = require("@octaviaflow/icons");
5244
- var import_react30 = require("react");
5335
+ var import_react31 = require("react");
5245
5336
  var import_jsx_runtime33 = require("react/jsx-runtime");
5246
5337
  function isAuthorObject(v) {
5247
5338
  return typeof v === "object" && v !== null && !("type" in v) && // exclude React elements
5248
5339
  "name" in v;
5249
5340
  }
5250
- var TestimonialCard = (0, import_react30.forwardRef)(function TestimonialCard2({
5341
+ var TestimonialCard = (0, import_react31.forwardRef)(function TestimonialCard2({
5251
5342
  quote,
5252
5343
  author,
5253
5344
  role: legacyRole,
@@ -5261,7 +5352,7 @@ var TestimonialCard = (0, import_react30.forwardRef)(function TestimonialCard2({
5261
5352
  className,
5262
5353
  ...rest
5263
5354
  }, ref) {
5264
- const reactId = (0, import_react30.useId)();
5355
+ const reactId = (0, import_react31.useId)();
5265
5356
  const baseId = providedId ?? `ods-testimonial-${reactId}`;
5266
5357
  const quoteId = `${baseId}-quote`;
5267
5358
  const authorId = `${baseId}-author`;
@@ -5383,10 +5474,10 @@ function TestimonialStrip({
5383
5474
 
5384
5475
  // src/marketing/components/WorkflowShowcase/WorkflowShowcase.tsx
5385
5476
  var import_framer_motion9 = require("framer-motion");
5386
- var import_react33 = require("react");
5477
+ var import_react34 = require("react");
5387
5478
 
5388
5479
  // src/marketing/components/WorkflowShowcase/ShowcaseScene.tsx
5389
- var import_react31 = require("react");
5480
+ var import_react32 = require("react");
5390
5481
  var import_jsx_runtime35 = require("react/jsx-runtime");
5391
5482
  function slots(n) {
5392
5483
  return Array.from({ length: n }, (_, i) => 208 + (i - (n - 1) / 2) * 80);
@@ -5459,9 +5550,9 @@ function ShowcaseScene({
5459
5550
  caption,
5460
5551
  onCoreClick
5461
5552
  }) {
5462
- const [pulse, setPulse] = (0, import_react31.useState)(false);
5463
- const pulseTimer = (0, import_react31.useRef)(null);
5464
- (0, import_react31.useEffect)(
5553
+ const [pulse, setPulse] = (0, import_react32.useState)(false);
5554
+ const pulseTimer = (0, import_react32.useRef)(null);
5555
+ (0, import_react32.useEffect)(
5465
5556
  () => () => {
5466
5557
  if (pulseTimer.current) clearTimeout(pulseTimer.current);
5467
5558
  },
@@ -5692,7 +5783,7 @@ function ShowcaseScene({
5692
5783
 
5693
5784
  // src/marketing/components/WorkflowShowcase/ShowcaseSignup.tsx
5694
5785
  var import_framer_motion8 = require("framer-motion");
5695
- var import_react32 = require("react");
5786
+ var import_react33 = require("react");
5696
5787
  var import_react_aria5 = require("react-aria");
5697
5788
  var import_jsx_runtime36 = require("react/jsx-runtime");
5698
5789
  var OVERLAY_MOTION = {
@@ -5712,9 +5803,9 @@ function ShowcaseSignup({
5712
5803
  onboardingHref = "#"
5713
5804
  }) {
5714
5805
  const reducedMotion = (0, import_framer_motion8.useReducedMotion)();
5715
- const titleId = (0, import_react32.useId)();
5716
- const subtitleId = (0, import_react32.useId)();
5717
- const [values, setValues] = (0, import_react32.useState)({
5806
+ const titleId = (0, import_react33.useId)();
5807
+ const subtitleId = (0, import_react33.useId)();
5808
+ const [values, setValues] = (0, import_react33.useState)({
5718
5809
  name: "",
5719
5810
  email: "",
5720
5811
  company: "",
@@ -5932,53 +6023,53 @@ function WorkflowShowcase({
5932
6023
  }) {
5933
6024
  const reducedMotion = (0, import_framer_motion9.useReducedMotion)();
5934
6025
  const rotationActive = rotatePrompts && !defaultPrompt && examples.length > 0;
5935
- const [prompt, setPrompt] = (0, import_react33.useState)(
6026
+ const [prompt, setPrompt] = (0, import_react34.useState)(
5936
6027
  defaultPrompt || (rotationActive ? examples[0]?.prompt ?? "" : "")
5937
6028
  );
5938
- const [engagedWithPrompt, setEngagedWithPrompt] = (0, import_react33.useState)(false);
5939
- const [promptHovered, setPromptHovered] = (0, import_react33.useState)(false);
5940
- const [promptFocused, setPromptFocused] = (0, import_react33.useState)(false);
5941
- const rotateIdx = (0, import_react33.useRef)(0);
5942
- const [selected, setSelected] = (0, import_react33.useState)([]);
5943
- const [phase, setPhase] = (0, import_react33.useState)("idle");
5944
- const [shown, setShown] = (0, import_react33.useState)(0);
5945
- const [status, setStatus] = (0, import_react33.useState)("");
5946
- const [pickerOpen, setPickerOpen] = (0, import_react33.useState)(false);
5947
- const [schedIdx, setSchedIdx] = (0, import_react33.useState)(0);
5948
- const [swap, setSwap] = (0, import_react33.useState)(null);
5949
- const [signupError, setSignupError] = (0, import_react33.useState)(null);
5950
- const signupReturnPhase = (0, import_react33.useRef)("done");
5951
- const cardRef = (0, import_react33.useRef)(null);
5952
- const timers = (0, import_react33.useRef)([]);
5953
- const clearTimers = (0, import_react33.useCallback)(() => {
6029
+ const [engagedWithPrompt, setEngagedWithPrompt] = (0, import_react34.useState)(false);
6030
+ const [promptHovered, setPromptHovered] = (0, import_react34.useState)(false);
6031
+ const [promptFocused, setPromptFocused] = (0, import_react34.useState)(false);
6032
+ const rotateIdx = (0, import_react34.useRef)(0);
6033
+ const [selected, setSelected] = (0, import_react34.useState)([]);
6034
+ const [phase, setPhase] = (0, import_react34.useState)("idle");
6035
+ const [shown, setShown] = (0, import_react34.useState)(0);
6036
+ const [status, setStatus] = (0, import_react34.useState)("");
6037
+ const [pickerOpen, setPickerOpen] = (0, import_react34.useState)(false);
6038
+ const [schedIdx, setSchedIdx] = (0, import_react34.useState)(0);
6039
+ const [swap, setSwap] = (0, import_react34.useState)(null);
6040
+ const [signupError, setSignupError] = (0, import_react34.useState)(null);
6041
+ const signupReturnPhase = (0, import_react34.useRef)("done");
6042
+ const cardRef = (0, import_react34.useRef)(null);
6043
+ const timers = (0, import_react34.useRef)([]);
6044
+ const clearTimers = (0, import_react34.useCallback)(() => {
5954
6045
  for (const t of timers.current) clearTimeout(t);
5955
6046
  timers.current = [];
5956
6047
  }, []);
5957
- (0, import_react33.useEffect)(() => clearTimers, [clearTimers]);
6048
+ (0, import_react34.useEffect)(() => clearTimers, [clearTimers]);
5958
6049
  const tempo = reducedMotion ? 0.35 : 1;
5959
- const at = (0, import_react33.useCallback)(
6050
+ const at = (0, import_react34.useCallback)(
5960
6051
  (ms, fn) => {
5961
6052
  timers.current.push(setTimeout(fn, ms * tempo));
5962
6053
  },
5963
6054
  [tempo]
5964
6055
  );
5965
- const firstPhase = (0, import_react33.useRef)(true);
5966
- (0, import_react33.useEffect)(() => {
6056
+ const firstPhase = (0, import_react34.useRef)(true);
6057
+ (0, import_react34.useEffect)(() => {
5967
6058
  if (firstPhase.current) {
5968
6059
  firstPhase.current = false;
5969
6060
  return;
5970
6061
  }
5971
6062
  onPhaseChange?.(phase);
5972
6063
  }, [phase, onPhaseChange]);
5973
- const phaseRef = (0, import_react33.useRef)(phase);
6064
+ const phaseRef = (0, import_react34.useRef)(phase);
5974
6065
  phaseRef.current = phase;
5975
- const byName = (0, import_react33.useMemo)(() => new Map(connectors.map((c) => [c.name, c])), [connectors]);
5976
- const detected = (0, import_react33.useMemo)(() => detectConnectors(prompt, connectors), [prompt, connectors]);
5977
- const union = (0, import_react33.useMemo)(() => [.../* @__PURE__ */ new Set([...selected, ...detected])], [selected, detected]);
6066
+ const byName = (0, import_react34.useMemo)(() => new Map(connectors.map((c) => [c.name, c])), [connectors]);
6067
+ const detected = (0, import_react34.useMemo)(() => detectConnectors(prompt, connectors), [prompt, connectors]);
6068
+ const union = (0, import_react34.useMemo)(() => [.../* @__PURE__ */ new Set([...selected, ...detected])], [selected, detected]);
5978
6069
  const settled = SETTLED_PHASES.includes(phase);
5979
6070
  const engaged = phase !== "idle";
5980
6071
  const buildingNow = phase === "analyzing" || phase === "building";
5981
- const plan = (0, import_react33.useMemo)(() => {
6072
+ const plan = (0, import_react34.useMemo)(() => {
5982
6073
  if (!selected.length) return EMPTY_PLAN;
5983
6074
  return resolvePlan(selected, [], connectors);
5984
6075
  }, [selected, connectors]);
@@ -5987,10 +6078,10 @@ function WorkflowShowcase({
5987
6078
  const manualSchedule = schedules[schedIdx];
5988
6079
  const scheduleShown = inferredSchedule ?? (manualSchedule === "AUTO" ? "HOURLY" : manualSchedule);
5989
6080
  const triggers = WORKFLOW_SHOWCASE_TRIGGERS;
5990
- const [triggerIdx, setTriggerIdx] = (0, import_react33.useState)(0);
6081
+ const [triggerIdx, setTriggerIdx] = (0, import_react34.useState)(0);
5991
6082
  const manualTrigger = triggers[triggerIdx];
5992
6083
  const cadenceShown = manualTrigger === "SCHEDULE" ? scheduleShown : manualTrigger;
5993
- (0, import_react33.useEffect)(() => {
6084
+ (0, import_react34.useEffect)(() => {
5994
6085
  if (!rotationActive || engagedWithPrompt || promptHovered || promptFocused || phase !== "idle") {
5995
6086
  return;
5996
6087
  }
@@ -6000,7 +6091,7 @@ function WorkflowShowcase({
6000
6091
  }, 4200);
6001
6092
  return () => clearInterval(timer);
6002
6093
  }, [rotationActive, engagedWithPrompt, promptHovered, promptFocused, phase, examples]);
6003
- const startBuild = (0, import_react33.useCallback)(
6094
+ const startBuild = (0, import_react34.useCallback)(
6004
6095
  (text, baseSelected) => {
6005
6096
  if (phaseRef.current === "analyzing" || phaseRef.current === "building") return;
6006
6097
  phaseRef.current = "analyzing";
@@ -6037,7 +6128,7 @@ function WorkflowShowcase({
6037
6128
  },
6038
6129
  [clearTimers, connectors, onBuild, scheduleShown, manualTrigger, at]
6039
6130
  );
6040
- const resetToIdle = (0, import_react33.useCallback)(() => {
6131
+ const resetToIdle = (0, import_react34.useCallback)(() => {
6041
6132
  clearTimers();
6042
6133
  phaseRef.current = "idle";
6043
6134
  setPhase("idle");