@evinvest/uikit 0.26.0 → 0.29.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.
Files changed (83) hide show
  1. package/README.md +148 -17
  2. package/dist/components/account-menu.js +70 -0
  3. package/dist/components/account-menu.js.map +1 -0
  4. package/dist/components/alert-dialog.js +4 -3
  5. package/dist/components/alert-dialog.js.map +1 -1
  6. package/dist/components/app-shell.js +11 -0
  7. package/dist/components/app-shell.js.map +1 -1
  8. package/dist/components/auth-wall.js +42 -0
  9. package/dist/components/auth-wall.js.map +1 -0
  10. package/dist/components/checkbox.js +2 -0
  11. package/dist/components/checkbox.js.map +1 -1
  12. package/dist/components/command.js +180 -25
  13. package/dist/components/command.js.map +1 -1
  14. package/dist/components/context-menu.js +8 -8
  15. package/dist/components/context-menu.js.map +1 -1
  16. package/dist/components/dialog.js +4 -3
  17. package/dist/components/dialog.js.map +1 -1
  18. package/dist/components/drawer.js +9 -5
  19. package/dist/components/drawer.js.map +1 -1
  20. package/dist/components/dropdown-menu.js +13 -10
  21. package/dist/components/dropdown-menu.js.map +1 -1
  22. package/dist/components/field.js +3 -8
  23. package/dist/components/field.js.map +1 -1
  24. package/dist/components/info-tip.js +2 -2
  25. package/dist/components/info-tip.js.map +1 -1
  26. package/dist/components/input.js +2 -0
  27. package/dist/components/input.js.map +1 -1
  28. package/dist/components/menubar.js +18 -5
  29. package/dist/components/menubar.js.map +1 -1
  30. package/dist/components/native-select.js +2 -0
  31. package/dist/components/native-select.js.map +1 -1
  32. package/dist/components/navigation-menu.js +2 -2
  33. package/dist/components/navigation-menu.js.map +1 -1
  34. package/dist/components/popover.js +2 -2
  35. package/dist/components/popover.js.map +1 -1
  36. package/dist/components/select.js +4 -2
  37. package/dist/components/select.js.map +1 -1
  38. package/dist/components/sheet.js +4 -3
  39. package/dist/components/sheet.js.map +1 -1
  40. package/dist/components/sonner.js +1 -0
  41. package/dist/components/sonner.js.map +1 -1
  42. package/dist/components/switch.js +2 -0
  43. package/dist/components/switch.js.map +1 -1
  44. package/dist/components/table.js +24 -7
  45. package/dist/components/table.js.map +1 -1
  46. package/dist/components/textarea.js +2 -0
  47. package/dist/components/textarea.js.map +1 -1
  48. package/dist/components/toggle-group.js +1 -1
  49. package/dist/components/toggle-group.js.map +1 -1
  50. package/dist/components/top-bar.js +21 -0
  51. package/dist/components/top-bar.js.map +1 -0
  52. package/dist/generated/command.js +2 -2
  53. package/dist/generated/command.js.map +1 -1
  54. package/dist/generated/context-menu.js +1 -1
  55. package/dist/generated/context-menu.js.map +1 -1
  56. package/dist/generated/dropdown-menu.js +1 -1
  57. package/dist/generated/dropdown-menu.js.map +1 -1
  58. package/dist/generated/menubar.js +1 -1
  59. package/dist/generated/menubar.js.map +1 -1
  60. package/dist/generated/table.js +20 -3
  61. package/dist/generated/table.js.map +1 -1
  62. package/dist/generated/toggle.js +2 -2
  63. package/dist/generated/toggle.js.map +1 -1
  64. package/dist/index.d.ts +179 -76
  65. package/dist/index.js +6 -4
  66. package/dist/primitives/dismissable-layer.js +80 -15
  67. package/dist/primitives/dismissable-layer.js.map +1 -1
  68. package/dist/primitives/labelable.js +24 -0
  69. package/dist/primitives/labelable.js.map +1 -0
  70. package/dist/primitives/use-roving-focus.js +13 -7
  71. package/dist/primitives/use-roving-focus.js.map +1 -1
  72. package/package.json +1 -1
  73. package/styles/ev.css +4 -2
  74. package/styles/theme.css +7 -0
  75. package/styles/tokens.css +15 -3
  76. package/dist/components/chart.js +0 -151
  77. package/dist/components/chart.js.map +0 -1
  78. package/dist/components/resizable.js +0 -193
  79. package/dist/components/resizable.js.map +0 -1
  80. package/dist/generated/chart.js +0 -9
  81. package/dist/generated/chart.js.map +0 -1
  82. package/dist/generated/resizable.js +0 -11
  83. package/dist/generated/resizable.js.map +0 -1
package/README.md CHANGED
@@ -63,7 +63,7 @@ your Tailwind v4 entrypoint — this is the load-bearing part of the kit:
63
63
  | surfaces | `background` `card` `popover` `muted` `hover` |
64
64
  | ink | `ink` `ink-mid` `ink-soft` — hierarchy, loudest first |
65
65
  | lines | `border` `input` `ring` |
66
- | roles | `brand` `primary` `secondary` `positive` `accent-trace` `accent-debug` `accent-info` `accent-warn` `accent-error`, each with `on-*` where it gets filled; `primary-ink` — the primary role as ink on a surface (`#128377` fill under a white `on-primary`, `#2a9d8f` ink) |
66
+ | roles | `brand` `primary` `secondary` `positive` `accent-trace` `accent-debug` `accent-info` `accent-warn` `accent-error`, each with `on-*` where it gets filled; `primary-ink` — the primary role as ink on a surface (`#128377` fill under a white `on-primary`, `#2a9d8f` ink); `accent-error-ink` — the error role as text on its own tint (derived, a palette may pin it) |
67
67
  | scalars | `radius` `control-radius` `control-py` `display-scale` `band-py` `page-max` `page-px` `shell-rail-w` `shell-tab-bar-h` `shadow-*` `font-*` |
68
68
  | motion | `ev-ease-out` `ev-ease-in-out` `ev-dur-fast` `ev-dur-base` `ev-dur-slow` `ev-rise` `ev-stagger` `ev-stagger-section` |
69
69
  | charts | `chart-1` … `chart-5` |
@@ -106,7 +106,8 @@ It has two halves, each also shipped as a flat sheet of its own —
106
106
 
107
107
  - **the contract** — the `@theme inline` mapping, the geometry, and the tokens
108
108
  *derived* from a palette (`hover`, `ink-mid`, `ink-soft`, `border`, `input`
109
- from `ink`; `ring` from `primary-ink`). The derived ones are redeclared on
109
+ from `ink`; `ring` from `primary-ink`; `accent-error-ink` from `accent-error`
110
+ and `ink`). The derived ones are redeclared on
110
111
  `:root`, `[data-brand]`, `.light` and `.dark` at zero specificity, so a brand
111
112
  scope gets its own borders, hover and focus ring rather than inheriting the
112
113
  root's, and a palette may still pin any of them outright. The geometry
@@ -366,10 +367,11 @@ export function Nav() {
366
367
  }
