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

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,1792 @@ 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
+ nameLayout = "single",
3376
+ appearance = "card",
3377
+ teamSizes,
3378
+ plans,
3379
+ defaultPlan,
3380
+ useCases,
3381
+ showDetails = false,
3382
+ detailsLabel = "Anything else we should know?",
3383
+ detailsPlaceholder = "Current stack, timelines, must-have connectors\u2026",
3384
+ submitLabel = "Request access \u2192",
3385
+ footnote = "NO CREDIT CARD \xB7 SSO & SELF-HOSTING AVAILABLE",
3386
+ onSubmit,
3387
+ successTitle = "Request received.",
3388
+ successBody = "We review access requests weekly \u2014 you'll hear from us by email.",
3389
+ disabled = false,
3390
+ className
3513
3391
  }) {
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 {
3392
+ const uid = (0, import_react21.useId)();
3393
+ const [values, setValues] = (0, import_react21.useState)({
3394
+ email: "",
3395
+ name: "",
3396
+ firstName: "",
3397
+ lastName: "",
3398
+ company: "",
3399
+ teamSize: "",
3400
+ plan: defaultPlan ?? "",
3401
+ useCase: "",
3402
+ details: ""
3403
+ });
3404
+ const [phase, setPhase] = (0, import_react21.useState)("idle");
3405
+ const [error, setError] = (0, import_react21.useState)(null);
3406
+ const handleSubmit = async (event) => {
3407
+ event.preventDefault();
3408
+ if (phase === "submitting") return;
3409
+ if (!isValidEmail(values.email)) {
3410
+ setError("Please enter a valid work email.");
3411
+ return;
3412
+ }
3413
+ if (fields === "full" && nameLayout === "single" && !values.name?.trim()) {
3414
+ setError("Please enter your name.");
3415
+ return;
3416
+ }
3417
+ if (fields === "full" && nameLayout === "split" && !values.firstName?.trim()) {
3418
+ setError("Please enter your first name.");
3419
+ return;
3420
+ }
3421
+ if (fields === "full" && teamSizes?.length && !values.teamSize) {
3422
+ setError("Please select your team size.");
3423
+ return;
3424
+ }
3425
+ if (fields === "full" && useCases?.length && !values.useCase) {
3426
+ setError("Please select your primary use case.");
3427
+ return;
3428
+ }
3429
+ setError(null);
3430
+ setPhase("submitting");
3431
+ try {
3432
+ await onSubmit?.(values);
3433
+ setPhase("success");
3434
+ } catch {
3435
+ setError("Something went wrong \u2014 please try again.");
3436
+ setPhase("idle");
3437
+ }
3438
+ };
3439
+ const inline = appearance === "inline";
3440
+ if (phase === "success") {
3441
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3442
+ "div",
3443
+ {
3444
+ className: cn(
3445
+ "ods-mkt-access",
3446
+ "ods-mkt-access--success",
3447
+ inline && "ods-mkt-access--inline",
3448
+ className
3449
+ ),
3450
+ role: "status",
3451
+ children: [
3452
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("p", { className: "ods-mkt-access__success-mark", children: [
3453
+ "\u2713 ",
3454
+ successTitle
3455
+ ] }),
3456
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "ods-mkt-access__success-body", children: successBody })
3457
+ ]
3595
3458
  }
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]
3459
+ );
3460
+ }
3461
+ const busy = disabled || phase === "submitting";
3462
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3463
+ "form",
3464
+ {
3465
+ className: cn("ods-mkt-access", inline && "ods-mkt-access--inline", className),
3466
+ onSubmit: handleSubmit,
3467
+ noValidate: true,
3468
+ children: [
3469
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-mkt-access__head", children: [
3470
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h3", { className: "ods-mkt-access__title", children: title }),
3471
+ subtitle && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "ods-mkt-access__subtitle", children: subtitle })
3472
+ ] }),
3473
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
3474
+ "div",
3475
+ {
3476
+ className: cn(
3477
+ "ods-mkt-access__fields",
3478
+ fields === "full" && "ods-mkt-access__fields--full"
3479
+ ),
3480
+ children: [
3481
+ fields === "full" && nameLayout === "single" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-mkt-access__field", children: [
3482
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("label", { className: "ods-mkt-access__label", htmlFor: `${uid}-name`, children: "Full name" }),
3483
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3484
+ Input,
3485
+ {
3486
+ id: `${uid}-name`,
3487
+ autoComplete: "name",
3488
+ placeholder: "Ada Lovelace",
3489
+ value: values.name ?? "",
3490
+ onChange: (e) => setValues((v) => ({ ...v, name: e.target.value })),
3491
+ disabled: busy
3492
+ }
3493
+ )
3494
+ ] }),
3495
+ fields === "full" && nameLayout === "split" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
3496
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-mkt-access__field", children: [
3497
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("label", { className: "ods-mkt-access__label", htmlFor: `${uid}-firstname`, children: "First name" }),
3498
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3499
+ Input,
3500
+ {
3501
+ id: `${uid}-firstname`,
3502
+ autoComplete: "given-name",
3503
+ placeholder: "Ada",
3504
+ value: values.firstName ?? "",
3505
+ onChange: (e) => setValues((v) => ({ ...v, firstName: e.target.value })),
3506
+ disabled: busy
3507
+ }
3508
+ )
3509
+ ] }),
3510
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-mkt-access__field", children: [
3511
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("label", { className: "ods-mkt-access__label", htmlFor: `${uid}-lastname`, children: "Last name" }),
3512
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3513
+ Input,
3514
+ {
3515
+ id: `${uid}-lastname`,
3516
+ autoComplete: "family-name",
3517
+ placeholder: "Lovelace",
3518
+ value: values.lastName ?? "",
3519
+ onChange: (e) => setValues((v) => ({ ...v, lastName: e.target.value })),
3520
+ disabled: busy
3521
+ }
3522
+ )
3523
+ ] })
3524
+ ] }),
3525
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-mkt-access__field", children: [
3526
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("label", { className: "ods-mkt-access__label", htmlFor: `${uid}-email`, children: "Work email" }),
3527
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3528
+ Input,
3529
+ {
3530
+ id: `${uid}-email`,
3531
+ type: "email",
3532
+ autoComplete: "email",
3533
+ placeholder: "ada@company.com",
3534
+ value: values.email,
3535
+ onChange: (e) => setValues((v) => ({ ...v, email: e.target.value })),
3536
+ disabled: busy
3537
+ }
3538
+ )
3539
+ ] }),
3540
+ fields === "full" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-mkt-access__field", children: [
3541
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("label", { className: "ods-mkt-access__label", htmlFor: `${uid}-company`, children: "Company" }),
3542
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3543
+ Input,
3544
+ {
3545
+ id: `${uid}-company`,
3546
+ autoComplete: "organization",
3547
+ placeholder: "Acme Inc.",
3548
+ value: values.company ?? "",
3549
+ onChange: (e) => setValues((v) => ({ ...v, company: e.target.value })),
3550
+ disabled: busy
3551
+ }
3552
+ )
3553
+ ] }),
3554
+ fields === "full" && teamSizes && teamSizes.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-mkt-access__field", children: [
3555
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ods-mkt-access__label", id: `${uid}-teamsize-label`, children: "Team size" }),
3556
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3557
+ Select,
3558
+ {
3559
+ "aria-labelledby": `${uid}-teamsize-label`,
3560
+ placeholder: "Select team size\u2026",
3561
+ options: teamSizes.map((size) => ({ value: size, label: size })),
3562
+ value: values.teamSize ?? "",
3563
+ onChange: (teamSize) => setValues((v) => ({ ...v, teamSize })),
3564
+ disabled: busy
3565
+ }
3566
+ )
3567
+ ] }),
3568
+ fields === "full" && plans && plans.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-mkt-access__field", children: [
3569
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ods-mkt-access__label", id: `${uid}-plan-label`, children: "Plan (optional)" }),
3570
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3571
+ Select,
3572
+ {
3573
+ "aria-labelledby": `${uid}-plan-label`,
3574
+ placeholder: "Not sure yet",
3575
+ options: plans.map((plan) => ({ value: plan, label: plan })),
3576
+ value: values.plan ?? "",
3577
+ onChange: (plan) => setValues((v) => ({ ...v, plan })),
3578
+ disabled: busy
3579
+ }
3580
+ )
3581
+ ] }),
3582
+ fields === "full" && useCases && useCases.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-mkt-access__field", children: [
3583
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "ods-mkt-access__label", id: `${uid}-usecase-label`, children: "Primary use case" }),
3584
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3585
+ Select,
3586
+ {
3587
+ "aria-labelledby": `${uid}-usecase-label`,
3588
+ placeholder: "Select a use case\u2026",
3589
+ options: useCases.map((useCase) => ({ value: useCase, label: useCase })),
3590
+ value: values.useCase ?? "",
3591
+ onChange: (useCase) => setValues((v) => ({ ...v, useCase })),
3592
+ disabled: busy
3593
+ }
3594
+ )
3595
+ ] }),
3596
+ fields === "full" && showDetails && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "ods-mkt-access__field ods-mkt-access__field--wide", children: [
3597
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3598
+ "label",
3599
+ {
3600
+ className: "ods-mkt-access__label",
3601
+ id: `${uid}-details-label`,
3602
+ htmlFor: `${uid}-details`,
3603
+ children: detailsLabel
3604
+ }
3605
+ ),
3606
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3607
+ Textarea,
3608
+ {
3609
+ id: `${uid}-details`,
3610
+ "aria-labelledby": `${uid}-details-label`,
3611
+ placeholder: detailsPlaceholder,
3612
+ rows: 3,
3613
+ value: values.details ?? "",
3614
+ onChange: (e) => setValues((v) => ({ ...v, details: e.target.value })),
3615
+ disabled: busy
3616
+ }
3617
+ )
3618
+ ] }),
3619
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
3620
+ Button,
3621
+ {
3622
+ type: "submit",
3623
+ variant: "primary",
3624
+ loading: phase === "submitting",
3625
+ disabled,
3626
+ children: submitLabel
3627
+ }
3628
+ )
3629
+ ]
3630
+ }
3631
+ ),
3632
+ error && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "ods-mkt-access__error", role: "alert", children: error }),
3633
+ footnote && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "ods-mkt-access__footnote", children: footnote })
3634
+ ]
3635
+ }
3625
3636
  );
