@refraction-ui/react 0.11.0 → 0.12.0

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.
package/dist/index.cjs CHANGED
@@ -33356,6 +33356,8 @@ function Composer({
33356
33356
  toolbar = true,
33357
33357
  emoji = true,
33358
33358
  attachments = true,
33359
+ error,
33360
+ onRetry,
33359
33361
  onSubmit,
33360
33362
  onStop,
33361
33363
  onSlashCommand,
@@ -33496,148 +33498,154 @@ ${sel}
33496
33498
  submit();
33497
33499
  }
33498
33500
  }
33501
+ const iconBtn = "flex h-8 w-8 items-center justify-center rounded-lg text-muted-foreground hover:bg-accent hover:text-foreground";
33499
33502
  const toolbarBtn = (label, title, kind) => h(
33500
33503
  "button",
33501
33504
  {
33502
33505
  key: kind,
33503
33506
  type: "button",
33504
33507
  title,
33505
- className: "rounded px-1.5 py-0.5 text-xs text-muted-foreground hover:bg-accent hover:text-foreground",
33508
+ className: cn(iconBtn, "text-xs font-medium"),
33506
33509
  onMouseDown: (e2) => e2.preventDefault(),
33507
33510
  // keep textarea selection
33508
33511
  onClick: () => format(kind)
33509
33512
  },
33510
33513
  label
33511
33514
  );