367
368
  ```
368
369
 
369
- - **`AppShell`** — slots `rail`, `tabBar`, `banner`, `children`; `breakpoint`
370
+ - **`AppShell`** — slots `rail`, `topBar`, `tabBar`, `banner`, `children`; `breakpoint`
370
371
  (`"md"` | `"lg"`, default `lg`) is where the tab bar gives way to the rail.
371
372
  Fills `100dvh` less `--ev-shell-offset`; the rail sticks under that offset; the
372
373
  column reserves `--shell-tab-bar-h` plus the bottom safe area for the bar.
374
+ `topBar` sticks over the column, also under that offset, from `breakpoint` up.
373
375
  `mainProps` reaches the `<main>`. Layout only — no `"use client"`.
374
376
  - **`NavItem`** — `{ id, href, label, icon?, badge?, trailing?, external?,
375
377
  target?, match?, also?, disabled? }`, shared by the rail and the tab bar.
@@ -401,6 +403,10 @@ export function Nav() {
401
403
  - **`MobileAppBar`** `{ title, back?: { href } | { onClick }, right?,
402
404
  linkComponent?, hideFrom = "lg", labels: { back } }` — sticky under
403
405
  `--ev-shell-offset`; with `back` the title centres.
406
+ - **`TopBar`** `{ start?, end? }` — layout only, server-safe.
407
+ - **`AccountMenu`** `{ account: { name?, email }, manageHref?, switchHref,
408
+ groups?: NavGroup[], onSignOut, linkComponent?, labels: { trigger, manage,
409
+ switchAccount, signOut } }` — an initials avatar opening the menu.
404
410
  - **`PageFrame`** `{ title?, description?, eyebrow?, actions?, appBar?,
405
411
  width: "full" | "content", breakpoint }`, with **`PageHeading`** and
406
412
  **`SectionLabel`** (`tone: "muted" | "accent"`). With an `appBar` the heading
@@ -419,6 +425,25 @@ export function Nav() {
419
425
  Every string the kit renders or speaks is a `labels` entry with an English
420
426
  default.
421
427
 
428
+ ### The top bar contract
429
+
430
+ Every service under one account wears the same top bar, so a user moving
431
+ between them finds who they are and how to leave in the same place:
432
+
433
+ ```
434
+ [start: app switcher or service name] [end: service stats…] [AccountMenu]
435
+ AccountMenu: name / email · Manage account · ── service groups ── · Switch account · Sign out
436
+ ```
437
+
438
+ - The bar is utility only; product nav stays in the rail.
439
+ - The kit owns the fixed parts and their order. A service adds stats before the
440
+ `AccountMenu` and `groups` inside it — it never reorders or drops the rest.
441
+ - Data is the host's: each service reads its own session and passes `account`;
442
+ `manageHref` is the identity provider's account center, `switchHref` the
443
+ sign-in that shows the account chooser.
444
+ - A surface that cannot take the kit (the cabinet's public chip) hand-writes the
445
+ same order.
446
+
422
447
  ### Motion without a motion library
423
448
 
424
449
  The shell's motion is CSS in `tokens.css` (from `motion.css`), keyed on data
@@ -468,6 +493,93 @@ cn("p-4", "p-2"); // "p-2" — tailwind-merge resolves the conflict, rightmost w
468
493
  `cn` (clsx + tailwind-merge) is the mirror of the Rust `cn!` macro
469
494
  (`tailwind_fuse::tw_merge!`). A caller's `className`, passed last, beats the base.
470
495
 
496
+ ### Tables
497
+
498
+ ```tsx
499
+ import { Table, TableBody, TableCard, TableCell, TableHead, TableHeader, TableRow } from "@evinvest/uikit";
500
+
501
+ <TableCard>
502
+ <Table variant="card" density="compact">
503
+ <TableHeader>
504
+ <TableRow>
505
+ <TableHead>Key</TableHead>
506
+ <TableHead align="end">Amount</TableHead>
507
+ </TableRow>
508
+ </TableHeader>
509
+ <TableBody>
510
+ <TableRow>
511
+ <TableCell>sk_live_7f3a</TableCell>
512
+ <TableCell align="end">1,250.00</TableCell>
513
+ </TableRow>
514
+ </TableBody>
515
+ </Table>
516
+ </TableCard>
517
+ ```
518
+
519
+ - `variant="card"` — uppercase `text-xs tracking-wide text-ink-soft` head,
520
+ `px-5 py-3` cells, no hover on the head row. Meant to sit edge to edge in a
521
+ `TableCard`, the paddingless card surface.
522
+ - `density="compact"` — shorter head and rows; combines with either variant.
523
+ - `align="start" | "end"` on `TableHead` / `TableCell` — `end` right-aligns
524
+ with tabular numerals for figures. It replaces the obsolete HTML `align`
525
+ attribute on those two.
526
+ - `Table` sets no context: `variant` and `density` are inherited `--table-*`
527
+ custom properties the heads and cells read. `Table` stays a Server
528
+ Component, and a cell's own `className` (`px-0`, `text-center`) still wins.
529
+ - **`ListRows` / `ListRow` (TS-only)** — a table's phone form: label/value rows
530
+ split by hairlines, so the trailing columns are not scrolled off-screen.
531
+ `variant="card"` pads the rows for a `TableCard`; `ListRowLabel` /
532
+ `ListRowValue` compose a row by hand.
533
+
534
+ ```tsx
535
+ <TableCard className="sm:hidden">
536
+ <ListRows variant="card">
537
+ <ListRow label="sk_live_7f3a" description="api · aquafix" value="2026-09-30" />
538
+ </ListRows>
539
+ </TableCard>
540
+ ```
541
+ ### Command
542
+
543
+ By default `Command` filters and ranks its items on the client, as fzf does:
544
+ each space-separated term of the query must appear in the item's `value` as a
545
+ case-insensitive subsequence, and runs and word starts rank above scattered
546
+ matches. Ties keep your order. Ranking is CSS `order` within `CommandList` /
547
+ `CommandGroup`, which the arrows follow. When the
548
+ rows are already the answer to the query — a search endpoint, a ranking of your
549
+ own — pass `shouldFilter={false}` and own the query state:
550
+
551
+ ```tsx
552
+ const [query, setQuery] = useState("");
553
+ const { rows, loading } = useInvestorSearch(query); // your fetch, debounced or not
554
+
555
+ <Command shouldFilter={false} search={query} onSearchChange={setQuery}>
556
+ <CommandInput placeholder="Search investors…" />
557
+ <CommandList>
558
+ {loading ? <Spinner /> : <CommandEmpty>No investors found.</CommandEmpty>}
559
+ {rows.map((r) => (
560
+ <CommandItem key={r.id} value={r.id} onSelect={pick}>
561
+ {r.name}
562
+ </CommandItem>
563
+ ))}
564
+ </CommandList>
565
+ </Command>
566
+ ```
567
+
568
+ Then nothing is hidden or reordered: every mounted item renders in your order,
569
+ and `CommandEmpty` shows once a query is typed and no item is mounted. `value`
570
+ is then just what `onSelect` receives.
571
+
572
+ Keyboard works the same in both modes, as an ARIA combobox: focus stays in
573
+ `CommandInput`, which points at the highlighted row via
574
+ `aria-activedescendant`; ArrowUp / ArrowDown move the highlight (clamped,
575
+ disabled rows skipped), Enter fires the row's `onSelect`, and hover moves it
576
+ too. Home / End stay with the caret, and keys from any other focusable inside
577
+ Command (a button in `CommandEmpty`) are left alone. Rows stay out of the Tab order on purpose. The highlight sits on
578
+ the first row until the user moves it, so results that arrive after the
579
+ keystroke put Enter on the top hit. Without a `CommandInput` there is nothing to
580
+ hold focus, so a bare list is pointer-only. The live demo is `CommandDemo` in
581
+ `example/`.
582
+
471
583
  ## Rust ↔ TS parity
472
584
 
473
585
  The Rust crate is the source of truth; this package preserves its _semantics_
@@ -484,7 +596,7 @@ element across the two ports.
484
596
  | overlay placement | inline `position:fixed` + `data-side` | `Portal` + `useFloating` |
485
597
  | dismiss / focus trap | full-screen backdrop / native order | `useDismissableLayer` / `useFocusScope` |
486
598
 
487
- ### Component inventory (all 73 bricks)
599
+ ### Component inventory (all 75 bricks)
488
600
 
489
601
  - **Tier A — static (23):** badge, button, button-group, card, input, textarea,
490
602
  label, field, separator, skeleton, spinner, kbd, table, container, alert,
@@ -496,15 +608,14 @@ element across the two ports.
496
608
  - **Tier C — overlay (13):** tooltip, popover, hover-card, dropdown-menu,
497
609
  context-menu, menubar, navigation-menu, dialog, alert-dialog, sheet, drawer,
498
610
  select, command.
499
- - **Tier D — engines (6):** chart, calendar, date-time-picker, sonner (toaster),
500
- form, resizable.
611
+ - **Tier D — engines (4):** calendar, date-time-picker, sonner (toaster), form.
501
612
  - **Site chrome (7):** header (marketing / compact density, plus `hideNav`),
502
613
  footer, logo, and the shared status pages — `StatusScreen` with the `NotFound`
503
614
  / `Forbidden` / `ServerError` presets (404 / 403 / 500). The kit ships no
504
615
  artwork: `Logo` masks whatever `--brand-mark` / `--brand-aspect` the consumer
505
616
  declares beside the palette, and the status pages use it for their mark.
506
- - **App shell — TS-only (10):** app-shell, shell-nav, bottom-tab-bar,
507
- nav-badge (`NavBadge` / `NavDot`), mobile-app-bar, page-frame (`PageFrame` /
617
+ - **App shell — TS-only (12):** app-shell, shell-nav, bottom-tab-bar,
618
+ nav-badge (`NavBadge` / `NavDot`), top-bar, account-menu, mobile-app-bar, page-frame (`PageFrame` /
508
619
  `PageHeading` / `SectionLabel`), section-nav, resource-error, system-banner,
509
620
  settled — see [App shell](#app-shell).
510
621
  - **Features — composed screens (1):** terminal — a trading terminal over an
@@ -536,10 +647,31 @@ measuring needs host-only `web-sys`). Known gaps:
536
647
  no viewport-measured floating, native focus order (no trap). TS overlays use a
537
648
  real `Portal`, single-flip `useFloating` (absolute in the document, so it
538
649
  scrolls with the page natively), `useDismissableLayer`, and `useFocusScope`.
539
- - **chart:** the recharts plotting engine is not bundled. `ChartContainer` is a
540
- themed SVG host (emits `--color-*` from its config); `ChartTooltipContent` /
541
- `ChartLegendContent` are presentational and take explicit items. Draw series
542
- yourself inside the container.
650
+ - **overlay layers (TS):** every TS overlay is on one dismissable-layer stack.
651
+ A layer rendered inside another sits above it, even when both open in the
652
+ same commit; unrelated layers stack in the order they opened. Escape closes
653
+ the layer that was on top when it was pressed — a menu, `Select` or
654
+ `Popover` inside a `Dialog`, `Sheet` or `Drawer` closes first, the modal on
655
+ the next Escape — and a press inside a layer above is not "outside" the
656
+ ones below. A modal (`Dialog`, `AlertDialog`, `Sheet`, `Drawer`,
657
+ `CommandDialog`) bars the layers below from pointer-downs while it is open:
658
+ a click on its backdrop closes it alone. `Drawer` and `CommandDialog` close
659
+ on a click on their own scrim, not on any outside pointer-down. Toasts are
660
+ outside every layer: they never take Escape, and a click on one closes
661
+ nothing. The stack is shared through `globalThis`, so a micro-frontend
662
+ bundling its own uikit copy sees the host's layers; parent links are a
663
+ React context, so across two copies the nested layer's place falls back to
664
+ opening order. A non-uikit overlay joins through `useDismissableLayer`
665
+ (`modal`, `pointerOutside`) and `DismissableLayerScope`; mark a floating
666
+ region that should dismiss nothing with `LAYER_IGNORE_ATTR`.
667
+ - **overlay layers (Rust):** `Select`, `Popover`, `Dialog` and `DropdownMenu`
668
+ share one dismissable-layer stack per `VirtualDom`
669
+ (`primitives::use_dismissable_layer`): Escape and a click on a backdrop act
670
+ on the top layer only, so a `Select` inside a `Dialog` closes first. There is
671
+ no document listener — an Escape counts only once it reaches an open layer's
672
+ `onkeydown` — and `AlertDialog`, `Sheet`, `Drawer`, `ContextMenu`, `Menubar`,
673
+ `NavigationMenu`, `HoverCard`, `Tooltip`, `InfoTip`, `DateTimePicker`,
674
+ `CommandDialog` and `DropdownMenuSub` are not on the stack yet.
543
675
  - **terminal:** no plotting engine and no state. `TerminalChart` is a sized
544
676
  `relative` host that hands its element out (`ref` in TS, `id` in Rust) for a
545
677
  consumer-chosen charting library to mount into; `OrderForm` only prevents the
@@ -585,9 +717,8 @@ measuring needs host-only `web-sys`). Known gaps:
585
717
  onto an arbitrary child (no `Slot`), so it publishes them as a
586
718
  `FormControlContext` that `Input`/`Textarea` consume; wrap a bare element and
587
719
  you wire them yourself.
588
- - **resizable / carousel:** pointer-drag physics are TS-only (keyboard in Rust
589
- for resizable; prev/next + keyboard for carousel). Embla momentum is not
590
- reproduced.
720
+ - **carousel:** pointer-drag physics are TS-only (prev/next + keyboard in
721
+ Rust). Embla momentum is not reproduced.
591
722
  - **drawer:** the Vaul-style enter/exit motion is shared by both ports via
592
723
  `motion.css` (inlined into `tokens.css`), keyed on `data-slot` +
593
724
  `data-vaul-drawer-direction` + `data-state`; the panel stays mounted on close
@@ -605,8 +736,8 @@ measuring needs host-only `web-sys`). Known gaps:
605
736
  The lock-up is composed from `mark` + `brand` (display family) + `tagline`,
606
737
  or passed whole as `lockup`. Brand-coloured text reads `primary-ink`.
607
738
  - **TS-only for now:** `NativeSelect`, the `Field` id hand-off,
608
- `SelectValue` labels, and the whole [app shell](#app-shell) (`AppShell`,
609
- `ShellNav`, `BottomTabBar`, `NavBadge` / `NavDot`, `MobileAppBar`, `PageFrame` /
739
+ `SelectValue` labels, `ListRows` / `ListRow` (a table's phone form), and the whole [app shell](#app-shell) (`AppShell`,
740
+ `ShellNav`, `BottomTabBar`, `NavBadge` / `NavDot`, `TopBar`, `AccountMenu`, `MobileAppBar`, `PageFrame` /
610
741
  `PageHeading` / `SectionLabel`, `SectionNav`, `ResourceError`, `SystemBanner`,
611
742
  `Settled`) — no Dioxus twin. Its CSS (the entrances, the markers, the motion
612
743
  and shell tokens) ships in the shared sheet, so a Rust app can use the
@@ -0,0 +1,70 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../lib/cn.js";
5
+ import { Avatar, AvatarFallback } from "./avatar.js";
6
+ import {
7
+ DropdownMenu,
8
+ DropdownMenuContent,
9
+ DropdownMenuGroup,
10
+ DropdownMenuItem,
11
+ DropdownMenuLabel,
12
+ DropdownMenuSeparator,
13
+ DropdownMenuTrigger
14
+ } from "./dropdown-menu.js";
15
+ const initials = (account) => {
16
+ const words = account.name?.trim().split(/\s+/).filter(Boolean) ?? [];
17
+ const letters = words.length > 0 ? words.slice(0, 2).map((w) => Array.from(w)[0]) : [Array.from(account.email)[0]];
18
+ return letters.join("").toUpperCase();
19
+ };
20
+ function AccountMenu({
21
+ account,
22
+ manageHref,
23
+ switchHref,
24
+ groups = [],
25
+ onSignOut,
26
+ linkComponent,
27
+ labels = {},
28
+ className
29
+ }) {
30
+ const Link = linkComponent ?? "a";
31
+ return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
32
+ /* @__PURE__ */ jsx(
33
+ DropdownMenuTrigger,
34
+ {
35
+ "data-slot": "account-menu-trigger",
36
+ "aria-label": labels.trigger ?? "Account",
37
+ className: cn("rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring", className),
38
+ children: /* @__PURE__ */ jsx(Avatar, { children: /* @__PURE__ */ jsx(AvatarFallback, { className: "text-xs font-medium text-ink", children: initials(account) }) })
39
+ }
40
+ ),
41
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", className: "w-64", children: [
42
+ /* @__PURE__ */ jsxs(DropdownMenuLabel, { className: "flex flex-col", children: [
43
+ account.name && /* @__PURE__ */ jsx("span", { className: "truncate text-ink", children: account.name }),
44
+ /* @__PURE__ */ jsx("span", { className: "truncate font-normal text-ink-soft", children: account.email })
45
+ ] }),
46
+ manageHref && /* @__PURE__ */ jsx(DropdownMenuItem, { asChild: true, children: /* @__PURE__ */ jsx("a", { href: manageHref, children: labels.manage ?? "Manage account" }) }),
47
+ groups.map((group) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
48
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
49
+ /* @__PURE__ */ jsxs(DropdownMenuGroup, { children: [
50
+ group.label && /* @__PURE__ */ jsx(DropdownMenuLabel, { className: "text-xs text-ink-soft", children: group.label }),
51
+ group.items.map((item) => {
52
+ const Item = item.external ? "a" : Link;
53
+ const Icon = item.icon;
54
+ return /* @__PURE__ */ jsx(DropdownMenuItem, { asChild: true, children: /* @__PURE__ */ jsxs(Item, { href: item.href, children: [
55
+ Icon && /* @__PURE__ */ jsx(Icon, { className: "size-4" }),
56
+ item.label
57
+ ] }) }, item.id);
58
+ })
59
+ ] })
60
+ ] }, group.id)),
61
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
62
+ /* @__PURE__ */ jsx(DropdownMenuItem, { asChild: true, children: /* @__PURE__ */ jsx("a", { href: switchHref, children: labels.switchAccount ?? "Switch account" }) }),
63
+ /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: onSignOut, children: labels.signOut ?? "Sign out" })
64
+ ] })
65
+ ] });
66
+ }
67
+ export {
68
+ AccountMenu
69
+ };
70
+ //# sourceMappingURL=account-menu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/account-menu.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { Avatar, AvatarFallback } from \"./avatar\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"./dropdown-menu\";\nimport type { NavGroup } from \"./nav-model\";\n\nexport interface AccountMenuLabels {\n /** Accessible name of the trigger. Default `\"Account\"`. */\n trigger?: string;\n /** Default `\"Manage account\"`. */\n manage?: string;\n /** Default `\"Switch account\"`. */\n switchAccount?: string;\n /** Default `\"Sign out\"`. */\n signOut?: string;\n}\n\nexport interface AccountMenuProps {\n account: { name?: string | null | undefined; email: string };\n /** The account center, on the identity provider's origin; no item without it. */\n manageHref?: string | undefined;\n /** A full page load to the sign-in that offers the account chooser. */\n switchHref: string;\n /** The service's own entries, between the account center and switching. Draws `label`, `icon`, `external`. */\n groups?: readonly NavGroup[];\n onSignOut: () => void;\n linkComponent?: React.ElementType;\n labels?: AccountMenuLabels;\n className?: string;\n}\n\nconst initials = (account: AccountMenuProps[\"account\"]) => {\n const words = account.name?.trim().split(/\\s+/).filter(Boolean) ?? [];\n const letters = words.length > 0 ? words.slice(0, 2).map(w => Array.from(w)[0]) : [Array.from(account.email)[0]];\n return letters.join(\"\").toUpperCase();\n};\n\n/**\n * Who is signed in, top right. The order is fixed — the account, the account\n * center, the service's groups, switching, signing out — so every service's\n * menu reads the same; a service adds groups and nothing else.\n */\nexport function AccountMenu({\n account,\n manageHref,\n switchHref,\n groups = [],\n onSignOut,\n linkComponent,\n labels = {},\n className,\n}: AccountMenuProps) {\n const Link = linkComponent ?? \"a\";\n return (\n <DropdownMenu>\n <DropdownMenuTrigger\n data-slot=\"account-menu-trigger\"\n aria-label={labels.trigger ?? \"Account\"}\n className={cn(\"rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring\", className)}\n >\n <Avatar>\n <AvatarFallback className=\"text-xs font-medium text-ink\">{initials(account)}</AvatarFallback>\n </Avatar>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-64\">\n <DropdownMenuLabel className=\"flex flex-col\">\n {account.name && <span className=\"truncate text-ink\">{account.name}</span>}\n <span className=\"truncate font-normal text-ink-soft\">{account.email}</span>\n </DropdownMenuLabel>\n {manageHref && (\n <DropdownMenuItem asChild>\n <a href={manageHref}>{labels.manage ?? \"Manage account\"}</a>\n </DropdownMenuItem>\n )}\n {groups.map(group => (\n <React.Fragment key={group.id}>\n <DropdownMenuSeparator />\n <DropdownMenuGroup>\n {group.label && <DropdownMenuLabel className=\"text-xs text-ink-soft\">{group.label}</DropdownMenuLabel>}\n {group.items.map(item => {\n const Item = item.external ? \"a\" : Link;\n const Icon = item.icon;\n return (\n <DropdownMenuItem key={item.id} asChild>\n <Item href={item.href}>\n {Icon && <Icon className=\"size-4\" />}\n {item.label}\n </Item>\n </DropdownMenuItem>\n );\n })}\n </DropdownMenuGroup>\n </React.Fragment>\n ))}\n <DropdownMenuSeparator />\n <DropdownMenuItem asChild>\n <a href={switchHref}>{labels.switchAccount ?? \"Switch account\"}</a>\n </DropdownMenuItem>\n <DropdownMenuItem onClick={onSignOut}>{labels.signOut ?? \"Sign out\"}</DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n"],"mappings":";AAuEU,cAIF,YAJE;AArEV,YAAY,WAAW;AACvB,SAAS,UAAU;AACnB,SAAS,QAAQ,sBAAsB;AACvC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AA4BP,MAAM,WAAW,CAAC,YAAyC;AACzD,QAAM,QAAQ,QAAQ,MAAM,KAAK,EAAE,MAAM,KAAK,EAAE,OAAO,OAAO,KAAK,CAAC;AACpE,QAAM,UAAU,MAAM,SAAS,IAAI,MAAM,MAAM,GAAG,CAAC,EAAE,IAAI,OAAK,MAAM,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,KAAK,QAAQ,KAAK,EAAE,CAAC,CAAC;AAC/G,SAAO,QAAQ,KAAK,EAAE,EAAE,YAAY;AACtC;AAOO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA,SAAS,CAAC;AAAA,EACV;AAAA,EACA;AAAA,EACA,SAAS,CAAC;AAAA,EACV;AACF,GAAqB;AACnB,QAAM,OAAO,iBAAiB;AAC9B,SACE,qBAAC,gBACC;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,aAAU;AAAA,QACV,cAAY,OAAO,WAAW;AAAA,QAC9B,WAAW,GAAG,0EAA0E,SAAS;AAAA,QAEjG,8BAAC,UACC,8BAAC,kBAAe,WAAU,gCAAgC,mBAAS,OAAO,GAAE,GAC9E;AAAA;AAAA,IACF;AAAA,IACA,qBAAC,uBAAoB,OAAM,OAAM,WAAU,QACzC;AAAA,2BAAC,qBAAkB,WAAU,iBAC1B;AAAA,gBAAQ,QAAQ,oBAAC,UAAK,WAAU,qBAAqB,kBAAQ,MAAK;AAAA,QACnE,oBAAC,UAAK,WAAU,sCAAsC,kBAAQ,OAAM;AAAA,SACtE;AAAA,MACC,cACC,oBAAC,oBAAiB,SAAO,MACvB,8BAAC,OAAE,MAAM,YAAa,iBAAO,UAAU,kBAAiB,GAC1D;AAAA,MAED,OAAO,IAAI,WACV,qBAAC,MAAM,UAAN,EACC;AAAA,4BAAC,yBAAsB;AAAA,QACvB,qBAAC,qBACE;AAAA,gBAAM,SAAS,oBAAC,qBAAkB,WAAU,yBAAyB,gBAAM,OAAM;AAAA,UACjF,MAAM,MAAM,IAAI,UAAQ;AACvB,kBAAM,OAAO,KAAK,WAAW,MAAM;AACnC,kBAAM,OAAO,KAAK;AAClB,mBACE,oBAAC,oBAA+B,SAAO,MACrC,+BAAC,QAAK,MAAM,KAAK,MACd;AAAA,sBAAQ,oBAAC,QAAK,WAAU,UAAS;AAAA,cACjC,KAAK;AAAA,eACR,KAJqB,KAAK,EAK5B;AAAA,UAEJ,CAAC;AAAA,WACH;AAAA,WAhBmB,MAAM,EAiB3B,CACD;AAAA,MACD,oBAAC,yBAAsB;AAAA,MACvB,oBAAC,oBAAiB,SAAO,MACvB,8BAAC,OAAE,MAAM,YAAa,iBAAO,iBAAiB,kBAAiB,GACjE;AAAA,MACA,oBAAC,oBAAiB,SAAS,WAAY,iBAAO,WAAW,YAAW;AAAA,OACtE;AAAA,KACF;AAEJ;","names":[]}
@@ -4,7 +4,7 @@ import * as React from "react";
4
4
  import { cn } from "../lib/cn.js";