3626
- return { tools: visibleTools, runTool, helpers };
3627
3637
  }
3628
3638
 
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)(
3639
+ // src/components/BlogCard/BlogCard.tsx
3640
+ var import_react24 = require("react");
3641
+
3642
+ // src/components/Avatar/Avatar.tsx
3643
+ var import_react22 = require("react");
3644
+ var import_jsx_runtime10 = require("react/jsx-runtime");
3645
+ var PALETTE_SIZE = 12;
3646
+ function djb2(str) {
3647
+ let h = 5381;
3648
+ for (let i = 0; i < str.length; i++) h = (h << 5) + h + str.charCodeAt(i);
3649
+ return h >>> 0;
3650
+ }
3651
+ function paletteIndexFor(seed) {
3652
+ return djb2(seed) % PALETTE_SIZE;
3653
+ }
3654
+ function normalizeInitials(raw) {
3655
+ const trimmed = raw.trim();
3656
+ if (!trimmed) return "";
3657
+ const tokens = trimmed.split(/\s+/).filter(Boolean);
3658
+ if (tokens.length >= 2) {
3659
+ return (tokens[0].charAt(0) + tokens[1].charAt(0)).toUpperCase();
3660
+ }
3661
+ return trimmed.slice(0, 2).toUpperCase();
3662
+ }
3663
+ var STATUS_DEFAULT_LABEL = {
3664
+ online: "Online",
3665
+ offline: "Offline",
3666
+ busy: "Busy",
3667
+ away: "Away"
3668
+ };
3669
+ var Avatar = (0, import_react22.forwardRef)(function Avatar2({
3670
+ src,
3671
+ alt,
3672
+ initials,
3673
+ icon,
3674
+ fallback,
3675
+ seed,
3676
+ size = "md",
3677
+ shape = "circle",
3678
+ status,
3679
+ statusLabel,
3680
+ onClick,
3681
+ imgLoading = "lazy",
3682
+ referrerPolicy,
3683
+ className,
3684
+ ...rest
3685
+ }, ref) {
3686
+ const [imgFailed, setImgFailed] = (0, import_react22.useState)(false);
3687
+ const handleImgError = (0, import_react22.useCallback)(() => setImgFailed(true), []);
3688
+ const showImage = Boolean(src) && !imgFailed;
3689
+ const normalizedInitials = (0, import_react22.useMemo)(
3690
+ () => initials ? normalizeInitials(initials) : "",
3691
+ [initials]
3692
+ );
3693
+ const paletteIdx = (0, import_react22.useMemo)(() => {
3694
+ const resolved = seed ?? initials ?? alt ?? "?";
3695
+ return paletteIndexFor(resolved);
3696
+ }, [seed, initials, alt]);
3697
+ const baseName = alt || normalizedInitials || "Avatar";
3698
+ const announcedStatus = status ? statusLabel ?? STATUS_DEFAULT_LABEL[status] : "";
3699
+ const accessibleName = announcedStatus ? `${baseName}, ${announcedStatus}` : baseName;
3700
+ const isClickable = Boolean(onClick);
3701
+ const handleKeyDown = (e) => {
3702
+ if (!isClickable) return;
3703
+ if ((e.key === "Enter" || e.key === " ") && !e.repeat) {
3704
+ e.preventDefault();
3705
+ onClick?.();
3706
+ }
3707
+ };
3708
+ const rootClassName = cn(
3709
+ "ods-avatar",
3710
+ `ods-avatar--${size}`,
3711
+ `ods-avatar--${shape}`,
3712
+ isClickable && "ods-avatar--clickable",
3713
+ className
3714
+ );
3715
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3716
+ "div",
3717
+ {
3718
+ ...rest,
3719
+ ref,
3720
+ className: rootClassName,
3721
+ role: isClickable ? "button" : "img",
3722
+ "aria-label": accessibleName,
3723
+ tabIndex: isClickable ? 0 : void 0,
3724
+ onClick,
3725
+ onKeyDown: isClickable ? handleKeyDown : void 0,
3726
+ "data-status": status,
3727
+ children: [
3728
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "ods-avatar__inner", "data-palette": paletteIdx, children: showImage ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3729
+ "img",
3730
+ {
3731
+ src,
3732
+ alt: "",
3733
+ className: "ods-avatar__image",
3734
+ loading: imgLoading,
3735
+ decoding: "async",
3736
+ referrerPolicy,
3737
+ onError: handleImgError,
3738
+ draggable: false
3739
+ }
3740
+ ) : 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 ?? "?" }) }),
3741
+ status && // Decorative — the textual status is folded into the wrapper's
3742
+ // aria-label above. `aria-hidden` keeps AT from double-announcing.
3743
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3744
+ "span",
3745
+ {
3746
+ className: cn("ods-avatar__status", `ods-avatar__status--${status}`),
3747
+ "aria-hidden": "true"
3748
+ }
3749
+ )
3750
+ ]
3751
+ }
3752
+ );
3753
+ });
3754
+ Avatar.displayName = "Avatar";
3755
+ var AvatarStack = (0, import_react22.forwardRef)(
3756
+ function AvatarStack2({
3757
+ children,
3758
+ max,
3759
+ size = "md",
3760
+ renderOverflow,
3761
+ direction = "ltr",
3762
+ className,
3763
+ ...rest
3764
+ }, ref) {
3765
+ const arr = Array.isArray(children) ? children : [children];
3766
+ const visible = max && arr.length > max ? arr.slice(0, max) : arr;
3767
+ const overflow = max && arr.length > max ? arr.length - max : 0;
3768
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
3780
3769
  "div",
3781
3770
  {
3771
+ ...rest,
3772
+ ref,
3782
3773
  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",
3774
+ "ods-avatar-stack",
3775
+ `ods-avatar-stack--${direction}`,
3789
3776
  className
3790
3777
  ),
3791
- style: consumerStyle,
3792
3778
  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,
3779
+ visible,
3780
+ overflow > 0 && (renderOverflow ? renderOverflow(overflow) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
3781
+ "div",
3804
3782
  {
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
- }
3783
+ className: cn(
3784
+ "ods-avatar",
3785
+ `ods-avatar--${size}`,
3786
+ "ods-avatar--circle",
3787
+ "ods-avatar-stack__overflow"
3934
3788
  ),
3935
- document.body
3936
- ),
3937
- maxLength != null && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
3938
- "span",
3789
+ role: "img",
3790
+ "aria-label": `${overflow} more`,
3791
+ 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: [
3792
+ "+",
3793
+ overflow
3794
+ ] }) })
3795
+ }
3796
+ ))
3797
+ ]
3798
+ }
3799
+ );
3800
+ }
3801
+ );
3802
+ AvatarStack.displayName = "AvatarStack";
3803
+
3804
+ // src/components/Card/Card.tsx
3805
+ var import_framer_motion4 = require("framer-motion");
3806
+ var import_react23 = require("react");
3807
+ var import_jsx_runtime11 = require("react/jsx-runtime");
3808
+ var Card = (0, import_react23.forwardRef)(function Card2({
3809
+ variant = "default",
3810
+ padding = "md",
3811
+ radius = "md",
3812
+ hoverable = false,
3813
+ disabled = false,
3814
+ loading = false,
3815
+ asChild = false,
3816
+ onClick,
3817
+ onKeyDown,
3818
+ className,
3819
+ children,
3820
+ ...rest
3821
+ }, ref) {
3822
+ const reducedMotion = (0, import_framer_motion4.useReducedMotion)();
3823
+ const isInteractive = Boolean(onClick) && !disabled && !loading;
3824
+ const handleKeyDown = (e) => {
3825
+ onKeyDown?.(e);
3826
+ if (e.defaultPrevented) return;
3827
+ if (!isInteractive) return;
3828
+ if ((e.key === "Enter" || e.key === " ") && !e.repeat) {
3829
+ e.preventDefault();
3830
+ onClick?.(e);
3831
+ }
3832
+ };
3833
+ const rootClassName = cn(
3834
+ "ods-card",
3835
+ `ods-card--${variant}`,
3836
+ `ods-card--pad-${padding}`,
3837
+ `ods-card--radius-${radius}`,
3838
+ (hoverable || isInteractive) && !loading && !disabled && "ods-card--hoverable",
3839
+ isInteractive && "ods-card--clickable",
3840
+ disabled && "ods-card--disabled",
3841
+ loading && "ods-card--loading",
3842
+ className
3843
+ );
3844
+ if (asChild) {
3845
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3846
+ Slot,
3847
+ {
3848
+ ...rest,
3849
+ ref,
3850
+ className: rootClassName,
3851
+ onClick: isInteractive ? onClick : void 0,
3852
+ onKeyDown: isInteractive ? handleKeyDown : onKeyDown,
3853
+ "aria-disabled": disabled || void 0,
3854
+ children
3855
+ }
3856
+ );
3857
+ }
3858
+ if (loading) {
3859
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3860
+ "div",
3861
+ {
3862
+ ...rest,
3863
+ ref,
3864
+ className: rootClassName,
3865
+ "aria-busy": "true",
3866
+ "aria-label": "Loading",
3867
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "ods-card__skeleton", "data-ods-animate": "shimmer" })
3868
+ }
3869
+ );
3870
+ }
3871
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3872
+ import_framer_motion4.motion.div,
3873
+ {
3874
+ ...rest,
3875
+ ref,
3876
+ onClick: isInteractive ? onClick : void 0,
3877
+ onKeyDown: isInteractive ? handleKeyDown : onKeyDown,
3878
+ className: rootClassName,
3879
+ whileHover: (hoverable || isInteractive) && !reducedMotion ? { y: -2, transition: { duration: 0.15 } } : void 0,
3880
+ whileTap: isInteractive && !reducedMotion ? { scale: 0.99 } : void 0,
3881
+ role: isInteractive ? "button" : rest.role,
3882
+ tabIndex: isInteractive ? rest.tabIndex ?? 0 : rest.tabIndex,
3883
+ "aria-disabled": disabled || void 0,
3884
+ children
3885
+ }
3886
+ );
3887
+ });
3888
+ Card.displayName = "Card";
3889
+ var CardHeader = (0, import_react23.forwardRef)(
3890
+ function CardHeader2({ className, children, ...rest }, ref) {
3891
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { ...rest, ref, className: cn("ods-card__header", className), children });
3892
+ }
3893
+ );
3894
+ CardHeader.displayName = "Card.Header";
3895
+ var CardBody = (0, import_react23.forwardRef)(
3896
+ function CardBody2({ className, children, ...rest }, ref) {
3897
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { ...rest, ref, className: cn("ods-card__body", className), children });
3898
+ }
3899
+ );
3900
+ CardBody.displayName = "Card.Body";
3901
+ var CardFooter = (0, import_react23.forwardRef)(
3902
+ function CardFooter2({ className, children, ...rest }, ref) {
3903
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { ...rest, ref, className: cn("ods-card__footer", className), children });
3904
+ }
3905
+ );
3906
+ CardFooter.displayName = "Card.Footer";
3907
+ var CardTitle = (0, import_react23.forwardRef)(
3908
+ function CardTitle2({ as: Comp = "h3", className, children, ...rest }, ref) {
3909
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3910
+ Comp,
3911
+ {
3912
+ ...rest,
3913
+ ref,
3914
+ className: cn("ods-card__title", className),
3915
+ children
3916
+ }
3917
+ );
3918
+ }
3919
+ );
3920
+ CardTitle.displayName = "Card.Title";
3921
+ var CardDescription = (0, import_react23.forwardRef)(function CardDescription2({ className, children, ...rest }, ref) {
3922
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { ...rest, ref, className: cn("ods-card__description", className), children });
3923
+ });
3924
+ CardDescription.displayName = "Card.Description";
3925
+ Card.Header = CardHeader;
3926
+ Card.Body = CardBody;
3927
+ Card.Footer = CardFooter;
3928
+ Card.Title = CardTitle;
3929
+ Card.Description = CardDescription;
3930
+
3931
+ // src/components/BlogCard/BlogCard.tsx
3932
+ var import_jsx_runtime12 = require("react/jsx-runtime");
3933
+ var BlogCard = (0, import_react24.forwardRef)(
3934
+ function BlogCard2({
3935
+ cover,
3936
+ coverAspect = "16/9",
3937
+ category,
3938
+ tags,
3939
+ meta,
3940
+ readingTime,
3941
+ title,
3942
+ titleAs = "h3",
3943
+ description,
3944
+ author,
3945
+ footer,
3946
+ radius = "md",
3947
+ orientation = "vertical",
3948
+ href,
3949
+ onClick,
3950
+ id: providedId,
3951
+ className,
3952
+ ...rest
3953
+ }, ref) {
3954
+ const reactId = (0, import_react24.useId)();
3955
+ const baseId = providedId ?? `ods-blog-card-${reactId}`;
3956
+ const titleId = `${baseId}-title`;
3957
+ const descId = description ? `${baseId}-desc` : void 0;
3958
+ const tagList = tags ?? (category ? [category] : []);
3959
+ const body = /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
3960
+ cover && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3961
+ "div",
3962
+ {
3963
+ className: cn(
3964
+ "ods-blog-card__cover",
3965
+ `ods-blog-card__cover--aspect-${coverAspect.replace("/", "-")}`
3966
+ ),
3967
+ "aria-hidden": "true",
3968
+ children: cover
3969
+ }
3970
+ ),
3971
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "ods-blog-card__body", children: [
3972
+ (tagList.length > 0 || meta || readingTime) && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "ods-blog-card__meta", children: [
3973
+ 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)) }),
3974
+ meta && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "ods-blog-card__date", children: meta }),
3975
+ readingTime && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "ods-blog-card__reading", children: readingTime })
3976
+ ] }),
3977
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3978
+ Card.Title,
3979
+ {
3980
+ as: titleAs,
3981
+ id: titleId,
3982
+ className: "ods-blog-card__title",
3983
+ children: title
3984
+ }
3985
+ ),
3986
+ description && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3987
+ Card.Description,
3988
+ {
3989
+ id: descId,
3990
+ className: "ods-blog-card__desc",
3991
+ children: description
3992
+ }
3993
+ ),
3994
+ (author || footer) && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "ods-blog-card__footer", children: [
3995
+ author && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "ods-blog-card__author", children: [
3996
+ author.avatar && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
3997
+ Avatar,
3939
3998
  {
3940
- className: "ods-textarea__count",
3941
- "aria-live": charCount > maxLength ? "polite" : "off",
3942
- children: [
3943
- charCount,
3944
- "/",
3945
- maxLength
3946
- ]
3999
+ size: "sm",
4000
+ src: author.avatar.src,
4001
+ initials: author.avatar.initials,
4002
+ alt: author.avatar.alt ?? (typeof author.name === "string" ? author.name : void 0)
3947
4003
  }
3948
- )
4004
+ ),
4005
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "ods-blog-card__author-text", children: [
4006
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "ods-blog-card__author-name", children: author.name }),
4007
+ author.byline && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "ods-blog-card__author-byline", children: author.byline })
4008
+ ] })
3949
4009
  ] }),
