@refraction-ui/react 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3368,6 +3368,65 @@ function useConversation(config) {
3368
3368
  };
3369
3369
  }
3370
3370
  var h = React11.createElement;
3371
+ var svg = (children, size = 16) => h(
3372
+ "svg",
3373
+ {
3374
+ xmlns: "http://www.w3.org/2000/svg",
3375
+ viewBox: "0 0 24 24",
3376
+ width: size,
3377
+ height: size,
3378
+ fill: "none",
3379
+ stroke: "currentColor",
3380
+ strokeWidth: 2,
3381
+ strokeLinecap: "round",
3382
+ strokeLinejoin: "round",
3383
+ "aria-hidden": true
3384
+ },
3385
+ children
3386
+ );
3387
+ var IconAttach = () => svg(h("path", { d: "m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 17.93 8.81l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48" }));
3388
+ var IconBold = () => svg([
3389
+ h("path", { key: "a", d: "M6 4h8a4 4 0 0 1 0 8H6z" }),
3390
+ h("path", { key: "b", d: "M6 12h9a4 4 0 0 1 0 8H6z" })
3391
+ ]);
3392
+ var IconItalic = () => svg([
3393
+ h("line", { key: "a", x1: 19, y1: 4, x2: 10, y2: 4 }),
3394
+ h("line", { key: "b", x1: 14, y1: 20, x2: 5, y2: 20 }),
3395
+ h("line", { key: "c", x1: 15, y1: 4, x2: 9, y2: 20 })
3396
+ ]);
3397
+ var IconCode = () => svg([
3398
+ h("polyline", { key: "a", points: "16 18 22 12 16 6" }),
3399
+ h("polyline", { key: "b", points: "8 6 2 12 8 18" })
3400
+ ]);
3401
+ var IconLink = () => svg([
3402
+ h("path", { key: "a", d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }),
3403
+ h("path", { key: "b", d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
3404
+ ]);
3405
+ var IconQuote = () => svg([
3406
+ h("path", { key: "a", d: "M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V20c0 1 0 1 1 1z" }),
3407
+ h("path", { key: "b", d: "M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3c0 1 0 1 1 1z" })
3408
+ ]);
3409
+ var IconListUl = () => svg([
3410
+ h("line", { key: "a", x1: 8, y1: 6, x2: 21, y2: 6 }),
3411
+ h("line", { key: "b", x1: 8, y1: 12, x2: 21, y2: 12 }),
3412
+ h("line", { key: "c", x1: 8, y1: 18, x2: 21, y2: 18 }),
3413
+ h("line", { key: "d", x1: 3, y1: 6, x2: 3.01, y2: 6 }),
3414
+ h("line", { key: "e", x1: 3, y1: 12, x2: 3.01, y2: 12 }),
3415
+ h("line", { key: "f", x1: 3, y1: 18, x2: 3.01, y2: 18 })
3416
+ ]);
3417
+ var IconListOl = () => svg([
3418
+ h("line", { key: "a", x1: 10, y1: 6, x2: 21, y2: 6 }),
3419
+ h("line", { key: "b", x1: 10, y1: 12, x2: 21, y2: 12 }),
3420
+ h("line", { key: "c", x1: 10, y1: 18, x2: 21, y2: 18 }),
3421
+ h("path", { key: "d", d: "M4 6h1v4" }),
3422
+ h("path", { key: "e", d: "M4 10h2" }),
3423
+ h("path", { key: "f", d: "M6 18H4c0-1 2-2 2-3s-1-1.5-2-1" })
3424
+ ]);
3425
+ var IconArrowUp = () => svg([
3426
+ h("path", { key: "a", d: "M12 19V5" }),
3427
+ h("path", { key: "b", d: "m5 12 7-7 7 7" })
3428
+ ]);
3429
+ var IconStop = () => svg(h("rect", { x: 7, y: 7, width: 10, height: 10, rx: 1.5 }));
3371
3430
  var EMOJI = {
3372
3431
  smile: "\u{1F604}",
3373
3432
  grin: "\u{1F601}",
@@ -3420,13 +3479,15 @@ function detectTrigger(text, caret) {
3420
3479
  return { type, query, start: caret - query.length - 1, end: caret };
3421
3480
  }
3422
3481
  function Composer({
3423
- placeholder = "Type a message\u2026 (/ commands, @ mentions, : emoji)",
3482
+ placeholder = "Send a message\u2026",
3424
3483
  busy = false,
3425
3484
  slashCommands = [],
3426
3485
  mentions,
3427
3486
  toolbar = true,
3428
3487
  emoji = true,
3429
3488
  attachments = true,
3489
+ error,
3490
+ onRetry,
3430
3491
  onSubmit,
3431
3492
  onStop,
3432
3493
  onSlashCommand,
@@ -3567,148 +3628,157 @@ ${sel}
3567
3628
  submit();
3568
3629
  }
3569
3630
  }
3570
- const toolbarBtn = (label, title, kind) => h(
3631
+ const iconBtn = "flex h-8 w-8 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-accent hover:text-foreground";
3632
+ const toolbarBtn = (icon, title, kind) => h(
3571
3633
  "button",
3572
3634
  {
3573
3635
  key: kind,
3574
3636
  type: "button",
3575
3637
  title,
3576
- className: "rounded px-1.5 py-0.5 text-xs text-muted-foreground hover:bg-accent hover:text-foreground",
3638
+ "aria-label": title,
3639
+ className: iconBtn,
3577
3640
  onMouseDown: (e) => e.preventDefault(),
3578
3641
  // keep textarea selection
3579
3642
  onClick: () => format(kind)
3580
3643
  },
3581
- label
3644
+ icon
3582
3645
  );
3583
- return h(
3646
+ const divider = () => h("span", { className: "mx-1 h-5 w-px bg-border", "aria-hidden": true });
3647
+ const menu = menuOpen ? h(
3584
3648
  "div",
3585
- { className: "border-t border-border" },
3586
- // attachment chips
3587
- pending.length > 0 ? h(
3649
+ {
3650
+ className: "absolute bottom-full left-0 z-20 mb-2 w-72 overflow-hidden rounded-xl border border-border bg-popover shadow-lg",
3651
+ role: "listbox"
3652
+ },
3653
+ h(
3588
3654
  "div",
3589
- { className: "flex flex-wrap gap-2 px-3 pt-2" },
3590
- ...pending.map(
3591
- (a) => h(
3592
- "span",
3593
- { key: a.id, className: "inline-flex items-center gap-1 rounded bg-muted px-2 py-0.5 text-xs" },
3594
- a.name,
3595
- h(
3596
- "button",
3597
- {
3598
- type: "button",
3599
- className: "text-muted-foreground hover:text-destructive",
3600
- onClick: () => setPending((p) => p.filter((x) => x.id !== a.id))
3601
- },
3602
- "\u2715"
3603
- )
3604
- )
3655
+ { className: "border-b border-border px-3 py-1.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground" },
3656
+ trigger?.type === "/" ? "Commands" : trigger?.type === "@" ? "Mentions" : "Emoji"
3657
+ ),
3658
+ ...items.map(
3659
+ (it, i) => h(
3660
+ "button",
3661
+ {
3662
+ key: it.key,
3663
+ type: "button",
3664
+ role: "option",
3665
+ "aria-selected": i === active,
3666
+ className: cn(
3667
+ "flex w-full items-center gap-2 px-3 py-2 text-left text-sm",
3668
+ i === active ? "bg-accent" : "hover:bg-accent/50"
3669
+ ),
3670
+ onMouseEnter: () => setActive(i),
3671
+ onMouseDown: (e) => e.preventDefault(),
3672
+ onClick: () => selectItem(i)
3673
+ },
3674
+ it.icon ? h("span", { className: "w-4 text-center text-muted-foreground" }, it.icon) : null,
3675
+ h("span", { className: "flex-1 truncate" }, it.primary),
3676
+ it.secondary ? h("span", { className: "truncate text-xs text-muted-foreground" }, it.secondary) : null
3605
3677
  )
3606
- ) : null,
3607
- // toolbar
3608
- toolbar ? h(
3609
- "div",
3610
- { className: "flex items-center gap-0.5 px-2 pt-2" },
3611
- toolbarBtn("B", "Bold (\u2318B)", "bold"),
3612
- toolbarBtn("\u{1D456}", "Italic (\u2318I)", "italic"),
3613
- toolbarBtn("</>", "Code (\u2318E)", "code"),
3614
- toolbarBtn("\u{1F517}", "Link (\u2318K)", "link"),
3615
- toolbarBtn("\u275D", "Quote", "quote"),
3616
- toolbarBtn("\u2022", "Bulleted list", "ul"),
3617
- toolbarBtn("1.", "Numbered list", "ol")
3618
- ) : null,
3619
- // input row (relative for the popup menu)
3678
+ )
3679
+ ) : null;
3680
+ return h(
3681
+ "div",
3682
+ { className: "p-3" },
3620
3683
  h(
3621
3684
  "div",
3622
- { className: "relative flex items-end gap-2 p-3" },
3623
- menuOpen ? h(
3685
+ { className: "relative" },
3686
+ menu,
3687
+ // unified input card
3688
+ h(
3624
3689
  "div",
3625
3690
  {
3626
- className: "absolute bottom-full left-3 z-20 mb-1 w-72 overflow-hidden rounded-lg border border-border bg-popover shadow-lg",
3627
- role: "listbox"
3691
+ className: "overflow-hidden rounded-2xl border border-border bg-background transition focus-within:border-primary focus-within:ring-2 focus-within:ring-primary/40"
3628
3692
  },
3693
+ // error banner
3694
+ error ? h(
3695
+ "div",
3696
+ { className: "flex items-center gap-2 border-b border-border bg-destructive/5 px-3 py-2 text-xs text-destructive", role: "alert" },
3697
+ h("span", { className: "flex-1 truncate" }, error),
3698
+ onRetry ? h("button", { type: "button", className: "font-medium underline", onClick: () => onRetry() }, "Retry") : null
3699
+ ) : null,
3700
+ // attachment chips
3701
+ pending.length > 0 ? h(
3702
+ "div",
3703
+ { className: "flex flex-wrap gap-2 px-3 pt-3" },
3704
+ ...pending.map(
3705
+ (a) => h(
3706
+ "span",
3707
+ { key: a.id, className: "inline-flex items-center gap-1 rounded-md bg-muted px-2 py-0.5 text-xs" },
3708
+ a.name,
3709
+ h(
3710
+ "button",
3711
+ { type: "button", className: "text-muted-foreground hover:text-destructive", onClick: () => setPending((p) => p.filter((x) => x.id !== a.id)) },
3712
+ "\u2715"
3713
+ )
3714
+ )
3715
+ )
3716
+ ) : null,
3717
+ // textarea (borderless)
3718
+ h("textarea", {
3719
+ ref,
3720
+ 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",
3721
+ rows: 1,
3722
+ value,
3723
+ placeholder,
3724
+ autoFocus,
3725
+ "aria-label": "Message",
3726
+ onChange: (e) => syncFromTextarea(e.target),
3727
+ onClick: (e) => syncFromTextarea(e.currentTarget),
3728
+ onKeyUp: (e) => syncFromTextarea(e.currentTarget),
3729
+ onKeyDown,
3730
+ onBlur: () => setTimeout(() => setTrigger(null), 120)
3731
+ }),
3732
+ // bottom action bar
3629
3733
  h(
3630
3734
  "div",
3631
- { className: "border-b border-border px-2 py-1 text-[10px] uppercase tracking-wide text-muted-foreground" },
3632
- trigger?.type === "/" ? "Commands" : trigger?.type === "@" ? "Mentions" : "Emoji"
3633
- ),
3634
- ...items.map(
3635
- (it, i) => h(
3735
+ { className: "flex items-center gap-0.5 px-2 pb-2" },
3736
+ attachments ? h(
3737
+ React11.Fragment,
3738
+ null,
3739
+ h("input", {
3740
+ ref: fileRef,
3741
+ type: "file",
3742
+ accept: "image/*",
3743
+ multiple: true,
3744
+ className: "hidden",
3745
+ onChange: (e) => {
3746
+ onFiles(e.target.files);
3747
+ e.target.value = "";
3748
+ }
3749
+ }),
3750
+ h("button", { type: "button", className: iconBtn, "aria-label": "Attach image or GIF", onClick: () => fileRef.current?.click() }, h(IconAttach))
3751
+ ) : null,
3752
+ attachments && toolbar ? divider() : null,
3753
+ toolbar ? h(
3754
+ React11.Fragment,
3755
+ null,
3756
+ toolbarBtn(h(IconBold), "Bold (\u2318B)", "bold"),
3757
+ toolbarBtn(h(IconItalic), "Italic (\u2318I)", "italic"),
3758
+ toolbarBtn(h(IconCode), "Code (\u2318E)", "code"),
3759
+ toolbarBtn(h(IconLink), "Link (\u2318K)", "link"),
3760
+ divider(),
3761
+ toolbarBtn(h(IconQuote), "Quote", "quote"),
3762
+ toolbarBtn(h(IconListUl), "Bulleted list", "ul"),
3763
+ toolbarBtn(h(IconListOl), "Numbered list", "ol")
3764
+ ) : null,
3765
+ h("div", { className: "flex-1" }),
3766
+ busy ? h(
3767
+ "button",
3768
+ { type: "button", "aria-label": "Stop", className: "flex h-9 w-9 items-center justify-center rounded-full bg-primary text-primary-foreground transition hover:opacity-90", onClick: () => onStop?.() },
3769
+ h(IconStop)
3770
+ ) : h(
3636
3771
  "button",
3637
3772
  {
3638
- key: it.key,
3639
3773
  type: "button",
3640
- role: "option",
3641
- "aria-selected": i === active,
3642
- className: cn(
3643
- "flex w-full items-center gap-2 px-2 py-1.5 text-left text-sm",
3644
- i === active ? "bg-accent" : "hover:bg-accent/50"
3645
- ),
3646
- onMouseEnter: () => setActive(i),
3647
- onMouseDown: (e) => e.preventDefault(),
3648
- onClick: () => selectItem(i)
3774
+ "aria-label": "Send",
3775
+ className: "flex h-9 w-9 items-center justify-center rounded-full bg-primary text-primary-foreground transition hover:opacity-90 disabled:bg-muted disabled:text-muted-foreground disabled:opacity-100",
3776
+ disabled: !value.trim() && pending.length === 0,
3777
+ onClick: submit
3649
3778
  },
3650
- it.icon ? h("span", { className: "w-4 text-center text-muted-foreground" }, it.icon) : null,
3651
- h("span", { className: "flex-1 truncate" }, it.primary),
3652
- it.secondary ? h("span", { className: "truncate text-xs text-muted-foreground" }, it.secondary) : null
3779
+ h(IconArrowUp)
3653
3780
  )
3654
3781
  )
3655
- ) : null,
3656
- attachments ? h(
3657
- React11.Fragment,
3658
- null,
3659
- h("input", {
3660
- ref: fileRef,
3661
- type: "file",
3662
- accept: "image/*",
3663
- multiple: true,
3664
- className: "hidden",
3665
- onChange: (e) => {
3666
- onFiles(e.target.files);
3667
- e.target.value = "";
3668
- }
3669
- }),
3670
- h(
3671
- "button",
3672
- {
3673
- type: "button",
3674
- className: "rounded-md border border-border px-2 py-2 text-sm hover:bg-accent",
3675
- "aria-label": "Attach image or GIF",
3676
- onClick: () => fileRef.current?.click()
3677
- },
3678
- "\u{1F4CE}"
3679
- )
3680
- ) : null,
3681
- h("textarea", {
3682
- ref,
3683
- 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",
3684
- rows: 1,
3685
- value,
3686
- placeholder,
3687
- autoFocus,
3688
- "aria-label": "Message",
3689
- onChange: (e) => syncFromTextarea(e.target),
3690
- onClick: (e) => syncFromTextarea(e.currentTarget),
3691
- onKeyUp: (e) => syncFromTextarea(e.currentTarget),
3692
- onKeyDown,
3693
- onBlur: () => setTimeout(() => setTrigger(null), 120)
3694
- }),
3695
- busy ? h(
3696
- "button",
3697
- {
3698
- type: "button",
3699
- className: "rounded-md bg-primary px-3 py-2 text-sm font-medium text-primary-foreground",
3700
- onClick: () => onStop?.()
3701
- },
3702
- "Stop"
3703
- ) : h(
3704
- "button",
3705
- {
3706
- type: "button",
3707
- className: "rounded-md bg-primary px-3 py-2 text-sm font-medium text-primary-foreground disabled:opacity-50",
3708
- disabled: !value.trim() && pending.length === 0,
3709
- onClick: submit
3710
- },
3711
- "Send"
3712
3782
  )
3713
3783
  )
3714
3784
  );
@@ -4056,9 +4126,13 @@ function Chat({
4056
4126
  const timeline = selectMainTimeline(state.messages, state.threadingMode);
4057
4127
  const activeConv = state.conversations.find((c) => c.id === state.activeConversationId);
4058
4128
  const busy = state.status === "sending" || state.status === "streaming";
4129
+ const error = state.status === "error" ? state.error : null;
4130
+ const onRetry = () => void conversation.retryLast();
4059
4131
  const mainComposer = h2(Composer, {
4060
4132
  placeholder,
4061
4133
  busy,
4134
+ error,
4135
+ onRetry,
4062
4136
  slashCommands,
4063
4137
  mentions,
4064
4138
  onSlashCommand,
@@ -4069,6 +4143,8 @@ function Chat({
4069
4143
  const threadComposer = state.openThreadRootId ? h2(Composer, {
4070
4144
  placeholder: "Reply\u2026",
4071
4145
  busy,
4146
+ error,
4147
+ onRetry,
4072
4148
  slashCommands,
4073
4149
  mentions,
4074
4150
  onSlashCommand,
@@ -4372,14 +4448,56 @@ function useCookieConsent(config) {
4372
4448
  };
4373
4449
  }
4374
4450
  var h3 = React11.createElement;
4375
- var btnPrimary = "rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:opacity-90";
4376
- var btnGhost = "rounded-md border border-border px-3 py-1.5 text-sm hover:bg-accent";
4377
- var btnLink = "text-sm text-muted-foreground underline hover:text-foreground";
4451
+ var btnBase = "inline-flex items-center justify-center rounded-lg px-3.5 py-2 text-sm font-medium transition-colors";
4452
+ var btnPrimary = cn(btnBase, "bg-primary text-primary-foreground hover:opacity-90");
4453
+ var btnGhost = cn(btnBase, "border border-border hover:bg-accent");
4454
+ var btnLink = "text-sm font-medium text-muted-foreground underline-offset-4 hover:text-foreground hover:underline";
4455
+ function Toggle({
4456
+ checked,
4457
+ disabled,
4458
+ onChange,
4459
+ label
4460
+ }) {
4461
+ if (disabled) {
4462
+ return h3(
4463
+ "span",
4464
+ { className: "rounded-full bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground" },
4465
+ "Always on"
4466
+ );
4467
+ }
4468
+ return h3(
4469
+ "button",
4470
+ {
4471
+ type: "button",
4472
+ role: "switch",
4473
+ "aria-checked": checked,
4474
+ "aria-label": label,
4475
+ onClick: () => onChange(!checked),
4476
+ className: cn(
4477
+ "relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors",
4478
+ checked ? "bg-primary" : "bg-muted"
4479
+ )
4480
+ },
4481
+ h3("span", {
4482
+ className: cn(
4483
+ "inline-block h-4 w-4 transform rounded-full bg-background shadow transition-transform",
4484
+ checked ? "translate-x-[1.125rem]" : "translate-x-0.5"
4485
+ )
4486
+ })
4487
+ );
4488
+ }
4489
+ function CookieIcon() {
4490
+ return h3(
4491
+ "div",
4492
+ { className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-accent text-xl", "aria-hidden": true },
4493
+ "\u{1F36A}"
4494
+ );
4495
+ }
4378
4496
  function CookieConsent({
4379
4497
  consent,
4380
4498
  position = "bottom",
4381
4499
  title = "We use cookies",
4382
- description = "We use cookies to run the site, remember your preferences, and measure traffic. Choose which categories to allow.",
4500
+ description = "We use cookies to run the site, remember your preferences, and measure traffic. Choose which to allow.",
4383
4501
  policyUrl,
4384
4502
  policyLabel = "Cookie policy",
4385
4503
  className
@@ -4387,64 +4505,80 @@ function CookieConsent({
4387
4505
  const { state, acceptAll, rejectAll, savePreferences, setPreference } = consent;
4388
4506
  const [settings, setSettings] = React11.useState(false);
4389
4507
  if (!state.open) return null;
4390
- const wrapper = cn(
4391
- "fixed inset-x-0 z-50 p-4",
4392
- position === "bottom" ? "bottom-0" : "top-0",
4393
- className
4394
- );
4395
- const panel = "mx-auto max-w-3xl rounded-xl border border-border bg-background p-4 shadow-lg";
4396
- const header = h3(
4397
- "div",
4398
- null,
4399
- h3("h2", { className: "text-base font-semibold" }, title),
4400
- h3("p", { className: "mt-1 text-sm text-muted-foreground" }, description),
4401
- policyUrl ? h3("a", { href: policyUrl, target: "_blank", rel: "noreferrer", className: cn(btnLink, "mt-1 inline-block") }, policyLabel) : null
4402
- );
4403
- const promptActions = h3(
4508
+ const wrapper = cn("fixed inset-x-0 z-50 p-4", position === "bottom" ? "bottom-0" : "top-0", className);
4509
+ const panel = "mx-auto max-w-2xl overflow-hidden rounded-2xl border border-border bg-background shadow-lg";
4510
+ const policy = policyUrl ? h3("a", { href: policyUrl, target: "_blank", rel: "noreferrer", className: cn(btnLink, "whitespace-nowrap") }, policyLabel) : null;
4511
+ const promptView = h3(
4404
4512
  "div",
4405
- { className: "mt-3 flex flex-wrap items-center gap-2" },
4406
- h3("button", { type: "button", className: btnPrimary, onClick: () => acceptAll() }, "Accept all"),
4407
- h3("button", { type: "button", className: btnGhost, onClick: () => rejectAll() }, "Reject all"),
4408
- h3("button", { type: "button", className: cn(btnGhost, "ml-auto"), onClick: () => setSettings(true) }, "Manage preferences")
4513
+ { className: "flex flex-col gap-4 p-5 sm:flex-row sm:items-center" },
4514
+ h3(CookieIcon),
4515
+ h3(
4516
+ "div",
4517
+ { className: "min-w-0 flex-1" },
4518
+ h3("p", { className: "text-sm font-semibold" }, title),
4519
+ h3(
4520
+ "p",
4521
+ { className: "mt-0.5 text-sm leading-relaxed text-muted-foreground" },
4522
+ description,
4523
+ policy ? h3(React11.Fragment, null, " ", policy) : null
4524
+ )
4525
+ ),
4526
+ h3(
4527
+ "div",
4528
+ { className: "flex flex-wrap items-center gap-2 sm:shrink-0" },
4529
+ h3("button", { type: "button", className: btnLink, onClick: () => setSettings(true) }, "Customize"),
4530
+ h3("button", { type: "button", className: btnGhost, onClick: () => rejectAll() }, "Reject all"),
4531
+ h3("button", { type: "button", className: btnPrimary, onClick: () => acceptAll() }, "Accept all")
4532
+ )
4409
4533
  );
4410
4534
  const settingsView = h3(
4411
4535
  "div",
4412
- { className: "mt-3 space-y-2" },
4413
- ...state.categories.map(
4414
- (cat) => h3(
4415
- "label",
4416
- {
4417
- key: cat.id,
4418
- className: "flex items-start justify-between gap-3 rounded-md border border-border p-3"
4419
- },
4420
- h3(
4421
- "span",
4422
- { className: "min-w-0" },
4423
- h3("span", { className: "block text-sm font-medium" }, cat.label, cat.required ? " (required)" : ""),
4424
- cat.description ? h3("span", { className: "block text-xs text-muted-foreground" }, cat.description) : null
4425
- ),
4426
- h3("input", {
4427
- type: "checkbox",
4428
- className: "mt-0.5 h-4 w-4 accent-[hsl(var(--primary))]",
4429
- checked: !!state.preferences[cat.id],
4430
- disabled: cat.required,
4431
- "aria-label": cat.label,
4432
- onChange: (e) => setPreference(cat.id, e.target.checked)
4433
- })
4536
+ { className: "p-5" },
4537
+ h3(
4538
+ "div",
4539
+ { className: "flex items-center gap-3" },
4540
+ h3(CookieIcon),
4541
+ h3(
4542
+ "div",
4543
+ null,
4544
+ h3("p", { className: "text-sm font-semibold" }, "Cookie preferences"),
4545
+ h3("p", { className: "text-xs text-muted-foreground" }, "Toggle the categories you want to allow.")
4546
+ )
4547
+ ),
4548
+ h3(
4549
+ "div",
4550
+ { className: "mt-4 space-y-2" },
4551
+ ...state.categories.map(
4552
+ (cat) => h3(
4553
+ "div",
4554
+ { key: cat.id, className: "flex items-center justify-between gap-4 rounded-xl border border-border p-3" },
4555
+ h3(
4556
+ "div",
4557
+ { className: "min-w-0" },
4558
+ h3("p", { className: "text-sm font-medium" }, cat.label),
4559
+ cat.description ? h3("p", { className: "mt-0.5 text-xs text-muted-foreground" }, cat.description) : null
4560
+ ),
4561
+ h3(Toggle, {
4562
+ checked: !!state.preferences[cat.id],
4563
+ disabled: cat.required,
4564
+ label: cat.label,
4565
+ onChange: (v) => setPreference(cat.id, v)
4566
+ })
4567
+ )
4434
4568
  )
4435
4569
  ),
4436
4570
  h3(
4437
4571
  "div",
4438
- { className: "flex flex-wrap items-center gap-2 pt-1" },
4439
- h3("button", { type: "button", className: btnPrimary, onClick: () => savePreferences(state.preferences) }, "Save preferences"),
4440
- h3("button", { type: "button", className: btnGhost, onClick: () => acceptAll() }, "Accept all"),
4441
- h3("button", { type: "button", className: cn(btnLink, "ml-auto"), onClick: () => setSettings(false) }, "Back")
4572
+ { className: "mt-4 flex flex-wrap items-center gap-2" },
4573
+ h3("button", { type: "button", className: btnLink, onClick: () => setSettings(false) }, "\u2190 Back"),
4574
+ h3("button", { type: "button", className: cn(btnGhost, "sm:ml-auto"), onClick: () => acceptAll() }, "Accept all"),
4575
+ h3("button", { type: "button", className: btnPrimary, onClick: () => savePreferences(state.preferences) }, "Save preferences")
4442
4576
  )
4443
4577
  );
4444
4578
  return h3(
4445
4579
  "div",
4446
4580
  { className: wrapper, role: "dialog", "aria-label": "Cookie consent", "aria-modal": false },
4447
- h3("div", { className: panel }, header, settings ? settingsView : promptActions)
4581
+ h3("div", { className: panel }, settings ? settingsView : promptView)
4448
4582
  );
4449
4583
  }
4450
4584