5
5
  import { buttonVariants } from "./button.js";
6
6
  import { useControllableState } from "../primitives/use-controllable-state.js";
7
- import { useDismissableLayer } from "../primitives/dismissable-layer.js";
7
+ import { DismissableLayerScope, useDismissableLayer } from "../primitives/dismissable-layer.js";
8
8
  import { useFocusScope } from "../primitives/focus-scope.js";
9
9
  import { usePresence } from "../primitives/presence.js";
10
10
  import { mergeRefs } from "../primitives/merge-refs.js";
@@ -70,7 +70,8 @@ function AlertDialogContent({ className, children, ...props }) {
70
70
  const dismissRef = useDismissableLayer({
71
71
  enabled: open,
72
72
  onDismiss: () => setOpen(false),
73
- exclude: [triggerRef]
73
+ exclude: [triggerRef],
74
+ modal: true
74
75
  });
75
76
  if (!isPresent) return null;
76
77
  return /* @__PURE__ */ jsxs(Portal, { children: [
@@ -85,7 +86,7 @@ function AlertDialogContent({ className, children, ...props }) {
85
86
  ref: mergeRefs(focusRef, dismissRef, presRef),
86
87
  className: cn(ALERT_DIALOG_CONTENT, className),
87
88
  ...props,
88
- children
89
+ children: /* @__PURE__ */ jsx(DismissableLayerScope, { layer: dismissRef, children })
89
90
  }
90
91
  )
91
92
  ] });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/alert-dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { buttonVariants } from \"./button\";\nimport { useControllableState } from \"../primitives/use-controllable-state\";\nimport { useDismissableLayer } from \"../primitives/dismissable-layer\";\nimport { useFocusScope } from \"../primitives/focus-scope\";\nimport { usePresence } from \"../primitives/presence\";\nimport { mergeRefs } from \"../primitives/merge-refs\";\nimport { Portal } from \"../primitives/portal\";\nimport { Slot } from \"../primitives/slot\";\nimport {\n ALERT_DIALOG_CONTENT,\n ALERT_DIALOG_DESCRIPTION,\n ALERT_DIALOG_FOOTER,\n ALERT_DIALOG_HEADER,\n ALERT_DIALOG_OVERLAY,\n ALERT_DIALOG_TITLE,\n} from \"../generated/alert-dialog\";\n\ninterface AlertDialogContextValue {\n open: boolean;\n setOpen: (next: boolean) => void;\n triggerRef: React.RefObject<HTMLElement | null>;\n}\n\nconst AlertDialogContext = React.createContext<AlertDialogContextValue | null>(null);\n\nfunction useAlertDialog(): AlertDialogContextValue {\n const ctx = React.useContext(AlertDialogContext);\n if (!ctx) throw new Error(\"AlertDialog parts must be used within <AlertDialog>\");\n return ctx;\n}\n\nexport interface AlertDialogProps {\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n children?: React.ReactNode;\n}\n\nexport function AlertDialog({ open, defaultOpen = false, onOpenChange, children }: AlertDialogProps) {\n const [isOpen, setOpen] = useControllableState<boolean>({\n ...(open !== undefined ? { value: open } : {}),\n defaultValue: defaultOpen,\n ...(onOpenChange ? { onChange: onOpenChange } : {}),\n });\n const triggerRef = React.useRef<HTMLElement | null>(null);\n return (\n <AlertDialogContext.Provider value={{ open: isOpen, setOpen, triggerRef }}>\n {children}\n </AlertDialogContext.Provider>\n );\n}\n\nexport interface AlertDialogTriggerProps extends React.ComponentProps<\"button\"> {\n asChild?: boolean;\n}\n\nexport function AlertDialogTrigger({ asChild = false, onClick, ...props }: AlertDialogTriggerProps) {\n const { open, setOpen, triggerRef } = useAlertDialog();\n const Comp = asChild ? Slot : \"button\";\n return (\n <Comp\n data-slot=\"alert-dialog-trigger\"\n aria-expanded={open}\n ref={triggerRef as React.Ref<HTMLButtonElement>}\n onClick={(e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n setOpen(true);\n }}\n {...(props as Record<string, unknown>)}\n />\n );\n}\n\nexport interface AlertDialogOverlayProps extends React.ComponentProps<\"div\"> {}\n\nexport function AlertDialogOverlay({ className, ...props }: AlertDialogOverlayProps) {\n const { open, setOpen } = useAlertDialog();\n return (\n <div\n data-slot=\"alert-dialog-overlay\"\n data-state={open ? \"open\" : \"closed\"}\n onClick={() => setOpen(false)}\n className={cn(ALERT_DIALOG_OVERLAY, className)}\n {...(props as Record<string, unknown>)}\n />\n );\n}\n\nexport interface AlertDialogContentProps extends React.ComponentProps<\"div\"> {}\n\nexport function AlertDialogContent({ className, children, ...props }: AlertDialogContentProps) {\n const { open, setOpen, triggerRef } = useAlertDialog();\n const { isPresent, ref: presRef } = usePresence(open);\n const focusRef = useFocusScope(open, { returnFocusTo: triggerRef });\n const dismissRef = useDismissableLayer({\n enabled: open,\n onDismiss: () => setOpen(false),\n exclude: [triggerRef],\n });\n if (!isPresent) return null;\n return (\n <Portal>\n <AlertDialogOverlay />\n <div\n data-slot=\"alert-dialog-content\"\n role=\"alertdialog\"\n aria-modal=\"true\"\n data-state={open ? \"open\" : \"closed\"}\n ref={mergeRefs(focusRef, dismissRef, presRef as React.Ref<HTMLDivElement>)}\n className={cn(ALERT_DIALOG_CONTENT, className)}\n {...(props as Record<string, unknown>)}\n >\n {children}\n </div>\n </Portal>\n );\n}\n\nexport function AlertDialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"alert-dialog-header\"\n className={cn(ALERT_DIALOG_HEADER, className)}\n {...props}\n />\n );\n}\n\nexport function AlertDialogFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"alert-dialog-footer\"\n className={cn(ALERT_DIALOG_FOOTER, className)}\n {...props}\n />\n );\n}\n\nexport function AlertDialogTitle({ className, ...props }: React.ComponentProps<\"h2\">) {\n return (\n <h2\n data-slot=\"alert-dialog-title\"\n className={cn(ALERT_DIALOG_TITLE, className)}\n {...props}\n />\n );\n}\n\nexport function AlertDialogDescription({ className, ...props }: React.ComponentProps<\"p\">) {\n return (\n <p\n data-slot=\"alert-dialog-description\"\n className={cn(ALERT_DIALOG_DESCRIPTION, className)}\n {...props}\n />\n );\n}\n\nexport interface AlertDialogActionProps extends React.ComponentProps<\"button\"> {}\n\nexport function AlertDialogAction({ className, onClick, ...props }: AlertDialogActionProps) {\n const { setOpen } = useAlertDialog();\n return (\n <button\n data-slot=\"alert-dialog-action\"\n className={cn(buttonVariants(), className)}\n onClick={(e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n setOpen(false);\n }}\n {...(props as Record<string, unknown>)}\n />\n );\n}\n\nexport interface AlertDialogCancelProps extends React.ComponentProps<\"button\"> {}\n\nexport function AlertDialogCancel({ className, onClick, ...props }: AlertDialogCancelProps) {\n const { setOpen } = useAlertDialog();\n return (\n <button\n data-slot=\"alert-dialog-cancel\"\n className={cn(buttonVariants({ variant: \"outline\" }), className)}\n onClick={(e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n setOpen(false);\n }}\n {...(props as Record<string, unknown>)}\n />\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/alert-dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { buttonVariants } from \"./button\";\nimport { useControllableState } from \"../primitives/use-controllable-state\";\nimport { DismissableLayerScope, useDismissableLayer } from \"../primitives/dismissable-layer\";\nimport { useFocusScope } from \"../primitives/focus-scope\";\nimport { usePresence } from \"../primitives/presence\";\nimport { mergeRefs } from \"../primitives/merge-refs\";\nimport { Portal } from \"../primitives/portal\";\nimport { Slot } from \"../primitives/slot\";\nimport {\n ALERT_DIALOG_CONTENT,\n ALERT_DIALOG_DESCRIPTION,\n ALERT_DIALOG_FOOTER,\n ALERT_DIALOG_HEADER,\n ALERT_DIALOG_OVERLAY,\n ALERT_DIALOG_TITLE,\n} from \"../generated/alert-dialog\";\n\ninterface AlertDialogContextValue {\n open: boolean;\n setOpen: (next: boolean) => void;\n triggerRef: React.RefObject<HTMLElement | null>;\n}\n\nconst AlertDialogContext = React.createContext<AlertDialogContextValue | null>(null);\n\nfunction useAlertDialog(): AlertDialogContextValue {\n const ctx = React.useContext(AlertDialogContext);\n if (!ctx) throw new Error(\"AlertDialog parts must be used within <AlertDialog>\");\n return ctx;\n}\n\nexport interface AlertDialogProps {\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n children?: React.ReactNode;\n}\n\nexport function AlertDialog({ open, defaultOpen = false, onOpenChange, children }: AlertDialogProps) {\n const [isOpen, setOpen] = useControllableState<boolean>({\n ...(open !== undefined ? { value: open } : {}),\n defaultValue: defaultOpen,\n ...(onOpenChange ? { onChange: onOpenChange } : {}),\n });\n const triggerRef = React.useRef<HTMLElement | null>(null);\n return (\n <AlertDialogContext.Provider value={{ open: isOpen, setOpen, triggerRef }}>\n {children}\n </AlertDialogContext.Provider>\n );\n}\n\nexport interface AlertDialogTriggerProps extends React.ComponentProps<\"button\"> {\n asChild?: boolean;\n}\n\nexport function AlertDialogTrigger({ asChild = false, onClick, ...props }: AlertDialogTriggerProps) {\n const { open, setOpen, triggerRef } = useAlertDialog();\n const Comp = asChild ? Slot : \"button\";\n return (\n <Comp\n data-slot=\"alert-dialog-trigger\"\n aria-expanded={open}\n ref={triggerRef as React.Ref<HTMLButtonElement>}\n onClick={(e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n setOpen(true);\n }}\n {...(props as Record<string, unknown>)}\n />\n );\n}\n\nexport interface AlertDialogOverlayProps extends React.ComponentProps<\"div\"> {}\n\nexport function AlertDialogOverlay({ className, ...props }: AlertDialogOverlayProps) {\n const { open, setOpen } = useAlertDialog();\n return (\n <div\n data-slot=\"alert-dialog-overlay\"\n data-state={open ? \"open\" : \"closed\"}\n onClick={() => setOpen(false)}\n className={cn(ALERT_DIALOG_OVERLAY, className)}\n {...(props as Record<string, unknown>)}\n />\n );\n}\n\nexport interface AlertDialogContentProps extends React.ComponentProps<\"div\"> {}\n\nexport function AlertDialogContent({ className, children, ...props }: AlertDialogContentProps) {\n const { open, setOpen, triggerRef } = useAlertDialog();\n const { isPresent, ref: presRef } = usePresence(open);\n const focusRef = useFocusScope(open, { returnFocusTo: triggerRef });\n const dismissRef = useDismissableLayer({\n enabled: open,\n onDismiss: () => setOpen(false),\n exclude: [triggerRef],\n modal: true,\n });\n if (!isPresent) return null;\n return (\n <Portal>\n <AlertDialogOverlay />\n <div\n data-slot=\"alert-dialog-content\"\n role=\"alertdialog\"\n aria-modal=\"true\"\n data-state={open ? \"open\" : \"closed\"}\n ref={mergeRefs(focusRef, dismissRef, presRef as React.Ref<HTMLDivElement>)}\n className={cn(ALERT_DIALOG_CONTENT, className)}\n {...(props as Record<string, unknown>)}\n >\n <DismissableLayerScope layer={dismissRef}>{children}</DismissableLayerScope>\n </div>\n </Portal>\n );\n}\n\nexport function AlertDialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"alert-dialog-header\"\n className={cn(ALERT_DIALOG_HEADER, className)}\n {...props}\n />\n );\n}\n\nexport function AlertDialogFooter({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"alert-dialog-footer\"\n className={cn(ALERT_DIALOG_FOOTER, className)}\n {...props}\n />\n );\n}\n\nexport function AlertDialogTitle({ className, ...props }: React.ComponentProps<\"h2\">) {\n return (\n <h2\n data-slot=\"alert-dialog-title\"\n className={cn(ALERT_DIALOG_TITLE, className)}\n {...props}\n />\n );\n}\n\nexport function AlertDialogDescription({ className, ...props }: React.ComponentProps<\"p\">) {\n return (\n <p\n data-slot=\"alert-dialog-description\"\n className={cn(ALERT_DIALOG_DESCRIPTION, className)}\n {...props}\n />\n );\n}\n\nexport interface AlertDialogActionProps extends React.ComponentProps<\"button\"> {}\n\nexport function AlertDialogAction({ className, onClick, ...props }: AlertDialogActionProps) {\n const { setOpen } = useAlertDialog();\n return (\n <button\n data-slot=\"alert-dialog-action\"\n className={cn(buttonVariants(), className)}\n onClick={(e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n setOpen(false);\n }}\n {...(props as Record<string, unknown>)}\n />\n );\n}\n\nexport interface AlertDialogCancelProps extends React.ComponentProps<\"button\"> {}\n\nexport function AlertDialogCancel({ className, onClick, ...props }: AlertDialogCancelProps) {\n const { setOpen } = useAlertDialog();\n return (\n <button\n data-slot=\"alert-dialog-cancel\"\n className={cn(buttonVariants({ variant: \"outline\" }), className)}\n onClick={(e: React.MouseEvent<HTMLButtonElement>) => {\n onClick?.(e);\n setOpen(false);\n }}\n {...(props as Record<string, unknown>)}\n />\n );\n}\n"],"mappings":"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","names":[]}