3950
- toolbarEnabled && toolbarPosition === "bottom" && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3951
- TextareaToolbar,
3952
- {
3953
- tools: toolbar.tools,
3954
- onRun: toolbar.runTool
3955
- }
4010
+ footer && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "ods-blog-card__action", children: footer })
4011
+ ] })
4012
+ ] })
4013
+ ] });
4014
+ if (href) {
4015
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4016
+ Card,
4017
+ {
4018
+ asChild: true,
4019
+ radius,
4020
+ padding: "none",
4021
+ className: cn(
4022
+ "ods-blog-card",
4023
+ `ods-blog-card--${orientation}`,
4024
+ "ods-blog-card--interactive",
4025
+ className
3956
4026
  ),
3957
- error && errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3958
- "div",
4027
+ "aria-labelledby": titleId,
4028
+ "aria-describedby": descId,
4029
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4030
+ "a",
3959
4031
  {
3960
- id: hintId,
3961
- className: "ods-textarea__error-message",
3962
- role: "alert",
3963
- children: errorMessage
4032
+ ...rest,
4033
+ ref,
4034
+ href,
4035
+ id: baseId,
4036
+ onClick,
4037
+ children: body
3964
4038
  }
3965
- ) : helperText ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { id: hintId, className: "ods-textarea__hint", children: helperText }) : null
3966
- ]
4039
+ )
4040
+ }
4041
+ );
4042
+ }
4043
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
4044
+ Card,
4045
+ {
4046
+ ...rest,
4047
+ ref,
4048
+ id: baseId,
4049
+ radius,
4050
+ padding: "none",
4051
+ onClick,
4052
+ className: cn(
4053
+ "ods-blog-card",
4054
+ `ods-blog-card--${orientation}`,
4055
+ onClick && "ods-blog-card--interactive",
4056
+ className
4057
+ ),
4058
+ "aria-labelledby": titleId,
4059
+ "aria-describedby": descId,
4060
+ children: body
3967
4061
  }