33512
- return h(
33515
+ const menu = menuOpen ? h(
33513
33516
  "div",
33514
- { className: "border-t border-border" },
33515
- // attachment chips
33516
- pending.length > 0 ? h(
33517
+ {
33518
+ className: "absolute bottom-full left-0 z-20 mb-2 w-72 overflow-hidden rounded-xl border border-border bg-popover shadow-lg",
33519
+ role: "listbox"
33520
+ },
33521
+ h(
33517
33522
  "div",
33518
- { className: "flex flex-wrap gap-2 px-3 pt-2" },
33519
- ...pending.map(
33520
- (a2) => h(
33521
- "span",
33522
- { key: a2.id, className: "inline-flex items-center gap-1 rounded bg-muted px-2 py-0.5 text-xs" },
33523
- a2.name,
33524
- h(
33525
- "button",
33526
- {
33527
- type: "button",
33528
- className: "text-muted-foreground hover:text-destructive",
33529
- onClick: () => setPending((p2) => p2.filter((x2) => x2.id !== a2.id))
33530
- },
33531
- "\u2715"
33532
- )
33533
- )
33523
+ { className: "border-b border-border px-3 py-1.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground" },
33524
+ trigger?.type === "/" ? "Commands" : trigger?.type === "@" ? "Mentions" : "Emoji"
33525
+ ),
33526
+ ...items.map(
33527
+ (it2, i2) => h(
33528
+ "button",
33529
+ {
33530
+ key: it2.key,
33531
+ type: "button",
33532
+ role: "option",
33533
+ "aria-selected": i2 === active,
33534
+ className: cn(
33535
+ "flex w-full items-center gap-2 px-3 py-2 text-left text-sm",
33536
+ i2 === active ? "bg-accent" : "hover:bg-accent/50"
33537
+ ),
33538
+ onMouseEnter: () => setActive(i2),
33539
+ onMouseDown: (e2) => e2.preventDefault(),
33540
+ onClick: () => selectItem(i2)
33541
+ },
33542
+ it2.icon ? h("span", { className: "w-4 text-center text-muted-foreground" }, it2.icon) : null,
33543
+ h("span", { className: "flex-1 truncate" }, it2.primary),
33544
+ it2.secondary ? h("span", { className: "truncate text-xs text-muted-foreground" }, it2.secondary) : null
33534
33545
  )
33535
- ) : null,
33536
- // toolbar
33537
- toolbar ? h(
33538
- "div",
33539
- { className: "flex items-center gap-0.5 px-2 pt-2" },
33540
- toolbarBtn("B", "Bold (\u2318B)", "bold"),
33541
- toolbarBtn("\u{1D456}", "Italic (\u2318I)", "italic"),
33542
- toolbarBtn("</>", "Code (\u2318E)", "code"),
33543
- toolbarBtn("\u{1F517}", "Link (\u2318K)", "link"),
33544
- toolbarBtn("\u275D", "Quote", "quote"),
33545
- toolbarBtn("\u2022", "Bulleted list", "ul"),
33546
- toolbarBtn("1.", "Numbered list", "ol")
33547
- ) : null,
33548
- // input row (relative for the popup menu)
33546
+ )
33547
+ ) : null;
33548
+ return h(
33549
+ "div",
33550
+ { className: "p-3" },
33549
33551
  h(
33550
33552
  "div",
33551
- { className: "relative flex items-end gap-2 p-3" },
33552
- menuOpen ? h(
33553
+ { className: "relative" },
33554
+ menu,
33555
+ // unified input card
33556
+ h(
33553
33557
  "div",
33554
33558
  {
33555
- className: "absolute bottom-full left-3 z-20 mb-1 w-72 overflow-hidden rounded-lg border border-border bg-popover shadow-lg",
33556
- role: "listbox"
33559
+ className: "overflow-hidden rounded-2xl border border-border bg-background transition focus-within:border-primary focus-within:ring-2 focus-within:ring-primary/40"
33557
33560
  },
33561
+ // error banner
33562
+ error ? h(
33563
+ "div",
33564
+ { className: "flex items-center gap-2 border-b border-border bg-destructive/5 px-3 py-2 text-xs text-destructive", role: "alert" },
33565
+ h("span", { className: "flex-1 truncate" }, error),
33566
+ onRetry ? h("button", { type: "button", className: "font-medium underline", onClick: () => onRetry() }, "Retry") : null
33567
+ ) : null,
33568
+ // attachment chips
33569
+ pending.length > 0 ? h(
33570
+ "div",
33571
+ { className: "flex flex-wrap gap-2 px-3 pt-3" },
33572
+ ...pending.map(
33573
+ (a2) => h(
33574
+ "span",
33575
+ { key: a2.id, className: "inline-flex items-center gap-1 rounded-md bg-muted px-2 py-0.5 text-xs" },
33576
+ a2.name,
33577
+ h(
33578
+ "button",
33579
+ { type: "button", className: "text-muted-foreground hover:text-destructive", onClick: () => setPending((p2) => p2.filter((x2) => x2.id !== a2.id)) },
33580
+ "\u2715"
33581
+ )
33582
+ )
33583
+ )
33584
+ ) : null,
33585
+ // textarea (borderless)
33586
+ h("textarea", {
33587
+ ref,
33588
+ className: "block max-h-40 w-full resize-none bg-transparent px-3.5 py-3 text-sm placeholder:text-muted-foreground focus:outline-none",
33589
+ rows: 1,
33590
+ value,
33591
+ placeholder,
33592
+ autoFocus,
33593
+ "aria-label": "Message",
33594
+ onChange: (e2) => syncFromTextarea(e2.target),
33595
+ onClick: (e2) => syncFromTextarea(e2.currentTarget),
33596
+ onKeyUp: (e2) => syncFromTextarea(e2.currentTarget),
33597
+ onKeyDown,
33598
+ onBlur: () => setTimeout(() => setTrigger(null), 120)
33599
+ }),
33600
+ // bottom action bar
33558
33601
  h(
33559
33602
  "div",
33560
- { className: "border-b border-border px-2 py-1 text-[10px] uppercase tracking-wide text-muted-foreground" },
33561
- trigger?.type === "/" ? "Commands" : trigger?.type === "@" ? "Mentions" : "Emoji"
33562
- ),
33563
- ...items.map(
33564
- (it2, i2) => h(
33603
+ { className: "flex items-center gap-0.5 px-2 pb-2" },
33604
+ attachments ? h(
33605
+ React11__namespace.Fragment,
33606
+ null,
33607
+ h("input", {
33608
+ ref: fileRef,
33609
+ type: "file",
33610
+ accept: "image/*",
33611
+ multiple: true,
33612
+ className: "hidden",
33613
+ onChange: (e2) => {
33614
+ onFiles(e2.target.files);
33615
+ e2.target.value = "";
33616
+ }
33617
+ }),
33618
+ h("button", { type: "button", className: iconBtn, "aria-label": "Attach image or GIF", onClick: () => fileRef.current?.click() }, "\u{1F4CE}")
33619
+ ) : null,
33620
+ attachments && toolbar ? h("span", { className: "mx-1 h-5 w-px bg-border" }) : null,
33621
+ toolbar ? h(
33622
+ React11__namespace.Fragment,
33623
+ null,
33624
+ toolbarBtn("B", "Bold (\u2318B)", "bold"),
33625
+ toolbarBtn("\u{1D456}", "Italic (\u2318I)", "italic"),
33626
+ toolbarBtn("</>", "Code (\u2318E)", "code"),
33627
+ toolbarBtn("\u{1F517}", "Link (\u2318K)", "link"),
33628
+ toolbarBtn("\u275D", "Quote", "quote"),
33629
+ toolbarBtn("\u2022", "Bulleted list", "ul"),
33630
+ toolbarBtn("1.", "Numbered list", "ol")
33631
+ ) : null,
33632
+ h("div", { className: "flex-1" }),
33633
+ busy ? h(
33634
+ "button",
33635
+ { type: "button", "aria-label": "Stop", className: "flex h-8 w-8 items-center justify-center rounded-lg bg-primary text-primary-foreground", onClick: () => onStop?.() },
33636
+ "\u25A0"
33637
+ ) : h(
33565
33638
  "button",
33566
33639
  {
33567
- key: it2.key,
33568
33640
  type: "button",
33569
- role: "option",
33570
- "aria-selected": i2 === active,
33571
- className: cn(
33572
- "flex w-full items-center gap-2 px-2 py-1.5 text-left text-sm",
33573
- i2 === active ? "bg-accent" : "hover:bg-accent/50"
33574
- ),
33575
- onMouseEnter: () => setActive(i2),
33576
- onMouseDown: (e2) => e2.preventDefault(),
33577
- onClick: () => selectItem(i2)
33641
+ "aria-label": "Send",
33642
+ className: "flex h-8 w-8 items-center justify-center rounded-lg bg-primary text-base font-semibold text-primary-foreground transition disabled:opacity-40",
33643
+ disabled: !value.trim() && pending.length === 0,
33644
+ onClick: submit
33578
33645
  },
33579
- it2.icon ? h("span", { className: "w-4 text-center text-muted-foreground" }, it2.icon) : null,
33580
- h("span", { className: "flex-1 truncate" }, it2.primary),
33581
- it2.secondary ? h("span", { className: "truncate text-xs text-muted-foreground" }, it2.secondary) : null
33646
+ "\u2191"
33582
33647
  )
33583
33648
  )
33584
- ) : null,
33585
- attachments ? h(
33586
- React11__namespace.Fragment,
33587
- null,
33588
- h("input", {
33589
- ref: fileRef,
33590
- type: "file",
33591
- accept: "image/*",
33592
- multiple: true,
33593
- className: "hidden",
33594
- onChange: (e2) => {
33595
- onFiles(e2.target.files);
33596
- e2.target.value = "";
33597
- }
33598
- }),
33599
- h(
33600
- "button",
33601
- {
33602
- type: "button",
33603
- className: "rounded-md border border-border px-2 py-2 text-sm hover:bg-accent",
33604
- "aria-label": "Attach image or GIF",
33605
- onClick: () => fileRef.current?.click()
33606
- },
33607
- "\u{1F4CE}"
33608
- )
33609
- ) : null,
33610
- h("textarea", {
33611
- ref,
33612
- className: "max-h-40 flex-1 resize-none rounded-md border border-border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary",
33613
- rows: 1,
33614
- value,
33615
- placeholder,
33616
- autoFocus,
33617
- "aria-label": "Message",
33618
- onChange: (e2) => syncFromTextarea(e2.target),
33619
- onClick: (e2) => syncFromTextarea(e2.currentTarget),
33620
- onKeyUp: (e2) => syncFromTextarea(e2.currentTarget),
33621
- onKeyDown,
33622
- onBlur: () => setTimeout(() => setTrigger(null), 120)
33623
- }),
33624
- busy ? h(
33625
- "button",
33626
- {
33627
- type: "button",
33628
- className: "rounded-md bg-primary px-3 py-2 text-sm font-medium text-primary-foreground",
33629
- onClick: () => onStop?.()
33630
- },
33631
- "Stop"
33632
- ) : h(
33633
- "button",
33634
- {
33635
- type: "button",
33636
- className: "rounded-md bg-primary px-3 py-2 text-sm font-medium text-primary-foreground disabled:opacity-50",
33637
- disabled: !value.trim() && pending.length === 0,
33638
- onClick: submit
33639
- },
33640
- "Send"
33641
33649
  )
33642
33650
  )
33643
33651
  );
@@ -33985,9 +33993,13 @@ function Chat({
33985
33993
  const timeline = selectMainTimeline(state.messages, state.threadingMode);
33986
33994
  const activeConv = state.conversations.find((c2) => c2.id === state.activeConversationId);
33987
33995
  const busy = state.status === "sending" || state.status === "streaming";
33996
+ const error = state.status === "error" ? state.error : null;
33997
+ const onRetry = () => void conversation.retryLast();
33988
33998
  const mainComposer = h2(Composer, {
33989
33999
  placeholder,
33990
34000
  busy,
34001
+ error,
34002
+ onRetry,
33991
34003
  slashCommands,
33992
34004
  mentions,
33993
34005
  onSlashCommand,
@@ -33998,6 +34010,8 @@ function Chat({
33998
34010
  const threadComposer = state.openThreadRootId ? h2(Composer, {
33999
34011
  placeholder: "Reply\u2026",
34000
34012
  busy,
34013
+ error,
34014
+ onRetry,
34001
34015
  slashCommands,
34002
34016
  mentions,
34003
34017
  onSlashCommand,
@@ -34301,14 +34315,56 @@ function useCookieConsent(config) {
34301
34315
  };
34302
34316
  }
34303
34317
  var h3 = React11__namespace.createElement;
34304
- var btnPrimary = "rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:opacity-90";
34305
- var btnGhost = "rounded-md border border-border px-3 py-1.5 text-sm hover:bg-accent";
34306
- var btnLink = "text-sm text-muted-foreground underline hover:text-foreground";
34318
+ var btnBase = "inline-flex items-center justify-center rounded-lg px-3.5 py-2 text-sm font-medium transition-colors";
34319
+ var btnPrimary = cn(btnBase, "bg-primary text-primary-foreground hover:opacity-90");
34320
+ var btnGhost = cn(btnBase, "border border-border hover:bg-accent");
34321
+ var btnLink = "text-sm font-medium text-muted-foreground underline-offset-4 hover:text-foreground hover:underline";
34322
+ function Toggle({
34323
+ checked,
34324
+ disabled,
34325
+ onChange,
34326
+ label
34327
+ }) {
34328
+ if (disabled) {
34329
+ return h3(
34330
+ "span",
34331
+ { className: "rounded-full bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground" },
34332
+ "Always on"
34333
+ );
34334
+ }
34335
+ return h3(
34336
+ "button",
34337
+ {
34338
+ type: "button",
34339
+ role: "switch",
34340
+ "aria-checked": checked,
34341
+ "aria-label": label,
34342
+ onClick: () => onChange(!checked),
34343
+ className: cn(
34344
+ "relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors",
34345
+ checked ? "bg-primary" : "bg-muted"
34346
+ )
34347
+ },
34348
+ h3("span", {
34349
+ className: cn(
34350
+ "inline-block h-4 w-4 transform rounded-full bg-background shadow transition-transform",
34351
+ checked ? "translate-x-[1.125rem]" : "translate-x-0.5"
34352
+ )
34353
+ })
34354
+ );
34355
+ }
34356
+ function CookieIcon() {
34357
+ return h3(
34358
+ "div",
34359
+ { className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-accent text-xl", "aria-hidden": true },
34360
+ "\u{1F36A}"
34361
+ );
34362
+ }
34307
34363
  function CookieConsent({
34308
34364
  consent,
34309
34365
  position = "bottom",
34310
34366
  title = "We use cookies",
34311
- description = "We use cookies to run the site, remember your preferences, and measure traffic. Choose which categories to allow.",
34367
+ description = "We use cookies to run the site, remember your preferences, and measure traffic. Choose which to allow.",
34312
34368
  policyUrl,
34313
34369
  policyLabel = "Cookie policy",
34314
34370
  className
@@ -34316,64 +34372,80 @@ function CookieConsent({
34316
34372
  const { state, acceptAll, rejectAll, savePreferences, setPreference } = consent;
34317
34373
  const [settings, setSettings] = React11__namespace.useState(false);
34318
34374
  if (!state.open) return null;
34319
- const wrapper = cn(
34320
- "fixed inset-x-0 z-50 p-4",
34321
- position === "bottom" ? "bottom-0" : "top-0",
34322
- className
34323
- );
34324
- const panel = "mx-auto max-w-3xl rounded-xl border border-border bg-background p-4 shadow-lg";
34325
- const header = h3(
34375
+ const wrapper = cn("fixed inset-x-0 z-50 p-4", position === "bottom" ? "bottom-0" : "top-0", className);
34376
+ const panel = "mx-auto max-w-2xl overflow-hidden rounded-2xl border border-border bg-background shadow-lg";
34377
+ const policy = policyUrl ? h3("a", { href: policyUrl, target: "_blank", rel: "noreferrer", className: cn(btnLink, "whitespace-nowrap") }, policyLabel) : null;
34378
+ const promptView = h3(
34326
34379
  "div",
34327
- null,
34328
- h3("h2", { className: "text-base font-semibold" }, title),
34329
- h3("p", { className: "mt-1 text-sm text-muted-foreground" }, description),
34330
- policyUrl ? h3("a", { href: policyUrl, target: "_blank", rel: "noreferrer", className: cn(btnLink, "mt-1 inline-block") }, policyLabel) : null
34331
- );
34332
- const promptActions = h3(
34333
- "div",
34334
- { className: "mt-3 flex flex-wrap items-center gap-2" },
34335
- h3("button", { type: "button", className: btnPrimary, onClick: () => acceptAll() }, "Accept all"),
34336
- h3("button", { type: "button", className: btnGhost, onClick: () => rejectAll() }, "Reject all"),
34337
- h3("button", { type: "button", className: cn(btnGhost, "ml-auto"), onClick: () => setSettings(true) }, "Manage preferences")
34380
+ { className: "flex flex-col gap-4 p-5 sm:flex-row sm:items-center" },
34381
+ h3(CookieIcon),
34382
+ h3(
34383
+ "div",
34384
+ { className: "min-w-0 flex-1" },
34385
+ h3("p", { className: "text-sm font-semibold" }, title),
34386
+ h3(
34387
+ "p",
34388
+ { className: "mt-0.5 text-sm leading-relaxed text-muted-foreground" },
34389
+ description,
34390
+ policy ? h3(React11__namespace.Fragment, null, " ", policy) : null
34391
+ )
34392
+ ),
34393
+ h3(
34394
+ "div",
34395
+ { className: "flex flex-wrap items-center gap-2 sm:shrink-0" },
34396
+ h3("button", { type: "button", className: btnLink, onClick: () => setSettings(true) }, "Customize"),
34397
+ h3("button", { type: "button", className: btnGhost, onClick: () => rejectAll() }, "Reject all"),
34398
+ h3("button", { type: "button", className: btnPrimary, onClick: () => acceptAll() }, "Accept all")
34399
+ )
34338
34400
  );
34339
34401
  const settingsView = h3(
34340
34402
  "div",
34341
- { className: "mt-3 space-y-2" },
34342
- ...state.categories.map(
34343
- (cat) => h3(
34344
- "label",
34345
- {
34346
- key: cat.id,
34347
- className: "flex items-start justify-between gap-3 rounded-md border border-border p-3"
34348
- },
34349
- h3(
34350
- "span",
34351
- { className: "min-w-0" },
34352
- h3("span", { className: "block text-sm font-medium" }, cat.label, cat.required ? " (required)" : ""),
34353
- cat.description ? h3("span", { className: "block text-xs text-muted-foreground" }, cat.description) : null
34354
- ),
34355
- h3("input", {
34356
- type: "checkbox",
34357
- className: "mt-0.5 h-4 w-4 accent-[hsl(var(--primary))]",
34358
- checked: !!state.preferences[cat.id],
34359
- disabled: cat.required,
34360
- "aria-label": cat.label,
34361
- onChange: (e2) => setPreference(cat.id, e2.target.checked)
34362
- })
34403
+ { className: "p-5" },
34404
+ h3(
34405
+ "div",
34406
+ { className: "flex items-center gap-3" },
34407
+ h3(CookieIcon),
34408
+ h3(
34409
+ "div",
34410
+ null,
34411
+ h3("p", { className: "text-sm font-semibold" }, "Cookie preferences"),
34412
+ h3("p", { className: "text-xs text-muted-foreground" }, "Toggle the categories you want to allow.")
34413
+ )
34414
+ ),
34415
+ h3(
34416
+ "div",
34417
+ { className: "mt-4 space-y-2" },
34418
+ ...state.categories.map(
34419
+ (cat) => h3(
34420
+ "div",
34421
+ { key: cat.id, className: "flex items-center justify-between gap-4 rounded-xl border border-border p-3" },
34422
+ h3(
34423
+ "div",
34424
+ { className: "min-w-0" },
34425
+ h3("p", { className: "text-sm font-medium" }, cat.label),
34426
+ cat.description ? h3("p", { className: "mt-0.5 text-xs text-muted-foreground" }, cat.description) : null
34427
+ ),
34428
+ h3(Toggle, {
34429
+ checked: !!state.preferences[cat.id],
34430
+ disabled: cat.required,
34431
+ label: cat.label,
34432
+ onChange: (v2) => setPreference(cat.id, v2)
34433
+ })
34434
+ )
34363
34435
  )
34364
34436
  ),
34365
34437
  h3(
34366
34438
  "div",
34367
- { className: "flex flex-wrap items-center gap-2 pt-1" },
34368
- h3("button", { type: "button", className: btnPrimary, onClick: () => savePreferences(state.preferences) }, "Save preferences"),
34369
- h3("button", { type: "button", className: btnGhost, onClick: () => acceptAll() }, "Accept all"),
34370
- h3("button", { type: "button", className: cn(btnLink, "ml-auto"), onClick: () => setSettings(false) }, "Back")
34439
+ { className: "mt-4 flex flex-wrap items-center gap-2" },
34440
+ h3("button", { type: "button", className: btnLink, onClick: () => setSettings(false) }, "\u2190 Back"),
34441
+ h3("button", { type: "button", className: cn(btnGhost, "sm:ml-auto"), onClick: () => acceptAll() }, "Accept all"),
34442
+ h3("button", { type: "button", className: btnPrimary, onClick: () => savePreferences(state.preferences) }, "Save preferences")
34371
34443
  )
34372
34444
  );
34373
34445
  return h3(
34374
34446
  "div",
34375
34447
  { className: wrapper, role: "dialog", "aria-label": "Cookie consent", "aria-modal": false },
34376
- h3("div", { className: panel }, header, settings ? settingsView : promptActions)
34448
+ h3("div", { className: panel }, settings ? settingsView : promptView)
34377
34449
  );
34378
34450
  }
34379
34451