@@ -5,12 +5,14 @@ const AT = {
5
5
  md: {
6
6
  rail: "hidden md:flex",
7
7
  tabBar: "md:hidden",
8
+ topBar: "hidden md:block",
8
9
  reserve: "pb-[calc(var(--shell-tab-bar-h)+env(safe-area-inset-bottom,0px))] md:pb-0",
9
10
  banner: "md:px-8"
10
11
  },
11
12
  lg: {
12
13
  rail: "hidden lg:flex",
13
14
  tabBar: "lg:hidden",
15
+ topBar: "hidden lg:block",
14
16
  reserve: "pb-[calc(var(--shell-tab-bar-h)+env(safe-area-inset-bottom,0px))] lg:pb-0",
15
17
  banner: "lg:px-8"
16
18
  }
@@ -19,6 +21,7 @@ const present = (node) => node !== null && node !== void 0 && node !== false;
19
21
  function AppShell({
20
22
  rail,
21
23
  tabBar,
24
+ topBar,
22
25
  banner,
23
26
  breakpoint = "lg",
24
27
  mainProps,
@@ -55,6 +58,14 @@ function AppShell({
55
58
  "data-slot": "app-shell-content",
56
59
  className: cn("flex min-w-0 flex-1 flex-col", present(tabBar) && at.reserve),
57
60
  children: [
61
+ present(topBar) && /* @__PURE__ */ jsx(
62
+ "div",
63
+ {
64
+ "data-slot": "app-shell-top-bar",
65
+ className: cn("sticky top-[var(--ev-shell-offset,0px)] z-30", at.topBar),
66
+ children: topBar
67
+ }
68
+ ),
58
69
  present(banner) && /* @__PURE__ */ jsx(
59
70
  "div",
60
71
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/app-shell.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../lib/cn\";\n\n/** Where the shell swaps the bottom tab bar for the rail. */\nexport type ShellBreakpoint = \"md\" | \"lg\";\n\nexport interface AppShellProps extends React.ComponentProps<\"div\"> {\n /** The desktop rail — a `ShellNav`. Shown from `breakpoint` up. */\n rail?: React.ReactNode;\n /** The phone's tab bar — a `BottomTabBar`. Pinned to the viewport bottom below `breakpoint`. */\n tabBar?: React.ReactNode;\n /** Strips above the page (`SystemBanner`s). An empty slot takes no room. */\n banner?: React.ReactNode;\n /** Default `lg`. */\n breakpoint?: ShellBreakpoint;\n /** Props for the `<main>` the page renders into (an `id` for a skip link, say). */\n mainProps?: React.ComponentProps<\"main\">;\n}\n\n// Tailwind only sees whole class strings, so each breakpoint spells its own.\nconst AT = {\n md: {\n rail: \"hidden md:flex\",\n tabBar: \"md:hidden\",\n reserve: \"pb-[calc(var(--shell-tab-bar-h)+env(safe-area-inset-bottom,0px))] md:pb-0\",\n banner: \"md:px-8\",\n },\n lg: {\n rail: \"hidden lg:flex\",\n tabBar: \"lg:hidden\",\n reserve: \"pb-[calc(var(--shell-tab-bar-h)+env(safe-area-inset-bottom,0px))] lg:pb-0\",\n banner: \"lg:px-8\",\n },\n} as const;\n\nconst present = (node: React.ReactNode) => node !== null && node !== undefined && node !== false;\n\n/**\n * The signed-in app's frame: a rail beside a content column on a wide screen, a\n * bottom tab bar under it on a phone. Layout only — the nav, the banners and the\n * page are slots, so the shell knows nothing about routes, roles or data.\n *\n * Fills the viewport less `--ev-shell-offset` (the room a host shell's own header\n * takes); the rail sticks under that offset at full remaining height and scrolls\n * by itself when it cannot fit. The column reserves the tab bar's height plus\n * the bottom safe area, so the bar never covers content.\n */\nexport function AppShell({\n rail,\n tabBar,\n banner,\n breakpoint = \"lg\",\n mainProps,\n className,\n children,\n ...props\n}: AppShellProps) {\n const at = AT[breakpoint];\n return (\n <div\n data-slot=\"app-shell\"\n data-breakpoint={breakpoint}\n className={cn(\n \"flex min-h-[calc(100dvh-var(--ev-shell-offset,0px))] bg-background\",\n className,\n )}\n {...props}\n >\n {present(rail) && (\n <div\n data-slot=\"app-shell-rail\"\n className={cn(\n \"sticky top-[var(--ev-shell-offset,0px)] h-[calc(100dvh-var(--ev-shell-offset,0px))] shrink-0 self-start\",\n at.rail,\n )}\n >\n {rail}\n </div>\n )}\n <div\n data-slot=\"app-shell-content\"\n className={cn(\"flex min-w-0 flex-1 flex-col\", present(tabBar) && at.reserve)}\n >\n {present(banner) && (\n <div\n data-slot=\"app-shell-banner\"\n className={cn(\"flex flex-col gap-2 px-4 pt-4 empty:hidden\", at.banner)}\n >\n {banner}\n </div>\n )}\n <main {...mainProps} className={cn(\"min-w-0 flex-1\", mainProps?.className)}>\n {children}\n </main>\n </div>\n {present(tabBar) && (\n <div\n data-slot=\"app-shell-tab-bar\"\n className={cn(\"fixed inset-x-0 bottom-0 z-40\", at.tabBar)}\n >\n {tabBar}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"AAqEQ,cAUF,YAVE;AArER,YAAY,WAAW;AACvB,SAAS,UAAU;AAmBnB,MAAM,KAAK;AAAA,EACT,IAAI;AAAA,IACF,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,QAAQ;AAAA,EACV;AAAA,EACA,IAAI;AAAA,IACF,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,QAAQ;AAAA,EACV;AACF;AAEA,MAAM,UAAU,CAAC,SAA0B,SAAS,QAAQ,SAAS,UAAa,SAAS;AAYpF,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAkB;AAChB,QAAM,KAAK,GAAG,UAAU;AACxB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,mBAAiB;AAAA,MACjB,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA,gBAAQ,IAAI,KACX;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,WAAW;AAAA,cACT;AAAA,cACA,GAAG;AAAA,YACL;AAAA,YAEC;AAAA;AAAA,QACH;AAAA,QAEF;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,WAAW,GAAG,gCAAgC,QAAQ,MAAM,KAAK,GAAG,OAAO;AAAA,YAE1E;AAAA,sBAAQ,MAAM,KACb;AAAA,gBAAC;AAAA;AAAA,kBACC,aAAU;AAAA,kBACV,WAAW,GAAG,8CAA8C,GAAG,MAAM;AAAA,kBAEpE;AAAA;AAAA,cACH;AAAA,cAEF,oBAAC,UAAM,GAAG,WAAW,WAAW,GAAG,kBAAkB,WAAW,SAAS,GACtE,UACH;AAAA;AAAA;AAAA,QACF;AAAA,QACC,QAAQ,MAAM,KACb;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,WAAW,GAAG,iCAAiC,GAAG,MAAM;AAAA,YAEvD;AAAA;AAAA,QACH;AAAA;AAAA;AAAA,EAEJ;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/components/app-shell.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cn } from \"../lib/cn\";\n\n/** Where the shell swaps the bottom tab bar for the rail. */\nexport type ShellBreakpoint = \"md\" | \"lg\";\n\nexport interface AppShellProps extends React.ComponentProps<\"div\"> {\n /** The desktop rail — a `ShellNav`. Shown from `breakpoint` up. */\n rail?: React.ReactNode;\n /** The phone's tab bar — a `BottomTabBar`. Pinned to the viewport bottom below `breakpoint`. */\n tabBar?: React.ReactNode;\n /** A `TopBar`, pinned over the column from `breakpoint` up (a phone titles its screen with `MobileAppBar`). */\n topBar?: React.ReactNode;\n /** Strips above the page (`SystemBanner`s). An empty slot takes no room. */\n banner?: React.ReactNode;\n /** Default `lg`. */\n breakpoint?: ShellBreakpoint;\n /** Props for the `<main>` the page renders into (an `id` for a skip link, say). */\n mainProps?: React.ComponentProps<\"main\">;\n}\n\n// Tailwind only sees whole class strings, so each breakpoint spells its own.\nconst AT = {\n md: {\n rail: \"hidden md:flex\",\n tabBar: \"md:hidden\",\n topBar: \"hidden md:block\",\n reserve: \"pb-[calc(var(--shell-tab-bar-h)+env(safe-area-inset-bottom,0px))] md:pb-0\",\n banner: \"md:px-8\",\n },\n lg: {\n rail: \"hidden lg:flex\",\n tabBar: \"lg:hidden\",\n topBar: \"hidden lg:block\",\n reserve: \"pb-[calc(var(--shell-tab-bar-h)+env(safe-area-inset-bottom,0px))] lg:pb-0\",\n banner: \"lg:px-8\",\n },\n} as const;\n\nconst present = (node: React.ReactNode) => node !== null && node !== undefined && node !== false;\n\n/**\n * The signed-in app's frame: a rail beside a content column on a wide screen, a\n * bottom tab bar under it on a phone. Layout only — the nav, the top bar, the banners\n * and the page are slots, so the shell knows nothing about routes, roles or data.\n *\n * Fills the viewport less `--ev-shell-offset` (the room a host shell's own header\n * takes); the rail sticks under that offset at full remaining height and scrolls\n * by itself when it cannot fit. The column reserves the tab bar's height plus\n * the bottom safe area, so the bar never covers content.\n */\nexport function AppShell({\n rail,\n tabBar,\n topBar,\n banner,\n breakpoint = \"lg\",\n mainProps,\n className,\n children,\n ...props\n}: AppShellProps) {\n const at = AT[breakpoint];\n return (\n <div\n data-slot=\"app-shell\"\n data-breakpoint={breakpoint}\n className={cn(\n \"flex min-h-[calc(100dvh-var(--ev-shell-offset,0px))] bg-background\",\n className,\n )}\n {...props}\n >\n {present(rail) && (\n <div\n data-slot=\"app-shell-rail\"\n className={cn(\n \"sticky top-[var(--ev-shell-offset,0px)] h-[calc(100dvh-var(--ev-shell-offset,0px))] shrink-0 self-start\",\n at.rail,\n )}\n >\n {rail}\n </div>\n )}\n <div\n data-slot=\"app-shell-content\"\n className={cn(\"flex min-w-0 flex-1 flex-col\", present(tabBar) && at.reserve)}\n >\n {present(topBar) && (\n <div\n data-slot=\"app-shell-top-bar\"\n className={cn(\"sticky top-[var(--ev-shell-offset,0px)] z-30\", at.topBar)}\n >\n {topBar}\n </div>\n )}\n {present(banner) && (\n <div\n data-slot=\"app-shell-banner\"\n className={cn(\"flex flex-col gap-2 px-4 pt-4 empty:hidden\", at.banner)}\n >\n {banner}\n </div>\n )}\n <main {...mainProps} className={cn(\"min-w-0 flex-1\", mainProps?.className)}>\n {children}\n </main>\n </div>\n {present(tabBar) && (\n <div\n data-slot=\"app-shell-tab-bar\"\n className={cn(\"fixed inset-x-0 bottom-0 z-40\", at.tabBar)}\n >\n {tabBar}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"AA0EQ,cAUF,YAVE;AA1ER,YAAY,WAAW;AACvB,SAAS,UAAU;AAqBnB,MAAM,KAAK;AAAA,EACT,IAAI;AAAA,IACF,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,QAAQ;AAAA,EACV;AAAA,EACA,IAAI;AAAA,IACF,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,QAAQ;AAAA,EACV;AACF;AAEA,MAAM,UAAU,CAAC,SAA0B,SAAS,QAAQ,SAAS,UAAa,SAAS;AAYpF,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAkB;AAChB,QAAM,KAAK,GAAG,UAAU;AACxB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,mBAAiB;AAAA,MACjB,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA,gBAAQ,IAAI,KACX;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,WAAW;AAAA,cACT;AAAA,cACA,GAAG;AAAA,YACL;AAAA,YAEC;AAAA;AAAA,QACH;AAAA,QAEF;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,WAAW,GAAG,gCAAgC,QAAQ,MAAM,KAAK,GAAG,OAAO;AAAA,YAE1E;AAAA,sBAAQ,MAAM,KACb;AAAA,gBAAC;AAAA;AAAA,kBACC,aAAU;AAAA,kBACV,WAAW,GAAG,gDAAgD,GAAG,MAAM;AAAA,kBAEtE;AAAA;AAAA,cACH;AAAA,cAED,QAAQ,MAAM,KACb;AAAA,gBAAC;AAAA;AAAA,kBACC,aAAU;AAAA,kBACV,WAAW,GAAG,8CAA8C,GAAG,MAAM;AAAA,kBAEpE;AAAA;AAAA,cACH;AAAA,cAEF,oBAAC,UAAM,GAAG,WAAW,WAAW,GAAG,kBAAkB,WAAW,SAAS,GACtE,UACH;AAAA;AAAA;AAAA,QACF;AAAA,QACC,QAAQ,MAAM,KACb;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,WAAW,GAAG,iCAAiC,GAAG,MAAM;AAAA,YAEvD;AAAA;AAAA,QACH;AAAA;AAAA;AAAA,EAEJ;AAEJ;","names":[]}
@@ -0,0 +1,42 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import {
4
+ Empty,
5
+ EmptyContent,
6
+ EmptyDescription,
7
+ EmptyHeader,
8
+ EmptyMedia,
9
+ EmptyTitle
10
+ } from "./empty.js";
11
+ function AuthWall({ title, description, children, ...props }) {
12
+ return /* @__PURE__ */ jsxs(Empty, { ...props, children: [
13
+ /* @__PURE__ */ jsxs(EmptyHeader, { children: [
14
+ /* @__PURE__ */ jsx(EmptyMedia, { variant: "icon", children: /* @__PURE__ */ jsx(LockIcon, {}) }),
15
+ /* @__PURE__ */ jsx(EmptyTitle, { children: title }),
16
+ /* @__PURE__ */ jsx(EmptyDescription, { children: description })
17
+ ] }),
18
+ /* @__PURE__ */ jsx(EmptyContent, { children })
19
+ ] });
20
+ }
21
+ function LockIcon() {
22
+ return /* @__PURE__ */ jsxs(
23
+ "svg",
24
+ {
25
+ viewBox: "0 0 24 24",
26
+ fill: "none",
27
+ stroke: "currentColor",
28
+ strokeWidth: 2,
29
+ strokeLinecap: "round",
30
+ strokeLinejoin: "round",
31
+ "aria-hidden": true,
32
+ children: [
33
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "11", width: "18", height: "11", rx: "2", ry: "2" }),
34
+ /* @__PURE__ */ jsx("path", { d: "M7 11V7a5 5 0 0 1 10 0v4" })
35
+ ]
36
+ }
37
+ );
38
+ }
39
+ export {
40
+ AuthWall
41
+ };
42
+ //# sourceMappingURL=auth-wall.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/auth-wall.tsx"],"sourcesContent":["import * as React from \"react\";\nimport {\n Empty,\n EmptyContent,\n EmptyDescription,\n EmptyHeader,\n EmptyMedia,\n EmptyTitle,\n} from \"./empty\";\n\n/**\n * What a section shows a caller it is not for: an `Empty` with a lock, a title, why,\n * and an action slot. A guest gets the sign-in button there; a signed-in caller\n * without the permission gets no action, or the host's own (request access).\n */\nexport interface AuthWallProps extends Omit<React.ComponentProps<\"div\">, \"title\"> {\n title: React.ReactNode;\n description: React.ReactNode;\n}\n\nexport function AuthWall({ title, description, children, ...props }: AuthWallProps) {\n return (\n <Empty {...props}>\n <EmptyHeader>\n <EmptyMedia variant=\"icon\">\n <LockIcon />\n </EmptyMedia>\n <EmptyTitle>{title}</EmptyTitle>\n <EmptyDescription>{description}</EmptyDescription>\n </EmptyHeader>\n <EmptyContent>{children}</EmptyContent>\n </Empty>\n );\n}\n\n// lucide `lock`, inlined so the kit keeps its zero-icon-dep footprint.\nfunction LockIcon() {\n return (\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden\n >\n <rect x=\"3\" y=\"11\" width=\"18\" height=\"11\" rx=\"2\" ry=\"2\" />\n <path d=\"M7 11V7a5 5 0 0 1 10 0v4\" />\n </svg>\n );\n}\n"],"mappings":"AAuBM,SAEI,KAFJ;AAvBN,YAAY,WAAW;AACvB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAYA,SAAS,SAAS,EAAE,OAAO,aAAa,UAAU,GAAG,MAAM,GAAkB;AAClF,SACE,qBAAC,SAAO,GAAG,OACT;AAAA,yBAAC,eACC;AAAA,0BAAC,cAAW,SAAQ,QAClB,8BAAC,YAAS,GACZ;AAAA,MACA,oBAAC,cAAY,iBAAM;AAAA,MACnB,oBAAC,oBAAkB,uBAAY;AAAA,OACjC;AAAA,IACA,oBAAC,gBAAc,UAAS;AAAA,KAC1B;AAEJ;AAGA,SAAS,WAAW;AAClB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,SAAQ;AAAA,MACR,MAAK;AAAA,MACL,QAAO;AAAA,MACP,aAAa;AAAA,MACb,eAAc;AAAA,MACd,gBAAe;AAAA,MACf,eAAW;AAAA,MAEX;AAAA,4BAAC,UAAK,GAAE,KAAI,GAAE,MAAK,OAAM,MAAK,QAAO,MAAK,IAAG,KAAI,IAAG,KAAI;AAAA,QACxD,oBAAC,UAAK,GAAE,4BAA2B;AAAA;AAAA;AAAA,EACrC;AAEJ;","names":[]}
@@ -4,6 +4,7 @@ import * as React from "react";
4
4
  import { cn } from "../lib/cn.js";
5
5
  import { CHECKBOX_BASE, CHECKBOX_INDICATOR } from "../generated/checkbox.js";
6
6
  import { useControllableState } from "../primitives/use-controllable-state.js";
7
+ import { markLabelable } from "../primitives/labelable.js";
7
8
  import { useFieldControlId } from "./field-context.js";
8
9
  function Checkbox({
9
10
  className,
@@ -62,6 +63,7 @@ function Checkbox({
62
63
  }
63
64
  );
64
65
  }
66
+ markLabelable(Checkbox);
65
67
  export {
66
68
  Checkbox
67
69
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/checkbox.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { CHECKBOX_BASE, CHECKBOX_INDICATOR } from \"../generated/checkbox\";\nimport { useControllableState } from \"../primitives/use-controllable-state\";\nimport { useFieldControlId } from \"./field-context\";\n\nexport interface CheckboxProps\n extends Omit<React.ComponentProps<\"button\">, \"onChange\"> {\n checked?: boolean;\n defaultChecked?: boolean;\n onCheckedChange?: (checked: boolean) => void;\n}\n\nexport function Checkbox({\n className,\n checked,\n defaultChecked = false,\n onCheckedChange,\n onClick,\n disabled,\n id,\n ...props\n}: CheckboxProps) {\n const [on, setOn] = useControllableState<boolean>({\n ...(checked !== undefined ? { value: checked } : {}),\n defaultValue: defaultChecked,\n ...(onCheckedChange ? { onChange: onCheckedChange } : {}),\n });\n const state = on ? \"checked\" : \"unchecked\";\n return (\n <button\n type=\"button\"\n role=\"checkbox\"\n data-slot=\"checkbox\"\n data-state={state}\n aria-checked={on}\n disabled={disabled}\n id={useFieldControlId(id)}\n className={cn(CHECKBOX_BASE, className)}\n onClick={(e) => {\n onClick?.(e);\n setOn(!on);\n }}\n {...props}\n >\n {on && (\n <span\n data-slot=\"checkbox-indicator\"\n className={CHECKBOX_INDICATOR}\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"size-3.5\"\n aria-hidden=\"true\"\n >\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n </span>\n )}\n </button>\n );\n}\n"],"mappings":";AA+DY;AA7DZ,YAAY,WAAW;AACvB,SAAS,UAAU;AACnB,SAAS,eAAe,0BAA0B;AAClD,SAAS,4BAA4B;AACrC,SAAS,yBAAyB;AAS3B,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAkB;AAChB,QAAM,CAAC,IAAI,KAAK,IAAI,qBAA8B;AAAA,IAChD,GAAI,YAAY,SAAY,EAAE,OAAO,QAAQ,IAAI,CAAC;AAAA,IAClD,cAAc;AAAA,IACd,GAAI,kBAAkB,EAAE,UAAU,gBAAgB,IAAI,CAAC;AAAA,EACzD,CAAC;AACD,QAAM,QAAQ,KAAK,YAAY;AAC/B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,MAAK;AAAA,MACL,aAAU;AAAA,MACV,cAAY;AAAA,MACZ,gBAAc;AAAA,MACd;AAAA,MACA,IAAI,kBAAkB,EAAE;AAAA,MACxB,WAAW,GAAG,eAAe,SAAS;AAAA,MACtC,SAAS,CAAC,MAAM;AACd,kBAAU,CAAC;AACX,cAAM,CAAC,EAAE;AAAA,MACX;AAAA,MACC,GAAG;AAAA,MAEH,gBACC;AAAA,QAAC;AAAA;AAAA,UACC,aAAU;AAAA,UACV,WAAW;AAAA,UAEX;AAAA,YAAC;AAAA;AAAA,cACC,OAAM;AAAA,cACN,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,QAAO;AAAA,cACP,aAAY;AAAA,cACZ,eAAc;AAAA,cACd,gBAAe;AAAA,cACf,WAAU;AAAA,cACV,eAAY;AAAA,cAEZ,8BAAC,UAAK,GAAE,mBAAkB;AAAA;AAAA,UAC5B;AAAA;AAAA,MACF;AAAA;AAAA,EAEJ;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/components/checkbox.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { CHECKBOX_BASE, CHECKBOX_INDICATOR } from \"../generated/checkbox\";\nimport { useControllableState } from \"../primitives/use-controllable-state\";\nimport { markLabelable } from \"../primitives/labelable\";\nimport { useFieldControlId } from \"./field-context\";\n\nexport interface CheckboxProps\n extends Omit<React.ComponentProps<\"button\">, \"onChange\"> {\n checked?: boolean;\n defaultChecked?: boolean;\n onCheckedChange?: (checked: boolean) => void;\n}\n\nexport function Checkbox({\n className,\n checked,\n defaultChecked = false,\n onCheckedChange,\n onClick,\n disabled,\n id,\n ...props\n}: CheckboxProps) {\n const [on, setOn] = useControllableState<boolean>({\n ...(checked !== undefined ? { value: checked } : {}),\n defaultValue: defaultChecked,\n ...(onCheckedChange ? { onChange: onCheckedChange } : {}),\n });\n const state = on ? \"checked\" : \"unchecked\";\n return (\n <button\n type=\"button\"\n role=\"checkbox\"\n data-slot=\"checkbox\"\n data-state={state}\n aria-checked={on}\n disabled={disabled}\n id={useFieldControlId(id)}\n className={cn(CHECKBOX_BASE, className)}\n onClick={(e) => {\n onClick?.(e);\n setOn(!on);\n }}\n {...props}\n >\n {on && (\n <span\n data-slot=\"checkbox-indicator\"\n className={CHECKBOX_INDICATOR}\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"size-3.5\"\n aria-hidden=\"true\"\n >\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n </span>\n )}\n </button>\n );\n}\n\nmarkLabelable(Checkbox);\n"],"mappings":";AAgEY;AA9DZ,YAAY,WAAW;AACvB,SAAS,UAAU;AACnB,SAAS,eAAe,0BAA0B;AAClD,SAAS,4BAA4B;AACrC,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AAS3B,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAkB;AAChB,QAAM,CAAC,IAAI,KAAK,IAAI,qBAA8B;AAAA,IAChD,GAAI,YAAY,SAAY,EAAE,OAAO,QAAQ,IAAI,CAAC;AAAA,IAClD,cAAc;AAAA,IACd,GAAI,kBAAkB,EAAE,UAAU,gBAAgB,IAAI,CAAC;AAAA,EACzD,CAAC;AACD,QAAM,QAAQ,KAAK,YAAY;AAC/B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,MAAK;AAAA,MACL,aAAU;AAAA,MACV,cAAY;AAAA,MACZ,gBAAc;AAAA,MACd;AAAA,MACA,IAAI,kBAAkB,EAAE;AAAA,MACxB,WAAW,GAAG,eAAe,SAAS;AAAA,MACtC,SAAS,CAAC,MAAM;AACd,kBAAU,CAAC;AACX,cAAM,CAAC,EAAE;AAAA,MACX;AAAA,MACC,GAAG;AAAA,MAEH,gBACC;AAAA,QAAC;AAAA;AAAA,UACC,aAAU;AAAA,UACV,WAAW;AAAA,UAEX;AAAA,YAAC;AAAA;AAAA,cACC,OAAM;AAAA,cACN,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,QAAO;AAAA,cACP,aAAY;AAAA,cACZ,eAAc;AAAA,cACd,gBAAe;AAAA,cACf,WAAU;AAAA,cACV,eAAY;AAAA,cAEZ,8BAAC,UAAK,GAAE,mBAAkB;AAAA;AAAA,UAC5B;AAAA;AAAA,MACF;AAAA;AAAA,EAEJ;AAEJ;AAEA,cAAc,QAAQ;","names":[]}