@dirstack/ui 1.0.1 → 1.2.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.
Files changed (48) hide show
  1. package/README.md +17 -1
  2. package/dist/components/bar-list.d.ts +57 -0
  3. package/dist/components/bar-list.d.ts.map +1 -0
  4. package/dist/components/bar-list.js +117 -0
  5. package/dist/components/bar-list.js.map +1 -0
  6. package/dist/components/breakdown-card.d.ts +31 -0
  7. package/dist/components/breakdown-card.d.ts.map +1 -0
  8. package/dist/components/breakdown-card.js +41 -0
  9. package/dist/components/breakdown-card.js.map +1 -0
  10. package/dist/components/button.js +1 -1
  11. package/dist/components/dialog.d.ts +13 -13
  12. package/dist/components/dialog.d.ts.map +1 -1
  13. package/dist/components/dialog.js +10 -8
  14. package/dist/components/dialog.js.map +1 -1
  15. package/dist/components/form-section.js +1 -1
  16. package/dist/components/form-section.js.map +1 -1
  17. package/dist/components/form.js +1 -1
  18. package/dist/components/menu.js +4 -4
  19. package/dist/components/menu.js.map +1 -1
  20. package/dist/components/nav-button.js +1 -1
  21. package/dist/components/nav.js +1 -1
  22. package/dist/components/nav.js.map +1 -1
  23. package/dist/components/section-header.d.ts +7 -7
  24. package/dist/components/section-header.js +6 -6
  25. package/dist/components/section-header.js.map +1 -1
  26. package/dist/components/select.d.ts.map +1 -1
  27. package/dist/components/select.js +3 -3
  28. package/dist/components/select.js.map +1 -1
  29. package/dist/components/stat-strip.d.ts +6 -2
  30. package/dist/components/stat-strip.d.ts.map +1 -1
  31. package/dist/components/stat-strip.js +5 -4
  32. package/dist/components/stat-strip.js.map +1 -1
  33. package/dist/components/theme.d.ts +25 -0
  34. package/dist/components/theme.d.ts.map +1 -0
  35. package/dist/components/theme.js +128 -0
  36. package/dist/components/theme.js.map +1 -0
  37. package/dist/components/toaster.js +6 -1
  38. package/dist/components/toaster.js.map +1 -1
  39. package/dist/components/view-tabs.d.ts +26 -0
  40. package/dist/components/view-tabs.d.ts.map +1 -0
  41. package/dist/components/view-tabs.js +27 -0
  42. package/dist/components/view-tabs.js.map +1 -0
  43. package/dist/lib/icons.d.ts +1 -0
  44. package/dist/lib/icons.d.ts.map +1 -1
  45. package/dist/lib/icons.js +3 -2
  46. package/dist/lib/icons.js.map +1 -1
  47. package/dist/styles.css +24 -0
  48. package/package.json +5 -1
package/README.md CHANGED
@@ -23,7 +23,17 @@ export function Example() {
23
23
  }
24
24
  ```
25
25
 
26
- The shared helpers live at fixed subpaths: `@dirstack/ui/variants` (`cn`, `variants`, `VariantProps`), `@dirstack/ui/classes` (shared class strings), `@dirstack/ui/slot`, and `@dirstack/ui/interactive` (`interactiveVariants`).
26
+ The shared helpers live at fixed subpaths: `@dirstack/ui/variants` (`cn`, `variants`, `VariantProps`), `@dirstack/ui/classes` (shared class strings), `@dirstack/ui/slot`, `@dirstack/ui/interactive` (`interactiveVariants`), and `@dirstack/ui/animation` (`ANIMATION_DURATION`, `ANIMATION_EASING` — the shared motion timing, for syncing your own transitions with the library's).
27
+
28
+ ## Components
29
+
30
+ Every file in `src/components` is its own subpath (`@dirstack/ui/<name>`):
31
+
32
+ - **Layout**: `page`, `stack`, `heading`, `section-header`, `card`, `detail-row`, `steps`, `nav`, `nav-button`
33
+ - **Forms**: `form`, `form-section`, `form-row`, `label`, `input`, `number-input`, `search-input`, `textarea`, `select`, `switch`, `copy-input`
34
+ - **Actions & overlays**: `button`, `copy-button`, `menu`, `popover`, `dialog`, `tooltip`, `tabs`, `toaster`, `kbd`
35
+ - **Display & feedback**: `badge`, `callout`, `empty-state`, `skeleton`, `avatar`, `entity-avatar`, `pagination`
36
+ - **Dashboard**: `animated-number`, `chart`, `stat-strip`, `bar-list` (ranked share bars, `BarList.Row` / `BarList.Skeleton`), `breakdown-card` (titled panel with optional tabs and footer), `view-tabs` (tab switcher bound to a `?view=` param), `theme` (`useTheme` + `ThemeSelect`)
27
37
 
28
38
  ## Styles
29
39
 
@@ -38,6 +48,12 @@ That stylesheet carries the full design-token set (`--color-*` mirroring the sha
38
48
 
39
49
  Every color is a `light-dark()` pair driven by `color-scheme`: left alone it follows the OS, and `data-theme="dark"` or `data-theme="light"` on `<html>` forces one. The theme expects the consumer to load the "Inter Variable" font; without it, type falls back to the system stack.
40
50
 
51
+ `@dirstack/ui/theme` manages that attribute for you: `useTheme()` returns `[theme, setTheme]` (`"system" | "light" | "dark"`), persists the choice in `localStorage.theme`, keeps tabs in sync, and `ThemeSelect` is a ready-made segmented System / Light / Dark control. The hook applies the stored theme once it mounts; to avoid a flash of the wrong scheme, apply it before first paint with this inline script in `<head>`:
52
+
53
+ ```html
54
+ <script>try{var t=localStorage.theme;if(t==="light"||t==="dark")document.documentElement.dataset.theme=t}catch(e){}</script>
55
+ ```
56
+
41
57
  Override any token in your own CSS to re-theme:
42
58
 
43
59
  ```css