3968
4062
  );
3969
4063
  }
3970
4064
  );
3971
- Textarea.displayName = "Textarea";
3972
- function TextareaToolbar({
3973
- tools,
3974
- onRun
4065
+ BlogCard.displayName = "BlogCard";
4066
+
4067
+ // src/marketing/components/BlogGrid/BlogGrid.tsx
4068
+ var import_jsx_runtime13 = require("react/jsx-runtime");
4069
+ function BlogGrid({
4070
+ posts,
4071
+ columns = 3,
4072
+ viewAllHref,
4073
+ viewAllLabel = "View all posts \u2192",
4074
+ className,
4075
+ ...rest
3975
4076
  }) {
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
- }
4077
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { ...rest, className: cn("ods-mkt-blog", className), children: [
4078
+ /* @__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)(
4079
+ BlogCard,
4080
+ {
4081
+ title: post.title,
4082
+ description: post.excerpt,
4083
+ href: post.href,
4084
+ meta: post.date,
4085
+ readingTime: post.readTime,
4086
+ tags: post.tags,
4087
+ cover: post.cover,
4088
+ author: post.author
3986
4089
  },
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
- );
4090
+ post.id
4091
+ )) }),
4092
+ 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 }) })
4093
+ ] });
4013
4094
  }
4014
4095
 
4015
4096
  // src/marketing/components/ContactForm/ContactForm.tsx
4097
+ var import_react25 = require("react");
4016
4098
  var import_jsx_runtime14 = require("react/jsx-runtime");
4017
4099
  function ContactForm({
4018
4100
  title = "Talk to us",
@@ -4158,7 +4240,7 @@ function ContactForm({
4158
4240
  {
4159
4241
  id: `${uid}-message`,
4160
4242
  "aria-labelledby": `${uid}-message-label`,
4161
- placeholder: "What are you trying to build?",
4243
+ placeholder: selectedTopic?.messagePlaceholder ?? "What are you trying to build?",
4162
4244
  rows: 4,
4163
4245
  value: values.message,
4164
4246
  onChange: (e) => setValues((v) => ({ ...v, message: e.target.value })),
@@ -4973,7 +5055,24 @@ function MarketingHero({
4973
5055
  }
4974
5056
 
4975
5057
  // src/marketing/components/MarketingNav/MarketingNav.tsx
5058
+ var import_react29 = require("react");
4976
5059
  var import_jsx_runtime27 = require("react/jsx-runtime");
5060
+ function BurgerGlyph({ open }) {
5061
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
5062
+ "svg",
5063
+ {
5064
+ viewBox: "0 0 24 24",
5065
+ width: "20",
5066
+ height: "20",
5067
+ fill: "none",
5068
+ stroke: "currentColor",
5069
+ strokeWidth: "2",
5070
+ strokeLinecap: "round",
5071
+ "aria-hidden": "true",
5072
+ 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" })
5073
+ }
5074
+ );
5075
+ }
4977
5076
  function MarketingNav({
4978
5077
  brand,
4979
5078
  brandHref = "/",
@@ -4983,6 +5082,7 @@ function MarketingNav({
4983
5082
  className,
4984
5083
  ...rest
4985
5084
  }) {
5085
+ const [mobileOpen, setMobileOpen] = (0, import_react29.useState)(false);
4986
5086
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("header", { ...rest, className: cn("ods-mkt-nav", sticky && "ods-mkt-nav--sticky", className), children: [
4987
5087
  /* @__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
5088
  /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(BrandMark, {}),
@@ -4998,7 +5098,35 @@ function MarketingNav({
4998
5098
  },
4999
5099
  link.href
5000
5100
  )) }),
5001
- actions && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "ods-mkt-nav__actions", children: actions })
5101
+ actions && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "ods-mkt-nav__actions", children: actions }),
5102
+ (links.length > 0 || actions) && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
5103
+ "button",
5104
+ {
5105
+ type: "button",
5106
+ className: "ods-mkt-nav__toggle",
5107
+ "aria-expanded": mobileOpen,
5108
+ "aria-label": "Toggle menu",
5109
+ onClick: () => setMobileOpen((open) => !open),
5110
+ children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(BurgerGlyph, { open: mobileOpen })
5111
+ }
5112
+ ),
5113
+ mobileOpen && /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)("nav", { className: "ods-mkt-nav__mobile", "aria-label": "Main", children: [
5114
+ links.map((link) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
5115
+ "a",
5116
+ {
5117
+ href: link.href,
5118
+ className: cn(
5119
+ "ods-mkt-nav__mobile-link",
5120
+ link.active && "ods-mkt-nav__mobile-link--active"
5121
+ ),
5122
+ "aria-current": link.active ? "page" : void 0,
5123
+ onClick: () => setMobileOpen(false),
5124
+ children: link.label
5125
+ },
5126
+ link.href
5127
+ )),
5128
+ actions && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "ods-mkt-nav__mobile-actions", children: actions })
5129
+ ] })
5002
5130
  ] });
5003
5131
  }
5004
5132
 
@@ -5122,10 +5250,10 @@ function PricingTiers({ tiers, note, className, ...rest }) {
5122
5250
  var import_framer_motion7 = require("framer-motion");
5123
5251
 
5124
5252
  // src/components/CountUp/CountUp.tsx
5125
- var import_react29 = require("react");
5253
+ var import_react30 = require("react");
5126
5254
  var import_jsx_runtime31 = require("react/jsx-runtime");
5127
5255
  var easeOutCubic = (t) => 1 - Math.pow(1 - t, 3);
5128
- var CountUp = (0, import_react29.forwardRef)(
5256
+ var CountUp = (0, import_react30.forwardRef)(
5129
5257
  function CountUp2({
5130
5258
  value,
5131
5259
  from = 0,
@@ -5141,9 +5269,9 @@ var CountUp = (0, import_react29.forwardRef)(
5141
5269
  className,
5142
5270
  ...rest
5143
5271
  }, 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)(() => {
5272
+ const [n, setN] = (0, import_react30.useState)(disableAnimation ? value : from);
5273
+ const lastRendered = (0, import_react30.useRef)(disableAnimation ? value : from);
5274
+ (0, import_react30.useEffect)(() => {
5147
5275
  const reduced = typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
5148
5276
  if (disableAnimation || reduced) {
5149
5277
  setN(value);
@@ -5241,13 +5369,13 @@ function StatBand({ items, orientation = "column", className, ...rest }) {
5241
5369
 
5242
5370
  // src/components/TestimonialCard/TestimonialCard.tsx
5243
5371
  var import_icons9 = require("@octaviaflow/icons");
5244
- var import_react30 = require("react");
5372
+ var import_react31 = require("react");
5245
5373
  var import_jsx_runtime33 = require("react/jsx-runtime");
5246
5374
  function isAuthorObject(v) {
5247
5375
  return typeof v === "object" && v !== null && !("type" in v) && // exclude React elements
5248
5376
  "name" in v;
5249
5377
  }
5250
- var TestimonialCard = (0, import_react30.forwardRef)(function TestimonialCard2({
5378
+ var TestimonialCard = (0, import_react31.forwardRef)(function TestimonialCard2({
5251
5379
  quote,
5252
5380
  author,
5253
5381
  role: legacyRole,
@@ -5261,7 +5389,7 @@ var TestimonialCard = (0, import_react30.forwardRef)(function TestimonialCard2({
5261
5389
  className,
5262
5390
  ...rest
5263
5391
  }, ref) {
5264
- const reactId = (0, import_react30.useId)();
5392
+ const reactId = (0, import_react31.useId)();
5265
5393
  const baseId = providedId ?? `ods-testimonial-${reactId}`;
5266
5394
  const quoteId = `${baseId}-quote`;
5267
5395
  const authorId = `${baseId}-author`;
@@ -5383,10 +5511,10 @@ function TestimonialStrip({
5383
5511
 
5384
5512
  // src/marketing/components/WorkflowShowcase/WorkflowShowcase.tsx
5385
5513
  var import_framer_motion9 = require("framer-motion");
5386
- var import_react33 = require("react");
5514
+ var import_react34 = require("react");
5387
5515
 
5388
5516
  // src/marketing/components/WorkflowShowcase/ShowcaseScene.tsx
5389
- var import_react31 = require("react");
5517
+ var import_react32 = require("react");
5390
5518
  var import_jsx_runtime35 = require("react/jsx-runtime");
5391
5519
  function slots(n) {
5392
5520
  return Array.from({ length: n }, (_, i) => 208 + (i - (n - 1) / 2) * 80);
@@ -5459,9 +5587,9 @@ function ShowcaseScene({
5459
5587
  caption,
5460
5588
  onCoreClick
5461
5589
  }) {
5462
- const [pulse, setPulse] = (0, import_react31.useState)(false);
5463
- const pulseTimer = (0, import_react31.useRef)(null);
5464
- (0, import_react31.useEffect)(
5590
+ const [pulse, setPulse] = (0, import_react32.useState)(false);
5591
+ const pulseTimer = (0, import_react32.useRef)(null);
5592
+ (0, import_react32.useEffect)(
5465
5593
  () => () => {
5466
5594
  if (pulseTimer.current) clearTimeout(pulseTimer.current);
5467
5595
  },
@@ -5692,7 +5820,7 @@ function ShowcaseScene({
5692
5820
 
5693
5821
  // src/marketing/components/WorkflowShowcase/ShowcaseSignup.tsx
5694
5822
  var import_framer_motion8 = require("framer-motion");
5695
- var import_react32 = require("react");
5823
+ var import_react33 = require("react");
5696
5824
  var import_react_aria5 = require("react-aria");
5697
5825
  var import_jsx_runtime36 = require("react/jsx-runtime");
5698
5826
  var OVERLAY_MOTION = {
@@ -5712,9 +5840,9 @@ function ShowcaseSignup({
5712
5840
  onboardingHref = "#"
5713
5841
  }) {
5714
5842
  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)({
5843
+ const titleId = (0, import_react33.useId)();
5844
+ const subtitleId = (0, import_react33.useId)();
5845
+ const [values, setValues] = (0, import_react33.useState)({
5718
5846
  name: "",
5719
5847
  email: "",
5720
5848
  company: "",
@@ -5932,53 +6060,53 @@ function WorkflowShowcase({
5932
6060
  }) {
5933
6061
  const reducedMotion = (0, import_framer_motion9.useReducedMotion)();
5934
6062
  const rotationActive = rotatePrompts && !defaultPrompt && examples.length > 0;
5935
- const [prompt, setPrompt] = (0, import_react33.useState)(
6063
+ const [prompt, setPrompt] = (0, import_react34.useState)(
5936
6064
  defaultPrompt || (rotationActive ? examples[0]?.prompt ?? "" : "")
5937
6065
  );
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)(() => {
6066
+ const [engagedWithPrompt, setEngagedWithPrompt] = (0, import_react34.useState)(false);
6067
+ const [promptHovered, setPromptHovered] = (0, import_react34.useState)(false);
6068
+ const [promptFocused, setPromptFocused] = (0, import_react34.useState)(false);
6069
+ const rotateIdx = (0, import_react34.useRef)(0);
6070
+ const [selected, setSelected] = (0, import_react34.useState)([]);
6071
+ const [phase, setPhase] = (0, import_react34.useState)("idle");
6072
+ const [shown, setShown] = (0, import_react34.useState)(0);
6073
+ const [status, setStatus] = (0, import_react34.useState)("");
6074
+ const [pickerOpen, setPickerOpen] = (0, import_react34.useState)(false);
6075
+ const [schedIdx, setSchedIdx] = (0, import_react34.useState)(0);
6076
+ const [swap, setSwap] = (0, import_react34.useState)(null);
6077
+ const [signupError, setSignupError] = (0, import_react34.useState)(null);
6078
+ const signupReturnPhase = (0, import_react34.useRef)("done");
6079
+ const cardRef = (0, import_react34.useRef)(null);
6080
+ const timers = (0, import_react34.useRef)([]);
6081
+ const clearTimers = (0, import_react34.useCallback)(() => {
5954
6082
  for (const t of timers.current) clearTimeout(t);
5955
6083
  timers.current = [];
5956
6084
  }, []);
5957
- (0, import_react33.useEffect)(() => clearTimers, [clearTimers]);
6085
+ (0, import_react34.useEffect)(() => clearTimers, [clearTimers]);
5958
6086
  const tempo = reducedMotion ? 0.35 : 1;
5959
- const at = (0, import_react33.useCallback)(
6087
+ const at = (0, import_react34.useCallback)(
5960
6088
  (ms, fn) => {
5961
6089
  timers.current.push(setTimeout(fn, ms * tempo));
5962
6090
  },
5963
6091
  [tempo]
5964
6092
  );
5965
- const firstPhase = (0, import_react33.useRef)(true);
5966
- (0, import_react33.useEffect)(() => {
6093
+ const firstPhase = (0, import_react34.useRef)(true);
6094
+ (0, import_react34.useEffect)(() => {
5967
6095
  if (firstPhase.current) {
5968
6096
  firstPhase.current = false;
5969
6097
  return;
5970
6098
  }
5971
6099
  onPhaseChange?.(phase);
5972
6100
  }, [phase, onPhaseChange]);
5973
- const phaseRef = (0, import_react33.useRef)(phase);
6101
+ const phaseRef = (0, import_react34.useRef)(phase);
5974
6102
  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]);
6103
+ const byName = (0, import_react34.useMemo)(() => new Map(connectors.map((c) => [c.name, c])), [connectors]);
6104
+ const detected = (0, import_react34.useMemo)(() => detectConnectors(prompt, connectors), [prompt, connectors]);
6105
+ const union = (0, import_react34.useMemo)(() => [.../* @__PURE__ */ new Set([...selected, ...detected])], [selected, detected]);
5978
6106
  const settled = SETTLED_PHASES.includes(phase);
5979
6107
  const engaged = phase !== "idle";
5980
6108
  const buildingNow = phase === "analyzing" || phase === "building";
5981
- const plan = (0, import_react33.useMemo)(() => {
6109
+ const plan = (0, import_react34.useMemo)(() => {
5982
6110
  if (!selected.length) return EMPTY_PLAN;
5983
6111
  return resolvePlan(selected, [], connectors);
5984
6112
  }, [selected, connectors]);
@@ -5987,10 +6115,10 @@ function WorkflowShowcase({
5987
6115
  const manualSchedule = schedules[schedIdx];
5988
6116
  const scheduleShown = inferredSchedule ?? (manualSchedule === "AUTO" ? "HOURLY" : manualSchedule);
5989
6117
  const triggers = WORKFLOW_SHOWCASE_TRIGGERS;
5990
- const [triggerIdx, setTriggerIdx] = (0, import_react33.useState)(0);
6118
+ const [triggerIdx, setTriggerIdx] = (0, import_react34.useState)(0);
5991
6119
  const manualTrigger = triggers[triggerIdx];
5992
6120
  const cadenceShown = manualTrigger === "SCHEDULE" ? scheduleShown : manualTrigger;
5993
- (0, import_react33.useEffect)(() => {
6121
+ (0, import_react34.useEffect)(() => {
5994
6122
  if (!rotationActive || engagedWithPrompt || promptHovered || promptFocused || phase !== "idle") {
5995
6123
  return;
5996
6124
  }
@@ -6000,7 +6128,7 @@ function WorkflowShowcase({
6000
6128
  }, 4200);
6001
6129
  return () => clearInterval(timer);
6002
6130
  }, [rotationActive, engagedWithPrompt, promptHovered, promptFocused, phase, examples]);
6003
- const startBuild = (0, import_react33.useCallback)(
6131
+ const startBuild = (0, import_react34.useCallback)(
6004
6132
  (text, baseSelected) => {
6005
6133
  if (phaseRef.current === "analyzing" || phaseRef.current === "building") return;
6006
6134
  phaseRef.current = "analyzing";
@@ -6037,7 +6165,7 @@ function WorkflowShowcase({
6037
6165
  },
6038
6166
  [clearTimers, connectors, onBuild, scheduleShown, manualTrigger, at]
6039
6167
  );
6040
- const resetToIdle = (0, import_react33.useCallback)(() => {
6168
+ const resetToIdle = (0, import_react34.useCallback)(() => {
6041
6169
  clearTimers();
6042
6170
  phaseRef.current = "idle";
6043
6171
  setPhase("idle");