@@ -0,0 +1,57 @@
1
+ import { Format } from "@number-flow/react";
2
+ import { useRender } from "@base-ui/react/use-render";
3
+ import { ComponentProps, ReactNode } from "react";
4
+ //#region src/components/bar-list.d.ts
5
+ /**
6
+ * Ranked breakdown list: each row is a label sitting on a soft bar whose width is the row's
7
+ * share of the largest value, measured across the label column, with the value right-aligned
8
+ * beside it. Rows are plain divs by default — a row that links out carries its `href` on the
9
+ * arrow beside the label. Pass `render={<button type="button" />}` (or a link) to make the
10
+ * whole row a click target, e.g. click-to-filter.
11
+ */
12
+ declare function BarListRoot({ className, ...props }: ComponentProps<"div">): import("react").JSX.Element;
13
+ export type BarListRowProps = Omit<useRender.ComponentProps<"div">, "children"> & {
14
+ /**
15
+ * Row text; string labels truncate.
16
+ */
17
+ label: ReactNode;
18
+ /**
19
+ * Leading visual (favicon, flag, icon tile), 16px.
20
+ */
21
+ icon?: ReactNode;
22
+ value: number;
23
+ /**
24
+ * The largest value in the list; the bar spans `value / max` of the row.
25
+ */
26
+ max: number;
27
+ format?: Format;
28
+ /**
29
+ * An extra figure (revenue, conversion rate) in its own muted column before the value.
30
+ */
31
+ secondaryValue?: number | string;
32
+ secondaryFormat?: Format;
33
+ /**
34
+ * External URL, shown as an arrow beside the label that opens in a new tab. Don't combine
35
+ * with a `render` that makes the row itself a button or link — nested interactive content.
36
+ */
37
+ href?: string;
38
+ };
39
+ declare function BarListRow({ label, icon, value, max, format, secondaryValue, secondaryFormat, href, render, className, style, ...props }: BarListRowProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
40
+ export type BarListSkeletonProps = ComponentProps<"div"> & {
41
+ /**
42
+ * Rows to draw, matching the list that replaces it.
43
+ */
44
+ rows?: number;
45
+ };
46
+ /**
47
+ * Loading stand-in at the exact row height of `BarList.Row`, so swapping in data causes no
48
+ * layout shift.
49
+ */
50
+ declare function BarListSkeleton({ rows, className, ...props }: BarListSkeletonProps): import("react").JSX.Element;
51
+ declare const BarList: typeof BarListRoot & {
52
+ Row: typeof BarListRow;
53
+ Skeleton: typeof BarListSkeleton;
54
+ };
55
+ //#endregion
56
+ export { BarList };
57
+ //# sourceMappingURL=bar-list.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bar-list.d.ts","names":[],"sources":["../../src/components/bar-list.tsx"],"mappings":";;;;;;;;;;;iBAeS,cAAc,cAAc,SAAS,wCAAqB,IAAA;YAMvD,kBAAkB,KAAK,UAAU;;;;EAI3C,OAAO;;;;EAIP,OAAO;EACP;;;;EAIA;EACA,SAAS;;;;EAIT;EACA,kBAAkB;;;;;EAKlB;;iBAGO,aACP,OACA,MACA,OACA,KACA,QACA,gBACA,iBACA,MACA,QACA,WACA,UACG,SACF,kCAAe,+CAAA;YAqEN,uBAAuB;;;;EAIjC;;;;;;iBAOO,kBAAkB,MAAU,cAAc,SAAS,uCAAoB,IAAA;cAmB1E,gBAAO"}
@@ -0,0 +1,117 @@
1
+ import { AnimatedNumber } from "./animated-number.js";
2
+ import { cn } from "../lib/variants.js";
3
+ import { Skeleton } from "./skeleton.js";
4
+ import { ArrowUpRightIcon } from "../lib/icons.js";
5
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
+ import { useRender } from "@base-ui/react/use-render";
7
+ //#region src/components/bar-list.tsx
8
+ /**
9
+ * Ranked breakdown list: each row is a label sitting on a soft bar whose width is the row's
10
+ * share of the largest value, measured across the label column, with the value right-aligned
11
+ * beside it. Rows are plain divs by default — a row that links out carries its `href` on the
12
+ * arrow beside the label. Pass `render={<button type="button" />}` (or a link) to make the
13
+ * whole row a click target, e.g. click-to-filter.
14
+ */
15
+ function BarListRoot({ className, ...props }) {
16
+ return /* @__PURE__ */ jsx("div", {
17
+ className: cn("flex flex-col gap-1", className),
18
+ ...props
19
+ });
20
+ }
21
+ const rowClasses = "flex h-7.5 w-full items-center gap-3 pr-2.5";
22
+ function BarListRow({ label, icon, value, max, format, secondaryValue, secondaryFormat, href, render, className, style, ...props }) {
23
+ const share = max > 0 ? Math.max(value / max, 0) : 0;
24
+ return useRender({
25
+ render,
26
+ defaultTagName: "div",
27
+ props: {
28
+ className: cn(rowClasses, "group/bar rounded-md text-start text-sm", "[&:is(button,a)]:cursor-pointer [&:is(button,a)]:hover:bg-foreground/3", "focus-visible:outline-[3px] focus-visible:outline-border/50", className),
29
+ style: {
30
+ "--bar-share": share,
31
+ ...style
32
+ },
33
+ children: /* @__PURE__ */ jsxs(Fragment, { children: [
34
+ /* @__PURE__ */ jsxs("div", {
35
+ className: "relative flex h-full min-w-0 flex-1 items-center gap-2 pl-2.5",
36
+ children: [
37
+ /* @__PURE__ */ jsx("div", {
38
+ "aria-hidden": true,
39
+ className: "absolute inset-y-0 left-0 w-[calc(var(--bar-share)*100%)] min-w-1.5 origin-left animate-fill-bar rounded-md bg-muted transition-[width] duration-500 ease-out-expo"
40
+ }),
41
+ icon && /* @__PURE__ */ jsx("span", {
42
+ className: "relative flex size-4 shrink-0 items-center justify-center",
43
+ children: icon
44
+ }),
45
+ /* @__PURE__ */ jsx("span", {
46
+ className: "relative min-w-0 truncate",
47
+ children: label
48
+ }),
49
+ href && /* @__PURE__ */ jsx("a", {
50
+ href,
51
+ target: "_blank",
52
+ rel: "noreferrer",
53
+ "aria-label": typeof label === "string" ? `Open ${label} website` : "Open website",
54
+ className: "relative flex size-5 shrink-0 items-center justify-center rounded text-muted-foreground hover:text-foreground",
55
+ children: /* @__PURE__ */ jsx(ArrowUpRightIcon, { className: "size-3.5" })
56
+ })
57
+ ]
58
+ }),
59
+ secondaryValue !== void 0 && /* @__PURE__ */ jsx("span", {
60
+ className: "min-w-16 shrink-0 text-right text-muted-foreground tabular-nums",
61
+ children: /* @__PURE__ */ jsx(AnimatedNumber, {
62
+ value: secondaryValue,
63
+ format: secondaryFormat
64
+ })
65
+ }),
66
+ /* @__PURE__ */ jsx("span", {
67
+ className: "min-w-12 shrink-0 text-right text-muted-foreground tabular-nums",
68
+ children: /* @__PURE__ */ jsx(AnimatedNumber, {
69
+ value,
70
+ format
71
+ })
72
+ })
73
+ ] }),
74
+ ...props
75
+ }
76
+ });
77
+ }
78
+ /**
79
+ * Label bars step down like a ranked list would, so the swap to data reads as a fill-in
80
+ * rather than a reflow.
81
+ */
82
+ const skeletonShares = [
83
+ "70%",
84
+ "55%",
85
+ "45%",
86
+ "35%",
87
+ "30%"
88
+ ];
89
+ /**
90
+ * Loading stand-in at the exact row height of `BarList.Row`, so swapping in data causes no
91
+ * layout shift.
92
+ */
93
+ function BarListSkeleton({ rows = 5, className, ...props }) {
94
+ return /* @__PURE__ */ jsx(BarListRoot, {
95
+ "aria-hidden": true,
96
+ className,
97
+ ...props,
98
+ children: Array.from({ length: rows }, (_, index) => /* @__PURE__ */ jsxs("div", {
99
+ className: rowClasses,
100
+ children: [/* @__PURE__ */ jsx("div", {
101
+ className: "relative h-full min-w-0 flex-1",
102
+ children: /* @__PURE__ */ jsx(Skeleton, {
103
+ className: "absolute inset-y-0 left-0",
104
+ style: { width: skeletonShares[index % skeletonShares.length] }
105
+ })
106
+ }), /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-8 shrink-0" })]
107
+ }, index))
108
+ });
109
+ }
110
+ const BarList = Object.assign(BarListRoot, {
111
+ Row: BarListRow,
112
+ Skeleton: BarListSkeleton
113
+ });
114
+ //#endregion
115
+ export { BarList };
116
+
117
+ //# sourceMappingURL=bar-list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bar-list.js","names":[],"sources":["../../src/components/bar-list.tsx"],"sourcesContent":["import { useRender } from \"@base-ui/react/use-render\"\nimport type { Format } from \"@number-flow/react\"\nimport type { ComponentProps, CSSProperties, ReactNode } from \"react\"\nimport { AnimatedNumber } from \"~/components/animated-number\"\nimport { Skeleton } from \"~/components/skeleton\"\nimport { ArrowUpRightIcon } from \"~/lib/icons\"\nimport { cn } from \"~/lib/variants\"\n\n/**\n * Ranked breakdown list: each row is a label sitting on a soft bar whose width is the row's\n * share of the largest value, measured across the label column, with the value right-aligned\n * beside it. Rows are plain divs by default — a row that links out carries its `href` on the\n * arrow beside the label. Pass `render={<button type=\"button\" />}` (or a link) to make the\n * whole row a click target, e.g. click-to-filter.\n */\nfunction BarListRoot({ className, ...props }: ComponentProps<\"div\">) {\n return <div className={cn(\"flex flex-col gap-1\", className)} {...props} />\n}\n\nconst rowClasses = \"flex h-7.5 w-full items-center gap-3 pr-2.5\"\n\nexport type BarListRowProps = Omit<useRender.ComponentProps<\"div\">, \"children\"> & {\n /**\n * Row text; string labels truncate.\n */\n label: ReactNode\n /**\n * Leading visual (favicon, flag, icon tile), 16px.\n */\n icon?: ReactNode\n value: number\n /**\n * The largest value in the list; the bar spans `value / max` of the row.\n */\n max: number\n format?: Format\n /**\n * An extra figure (revenue, conversion rate) in its own muted column before the value.\n */\n secondaryValue?: number | string\n secondaryFormat?: Format\n /**\n * External URL, shown as an arrow beside the label that opens in a new tab. Don't combine\n * with a `render` that makes the row itself a button or link — nested interactive content.\n */\n href?: string\n}\n\nfunction BarListRow({\n label,\n icon,\n value,\n max,\n format,\n secondaryValue,\n secondaryFormat,\n href,\n render,\n className,\n style,\n ...props\n}: BarListRowProps) {\n const share = max > 0 ? Math.max(value / max, 0) : 0\n\n return useRender({\n render,\n defaultTagName: \"div\",\n props: {\n className: cn(\n rowClasses,\n \"group/bar rounded-md text-start text-sm\",\n // Rendered as a button or link, the row reads as a target: pointer, soft hover fill\n // and the shared focus outline.\n \"[&:is(button,a)]:cursor-pointer [&:is(button,a)]:hover:bg-foreground/3\",\n \"focus-visible:outline-[3px] focus-visible:outline-border/50\",\n className,\n ),\n style: { \"--bar-share\": share, ...style } as CSSProperties,\n children: (\n <>\n {/* The bar lives in the label column only: a 100% share reaches the gap before the\n value, never under it, so the numbers always sit on clean ground. */}\n <div className=\"relative flex h-full min-w-0 flex-1 items-center gap-2 pl-2.5\">\n <div\n aria-hidden\n className=\"absolute inset-y-0 left-0 w-[calc(var(--bar-share)*100%)] min-w-1.5 origin-left animate-fill-bar rounded-md bg-muted transition-[width] duration-500 ease-out-expo\"\n />\n\n {icon && (\n <span className=\"relative flex size-4 shrink-0 items-center justify-center\">\n {icon}\n </span>\n )}\n <span className=\"relative min-w-0 truncate\">{label}</span>\n\n {href && (\n <a\n href={href}\n target=\"_blank\"\n rel=\"noreferrer\"\n aria-label={typeof label === \"string\" ? `Open ${label} website` : \"Open website\"}\n className=\"relative flex size-5 shrink-0 items-center justify-center rounded text-muted-foreground hover:text-foreground\"\n >\n <ArrowUpRightIcon className=\"size-3.5\" />\n </a>\n )}\n </div>\n\n {secondaryValue !== undefined && (\n <span className=\"min-w-16 shrink-0 text-right text-muted-foreground tabular-nums\">\n <AnimatedNumber value={secondaryValue} format={secondaryFormat} />\n </span>\n )}\n\n <span className=\"min-w-12 shrink-0 text-right text-muted-foreground tabular-nums\">\n <AnimatedNumber value={value} format={format} />\n </span>\n </>\n ),\n ...props,\n },\n })\n}\n\n/**\n * Label bars step down like a ranked list would, so the swap to data reads as a fill-in\n * rather than a reflow.\n */\nconst skeletonShares = [\"70%\", \"55%\", \"45%\", \"35%\", \"30%\"] as const\n\nexport type BarListSkeletonProps = ComponentProps<\"div\"> & {\n /**\n * Rows to draw, matching the list that replaces it.\n */\n rows?: number\n}\n\n/**\n * Loading stand-in at the exact row height of `BarList.Row`, so swapping in data causes no\n * layout shift.\n */\nfunction BarListSkeleton({ rows = 5, className, ...props }: BarListSkeletonProps) {\n return (\n <BarListRoot aria-hidden className={className} {...props}>\n {Array.from({ length: rows }, (_, index) => (\n <div key={index} className={rowClasses}>\n <div className=\"relative h-full min-w-0 flex-1\">\n <Skeleton\n className=\"absolute inset-y-0 left-0\"\n style={{ width: skeletonShares[index % skeletonShares.length] }}\n />\n </div>\n\n <Skeleton className=\"h-3.5 w-8 shrink-0\" />\n </div>\n ))}\n </BarListRoot>\n )\n}\n\nconst BarList = Object.assign(BarListRoot, { Row: BarListRow, Skeleton: BarListSkeleton })\n\nexport { BarList }\n"],"mappings":";;;;;;;;;;;;;;AAeA,SAAS,YAAY,EAAE,WAAW,GAAG,SAAgC;CACnE,OAAO,oBAAC,OAAD;EAAK,WAAW,GAAG,uBAAuB,SAAS;EAAG,GAAI;CAAQ,CAAA;AAC3E;AAEA,MAAM,aAAa;AA6BnB,SAAS,WAAW,EAClB,OACA,MACA,OACA,KACA,QACA,gBACA,iBACA,MACA,QACA,WACA,OACA,GAAG,SACe;CAClB,MAAM,QAAQ,MAAM,IAAI,KAAK,IAAI,QAAQ,KAAK,CAAC,IAAI;CAEnD,OAAO,UAAU;EACf;EACA,gBAAgB;EAChB,OAAO;GACL,WAAW,GACT,YACA,2CAGA,0EACA,+DACA,SACF;GACA,OAAO;IAAE,eAAe;IAAO,GAAG;GAAM;GACxC,UACE,qBAAA,UAAA,EAAA,UAAA;IAGE,qBAAC,OAAD;KAAK,WAAU;KAAf,UAAA;MACE,oBAAC,OAAD;OACE,eAAA;OACA,WAAU;MACX,CAAA;MAEA,QACC,oBAAC,QAAD;OAAM,WAAU;OACb,UAAA;MACG,CAAA;MAER,oBAAC,QAAD;OAAM,WAAU;OAA6B,UAAA;MAAY,CAAA;MAExD,QACC,oBAAC,KAAD;OACQ;OACN,QAAO;OACP,KAAI;OACJ,cAAY,OAAO,UAAU,WAAW,QAAQ,MAAM,YAAY;OAClE,WAAU;OAEV,UAAA,oBAAC,kBAAD,EAAkB,WAAU,WAAY,CAAA;MACvC,CAAA;KAEF;;IAEJ,mBAAmB,KAAA,KAClB,oBAAC,QAAD;KAAM,WAAU;KACd,UAAA,oBAAC,gBAAD;MAAgB,OAAO;MAAgB,QAAQ;KAAkB,CAAA;IAC7D,CAAA;IAGR,oBAAC,QAAD;KAAM,WAAU;KACd,UAAA,oBAAC,gBAAD;MAAuB;MAAe;KAAS,CAAA;IAC3C,CAAA;GACN,EAAA,CAAA;GAEJ,GAAG;EACL;CACF,CAAC;AACH;;;;;AAMA,MAAM,iBAAiB;CAAC;CAAO;CAAO;CAAO;CAAO;AAAK;;;;;AAazD,SAAS,gBAAgB,EAAE,OAAO,GAAG,WAAW,GAAG,SAA+B;CAChF,OACE,oBAAC,aAAD;EAAa,eAAA;EAAuB;EAAW,GAAI;EAChD,UAAA,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,UAChC,qBAAC,OAAD;GAAiB,WAAW;GAA5B,UAAA,CACE,oBAAC,OAAD;IAAK,WAAU;IACb,UAAA,oBAAC,UAAD;KACE,WAAU;KACV,OAAO,EAAE,OAAO,eAAe,QAAQ,eAAe,QAAQ;IAC/D,CAAA;GACE,CAAA,GAEL,oBAAC,UAAD,EAAU,WAAU,qBAAsB,CAAA,CACvC;EATK,GAAA,KASL,CACN;CACU,CAAA;AAEjB;AAEA,MAAM,UAAU,OAAO,OAAO,aAAa;CAAE,KAAK;CAAY,UAAU;AAAgB,CAAC"}
@@ -0,0 +1,31 @@
1
+ import { Card } from "./card.js";
2
+ import { ComponentProps, ReactNode } from "react";
3
+ //#region src/components/breakdown-card.d.ts
4
+ export type BreakdownCardProps = ComponentProps<typeof Card> & {
5
+ title: ReactNode;
6
+ description?: ReactNode;
7
+ /**
8
+ * Header actions, right-aligned beside the title (a menu, a time-range control).
9
+ */
10
+ actions?: ReactNode;
11
+ /**
12
+ * A view switcher (e.g. `ViewTabs`) pinned to the top of the body, above the children.
13
+ */
14
+ tabs?: ReactNode;
15
+ /**
16
+ * A compact hairline-topped strip under the body, for a "Details" link or a total.
17
+ */
18
+ footer?: ReactNode;
19
+ /**
20
+ * Drop the body panel's padding, for a body that draws its own edges (a divided row list).
21
+ */
22
+ flush?: boolean;
23
+ };
24
+ /**
25
+ * A dashboard panel: a titled header panel above a flex body panel, hairline-divided, with an
26
+ * optional footer strip. The body grows to fill the card so panels in the same row keep an
27
+ * even height.
28
+ */
29
+ export declare function BreakdownCard({ title, description, actions, tabs, footer, flush, children, ...props }: BreakdownCardProps): import("react").JSX.Element;
30
+ //#endregion
31
+ //# sourceMappingURL=breakdown-card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"breakdown-card.d.ts","names":[],"sources":["../../src/components/breakdown-card.tsx"],"mappings":";;;YAIY,qBAAqB,sBAAsB;EACrD,OAAO;EACP,cAAc;;;;EAId,UAAU;;;;EAIV,OAAO;;;;EAIP,SAAS;;;;EAIT;;;;;;;wBAQc,gBACd,OACA,aACA,SACA,MACA,QACA,OACA,aACG,SACF,qCAAkB,IAAA"}
@@ -0,0 +1,41 @@
1
+ import { cn } from "../lib/variants.js";
2
+ import { Card, CardHeader, CardPanel } from "./card.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ //#region src/components/breakdown-card.tsx
5
+ /**
6
+ * A dashboard panel: a titled header panel above a flex body panel, hairline-divided, with an
7
+ * optional footer strip. The body grows to fill the card so panels in the same row keep an
8
+ * even height.
9
+ */
10
+ function BreakdownCard({ title, description, actions, tabs, footer, flush, children, ...props }) {
11
+ return /* @__PURE__ */ jsxs(Card, {
12
+ ...props,
13
+ children: [
14
+ /* @__PURE__ */ jsx(CardPanel, {
15
+ className: "p-4 md:p-4",
16
+ children: /* @__PURE__ */ jsx(CardHeader, {
17
+ size: "panel",
18
+ className: "gap-y-0.5",
19
+ title,
20
+ description,
21
+ actions
22
+ })
23
+ }),
24
+ /* @__PURE__ */ jsxs(CardPanel, {
25
+ className: cn("flex flex-1 flex-col gap-3 p-4 md:p-4", flush && "p-0 md:p-0"),
26
+ children: [tabs && /* @__PURE__ */ jsx("div", {
27
+ className: cn("flex", flush && "px-4 pt-4"),
28
+ children: tabs
29
+ }), children]
30
+ }),
31
+ footer && /* @__PURE__ */ jsx(CardPanel, {
32
+ className: "px-4 py-2.5 text-sm md:px-4 md:py-2.5",
33
+ children: footer
34
+ })
35
+ ]
36
+ });
37
+ }
38
+ //#endregion
39
+ export { BreakdownCard };
40
+
41
+ //# sourceMappingURL=breakdown-card.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"breakdown-card.js","names":[],"sources":["../../src/components/breakdown-card.tsx"],"sourcesContent":["import type { ComponentProps, ReactNode } from \"react\"\nimport { Card, CardHeader, CardPanel } from \"~/components/card\"\nimport { cn } from \"~/lib/variants\"\n\nexport type BreakdownCardProps = ComponentProps<typeof Card> & {\n title: ReactNode\n description?: ReactNode\n /**\n * Header actions, right-aligned beside the title (a menu, a time-range control).\n */\n actions?: ReactNode\n /**\n * A view switcher (e.g. `ViewTabs`) pinned to the top of the body, above the children.\n */\n tabs?: ReactNode\n /**\n * A compact hairline-topped strip under the body, for a \"Details\" link or a total.\n */\n footer?: ReactNode\n /**\n * Drop the body panel's padding, for a body that draws its own edges (a divided row list).\n */\n flush?: boolean\n}\n\n/**\n * A dashboard panel: a titled header panel above a flex body panel, hairline-divided, with an\n * optional footer strip. The body grows to fill the card so panels in the same row keep an\n * even height.\n */\nexport function BreakdownCard({\n title,\n description,\n actions,\n tabs,\n footer,\n flush,\n children,\n ...props\n}: BreakdownCardProps) {\n return (\n <Card {...props}>\n <CardPanel className=\"p-4 md:p-4\">\n <CardHeader\n size=\"panel\"\n className=\"gap-y-0.5\"\n title={title}\n description={description}\n actions={actions}\n />\n </CardPanel>\n\n <CardPanel className={cn(\"flex flex-1 flex-col gap-3 p-4 md:p-4\", flush && \"p-0 md:p-0\")}>\n {tabs && <div className={cn(\"flex\", flush && \"px-4 pt-4\")}>{tabs}</div>}\n {children}\n </CardPanel>\n\n {footer && <CardPanel className=\"px-4 py-2.5 text-sm md:px-4 md:py-2.5\">{footer}</CardPanel>}\n </Card>\n )\n}\n"],"mappings":";;;;;;;;;AA8BA,SAAgB,cAAc,EAC5B,OACA,aACA,SACA,MACA,QACA,OACA,UACA,GAAG,SACkB;CACrB,OACE,qBAAC,MAAD;EAAM,GAAI;EAAV,UAAA;GACE,oBAAC,WAAD;IAAW,WAAU;IACnB,UAAA,oBAAC,YAAD;KACE,MAAK;KACL,WAAU;KACH;KACM;KACJ;IACV,CAAA;GACQ,CAAA;GAEX,qBAAC,WAAD;IAAW,WAAW,GAAG,yCAAyC,SAAS,YAAY;IAAvF,UAAA,CACG,QAAQ,oBAAC,OAAD;KAAK,WAAW,GAAG,QAAQ,SAAS,WAAW;KAAI,UAAA;IAAU,CAAA,GACrE,QACQ;;GAEV,UAAU,oBAAC,WAAD;IAAW,WAAU;IAAyC,UAAA;GAAkB,CAAA;EACvF;;AAEV"}
@@ -1,7 +1,7 @@
1
1
  import { cn, variants } from "../lib/variants.js";
2
2
  import { slot } from "../lib/slot.js";
3
- import { controlTextClasses } from "../lib/classes.js";
4
3
  import { LoaderIcon } from "../lib/icons.js";
4
+ import { controlTextClasses } from "../lib/classes.js";
5
5
  import { interactiveVariants } from "../lib/interactive.js";
6
6
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
7
  import { mergeProps } from "@base-ui/react";
@@ -1,6 +1,6 @@
1
1
  import { VariantProps } from "../lib/variants.js";
2
- import { Button } from "./button.js";
3
2
  import { SectionHeader } from "./section-header.js";
3
+ import { Button } from "./button.js";
4
4
  import { ComponentProps, ReactNode } from "react";
5
5
  import { Dialog as Dialog$1 } from "@base-ui/react/dialog";
6
6
  //#region src/components/dialog.d.ts
@@ -26,16 +26,16 @@ declare const dialogContentVariants: import("tailwind-variants").TVReturnType<{
26
26
  * (`[[role=dialog]~&]`) sits a little lower still, so both remain visible.
27
27
  */
28
28
  fixed: {
29
- true: "top-[10vh] max-h-[calc(90vh-2rem)] [[role=dialog]~&]:top-[15vh] [[role=dialog]~&]:max-h-[calc(85vh-2rem)]";
30
- false: "top-1/2 -translate-y-1/2 max-h-[calc(100vh-2rem)]";
29
+ true: "sm:top-[10vh] sm:max-h-[calc(90vh-2rem)] sm:[[role=dialog]~&]:top-[15vh] sm:[[role=dialog]~&]:max-h-[calc(85vh-2rem)]";
30
+ false: "sm:top-1/2 sm:-translate-y-1/2 sm:max-h-[calc(100vh-2rem)]";
31
31
  };
32
32
  /**
33
33
  * Drop the dialog's own padding and gap so sections can run edge to edge and draw their
34
34
  * own borders, e.g. a bordered header over a scrolling body over a bordered footer.
35
35
  */
36
36
  flush: {
37
- true: "gap-0 p-0";
38
- false: "gap-6 p-6";
37
+ true: "gap-0 p-0 max-sm:pb-[env(safe-area-inset-bottom)]";
38
+ false: "gap-6 p-6 max-sm:pb-[calc(1.5rem+env(safe-area-inset-bottom))]";
39
39
  };
40
40
  }, undefined, string[], {
41
41
  size: {
@@ -55,16 +55,16 @@ declare const dialogContentVariants: import("tailwind-variants").TVReturnType<{
55
55
  * (`[[role=dialog]~&]`) sits a little lower still, so both remain visible.
56
56
  */
57
57
  fixed: {
58
- true: "top-[10vh] max-h-[calc(90vh-2rem)] [[role=dialog]~&]:top-[15vh] [[role=dialog]~&]:max-h-[calc(85vh-2rem)]";
59
- false: "top-1/2 -translate-y-1/2 max-h-[calc(100vh-2rem)]";
58
+ true: "sm:top-[10vh] sm:max-h-[calc(90vh-2rem)] sm:[[role=dialog]~&]:top-[15vh] sm:[[role=dialog]~&]:max-h-[calc(85vh-2rem)]";
59
+ false: "sm:top-1/2 sm:-translate-y-1/2 sm:max-h-[calc(100vh-2rem)]";
60
60
  };
61
61
  /**
62
62
  * Drop the dialog's own padding and gap so sections can run edge to edge and draw their
63
63
  * own borders, e.g. a bordered header over a scrolling body over a bordered footer.
64
64
  */
65
65
  flush: {
66
- true: "gap-0 p-0";
67
- false: "gap-6 p-6";
66
+ true: "gap-0 p-0 max-sm:pb-[env(safe-area-inset-bottom)]";
67
+ false: "gap-6 p-6 max-sm:pb-[calc(1.5rem+env(safe-area-inset-bottom))]";
68
68
  };
69
69
  }, undefined, import("tailwind-variants").TVReturnTypeLike<{
70
70
  size: {
@@ -84,16 +84,16 @@ declare const dialogContentVariants: import("tailwind-variants").TVReturnType<{
84
84
  * (`[[role=dialog]~&]`) sits a little lower still, so both remain visible.
85
85
  */
86
86
  fixed: {
87
- true: "top-[10vh] max-h-[calc(90vh-2rem)] [[role=dialog]~&]:top-[15vh] [[role=dialog]~&]:max-h-[calc(85vh-2rem)]";
88
- false: "top-1/2 -translate-y-1/2 max-h-[calc(100vh-2rem)]";
87
+ true: "sm:top-[10vh] sm:max-h-[calc(90vh-2rem)] sm:[[role=dialog]~&]:top-[15vh] sm:[[role=dialog]~&]:max-h-[calc(85vh-2rem)]";
88
+ false: "sm:top-1/2 sm:-translate-y-1/2 sm:max-h-[calc(100vh-2rem)]";
89
89
  };
90
90
  /**
91
91
  * Drop the dialog's own padding and gap so sections can run edge to edge and draw their
92
92
  * own borders, e.g. a bordered header over a scrolling body over a bordered footer.
93
93
  */
94
94
  flush: {
95
- true: "gap-0 p-0";
96
- false: "gap-6 p-6";
95
+ true: "gap-0 p-0 max-sm:pb-[env(safe-area-inset-bottom)]";
96
+ false: "gap-6 p-6 max-sm:pb-[calc(1.5rem+env(safe-area-inset-bottom))]";
97
97
  };
98
98
  }, undefined>>;
99
99
  type DialogContentProps = Omit<Dialog$1.Popup.Props, "className"> & VariantProps<typeof dialogContentVariants> & {
@@ -1 +1 @@
1
- {"version":3,"file":"dialog.d.ts","names":[],"sources":["../../src/components/dialog.tsx"],"mappings":";;;;;;cAcM,eAAM,SAAA;cACN,eAAa,SAAA;cACb,6BAAW,0BAAA,8BAAA,gDAAA,cAAA;cACX,mCAAiB,0BAAA,8BAAA,sDAAA,cAAA;cAiBjB,mDAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAiDtB,qBAAqB,KAAK,SAAgB,MAAM,sBACnD,oBAAoB;EAClB;;;;;EAMA;;iBAGK,gBACP,WACA,UACA,MACA,OACA,OACA,cACG,SACF,qCAAkB,IAAA;cAsBf,kDAAoB;;;;;;;;;;;;;;;;;;;;;;;;;KAYrB,oBAAoB,KAAK,sBAAsB,2CAClD,oBAAoB;EAClB,OAAO;EACP,cAAc;;;;;;;iBAQT,eACP,OACA,aACA,UACA,WACA,aACG,SACF,oCAAiB,IAAA;cAgBd,kDAAoB;;;;;;;;;;;;;;;;;;;;;;;;;KAcrB,oBAAoB,wBAAwB,oBAAoB;iBAE5D,eAAe,WAAW,aAAa,SAAS,oCAAiB,IAAA;iBAIjE,iBAAiB,SAAS,sBAAsB,0BAAO,IAAA"}
1
+ {"version":3,"file":"dialog.d.ts","names":[],"sources":["../../src/components/dialog.tsx"],"mappings":";;;;;;cAcM,eAAM,SAAA;cACN,eAAa,SAAA;cACb,6BAAW,0BAAA,8BAAA,gDAAA,cAAA;cACX,mCAAiB,0BAAA,8BAAA,sDAAA,cAAA;cAiBjB,mDAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAqDtB,qBAAqB,KAAK,SAAgB,MAAM,sBACnD,oBAAoB;EAClB;;;;;EAMA;;iBAGK,gBACP,WACA,UACA,MACA,OACA,OACA,cACG,SACF,qCAAkB,IAAA;cAsBf,kDAAoB;;;;;;;;;;;;;;;;;;;;;;;;;KAYrB,oBAAoB,KAAK,sBAAsB,2CAClD,oBAAoB;EAClB,OAAO;EACP,cAAc;;;;;;;iBAQT,eACP,OACA,aACA,UACA,WACA,aACG,SACF,oCAAiB,IAAA;cAgBd,kDAAoB;;;;;;;;;;;;;;;;;;;;;;;;;KAcrB,oBAAoB,wBAAwB,oBAAoB;iBAE5D,eAAe,WAAW,aAAa,SAAS,oCAAiB,IAAA;iBAIjE,iBAAiB,SAAS,sBAAsB,0BAAO,IAAA"}
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import { cn, variants } from "../lib/variants.js";
3
3
  import { XIcon } from "../lib/icons.js";
4
- import { Button } from "./button.js";
5
4
  import { SectionHeader, SectionHeaderBody, SectionHeaderDescription, SectionHeaderTitle } from "./section-header.js";
5
+ import { Button } from "./button.js";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
7
7
  import { Dialog as Dialog$1 } from "@base-ui/react/dialog";
8
8
  //#region src/components/dialog.tsx
@@ -19,11 +19,13 @@ function DialogOverlay({ className, ...props }) {
19
19
  }
20
20
  const dialogContentVariants = variants({
21
21
  base: [
22
- "fixed left-1/2 z-50 grid w-[calc(100vw-2rem)] -translate-x-1/2 overflow-y-auto",
23
- "border bg-card shadow-sm rounded-xl",
22
+ "fixed z-50 grid overflow-y-auto border bg-card shadow-sm",
23
+ "max-sm:inset-x-0 max-sm:bottom-0 max-sm:max-h-[calc(100dvh-3rem)] max-sm:rounded-t-2xl max-sm:border-b-0",
24
+ "sm:left-1/2 sm:w-[calc(100vw-2rem)] sm:-translate-x-1/2 sm:rounded-xl",
24
25
  "data-open:animate-in data-closed:animate-out",
25
26
  "data-open:fade-in-0 data-closed:fade-out-0",
26
- "data-open:slide-in-from-bottom-4 data-closed:slide-out-to-bottom-4"
27
+ "data-open:slide-in-from-bottom-4 data-closed:slide-out-to-bottom-4",
28
+ "max-sm:data-open:slide-in-from-bottom-8 max-sm:data-closed:slide-out-to-bottom-8"
27
29
  ],
28
30
  variants: {
29
31
  size: {
@@ -43,16 +45,16 @@ const dialogContentVariants = variants({
43
45
  * (`[[role=dialog]~&]`) sits a little lower still, so both remain visible.
44
46
  */
45
47
  fixed: {
46
- true: "top-[10vh] max-h-[calc(90vh-2rem)] [[role=dialog]~&]:top-[15vh] [[role=dialog]~&]:max-h-[calc(85vh-2rem)]",
47
- false: "top-1/2 -translate-y-1/2 max-h-[calc(100vh-2rem)]"
48
+ true: "sm:top-[10vh] sm:max-h-[calc(90vh-2rem)] sm:[[role=dialog]~&]:top-[15vh] sm:[[role=dialog]~&]:max-h-[calc(85vh-2rem)]",
49
+ false: "sm:top-1/2 sm:-translate-y-1/2 sm:max-h-[calc(100vh-2rem)]"
48
50
  },
49
51
  /**
50
52
  * Drop the dialog's own padding and gap so sections can run edge to edge and draw their
51
53
  * own borders, e.g. a bordered header over a scrolling body over a bordered footer.
52
54
  */
53
55
  flush: {
54
- true: "gap-0 p-0",
55
- false: "gap-6 p-6"
56
+ true: "gap-0 p-0 max-sm:pb-[env(safe-area-inset-bottom)]",
57
+ false: "gap-6 p-6 max-sm:pb-[calc(1.5rem+env(safe-area-inset-bottom))]"
56
58
  }
57
59
  },
58
60
  defaultVariants: {
@@ -1 +1 @@
1
- {"version":3,"file":"dialog.js","names":["DialogPrimitive"],"sources":["../../src/components/dialog.tsx"],"sourcesContent":["\"use client\"\n\nimport { Dialog as DialogPrimitive } from \"@base-ui/react/dialog\"\nimport type { ComponentProps, ReactNode } from \"react\"\nimport { Button } from \"~/components/button\"\nimport {\n SectionHeader,\n SectionHeaderBody,\n SectionHeaderDescription,\n SectionHeaderTitle,\n} from \"~/components/section-header\"\nimport { XIcon } from \"~/lib/icons\"\nimport { variants, cn, type VariantProps } from \"~/lib/variants\"\n\nconst Dialog = DialogPrimitive.Root\nconst DialogTrigger = DialogPrimitive.Trigger\nconst DialogTitle = DialogPrimitive.Title\nconst DialogDescription = DialogPrimitive.Description\n\n/** The dashed, blurred backdrop behind the dialog. Rendered by `DialogContent`. */\nfunction DialogOverlay({ className, ...props }: DialogPrimitive.Backdrop.Props) {\n return (\n <DialogPrimitive.Backdrop\n className={cn(\n \"fixed inset-0 z-50 bg-dashed backdrop-blur-xs\",\n \"data-open:animate-in data-closed:animate-out\",\n \"data-open:fade-in-0 data-closed:fade-out-0\",\n className,\n )}\n {...props}\n />\n )\n}\n\nconst dialogContentVariants = variants({\n base: [\n \"fixed left-1/2 z-50 grid w-[calc(100vw-2rem)] -translate-x-1/2 overflow-y-auto\",\n \"border bg-card shadow-sm rounded-xl\",\n \"data-open:animate-in data-closed:animate-out\",\n \"data-open:fade-in-0 data-closed:fade-out-0\",\n \"data-open:slide-in-from-bottom-4 data-closed:slide-out-to-bottom-4\",\n ],\n\n variants: {\n size: {\n xs: \"max-w-xs\",\n sm: \"max-w-sm\",\n md: \"max-w-md\",\n lg: \"max-w-lg\",\n xl: \"max-w-xl\",\n \"2xl\": \"max-w-2xl\",\n \"3xl\": \"max-w-3xl\",\n \"4xl\": \"max-w-4xl\",\n \"5xl\": \"max-w-5xl\",\n },\n\n /**\n * Pin the dialog near the top of the viewport instead of centering it, so its height can\n * change without the whole thing jumping. A dialog stacked on top of another one\n * (`[[role=dialog]~&]`) sits a little lower still, so both remain visible.\n */\n fixed: {\n true: \"top-[10vh] max-h-[calc(90vh-2rem)] [[role=dialog]~&]:top-[15vh] [[role=dialog]~&]:max-h-[calc(85vh-2rem)]\",\n false: \"top-1/2 -translate-y-1/2 max-h-[calc(100vh-2rem)]\",\n },\n\n /**\n * Drop the dialog's own padding and gap so sections can run edge to edge and draw their\n * own borders, e.g. a bordered header over a scrolling body over a bordered footer.\n */\n flush: {\n true: \"gap-0 p-0\",\n false: \"gap-6 p-6\",\n },\n },\n\n defaultVariants: {\n size: \"md\",\n fixed: true,\n flush: false,\n },\n})\n\ntype DialogContentProps = Omit<DialogPrimitive.Popup.Props, \"className\"> &\n VariantProps<typeof dialogContentVariants> & {\n className?: string\n\n /**\n * Hide the close button in the top right corner, for a dialog that must be dismissed\n * through one of its own actions.\n */\n hideClose?: boolean\n }\n\nfunction DialogContent({\n className,\n children,\n size,\n fixed,\n flush,\n hideClose,\n ...props\n}: DialogContentProps) {\n return (\n <DialogPrimitive.Portal>\n <DialogOverlay />\n\n <DialogPrimitive.Popup\n className={dialogContentVariants({ size, fixed, flush, className })}\n {...props}\n >\n {children}\n\n {!hideClose && (\n <DialogPrimitive.Close\n render={<Button variant=\"ghost\" size=\"sm\" aria-label=\"Close\" prefix={<XIcon />} />}\n className=\"absolute top-3 right-3\"\n />\n )}\n </DialogPrimitive.Popup>\n </DialogPrimitive.Portal>\n )\n}\n\nconst dialogHeaderVariants = variants({\n variants: {\n /**\n * Give the header its own hairline and padding, for a `flush` dialog whose body scrolls\n * under it. The extra right padding clears the dialog's close button.\n */\n bordered: {\n true: \"border-b px-6 py-5 pr-14\",\n },\n },\n})\n\ntype DialogHeaderProps = Omit<ComponentProps<typeof SectionHeader>, \"title\" | \"description\"> &\n VariantProps<typeof dialogHeaderVariants> & {\n title: ReactNode\n description?: ReactNode\n }\n\n/**\n * The standard dialog heading: a card-scale `SectionHeader` whose title and description are wired to\n * Base UI's `DialogTitle` / `DialogDescription` for accessibility. Extra children render after\n * them.\n */\nfunction DialogHeader({\n title,\n description,\n bordered,\n className,\n children,\n ...props\n}: DialogHeaderProps) {\n return (\n <SectionHeader size=\"card\" className={dialogHeaderVariants({ bordered, className })} {...props}>\n <SectionHeaderBody>\n <DialogTitle render={<SectionHeaderTitle />}>{title}</DialogTitle>\n\n {description && (\n <DialogDescription render={<SectionHeaderDescription />}>{description}</DialogDescription>\n )}\n </SectionHeaderBody>\n\n {children}\n </SectionHeader>\n )\n}\n\nconst dialogFooterVariants = variants({\n base: \"flex flex-col-reverse flex-wrap gap-x-2 gap-y-4 sm:flex-row sm:justify-end\",\n\n variants: {\n /**\n * Give the footer its own hairline and padding, for a `flush` dialog where the body\n * scrolls behind it.\n */\n bordered: {\n true: \"border-t px-6 py-4\",\n },\n },\n})\n\ntype DialogFooterProps = ComponentProps<\"div\"> & VariantProps<typeof dialogFooterVariants>\n\nfunction DialogFooter({ className, bordered, ...props }: DialogFooterProps) {\n return <div className={dialogFooterVariants({ bordered, className })} {...props} />\n}\n\nfunction DialogClose({ ...props }: ComponentProps<typeof Button>) {\n return <DialogPrimitive.Close render={<Button variant=\"secondary\" {...props} />} />\n}\n\nexport {\n Dialog,\n DialogClose,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n}\n"],"mappings":";;;;;;;;AAcA,MAAM,SAASA,SAAgB;AAC/B,MAAM,gBAAgBA,SAAgB;AACtC,MAAM,cAAcA,SAAgB;AACpC,MAAM,oBAAoBA,SAAgB;;AAG1C,SAAS,cAAc,EAAE,WAAW,GAAG,SAAyC;CAC9E,OACE,oBAACA,SAAgB,UAAjB;EACE,WAAW,GACT,iDACA,gDACA,8CACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,MAAM,wBAAwB,SAAS;CACrC,MAAM;EACJ;EACA;EACA;EACA;EACA;CACF;CAEA,UAAU;EACR,MAAM;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,OAAO;GACP,OAAO;GACP,OAAO;GACP,OAAO;EACT;;;;;;EAOA,OAAO;GACL,MAAM;GACN,OAAO;EACT;;;;;EAMA,OAAO;GACL,MAAM;GACN,OAAO;EACT;CACF;CAEA,iBAAiB;EACf,MAAM;EACN,OAAO;EACP,OAAO;CACT;AACF,CAAC;AAaD,SAAS,cAAc,EACrB,WACA,UACA,MACA,OACA,OACA,WACA,GAAG,SACkB;CACrB,OACE,qBAACA,SAAgB,QAAjB,EAAA,UAAA,CACE,oBAAC,eAAD,CAAgB,CAAA,GAEhB,qBAACA,SAAgB,OAAjB;EACE,WAAW,sBAAsB;GAAE;GAAM;GAAO;GAAO;EAAU,CAAC;EAClE,GAAI;EAFN,UAAA,CAIG,UAEA,CAAC,aACA,oBAACA,SAAgB,OAAjB;GACE,QAAQ,oBAAC,QAAD;IAAQ,SAAQ;IAAQ,MAAK;IAAK,cAAW;IAAQ,QAAQ,oBAAC,OAAD,CAAQ,CAAA;GAAI,CAAA;GACjF,WAAU;EACX,CAAA,CAEkB;CACD,CAAA,CAAA,EAAA,CAAA;AAE5B;AAEA,MAAM,uBAAuB,SAAS,EACpC,UAAU;;;;;AAKR,UAAU,EACR,MAAM,2BACR,EACF,EACF,CAAC;;;;;;AAaD,SAAS,aAAa,EACpB,OACA,aACA,UACA,WACA,UACA,GAAG,SACiB;CACpB,OACE,qBAAC,eAAD;EAAe,MAAK;EAAO,WAAW,qBAAqB;GAAE;GAAU;EAAU,CAAC;EAAG,GAAI;EAAzF,UAAA,CACE,qBAAC,mBAAD,EAAA,UAAA,CACE,oBAAC,aAAD;GAAa,QAAQ,oBAAC,oBAAD,CAAqB,CAAA;GAAI,UAAA;EAAmB,CAAA,GAEhE,eACC,oBAAC,mBAAD;GAAmB,QAAQ,oBAAC,0BAAD,CAA2B,CAAA;GAAI,UAAA;EAA+B,CAAA,CAE1E,EAAA,CAAA,GAElB,QACY;;AAEnB;AAEA,MAAM,uBAAuB,SAAS;CACpC,MAAM;CAEN,UAAU;;;;;AAKR,UAAU,EACR,MAAM,qBACR,EACF;AACF,CAAC;AAID,SAAS,aAAa,EAAE,WAAW,UAAU,GAAG,SAA4B;CAC1E,OAAO,oBAAC,OAAD;EAAK,WAAW,qBAAqB;GAAE;GAAU;EAAU,CAAC;EAAG,GAAI;CAAQ,CAAA;AACpF;AAEA,SAAS,YAAY,EAAE,GAAG,SAAwC;CAChE,OAAO,oBAACA,SAAgB,OAAjB,EAAuB,QAAQ,oBAAC,QAAD;EAAQ,SAAQ;EAAY,GAAI;CAAQ,CAAA,EAAI,CAAA;AACpF"}
1
+ {"version":3,"file":"dialog.js","names":["DialogPrimitive"],"sources":["../../src/components/dialog.tsx"],"sourcesContent":["\"use client\"\n\nimport { Dialog as DialogPrimitive } from \"@base-ui/react/dialog\"\nimport type { ComponentProps, ReactNode } from \"react\"\nimport { Button } from \"~/components/button\"\nimport {\n SectionHeader,\n SectionHeaderBody,\n SectionHeaderDescription,\n SectionHeaderTitle,\n} from \"~/components/section-header\"\nimport { XIcon } from \"~/lib/icons\"\nimport { variants, cn, type VariantProps } from \"~/lib/variants\"\n\nconst Dialog = DialogPrimitive.Root\nconst DialogTrigger = DialogPrimitive.Trigger\nconst DialogTitle = DialogPrimitive.Title\nconst DialogDescription = DialogPrimitive.Description\n\n/** The dashed, blurred backdrop behind the dialog. Rendered by `DialogContent`. */\nfunction DialogOverlay({ className, ...props }: DialogPrimitive.Backdrop.Props) {\n return (\n <DialogPrimitive.Backdrop\n className={cn(\n \"fixed inset-0 z-50 bg-dashed backdrop-blur-xs\",\n \"data-open:animate-in data-closed:animate-out\",\n \"data-open:fade-in-0 data-closed:fade-out-0\",\n className,\n )}\n {...props}\n />\n )\n}\n\nconst dialogContentVariants = variants({\n base: [\n \"fixed z-50 grid overflow-y-auto border bg-card shadow-sm\",\n // Under `sm` the dialog is a bottom sheet: full width, pinned to the bottom edge,\n // padded past the home indicator.\n \"max-sm:inset-x-0 max-sm:bottom-0 max-sm:max-h-[calc(100dvh-3rem)] max-sm:rounded-t-2xl max-sm:border-b-0\",\n \"sm:left-1/2 sm:w-[calc(100vw-2rem)] sm:-translate-x-1/2 sm:rounded-xl\",\n \"data-open:animate-in data-closed:animate-out\",\n \"data-open:fade-in-0 data-closed:fade-out-0\",\n \"data-open:slide-in-from-bottom-4 data-closed:slide-out-to-bottom-4\",\n \"max-sm:data-open:slide-in-from-bottom-8 max-sm:data-closed:slide-out-to-bottom-8\",\n ],\n\n variants: {\n size: {\n xs: \"max-w-xs\",\n sm: \"max-w-sm\",\n md: \"max-w-md\",\n lg: \"max-w-lg\",\n xl: \"max-w-xl\",\n \"2xl\": \"max-w-2xl\",\n \"3xl\": \"max-w-3xl\",\n \"4xl\": \"max-w-4xl\",\n \"5xl\": \"max-w-5xl\",\n },\n\n /**\n * Pin the dialog near the top of the viewport instead of centering it, so its height can\n * change without the whole thing jumping. A dialog stacked on top of another one\n * (`[[role=dialog]~&]`) sits a little lower still, so both remain visible.\n */\n fixed: {\n true: \"sm:top-[10vh] sm:max-h-[calc(90vh-2rem)] sm:[[role=dialog]~&]:top-[15vh] sm:[[role=dialog]~&]:max-h-[calc(85vh-2rem)]\",\n false: \"sm:top-1/2 sm:-translate-y-1/2 sm:max-h-[calc(100vh-2rem)]\",\n },\n\n /**\n * Drop the dialog's own padding and gap so sections can run edge to edge and draw their\n * own borders, e.g. a bordered header over a scrolling body over a bordered footer.\n */\n flush: {\n true: \"gap-0 p-0 max-sm:pb-[env(safe-area-inset-bottom)]\",\n false: \"gap-6 p-6 max-sm:pb-[calc(1.5rem+env(safe-area-inset-bottom))]\",\n },\n },\n\n defaultVariants: {\n size: \"md\",\n fixed: true,\n flush: false,\n },\n})\n\ntype DialogContentProps = Omit<DialogPrimitive.Popup.Props, \"className\"> &\n VariantProps<typeof dialogContentVariants> & {\n className?: string\n\n /**\n * Hide the close button in the top right corner, for a dialog that must be dismissed\n * through one of its own actions.\n */\n hideClose?: boolean\n }\n\nfunction DialogContent({\n className,\n children,\n size,\n fixed,\n flush,\n hideClose,\n ...props\n}: DialogContentProps) {\n return (\n <DialogPrimitive.Portal>\n <DialogOverlay />\n\n <DialogPrimitive.Popup\n className={dialogContentVariants({ size, fixed, flush, className })}\n {...props}\n >\n {children}\n\n {!hideClose && (\n <DialogPrimitive.Close\n render={<Button variant=\"ghost\" size=\"sm\" aria-label=\"Close\" prefix={<XIcon />} />}\n className=\"absolute top-3 right-3\"\n />\n )}\n </DialogPrimitive.Popup>\n </DialogPrimitive.Portal>\n )\n}\n\nconst dialogHeaderVariants = variants({\n variants: {\n /**\n * Give the header its own hairline and padding, for a `flush` dialog whose body scrolls\n * under it. The extra right padding clears the dialog's close button.\n */\n bordered: {\n true: \"border-b px-6 py-5 pr-14\",\n },\n },\n})\n\ntype DialogHeaderProps = Omit<ComponentProps<typeof SectionHeader>, \"title\" | \"description\"> &\n VariantProps<typeof dialogHeaderVariants> & {\n title: ReactNode\n description?: ReactNode\n }\n\n/**\n * The standard dialog heading: a card-scale `SectionHeader` whose title and description are wired to\n * Base UI's `DialogTitle` / `DialogDescription` for accessibility. Extra children render after\n * them.\n */\nfunction DialogHeader({\n title,\n description,\n bordered,\n className,\n children,\n ...props\n}: DialogHeaderProps) {\n return (\n <SectionHeader size=\"card\" className={dialogHeaderVariants({ bordered, className })} {...props}>\n <SectionHeaderBody>\n <DialogTitle render={<SectionHeaderTitle />}>{title}</DialogTitle>\n\n {description && (\n <DialogDescription render={<SectionHeaderDescription />}>{description}</DialogDescription>\n )}\n </SectionHeaderBody>\n\n {children}\n </SectionHeader>\n )\n}\n\nconst dialogFooterVariants = variants({\n base: \"flex flex-col-reverse flex-wrap gap-x-2 gap-y-4 sm:flex-row sm:justify-end\",\n\n variants: {\n /**\n * Give the footer its own hairline and padding, for a `flush` dialog where the body\n * scrolls behind it.\n */\n bordered: {\n true: \"border-t px-6 py-4\",\n },\n },\n})\n\ntype DialogFooterProps = ComponentProps<\"div\"> & VariantProps<typeof dialogFooterVariants>\n\nfunction DialogFooter({ className, bordered, ...props }: DialogFooterProps) {\n return <div className={dialogFooterVariants({ bordered, className })} {...props} />\n}\n\nfunction DialogClose({ ...props }: ComponentProps<typeof Button>) {\n return <DialogPrimitive.Close render={<Button variant=\"secondary\" {...props} />} />\n}\n\nexport {\n Dialog,\n DialogClose,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n}\n"],"mappings":";;;;;;;;AAcA,MAAM,SAASA,SAAgB;AAC/B,MAAM,gBAAgBA,SAAgB;AACtC,MAAM,cAAcA,SAAgB;AACpC,MAAM,oBAAoBA,SAAgB;;AAG1C,SAAS,cAAc,EAAE,WAAW,GAAG,SAAyC;CAC9E,OACE,oBAACA,SAAgB,UAAjB;EACE,WAAW,GACT,iDACA,gDACA,8CACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,MAAM,wBAAwB,SAAS;CACrC,MAAM;EACJ;EAGA;EACA;EACA;EACA;EACA;EACA;CACF;CAEA,UAAU;EACR,MAAM;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,OAAO;GACP,OAAO;GACP,OAAO;GACP,OAAO;EACT;;;;;;EAOA,OAAO;GACL,MAAM;GACN,OAAO;EACT;;;;;EAMA,OAAO;GACL,MAAM;GACN,OAAO;EACT;CACF;CAEA,iBAAiB;EACf,MAAM;EACN,OAAO;EACP,OAAO;CACT;AACF,CAAC;AAaD,SAAS,cAAc,EACrB,WACA,UACA,MACA,OACA,OACA,WACA,GAAG,SACkB;CACrB,OACE,qBAACA,SAAgB,QAAjB,EAAA,UAAA,CACE,oBAAC,eAAD,CAAgB,CAAA,GAEhB,qBAACA,SAAgB,OAAjB;EACE,WAAW,sBAAsB;GAAE;GAAM;GAAO;GAAO;EAAU,CAAC;EAClE,GAAI;EAFN,UAAA,CAIG,UAEA,CAAC,aACA,oBAACA,SAAgB,OAAjB;GACE,QAAQ,oBAAC,QAAD;IAAQ,SAAQ;IAAQ,MAAK;IAAK,cAAW;IAAQ,QAAQ,oBAAC,OAAD,CAAQ,CAAA;GAAI,CAAA;GACjF,WAAU;EACX,CAAA,CAEkB;CACD,CAAA,CAAA,EAAA,CAAA;AAE5B;AAEA,MAAM,uBAAuB,SAAS,EACpC,UAAU;;;;;AAKR,UAAU,EACR,MAAM,2BACR,EACF,EACF,CAAC;;;;;;AAaD,SAAS,aAAa,EACpB,OACA,aACA,UACA,WACA,UACA,GAAG,SACiB;CACpB,OACE,qBAAC,eAAD;EAAe,MAAK;EAAO,WAAW,qBAAqB;GAAE;GAAU;EAAU,CAAC;EAAG,GAAI;EAAzF,UAAA,CACE,qBAAC,mBAAD,EAAA,UAAA,CACE,oBAAC,aAAD;GAAa,QAAQ,oBAAC,oBAAD,CAAqB,CAAA;GAAI,UAAA;EAAmB,CAAA,GAEhE,eACC,oBAAC,mBAAD;GAAmB,QAAQ,oBAAC,0BAAD,CAA2B,CAAA;GAAI,UAAA;EAA+B,CAAA,CAE1E,EAAA,CAAA,GAElB,QACY;;AAEnB;AAEA,MAAM,uBAAuB,SAAS;CACpC,MAAM;CAEN,UAAU;;;;;AAKR,UAAU,EACR,MAAM,qBACR,EACF;AACF,CAAC;AAID,SAAS,aAAa,EAAE,WAAW,UAAU,GAAG,SAA4B;CAC1E,OAAO,oBAAC,OAAD;EAAK,WAAW,qBAAqB;GAAE;GAAU;EAAU,CAAC;EAAG,GAAI;CAAQ,CAAA;AACpF;AAEA,SAAS,YAAY,EAAE,GAAG,SAAwC;CAChE,OAAO,oBAACA,SAAgB,OAAjB,EAAuB,QAAQ,oBAAC,QAAD;EAAQ,SAAQ;EAAY,GAAI;CAAQ,CAAA,EAAI,CAAA;AACpF"}
@@ -18,7 +18,7 @@ function FormSection({ id, title, description, actions, footer, className, child
18
18
  children: /* @__PURE__ */ jsxs(Fragment, { children: [
19
19
  (title || actions) && /* @__PURE__ */ jsx(SectionHeader, {
20
20
  size: "panel",
21
- className: "py-4",
21
+ className: "py-4 max-sm:flex-col max-sm:items-stretch",
22
22
  title,
23
23
  description,
24
24
  actions
@@ -1 +1 @@
1
- {"version":3,"file":"form-section.js","names":[],"sources":["../../src/components/form-section.tsx"],"sourcesContent":["import { useRender } from \"@base-ui/react/use-render\"\nimport type { ReactNode } from \"react\"\nimport { SectionHeader } from \"~/components/section-header\"\nimport { cn } from \"~/lib/variants\"\n\ntype FormSectionProps = Omit<useRender.ComponentProps<\"section\">, \"title\"> & {\n id?: string\n /**\n * Optional heading. Leave it out inside the settings dialog, whose header owns the title.\n */\n title?: ReactNode\n description?: ReactNode\n actions?: ReactNode\n /**\n * Right-aligned actions after the rows, e.g. a save button. Rendered as the last row.\n */\n footer?: ReactNode\n}\n\n/**\n * A block of settings: one padded container whose children (optional heading, the rows,\n * optional footer) stack with hairlines between them. Renders as a `<form>` via `render`\n * when the rows are fields, so the form is the only wrapper.\n */\nexport function FormSection({\n id,\n title,\n description,\n actions,\n footer,\n className,\n children,\n render,\n ...props\n}: FormSectionProps) {\n return useRender({\n render,\n defaultTagName: \"section\",\n props: {\n id,\n className: cn(\"flex flex-col divide-y px-5 md:px-6\", className),\n children: (\n <>\n {(title || actions) && (\n <SectionHeader\n size=\"panel\"\n className=\"py-4\"\n title={title}\n description={description}\n actions={actions}\n />\n )}\n\n {children}\n\n {footer && <div className=\"flex justify-end py-5\">{footer}</div>}\n </>\n ),\n ...props,\n },\n })\n}\n"],"mappings":";;;;;;;;;;AAwBA,SAAgB,YAAY,EAC1B,IACA,OACA,aACA,SACA,QACA,WACA,UACA,QACA,GAAG,SACgB;CACnB,OAAO,UAAU;EACf;EACA,gBAAgB;EAChB,OAAO;GACL;GACA,WAAW,GAAG,uCAAuC,SAAS;GAC9D,UACE,qBAAA,UAAA,EAAA,UAAA;KACI,SAAS,YACT,oBAAC,eAAD;KACE,MAAK;KACL,WAAU;KACH;KACM;KACJ;IACV,CAAA;IAGF;IAEA,UAAU,oBAAC,OAAD;KAAK,WAAU;KAAyB,UAAA;IAAY,CAAA;GAC/D,EAAA,CAAA;GAEJ,GAAG;EACL;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"form-section.js","names":[],"sources":["../../src/components/form-section.tsx"],"sourcesContent":["import { useRender } from \"@base-ui/react/use-render\"\nimport type { ReactNode } from \"react\"\nimport { SectionHeader } from \"~/components/section-header\"\nimport { cn } from \"~/lib/variants\"\n\ntype FormSectionProps = Omit<useRender.ComponentProps<\"section\">, \"title\"> & {\n id?: string\n /**\n * Optional heading. Leave it out inside the settings dialog, whose header owns the title.\n */\n title?: ReactNode\n description?: ReactNode\n actions?: ReactNode\n /**\n * Right-aligned actions after the rows, e.g. a save button. Rendered as the last row.\n */\n footer?: ReactNode\n}\n\n/**\n * A block of settings: one padded container whose children (optional heading, the rows,\n * optional footer) stack with hairlines between them. Renders as a `<form>` via `render`\n * when the rows are fields, so the form is the only wrapper.\n */\nexport function FormSection({\n id,\n title,\n description,\n actions,\n footer,\n className,\n children,\n render,\n ...props\n}: FormSectionProps) {\n return useRender({\n render,\n defaultTagName: \"section\",\n props: {\n id,\n className: cn(\"flex flex-col divide-y px-5 md:px-6\", className),\n children: (\n <>\n {(title || actions) && (\n <SectionHeader\n size=\"panel\"\n className=\"py-4 max-sm:flex-col max-sm:items-stretch\"\n title={title}\n description={description}\n actions={actions}\n />\n )}\n\n {children}\n\n {footer && <div className=\"flex justify-end py-5\">{footer}</div>}\n </>\n ),\n ...props,\n },\n })\n}\n"],"mappings":";;;;;;;;;;AAwBA,SAAgB,YAAY,EAC1B,IACA,OACA,aACA,SACA,QACA,WACA,UACA,QACA,GAAG,SACgB;CACnB,OAAO,UAAU;EACf;EACA,gBAAgB;EAChB,OAAO;GACL;GACA,WAAW,GAAG,uCAAuC,SAAS;GAC9D,UACE,qBAAA,UAAA,EAAA,UAAA;KACI,SAAS,YACT,oBAAC,eAAD;KACE,MAAK;KACL,WAAU;KACH;KACM;KACJ;IACV,CAAA;IAGF;IAEA,UAAU,oBAAC,OAAD;KAAK,WAAU;KAAyB,UAAA;IAAY,CAAA;GAC/D,EAAA,CAAA;GAEJ,GAAG;EACL;CACF,CAAC;AACH"}
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/variants.js";
3
- import { controlTextClasses, descriptionClasses } from "../lib/classes.js";
4
3
  import { Stack } from "./stack.js";
4
+ import { controlTextClasses, descriptionClasses } from "../lib/classes.js";
5
5
  import { Label } from "./label.js";
6
6
  import { jsx } from "react/jsx-runtime";
7
7
  import { useRender } from "@base-ui/react/use-render";
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/variants.js";
3
- import { controlTextClasses, eyebrowClasses, popoverAnimationClasses } from "../lib/classes.js";
4
3
  import { CheckIcon, CircleIcon } from "../lib/icons.js";
4
+ import { controlTextClasses, eyebrowClasses, popoverAnimationClasses } from "../lib/classes.js";
5
5
  import { Kbd } from "./kbd.js";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
7
7
  import { Menu as Menu$1 } from "@base-ui/react/menu";
@@ -24,13 +24,13 @@ function MenuContent({ className, sideOffset = 4, align, side, ...props }) {
24
24
  }
25
25
  function MenuItem({ className, inset, ...props }) {
26
26
  return /* @__PURE__ */ jsx(Menu$1.Item, {
27
- className: cn(`relative flex m-0 cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 ${controlTextClasses} outline-hidden focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0`, inset && "pl-8", className),
27
+ className: cn(`relative flex m-0 cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 pointer-coarse:min-h-10 ${controlTextClasses} outline-hidden focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0`, inset && "pl-8", className),
28
28
  ...props
29
29
  });
30
30
  }
31
31
  function MenuCheckboxItem({ className, children, checked, ...props }) {
32
32
  return /* @__PURE__ */ jsxs(Menu$1.CheckboxItem, {
33
- className: cn(`relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 ${controlTextClasses} outline-hidden focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50`, className),
33
+ className: cn(`relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 pointer-coarse:min-h-10 ${controlTextClasses} outline-hidden focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50`, className),
34
34
  checked,
35
35
  ...props,
36
36
  children: [/* @__PURE__ */ jsx("span", {
@@ -41,7 +41,7 @@ function MenuCheckboxItem({ className, children, checked, ...props }) {
41
41
  }
42
42
  function MenuRadioItem({ className, children, ...props }) {
43
43
  return /* @__PURE__ */ jsxs(Menu$1.RadioItem, {
44
- className: cn(`relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 ${controlTextClasses} outline-hidden focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50`, className),
44
+ className: cn(`relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 pointer-coarse:min-h-10 ${controlTextClasses} outline-hidden focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50`, className),
45
45
  ...props,
46
46
  children: [/* @__PURE__ */ jsx("span", {
47
47
  className: "absolute left-2 flex size-3.5 items-center justify-center",
@@ -1 +1 @@
1
- {"version":3,"file":"menu.js","names":["MenuPrimitive"],"sources":["../../src/components/menu.tsx"],"sourcesContent":["\"use client\"\n\nimport { Menu as MenuPrimitive } from \"@base-ui/react/menu\"\nimport type { ComponentProps } from \"react\"\nimport { Kbd } from \"~/components/kbd\"\nimport { controlTextClasses, eyebrowClasses, popoverAnimationClasses } from \"~/lib/classes\"\nimport { CheckIcon, CircleIcon } from \"~/lib/icons\"\nimport { cn } from \"~/lib/variants\"\n\nconst Menu = MenuPrimitive.Root\nconst MenuTrigger = MenuPrimitive.Trigger\nconst MenuGroup = MenuPrimitive.Group\nconst MenuRadioGroup = MenuPrimitive.RadioGroup\n\nfunction MenuContent({\n className,\n sideOffset = 4,\n align,\n side,\n ...props\n}: MenuPrimitive.Popup.Props &\n Pick<MenuPrimitive.Positioner.Props, \"align\" | \"side\" | \"sideOffset\">) {\n return (\n <MenuPrimitive.Portal>\n <MenuPrimitive.Positioner\n sideOffset={sideOffset}\n align={align}\n side={side}\n className=\"isolate z-50\"\n >\n <MenuPrimitive.Popup\n className={cn(\n \"min-w-40 flex flex-col rounded-md border bg-popover p-1 text-popover-foreground shadow-sm backdrop-blur-xs\",\n popoverAnimationClasses,\n className,\n )}\n {...props}\n />\n </MenuPrimitive.Positioner>\n </MenuPrimitive.Portal>\n )\n}\n\nfunction MenuItem({\n className,\n inset,\n ...props\n}: MenuPrimitive.Item.Props & {\n inset?: boolean\n}) {\n return (\n <MenuPrimitive.Item\n className={cn(\n `relative flex m-0 cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 ${controlTextClasses} outline-hidden focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0`,\n inset && \"pl-8\",\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction MenuCheckboxItem({\n className,\n children,\n checked,\n ...props\n}: MenuPrimitive.CheckboxItem.Props) {\n return (\n <MenuPrimitive.CheckboxItem\n className={cn(\n `relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 ${controlTextClasses} outline-hidden focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50`,\n className,\n )}\n checked={checked}\n {...props}\n >\n <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n <MenuPrimitive.CheckboxItemIndicator>\n <CheckIcon />\n </MenuPrimitive.CheckboxItemIndicator>\n </span>\n {children}\n </MenuPrimitive.CheckboxItem>\n )\n}\n\nfunction MenuRadioItem({ className, children, ...props }: MenuPrimitive.RadioItem.Props) {\n return (\n <MenuPrimitive.RadioItem\n className={cn(\n `relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 ${controlTextClasses} outline-hidden focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50`,\n className,\n )}\n {...props}\n >\n <span className=\"absolute left-2 flex size-3.5 items-center justify-center\">\n <MenuPrimitive.RadioItemIndicator>\n <CircleIcon className=\"fill-current size-2\" />\n </MenuPrimitive.RadioItemIndicator>\n </span>\n {children}\n </MenuPrimitive.RadioItem>\n )\n}\n\nfunction MenuGroupLabel({\n className,\n inset,\n ...props\n}: MenuPrimitive.GroupLabel.Props & {\n inset?: boolean\n}) {\n return (\n <MenuPrimitive.GroupLabel\n className={cn(\"px-2 pt-1.5 pb-1\", eyebrowClasses, inset && \"pl-8\", className)}\n {...props}\n />\n )\n}\n\nfunction MenuSeparator({ className, ...props }: MenuPrimitive.Separator.Props) {\n return (\n <MenuPrimitive.Separator className={cn(\"-mx-1 my-1 h-px bg-border\", className)} {...props} />\n )\n}\n\nfunction MenuShortcut({ className, ...props }: ComponentProps<typeof Kbd>) {\n return <Kbd className={cn(\"ml-auto\", className)} {...props} />\n}\n\nexport {\n Menu,\n MenuCheckboxItem,\n MenuContent,\n MenuGroup,\n MenuItem,\n MenuGroupLabel,\n MenuRadioGroup,\n MenuRadioItem,\n MenuSeparator,\n MenuShortcut,\n MenuTrigger,\n}\n"],"mappings":";;;;;;;;AASA,MAAM,OAAOA,OAAc;AAC3B,MAAM,cAAcA,OAAc;AAClC,MAAM,YAAYA,OAAc;AAChC,MAAM,iBAAiBA,OAAc;AAErC,SAAS,YAAY,EACnB,WACA,aAAa,GACb,OACA,MACA,GAAG,SAEoE;CACvE,OACE,oBAACA,OAAc,QAAf,EAAA,UACE,oBAACA,OAAc,YAAf;EACc;EACL;EACD;EACN,WAAU;EAEV,UAAA,oBAACA,OAAc,OAAf;GACE,WAAW,GACT,8GACA,yBACA,SACF;GACA,GAAI;EACL,CAAA;CACuB,CAAA,EACN,CAAA;AAE1B;AAEA,SAAS,SAAS,EAChB,WACA,OACA,GAAG,SAGF;CACD,OACE,oBAACA,OAAc,MAAf;EACE,WAAW,GACT,0FAA0F,mBAAmB,0JAC7G,SAAS,QACT,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,iBAAiB,EACxB,WACA,UACA,SACA,GAAG,SACgC;CACnC,OACE,qBAACA,OAAc,cAAf;EACE,WAAW,GACT,qFAAqF,mBAAmB,0HACxG,SACF;EACS;EACT,GAAI;EANN,UAAA,CAQE,oBAAC,QAAD;GAAM,WAAU;GACd,UAAA,oBAACA,OAAc,uBAAf,EAAA,UACE,oBAAC,WAAD,CAAY,CAAA,EACuB,CAAA;EACjC,CAAA,GACL,QACyB;;AAEhC;AAEA,SAAS,cAAc,EAAE,WAAW,UAAU,GAAG,SAAwC;CACvF,OACE,qBAACA,OAAc,WAAf;EACE,WAAW,GACT,qFAAqF,mBAAmB,0HACxG,SACF;EACA,GAAI;EALN,UAAA,CAOE,oBAAC,QAAD;GAAM,WAAU;GACd,UAAA,oBAACA,OAAc,oBAAf,EAAA,UACE,oBAAC,YAAD,EAAY,WAAU,sBAAuB,CAAA,EACb,CAAA;EAC9B,CAAA,GACL,QACsB;;AAE7B;AAEA,SAAS,eAAe,EACtB,WACA,OACA,GAAG,SAGF;CACD,OACE,oBAACA,OAAc,YAAf;EACE,WAAW,GAAG,oBAAoB,gBAAgB,SAAS,QAAQ,SAAS;EAC5E,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,cAAc,EAAE,WAAW,GAAG,SAAwC;CAC7E,OACE,oBAACA,OAAc,WAAf;EAAyB,WAAW,GAAG,6BAA6B,SAAS;EAAG,GAAI;CAAQ,CAAA;AAEhG;AAEA,SAAS,aAAa,EAAE,WAAW,GAAG,SAAqC;CACzE,OAAO,oBAAC,KAAD;EAAK,WAAW,GAAG,WAAW,SAAS;EAAG,GAAI;CAAQ,CAAA;AAC/D"}
1
+ {"version":3,"file":"menu.js","names":["MenuPrimitive"],"sources":["../../src/components/menu.tsx"],"sourcesContent":["\"use client\"\n\nimport { Menu as MenuPrimitive } from \"@base-ui/react/menu\"\nimport type { ComponentProps } from \"react\"\nimport { Kbd } from \"~/components/kbd\"\nimport { controlTextClasses, eyebrowClasses, popoverAnimationClasses } from \"~/lib/classes\"\nimport { CheckIcon, CircleIcon } from \"~/lib/icons\"\nimport { cn } from \"~/lib/variants\"\n\nconst Menu = MenuPrimitive.Root\nconst MenuTrigger = MenuPrimitive.Trigger\nconst MenuGroup = MenuPrimitive.Group\nconst MenuRadioGroup = MenuPrimitive.RadioGroup\n\nfunction MenuContent({\n className,\n sideOffset = 4,\n align,\n side,\n ...props\n}: MenuPrimitive.Popup.Props &\n Pick<MenuPrimitive.Positioner.Props, \"align\" | \"side\" | \"sideOffset\">) {\n return (\n <MenuPrimitive.Portal>\n <MenuPrimitive.Positioner\n sideOffset={sideOffset}\n align={align}\n side={side}\n className=\"isolate z-50\"\n >\n <MenuPrimitive.Popup\n className={cn(\n \"min-w-40 flex flex-col rounded-md border bg-popover p-1 text-popover-foreground shadow-sm backdrop-blur-xs\",\n popoverAnimationClasses,\n className,\n )}\n {...props}\n />\n </MenuPrimitive.Positioner>\n </MenuPrimitive.Portal>\n )\n}\n\nfunction MenuItem({\n className,\n inset,\n ...props\n}: MenuPrimitive.Item.Props & {\n inset?: boolean\n}) {\n return (\n <MenuPrimitive.Item\n className={cn(\n `relative flex m-0 cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 pointer-coarse:min-h-10 ${controlTextClasses} outline-hidden focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0`,\n inset && \"pl-8\",\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction MenuCheckboxItem({\n className,\n children,\n checked,\n ...props\n}: MenuPrimitive.CheckboxItem.Props) {\n return (\n <MenuPrimitive.CheckboxItem\n className={cn(\n `relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 pointer-coarse:min-h-10 ${controlTextClasses} outline-hidden focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50`,\n className,\n )}\n checked={checked}\n {...props}\n >\n <span className=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\">\n <MenuPrimitive.CheckboxItemIndicator>\n <CheckIcon />\n </MenuPrimitive.CheckboxItemIndicator>\n </span>\n {children}\n </MenuPrimitive.CheckboxItem>\n )\n}\n\nfunction MenuRadioItem({ className, children, ...props }: MenuPrimitive.RadioItem.Props) {\n return (\n <MenuPrimitive.RadioItem\n className={cn(\n `relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 pointer-coarse:min-h-10 ${controlTextClasses} outline-hidden focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50`,\n className,\n )}\n {...props}\n >\n <span className=\"absolute left-2 flex size-3.5 items-center justify-center\">\n <MenuPrimitive.RadioItemIndicator>\n <CircleIcon className=\"fill-current size-2\" />\n </MenuPrimitive.RadioItemIndicator>\n </span>\n {children}\n </MenuPrimitive.RadioItem>\n )\n}\n\nfunction MenuGroupLabel({\n className,\n inset,\n ...props\n}: MenuPrimitive.GroupLabel.Props & {\n inset?: boolean\n}) {\n return (\n <MenuPrimitive.GroupLabel\n className={cn(\"px-2 pt-1.5 pb-1\", eyebrowClasses, inset && \"pl-8\", className)}\n {...props}\n />\n )\n}\n\nfunction MenuSeparator({ className, ...props }: MenuPrimitive.Separator.Props) {\n return (\n <MenuPrimitive.Separator className={cn(\"-mx-1 my-1 h-px bg-border\", className)} {...props} />\n )\n}\n\nfunction MenuShortcut({ className, ...props }: ComponentProps<typeof Kbd>) {\n return <Kbd className={cn(\"ml-auto\", className)} {...props} />\n}\n\nexport {\n Menu,\n MenuCheckboxItem,\n MenuContent,\n MenuGroup,\n MenuItem,\n MenuGroupLabel,\n MenuRadioGroup,\n MenuRadioItem,\n MenuSeparator,\n MenuShortcut,\n MenuTrigger,\n}\n"],"mappings":";;;;;;;;AASA,MAAM,OAAOA,OAAc;AAC3B,MAAM,cAAcA,OAAc;AAClC,MAAM,YAAYA,OAAc;AAChC,MAAM,iBAAiBA,OAAc;AAErC,SAAS,YAAY,EACnB,WACA,aAAa,GACb,OACA,MACA,GAAG,SAEoE;CACvE,OACE,oBAACA,OAAc,QAAf,EAAA,UACE,oBAACA,OAAc,YAAf;EACc;EACL;EACD;EACN,WAAU;EAEV,UAAA,oBAACA,OAAc,OAAf;GACE,WAAW,GACT,8GACA,yBACA,SACF;GACA,GAAI;EACL,CAAA;CACuB,CAAA,EACN,CAAA;AAE1B;AAEA,SAAS,SAAS,EAChB,WACA,OACA,GAAG,SAGF;CACD,OACE,oBAACA,OAAc,MAAf;EACE,WAAW,GACT,kHAAkH,mBAAmB,0JACrI,SAAS,QACT,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,iBAAiB,EACxB,WACA,UACA,SACA,GAAG,SACgC;CACnC,OACE,qBAACA,OAAc,cAAf;EACE,WAAW,GACT,6GAA6G,mBAAmB,0HAChI,SACF;EACS;EACT,GAAI;EANN,UAAA,CAQE,oBAAC,QAAD;GAAM,WAAU;GACd,UAAA,oBAACA,OAAc,uBAAf,EAAA,UACE,oBAAC,WAAD,CAAY,CAAA,EACuB,CAAA;EACjC,CAAA,GACL,QACyB;;AAEhC;AAEA,SAAS,cAAc,EAAE,WAAW,UAAU,GAAG,SAAwC;CACvF,OACE,qBAACA,OAAc,WAAf;EACE,WAAW,GACT,6GAA6G,mBAAmB,0HAChI,SACF;EACA,GAAI;EALN,UAAA,CAOE,oBAAC,QAAD;GAAM,WAAU;GACd,UAAA,oBAACA,OAAc,oBAAf,EAAA,UACE,oBAAC,YAAD,EAAY,WAAU,sBAAuB,CAAA,EACb,CAAA;EAC9B,CAAA,GACL,QACsB;;AAE7B;AAEA,SAAS,eAAe,EACtB,WACA,OACA,GAAG,SAGF;CACD,OACE,oBAACA,OAAc,YAAf;EACE,WAAW,GAAG,oBAAoB,gBAAgB,SAAS,QAAQ,SAAS;EAC5E,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,cAAc,EAAE,WAAW,GAAG,SAAwC;CAC7E,OACE,oBAACA,OAAc,WAAf;EAAyB,WAAW,GAAG,6BAA6B,SAAS;EAAG,GAAI;CAAQ,CAAA;AAEhG;AAEA,SAAS,aAAa,EAAE,WAAW,GAAG,SAAqC;CACzE,OAAO,oBAAC,KAAD;EAAK,WAAW,GAAG,WAAW,SAAS;EAAG,GAAI;CAAQ,CAAA;AAC/D"}
@@ -1,8 +1,8 @@
1
1
  import { cn } from "../lib/variants.js";
2
+ import { Skeleton } from "./skeleton.js";
2
3
  import { Button } from "./button.js";
3
4
  import { EntityAvatar } from "./entity-avatar.js";
4
5
  import { navItem } from "./nav.js";
5
- import { Skeleton } from "./skeleton.js";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
7
7
  //#region src/components/nav-button.tsx
8
8
  function NavButtonShell({ leading, variant = "flat", children, className, ...props }) {
@@ -41,7 +41,7 @@ const navAffixClasses = "[&>*:first-child]:ml-0 [&>*:last-child]:mr-0";
41
41
  const navItem = variants({
42
42
  base: "w-full justify-start gap-2.5 px-2.5",
43
43
  variants: { variant: {
44
- link: ["py-2 text-muted-foreground text-sm/tight hover:bg-foreground/3 hover:text-foreground [&>svg:first-child]:ml-0.5 [&>svg]:mr-0.5 [&>svg]:size-4 [&>svg]:shrink-0", "data-[status=active]:bg-foreground/5 data-[status=active]:text-foreground data-[status=active]:hover:bg-foreground/5"],
44
+ link: ["py-2 pointer-coarse:min-h-11 text-muted-foreground text-sm/tight hover:bg-foreground/3 hover:text-foreground [&>svg:first-child]:ml-0.5 [&>svg]:mr-0.5 [&>svg]:size-4 [&>svg]:shrink-0", "data-[status=active]:bg-foreground/5 data-[status=active]:text-foreground data-[status=active]:hover:bg-foreground/5"],
45
45
  bordered: [navAffixClasses, "border-border bg-card hover:bg-card hover:border-ring"],
46
46
  flat: navAffixClasses
47
47
  } },