@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
@@ -1 +1 @@
1
- {"version":3,"file":"nav.js","names":[],"sources":["../../src/components/nav.tsx"],"sourcesContent":["import type { ComponentProps } from \"react\"\nimport { eyebrowClasses } from \"~/lib/classes\"\nimport { cn, variants } from \"~/lib/variants\"\n\nexport function Nav({ className, ...props }: ComponentProps<\"nav\">) {\n return <nav className={cn(\"flex flex-col gap-0.5 px-3 py-2\", className)} {...props} />\n}\n\n/**\n * Muted group label above a run of nav links.\n */\nexport function NavLabel({ className, ...props }: ComponentProps<\"p\">) {\n return <p className={cn(\"px-2.5 pt-1 pb-1.5\", eyebrowClasses, className)} {...props} />\n}\n\n/**\n * The affixes of a nav row that leads with its own visual (an avatar, a skeleton) sit flush\n * against the row's inset, so the Button's optical pull-in is zeroed on both ends.\n */\nconst navAffixClasses = \"[&>*:first-child]:ml-0 [&>*:last-child]:mr-0\"\n\n/**\n * The one nav row, applied to a `ghost` `md` `Button`: every item starts from the same width,\n * inset and gap, and each variant is the shape on top of it.\n *\n * `link` — a nav link or a settings tab: muted at rest, full color on hover, and a flat darker\n * tint with dark text when active. The 16px icon sits centered in a 20px box, so labels start on\n * the same column as a row that leads with a 20px avatar (10px inset + 20px avatar + 10px gap).\n * The active state keys off `data-status=\"active\"`, which router links set on their own and plain\n * buttons set by hand, so the active color always wins over the resting one. Its tints are flat\n * foreground over the canvas, so hover and active share the canvas hue exactly.\n *\n * `bordered` — a bordered row on a card surface, for a row that leads with its own visual and\n * opens a menu.\n *\n * `flat` — the same row without the border or the surface.\n */\nexport const navItem = variants({\n base: \"w-full justify-start gap-2.5 px-2.5\",\n\n variants: {\n variant: {\n link: [\n \"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\",\n \"data-[status=active]:bg-foreground/5 data-[status=active]:text-foreground data-[status=active]:hover:bg-foreground/5\",\n ],\n bordered: [navAffixClasses, \"border-border bg-card hover:bg-card hover:border-ring\"],\n flat: navAffixClasses,\n },\n },\n\n defaultVariants: {\n variant: \"link\",\n },\n})\n"],"mappings":";;;;AAIA,SAAgB,IAAI,EAAE,WAAW,GAAG,SAAgC;CAClE,OAAO,oBAAC,OAAD;EAAK,WAAW,GAAG,mCAAmC,SAAS;EAAG,GAAI;CAAQ,CAAA;AACvF;;;;AAKA,SAAgB,SAAS,EAAE,WAAW,GAAG,SAA8B;CACrE,OAAO,oBAAC,KAAD;EAAG,WAAW,GAAG,sBAAsB,gBAAgB,SAAS;EAAG,GAAI;CAAQ,CAAA;AACxF;;;;;AAMA,MAAM,kBAAkB;;;;;;;;;;;;;;;;;AAkBxB,MAAa,UAAU,SAAS;CAC9B,MAAM;CAEN,UAAU,EACR,SAAS;EACP,MAAM,CACJ,kKACA,sHACF;EACA,UAAU,CAAC,iBAAiB,uDAAuD;EACnF,MAAM;CACR,EACF;CAEA,iBAAiB,EACf,SAAS,OACX;AACF,CAAC"}
1
+ {"version":3,"file":"nav.js","names":[],"sources":["../../src/components/nav.tsx"],"sourcesContent":["import type { ComponentProps } from \"react\"\nimport { eyebrowClasses } from \"~/lib/classes\"\nimport { cn, variants } from \"~/lib/variants\"\n\nexport function Nav({ className, ...props }: ComponentProps<\"nav\">) {\n return <nav className={cn(\"flex flex-col gap-0.5 px-3 py-2\", className)} {...props} />\n}\n\n/**\n * Muted group label above a run of nav links.\n */\nexport function NavLabel({ className, ...props }: ComponentProps<\"p\">) {\n return <p className={cn(\"px-2.5 pt-1 pb-1.5\", eyebrowClasses, className)} {...props} />\n}\n\n/**\n * The affixes of a nav row that leads with its own visual (an avatar, a skeleton) sit flush\n * against the row's inset, so the Button's optical pull-in is zeroed on both ends.\n */\nconst navAffixClasses = \"[&>*:first-child]:ml-0 [&>*:last-child]:mr-0\"\n\n/**\n * The one nav row, applied to a `ghost` `md` `Button`: every item starts from the same width,\n * inset and gap, and each variant is the shape on top of it.\n *\n * `link` — a nav link or a settings tab: muted at rest, full color on hover, and a flat darker\n * tint with dark text when active. The 16px icon sits centered in a 20px box, so labels start on\n * the same column as a row that leads with a 20px avatar (10px inset + 20px avatar + 10px gap).\n * The active state keys off `data-status=\"active\"`, which router links set on their own and plain\n * buttons set by hand, so the active color always wins over the resting one. Its tints are flat\n * foreground over the canvas, so hover and active share the canvas hue exactly.\n *\n * `bordered` — a bordered row on a card surface, for a row that leads with its own visual and\n * opens a menu.\n *\n * `flat` — the same row without the border or the surface.\n */\nexport const navItem = variants({\n base: \"w-full justify-start gap-2.5 px-2.5\",\n\n variants: {\n variant: {\n link: [\n \"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\",\n \"data-[status=active]:bg-foreground/5 data-[status=active]:text-foreground data-[status=active]:hover:bg-foreground/5\",\n ],\n bordered: [navAffixClasses, \"border-border bg-card hover:bg-card hover:border-ring\"],\n flat: navAffixClasses,\n },\n },\n\n defaultVariants: {\n variant: \"link\",\n },\n})\n"],"mappings":";;;;AAIA,SAAgB,IAAI,EAAE,WAAW,GAAG,SAAgC;CAClE,OAAO,oBAAC,OAAD;EAAK,WAAW,GAAG,mCAAmC,SAAS;EAAG,GAAI;CAAQ,CAAA;AACvF;;;;AAKA,SAAgB,SAAS,EAAE,WAAW,GAAG,SAA8B;CACrE,OAAO,oBAAC,KAAD;EAAG,WAAW,GAAG,sBAAsB,gBAAgB,SAAS;EAAG,GAAI;CAAQ,CAAA;AACxF;;;;;AAMA,MAAM,kBAAkB;;;;;;;;;;;;;;;;;AAkBxB,MAAa,UAAU,SAAS;CAC9B,MAAM;CAEN,UAAU,EACR,SAAS;EACP,MAAM,CACJ,0LACA,sHACF;EACA,UAAU,CAAC,iBAAiB,uDAAuD;EACnF,MAAM;CACR,EACF;CAEA,iBAAiB,EACf,SAAS,OACX;AACF,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import { VariantProps } from "../lib/variants.js";
2
- import { Stack } from "./stack.js";
3
2
  import { HeadingProps } from "./heading.js";
3
+ import { Stack } from "./stack.js";
4
4
  import { useRender } from "@base-ui/react/use-render";
5
5
  import { ComponentProps, ReactNode } from "react";
6
6
  //#region src/components/section-header.d.ts
@@ -10,8 +10,8 @@ declare const sectionHeaderVariants: import("tailwind-variants").TVReturnType<{
10
10
  center: "justify-center text-center";
11
11
  };
12
12
  size: {
13
- page: "gap-y-3 gap-x-6 lg:gap-x-12";
14
- card: "gap-y-2 gap-x-6";
13
+ page: "gap-y-3 gap-x-6 max-sm:flex-col max-sm:items-stretch lg:gap-x-12";
14
+ card: "gap-y-2 gap-x-6 max-sm:flex-col max-sm:items-stretch";
15
15
  panel: "gap-y-2 gap-x-6";
16
16
  hero: "gap-y-3 gap-x-6 lg:gap-x-12";
17
17
  };
@@ -25,8 +25,8 @@ declare const sectionHeaderVariants: import("tailwind-variants").TVReturnType<{
25
25
  center: "justify-center text-center";
26
26
  };
27
27
  size: {
28
- page: "gap-y-3 gap-x-6 lg:gap-x-12";
29
- card: "gap-y-2 gap-x-6";
28
+ page: "gap-y-3 gap-x-6 max-sm:flex-col max-sm:items-stretch lg:gap-x-12";
29
+ card: "gap-y-2 gap-x-6 max-sm:flex-col max-sm:items-stretch";
30
30
  panel: "gap-y-2 gap-x-6";
31
31
  hero: "gap-y-3 gap-x-6 lg:gap-x-12";
32
32
  };
@@ -40,8 +40,8 @@ declare const sectionHeaderVariants: import("tailwind-variants").TVReturnType<{
40
40
  center: "justify-center text-center";
41
41
  };
42
42
  size: {
43
- page: "gap-y-3 gap-x-6 lg:gap-x-12";
44
- card: "gap-y-2 gap-x-6";
43
+ page: "gap-y-3 gap-x-6 max-sm:flex-col max-sm:items-stretch lg:gap-x-12";
44
+ card: "gap-y-2 gap-x-6 max-sm:flex-col max-sm:items-stretch";
45
45
  panel: "gap-y-2 gap-x-6";
46
46
  hero: "gap-y-3 gap-x-6 lg:gap-x-12";
47
47
  };
@@ -1,7 +1,7 @@
1
1
  import { cn, variants } from "../lib/variants.js";
2
- import { descriptionClasses } from "../lib/classes.js";
3
- import { Stack } from "./stack.js";
4
2
  import { Heading } from "./heading.js";
3
+ import { Stack } from "./stack.js";
4
+ import { descriptionClasses } from "../lib/classes.js";
5
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
6
  import { useRender } from "@base-ui/react/use-render";
7
7
  import { createContext, use } from "react";
@@ -14,8 +14,8 @@ const sectionHeaderVariants = variants({
14
14
  center: "justify-center text-center"
15
15
  },
16
16
  size: {
17
- page: "gap-y-3 gap-x-6 lg:gap-x-12",
18
- card: "gap-y-2 gap-x-6",
17
+ page: "gap-y-3 gap-x-6 max-sm:flex-col max-sm:items-stretch lg:gap-x-12",
18
+ card: "gap-y-2 gap-x-6 max-sm:flex-col max-sm:items-stretch",
19
19
  panel: "gap-y-2 gap-x-6",
20
20
  hero: "gap-y-3 gap-x-6 lg:gap-x-12"
21
21
  },
@@ -135,9 +135,9 @@ function SectionHeaderDescription({ className, ...props }) {
135
135
  * truncates instead of squeezing the buttons.
136
136
  */
137
137
  function SectionHeaderActions({ className, ...props }) {
138
- const { wrap } = use(SectionHeaderContext);
138
+ const { size, wrap } = use(SectionHeaderContext);
139
139
  return /* @__PURE__ */ jsx(Stack, {
140
- className: cn("-my-0.5", !wrap && "shrink-0", className),
140
+ className: cn("-my-0.5", !wrap && "shrink-0", size === "page" && "max-sm:flex-row-reverse max-sm:justify-end", className),
141
141
  ...props
142
142
  });
143
143
  }
@@ -1 +1 @@
1
- {"version":3,"file":"section-header.js","names":[],"sources":["../../src/components/section-header.tsx"],"sourcesContent":["import { useRender } from \"@base-ui/react/use-render\"\nimport { type ComponentProps, createContext, type ReactNode, use } from \"react\"\nimport { Heading, type HeadingProps } from \"~/components/heading\"\nimport { Stack } from \"~/components/stack\"\nimport { descriptionClasses } from \"~/lib/classes\"\nimport { variants, cn, type VariantProps } from \"~/lib/variants\"\n\nconst sectionHeaderVariants = variants({\n base: \"flex items-center w-full min-w-0\",\n\n variants: {\n alignment: {\n left: \"justify-between text-start\",\n center: \"justify-center text-center\",\n },\n size: {\n page: \"gap-y-3 gap-x-6 lg:gap-x-12\",\n card: \"gap-y-2 gap-x-6\",\n panel: \"gap-y-2 gap-x-6\",\n hero: \"gap-y-3 gap-x-6 lg:gap-x-12\",\n },\n wrap: {\n true: \"flex-wrap\",\n false: \"flex-nowrap\",\n },\n },\n\n defaultVariants: {\n alignment: \"left\",\n wrap: true,\n },\n})\n\ntype SectionHeaderSize = NonNullable<VariantProps<typeof sectionHeaderVariants>[\"size\"]>\n\n/**\n * The title each header scale gets, alongside the spacing the variant above gives it: `page`\n * for a route heading, `card` for a standalone card, `panel` for a dashboard or list panel,\n * `hero` for the centered auth and callback screens. The `as` entry keeps the heading outline\n * intact — one `<h1>` per route, `<h2>` for everything nested inside it.\n */\nconst sectionHeaderTitles = {\n page: { size: \"h3\", as: \"h1\" },\n card: { size: \"h4\", as: \"h2\" },\n panel: { size: \"h5\", as: \"h2\" },\n hero: { size: \"h2\", as: \"h1\" },\n} as const satisfies Record<SectionHeaderSize, { size: HeadingProps[\"size\"]; as: string }>\n\nconst SectionHeaderContext = createContext<{ size: SectionHeaderSize; wrap: boolean }>({\n size: \"page\",\n wrap: true,\n})\n\ntype SectionHeaderProps = Omit<useRender.ComponentProps<\"div\">, \"title\"> &\n Omit<VariantProps<typeof sectionHeaderVariants>, \"wrap\"> & {\n /**\n * Keep the title block and the actions on one line instead of wrapping them.\n */\n wrap?: boolean\n /**\n * Title block. Given any of `title`/`description`/`leading`/`actions`, the header renders\n * its own `SectionHeaderBody` (empty if there is nothing but actions, so they still sit right);\n * leave them all out to compose the body by hand, e.g. an entity header with dot-joined\n * meta or the dashboard greeting.\n */\n title?: ReactNode\n description?: ReactNode\n actions?: ReactNode\n /**\n * Leading visual beside the title, e.g. an entity avatar.\n */\n leading?: ReactNode\n }\n\n/**\n * The one header for every surface in the app: pages, cards, list panels, dialogs and settings\n * sections. Pass `title`/`description`/`actions` for the standard block, or compose\n * `SectionHeaderBody` + `SectionHeaderActions` as children when the content is bespoke.\n */\nfunction SectionHeader({\n className,\n alignment = \"left\",\n size = \"page\",\n wrap = true,\n title,\n description,\n actions,\n leading,\n children,\n render,\n ...props\n}: SectionHeaderProps) {\n const element = useRender({\n render,\n defaultTagName: \"div\",\n props: {\n className: cn(sectionHeaderVariants({ alignment, size, wrap, className })),\n children: (\n <>\n {(title || description || leading || actions) && (\n <SectionHeaderBody\n leading={leading}\n className={cn(alignment === \"center\" && \"justify-center\")}\n >\n {title && <SectionHeaderTitle>{title}</SectionHeaderTitle>}\n {description && <SectionHeaderDescription>{description}</SectionHeaderDescription>}\n </SectionHeaderBody>\n )}\n\n {actions && <SectionHeaderActions>{actions}</SectionHeaderActions>}\n\n {children}\n </>\n ),\n ...props,\n },\n })\n\n return <SectionHeaderContext value={{ size, wrap }}>{element}</SectionHeaderContext>\n}\n\n/**\n * The page title. Its visual size and semantic level both follow the header's `size`, so a\n * page heading stays the route's single `<h1>` while cards and panels render `<h2>`s. Pass\n * `size`/`as` to override either one on its own.\n */\nfunction SectionHeaderTitle({ size, as, ...props }: HeadingProps) {\n const preset = sectionHeaderTitles[use(SectionHeaderContext).size]\n\n return <Heading size={size ?? preset.size} as={as ?? preset.as} {...props} />\n}\n\ntype SectionHeaderBodyProps = ComponentProps<\"div\"> & {\n /**\n * Leading visual, e.g. an entity avatar.\n */\n leading?: ReactNode\n}\n\n/**\n * The one title block for every header in the app (pages, cards, dialogs, settings sections):\n * an optional leading visual beside a column of `SectionHeaderTitle` + `SectionHeaderDescription`. Change the\n * spacing here and it changes everywhere.\n */\nfunction SectionHeaderBody({ leading, children, className, ...props }: SectionHeaderBodyProps) {\n return (\n <div className={cn(\"flex min-w-0 flex-1 items-center gap-3\", className)} {...props}>\n {leading}\n <div className=\"flex min-w-0 flex-col gap-1.5\">{children}</div>\n </div>\n )\n}\n\n/**\n * The muted line under a title. Wraps by default; pass `truncate` for one-line entity meta.\n */\nfunction SectionHeaderDescription({ className, ...props }: ComponentProps<\"p\">) {\n return <p className={cn(descriptionClasses, className)} {...props} />\n}\n\n/**\n * Right-aligned actions. Stops shrinking when the header is `wrap={false}`, so a long title\n * truncates instead of squeezing the buttons.\n */\nfunction SectionHeaderActions({ className, ...props }: ComponentProps<typeof Stack>) {\n const { wrap } = use(SectionHeaderContext)\n\n return <Stack className={cn(\"-my-0.5\", !wrap && \"shrink-0\", className)} {...props} />\n}\n\nexport {\n SectionHeader,\n SectionHeaderTitle,\n SectionHeaderBody,\n SectionHeaderDescription,\n SectionHeaderActions,\n}\n"],"mappings":";;;;;;;;AAOA,MAAM,wBAAwB,SAAS;CACrC,MAAM;CAEN,UAAU;EACR,WAAW;GACT,MAAM;GACN,QAAQ;EACV;EACA,MAAM;GACJ,MAAM;GACN,MAAM;GACN,OAAO;GACP,MAAM;EACR;EACA,MAAM;GACJ,MAAM;GACN,OAAO;EACT;CACF;CAEA,iBAAiB;EACf,WAAW;EACX,MAAM;CACR;AACF,CAAC;;;;;;;AAUD,MAAM,sBAAsB;CAC1B,MAAM;EAAE,MAAM;EAAM,IAAI;CAAK;CAC7B,MAAM;EAAE,MAAM;EAAM,IAAI;CAAK;CAC7B,OAAO;EAAE,MAAM;EAAM,IAAI;CAAK;CAC9B,MAAM;EAAE,MAAM;EAAM,IAAI;CAAK;AAC/B;AAEA,MAAM,uBAAuB,cAA0D;CACrF,MAAM;CACN,MAAM;AACR,CAAC;;;;;;AA4BD,SAAS,cAAc,EACrB,WACA,YAAY,QACZ,OAAO,QACP,OAAO,MACP,OACA,aACA,SACA,SACA,UACA,QACA,GAAG,SACkB;CACrB,MAAM,UAAU,UAAU;EACxB;EACA,gBAAgB;EAChB,OAAO;GACL,WAAW,GAAG,sBAAsB;IAAE;IAAW;IAAM;IAAM;GAAU,CAAC,CAAC;GACzE,UACE,qBAAA,UAAA,EAAA,UAAA;KACI,SAAS,eAAe,WAAW,YACnC,qBAAC,mBAAD;KACW;KACT,WAAW,GAAG,cAAc,YAAY,gBAAgB;KAF1D,UAAA,CAIG,SAAS,oBAAC,oBAAD,EAAA,UAAqB,MAA0B,CAAA,GACxD,eAAe,oBAAC,0BAAD,EAAA,UAA2B,YAAsC,CAAA,CAChE;;IAGpB,WAAW,oBAAC,sBAAD,EAAA,UAAuB,QAA8B,CAAA;IAEhE;GACD,EAAA,CAAA;GAEJ,GAAG;EACL;CACF,CAAC;CAED,OAAO,oBAAC,sBAAD;EAAsB,OAAO;GAAE;GAAM;EAAK;EAAI,UAAA;CAA8B,CAAA;AACrF;;;;;;AAOA,SAAS,mBAAmB,EAAE,MAAM,IAAI,GAAG,SAAuB;CAChE,MAAM,SAAS,oBAAoB,IAAI,oBAAoB,CAAC,CAAC;CAE7D,OAAO,oBAAC,SAAD;EAAS,MAAM,QAAQ,OAAO;EAAM,IAAI,MAAM,OAAO;EAAI,GAAI;CAAQ,CAAA;AAC9E;;;;;;AAcA,SAAS,kBAAkB,EAAE,SAAS,UAAU,WAAW,GAAG,SAAiC;CAC7F,OACE,qBAAC,OAAD;EAAK,WAAW,GAAG,0CAA0C,SAAS;EAAG,GAAI;EAA7E,UAAA,CACG,SACD,oBAAC,OAAD;GAAK,WAAU;GAAiC;EAAc,CAAA,CAC3D;;AAET;;;;AAKA,SAAS,yBAAyB,EAAE,WAAW,GAAG,SAA8B;CAC9E,OAAO,oBAAC,KAAD;EAAG,WAAW,GAAG,oBAAoB,SAAS;EAAG,GAAI;CAAQ,CAAA;AACtE;;;;;AAMA,SAAS,qBAAqB,EAAE,WAAW,GAAG,SAAuC;CACnF,MAAM,EAAE,SAAS,IAAI,oBAAoB;CAEzC,OAAO,oBAAC,OAAD;EAAO,WAAW,GAAG,WAAW,CAAC,QAAQ,YAAY,SAAS;EAAG,GAAI;CAAQ,CAAA;AACtF"}
1
+ {"version":3,"file":"section-header.js","names":[],"sources":["../../src/components/section-header.tsx"],"sourcesContent":["import { useRender } from \"@base-ui/react/use-render\"\nimport { type ComponentProps, createContext, type ReactNode, use } from \"react\"\nimport { Heading, type HeadingProps } from \"~/components/heading\"\nimport { Stack } from \"~/components/stack\"\nimport { descriptionClasses } from \"~/lib/classes\"\nimport { variants, cn, type VariantProps } from \"~/lib/variants\"\n\nconst sectionHeaderVariants = variants({\n base: \"flex items-center w-full min-w-0\",\n\n variants: {\n alignment: {\n left: \"justify-between text-start\",\n center: \"justify-center text-center\",\n },\n size: {\n page: \"gap-y-3 gap-x-6 max-sm:flex-col max-sm:items-stretch lg:gap-x-12\",\n card: \"gap-y-2 gap-x-6 max-sm:flex-col max-sm:items-stretch\",\n panel: \"gap-y-2 gap-x-6\",\n hero: \"gap-y-3 gap-x-6 lg:gap-x-12\",\n },\n wrap: {\n true: \"flex-wrap\",\n false: \"flex-nowrap\",\n },\n },\n\n defaultVariants: {\n alignment: \"left\",\n wrap: true,\n },\n})\n\ntype SectionHeaderSize = NonNullable<VariantProps<typeof sectionHeaderVariants>[\"size\"]>\n\n/**\n * The title each header scale gets, alongside the spacing the variant above gives it: `page`\n * for a route heading, `card` for a standalone card, `panel` for a dashboard or list panel,\n * `hero` for the centered auth and callback screens. The `as` entry keeps the heading outline\n * intact — one `<h1>` per route, `<h2>` for everything nested inside it.\n */\nconst sectionHeaderTitles = {\n page: { size: \"h3\", as: \"h1\" },\n card: { size: \"h4\", as: \"h2\" },\n panel: { size: \"h5\", as: \"h2\" },\n hero: { size: \"h2\", as: \"h1\" },\n} as const satisfies Record<SectionHeaderSize, { size: HeadingProps[\"size\"]; as: string }>\n\nconst SectionHeaderContext = createContext<{ size: SectionHeaderSize; wrap: boolean }>({\n size: \"page\",\n wrap: true,\n})\n\ntype SectionHeaderProps = Omit<useRender.ComponentProps<\"div\">, \"title\"> &\n Omit<VariantProps<typeof sectionHeaderVariants>, \"wrap\"> & {\n /**\n * Keep the title block and the actions on one line instead of wrapping them.\n */\n wrap?: boolean\n /**\n * Title block. Given any of `title`/`description`/`leading`/`actions`, the header renders\n * its own `SectionHeaderBody` (empty if there is nothing but actions, so they still sit right);\n * leave them all out to compose the body by hand, e.g. an entity header with dot-joined\n * meta or the dashboard greeting.\n */\n title?: ReactNode\n description?: ReactNode\n actions?: ReactNode\n /**\n * Leading visual beside the title, e.g. an entity avatar.\n */\n leading?: ReactNode\n }\n\n/**\n * The one header for every surface in the app: pages, cards, list panels, dialogs and settings\n * sections. Pass `title`/`description`/`actions` for the standard block, or compose\n * `SectionHeaderBody` + `SectionHeaderActions` as children when the content is bespoke.\n */\nfunction SectionHeader({\n className,\n alignment = \"left\",\n size = \"page\",\n wrap = true,\n title,\n description,\n actions,\n leading,\n children,\n render,\n ...props\n}: SectionHeaderProps) {\n const element = useRender({\n render,\n defaultTagName: \"div\",\n props: {\n className: cn(sectionHeaderVariants({ alignment, size, wrap, className })),\n children: (\n <>\n {(title || description || leading || actions) && (\n <SectionHeaderBody\n leading={leading}\n className={cn(alignment === \"center\" && \"justify-center\")}\n >\n {title && <SectionHeaderTitle>{title}</SectionHeaderTitle>}\n {description && <SectionHeaderDescription>{description}</SectionHeaderDescription>}\n </SectionHeaderBody>\n )}\n\n {actions && <SectionHeaderActions>{actions}</SectionHeaderActions>}\n\n {children}\n </>\n ),\n ...props,\n },\n })\n\n return <SectionHeaderContext value={{ size, wrap }}>{element}</SectionHeaderContext>\n}\n\n/**\n * The page title. Its visual size and semantic level both follow the header's `size`, so a\n * page heading stays the route's single `<h1>` while cards and panels render `<h2>`s. Pass\n * `size`/`as` to override either one on its own.\n */\nfunction SectionHeaderTitle({ size, as, ...props }: HeadingProps) {\n const preset = sectionHeaderTitles[use(SectionHeaderContext).size]\n\n return <Heading size={size ?? preset.size} as={as ?? preset.as} {...props} />\n}\n\ntype SectionHeaderBodyProps = ComponentProps<\"div\"> & {\n /**\n * Leading visual, e.g. an entity avatar.\n */\n leading?: ReactNode\n}\n\n/**\n * The one title block for every header in the app (pages, cards, dialogs, settings sections):\n * an optional leading visual beside a column of `SectionHeaderTitle` + `SectionHeaderDescription`. Change the\n * spacing here and it changes everywhere.\n */\nfunction SectionHeaderBody({ leading, children, className, ...props }: SectionHeaderBodyProps) {\n return (\n <div className={cn(\"flex min-w-0 flex-1 items-center gap-3\", className)} {...props}>\n {leading}\n <div className=\"flex min-w-0 flex-col gap-1.5\">{children}</div>\n </div>\n )\n}\n\n/**\n * The muted line under a title. Wraps by default; pass `truncate` for one-line entity meta.\n */\nfunction SectionHeaderDescription({ className, ...props }: ComponentProps<\"p\">) {\n return <p className={cn(descriptionClasses, className)} {...props} />\n}\n\n/**\n * Right-aligned actions. Stops shrinking when the header is `wrap={false}`, so a long title\n * truncates instead of squeezing the buttons.\n */\nfunction SectionHeaderActions({ className, ...props }: ComponentProps<typeof Stack>) {\n const { size, wrap } = use(SectionHeaderContext)\n\n return (\n <Stack\n className={cn(\n \"-my-0.5\",\n !wrap && \"shrink-0\",\n // Stacked under a page title, the trailing (primary) action leads the row.\n size === \"page\" && \"max-sm:flex-row-reverse max-sm:justify-end\",\n className,\n )}\n {...props}\n />\n )\n}\n\nexport {\n SectionHeader,\n SectionHeaderTitle,\n SectionHeaderBody,\n SectionHeaderDescription,\n SectionHeaderActions,\n}\n"],"mappings":";;;;;;;;AAOA,MAAM,wBAAwB,SAAS;CACrC,MAAM;CAEN,UAAU;EACR,WAAW;GACT,MAAM;GACN,QAAQ;EACV;EACA,MAAM;GACJ,MAAM;GACN,MAAM;GACN,OAAO;GACP,MAAM;EACR;EACA,MAAM;GACJ,MAAM;GACN,OAAO;EACT;CACF;CAEA,iBAAiB;EACf,WAAW;EACX,MAAM;CACR;AACF,CAAC;;;;;;;AAUD,MAAM,sBAAsB;CAC1B,MAAM;EAAE,MAAM;EAAM,IAAI;CAAK;CAC7B,MAAM;EAAE,MAAM;EAAM,IAAI;CAAK;CAC7B,OAAO;EAAE,MAAM;EAAM,IAAI;CAAK;CAC9B,MAAM;EAAE,MAAM;EAAM,IAAI;CAAK;AAC/B;AAEA,MAAM,uBAAuB,cAA0D;CACrF,MAAM;CACN,MAAM;AACR,CAAC;;;;;;AA4BD,SAAS,cAAc,EACrB,WACA,YAAY,QACZ,OAAO,QACP,OAAO,MACP,OACA,aACA,SACA,SACA,UACA,QACA,GAAG,SACkB;CACrB,MAAM,UAAU,UAAU;EACxB;EACA,gBAAgB;EAChB,OAAO;GACL,WAAW,GAAG,sBAAsB;IAAE;IAAW;IAAM;IAAM;GAAU,CAAC,CAAC;GACzE,UACE,qBAAA,UAAA,EAAA,UAAA;KACI,SAAS,eAAe,WAAW,YACnC,qBAAC,mBAAD;KACW;KACT,WAAW,GAAG,cAAc,YAAY,gBAAgB;KAF1D,UAAA,CAIG,SAAS,oBAAC,oBAAD,EAAA,UAAqB,MAA0B,CAAA,GACxD,eAAe,oBAAC,0BAAD,EAAA,UAA2B,YAAsC,CAAA,CAChE;;IAGpB,WAAW,oBAAC,sBAAD,EAAA,UAAuB,QAA8B,CAAA;IAEhE;GACD,EAAA,CAAA;GAEJ,GAAG;EACL;CACF,CAAC;CAED,OAAO,oBAAC,sBAAD;EAAsB,OAAO;GAAE;GAAM;EAAK;EAAI,UAAA;CAA8B,CAAA;AACrF;;;;;;AAOA,SAAS,mBAAmB,EAAE,MAAM,IAAI,GAAG,SAAuB;CAChE,MAAM,SAAS,oBAAoB,IAAI,oBAAoB,CAAC,CAAC;CAE7D,OAAO,oBAAC,SAAD;EAAS,MAAM,QAAQ,OAAO;EAAM,IAAI,MAAM,OAAO;EAAI,GAAI;CAAQ,CAAA;AAC9E;;;;;;AAcA,SAAS,kBAAkB,EAAE,SAAS,UAAU,WAAW,GAAG,SAAiC;CAC7F,OACE,qBAAC,OAAD;EAAK,WAAW,GAAG,0CAA0C,SAAS;EAAG,GAAI;EAA7E,UAAA,CACG,SACD,oBAAC,OAAD;GAAK,WAAU;GAAiC;EAAc,CAAA,CAC3D;;AAET;;;;AAKA,SAAS,yBAAyB,EAAE,WAAW,GAAG,SAA8B;CAC9E,OAAO,oBAAC,KAAD;EAAG,WAAW,GAAG,oBAAoB,SAAS;EAAG,GAAI;CAAQ,CAAA;AACtE;;;;;AAMA,SAAS,qBAAqB,EAAE,WAAW,GAAG,SAAuC;CACnF,MAAM,EAAE,MAAM,SAAS,IAAI,oBAAoB;CAE/C,OACE,oBAAC,OAAD;EACE,WAAW,GACT,WACA,CAAC,QAAQ,YAET,SAAS,UAAU,8CACnB,SACF;EACA,GAAI;CACL,CAAA;AAEL"}
@@ -1 +1 @@
1
- {"version":3,"file":"select.d.ts","names":[],"sources":["../../src/components/select.tsx"],"mappings":";;;;cAQM,eAAM,SAAA;cACN,6BAAW,0BAAA,8BAAA,2CAAA,cAAA;cACX,6BAAW,0BAAA,8BAAA,2CAAA,cAAA;iBAER,gBACP,UACA,WACA,OACA,OACA,SACG,SACF,SAAgB,QAAQ,QAAQ,oBAAoB,iCAAc,IAAA;iBAmB5D,uBAAuB,cAAc,SAAS,SAAgB,cAAc,wBAAK,IAAA;iBAWjF,yBAAyB,cAAc,SAAS,SAAgB,gBAAgB,wBAAK,IAAA;iBAqBrF,gBACP,WACA,UACA,OACA,MACA,YACA,yBACG,SACF,SAAgB,MAAM,QACvB,KACE,SAAgB,WAAW,mFAE5B,IAAA;iBA2BM,cAAc,cAAc,SAAS,SAAgB,WAAW,wBAAK,IAAA;iBASrE,aAAa,WAAW,UAAU,UAAU,SAAS,SAAgB,KAAK,wBAAK,IAAA;iBAoB/E,kBAAkB,cAAc,SAAS,SAAgB,UAAU,wBAAK,IAAA"}
1
+ {"version":3,"file":"select.d.ts","names":[],"sources":["../../src/components/select.tsx"],"mappings":";;;;cAQM,eAAM,SAAA;cACN,6BAAW,0BAAA,8BAAA,2CAAA,cAAA;cACX,6BAAW,0BAAA,8BAAA,2CAAA,cAAA;iBAER,gBACP,UACA,WACA,OACA,OACA,SACG,SACF,SAAgB,QAAQ,QAAQ,oBAAoB,iCAAc,IAAA;iBAmB5D,uBAAuB,cAAc,SAAS,SAAgB,cAAc,wBAAK,IAAA;iBAWjF,yBAAyB,cAAc,SAAS,SAAgB,gBAAgB,wBAAK,IAAA;iBAqBrF,gBACP,WACA,UACA,OACA,MACA,YACA,yBACG,SACF,SAAgB,MAAM,QACvB,KACE,SAAgB,WAAW,mFAE5B,IAAA;iBA8BM,cAAc,cAAc,SAAS,SAAgB,WAAW,wBAAK,IAAA;iBASrE,aAAa,WAAW,UAAU,UAAU,SAAS,SAAgB,KAAK,wBAAK,IAAA;iBAoB/E,kBAAkB,cAAc,SAAS,SAAgB,UAAU,wBAAK,IAAA"}
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { cn, variants } from "../lib/variants.js";
3
- import { controlTextClasses, popoverAnimationClasses } from "../lib/classes.js";
4
3
  import { CheckIcon, ChevronDownIcon, ChevronUpIcon, ChevronsUpDownIcon } from "../lib/icons.js";
4
+ import { controlTextClasses, popoverAnimationClasses } from "../lib/classes.js";
5
5
  import { inputVariants } from "./input.js";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
7
7
  import { Select as Select$1 } from "@base-ui/react/select";
@@ -57,7 +57,7 @@ function SelectContent({ className, children, align, side, sideOffset = 4, align
57
57
  children: [
58
58
  /* @__PURE__ */ jsx(SelectScrollUpButton, { className: selectScrollButtonVariants({ position: "top" }) }),
59
59
  /* @__PURE__ */ jsx(Select$1.List, {
60
- className: "p-1",
60
+ className: "max-h-(--available-height) overflow-y-auto p-1",
61
61
  children
62
62
  }),
63
63
  /* @__PURE__ */ jsx(SelectScrollDownButton, { className: selectScrollButtonVariants({ position: "bottom" }) })
@@ -74,7 +74,7 @@ function SelectLabel({ className, ...props }) {
74
74
  function SelectItem({ className, children, label, ...props }) {
75
75
  return /* @__PURE__ */ jsxs(Select$1.Item, {
76
76
  label: label ?? (typeof children === "string" ? children : void 0),
77
- className: cn(`relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 ${controlTextClasses} text-secondary-foreground outline-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50`, className),
77
+ className: cn(`relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 pointer-coarse:min-h-10 ${controlTextClasses} text-secondary-foreground outline-none focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50`, className),
78
78
  ...props,
79
79
  children: [/* @__PURE__ */ jsx("span", {
80
80
  className: "absolute left-2 flex size-3.5 items-center justify-center",
@@ -1 +1 @@
1
- {"version":3,"file":"select.js","names":["SelectPrimitive"],"sources":["../../src/components/select.tsx"],"sourcesContent":["\"use client\"\n\nimport { Select as SelectPrimitive } from \"@base-ui/react/select\"\nimport { inputVariants } from \"~/components/input\"\nimport { controlTextClasses, popoverAnimationClasses } from \"~/lib/classes\"\nimport { CheckIcon, ChevronDownIcon, ChevronUpIcon, ChevronsUpDownIcon } from \"~/lib/icons\"\nimport { variants, cn, type VariantProps } from \"~/lib/variants\"\n\nconst Select = SelectPrimitive.Root\nconst SelectGroup = SelectPrimitive.Group\nconst SelectValue = SelectPrimitive.Value\n\nfunction SelectTrigger({\n children,\n className,\n hover = true,\n focus = true,\n size,\n ...props\n}: SelectPrimitive.Trigger.Props & VariantProps<typeof inputVariants>) {\n return (\n <SelectPrimitive.Trigger\n className={cn(\n inputVariants({ hover, focus, size }),\n \"flex items-center justify-between\",\n className,\n )}\n {...props}\n >\n <span className=\"truncate\">{children}</span>\n\n <SelectPrimitive.Icon\n render={<ChevronsUpDownIcon className=\"ml-1 size-4 shrink-0 opacity-50\" />}\n />\n </SelectPrimitive.Trigger>\n )\n}\n\nfunction SelectScrollUpButton({ className, ...props }: SelectPrimitive.ScrollUpArrow.Props) {\n return (\n <SelectPrimitive.ScrollUpArrow\n className={cn(\"flex cursor-pointer items-center justify-center py-1\", className)}\n {...props}\n >\n <ChevronUpIcon />\n </SelectPrimitive.ScrollUpArrow>\n )\n}\n\nfunction SelectScrollDownButton({ className, ...props }: SelectPrimitive.ScrollDownArrow.Props) {\n return (\n <SelectPrimitive.ScrollDownArrow\n className={cn(\"flex cursor-pointer items-center justify-center py-1\", className)}\n {...props}\n >\n <ChevronDownIcon />\n </SelectPrimitive.ScrollDownArrow>\n )\n}\n\nconst selectScrollButtonVariants = variants({\n base: \"absolute inset-x-0 z-10 bg-background animate-in fade-in-0 duration-300\",\n variants: {\n position: {\n top: \"top-0 mask-b-from-0\",\n bottom: \"bottom-0 mask-t-from-0\",\n },\n },\n})\n\nfunction SelectContent({\n className,\n children,\n align,\n side,\n sideOffset = 4,\n alignItemWithTrigger = false,\n ...props\n}: SelectPrimitive.Popup.Props &\n Pick<\n SelectPrimitive.Positioner.Props,\n \"align\" | \"side\" | \"sideOffset\" | \"alignItemWithTrigger\"\n >) {\n return (\n <SelectPrimitive.Portal>\n <SelectPrimitive.Positioner\n sideOffset={sideOffset}\n align={align}\n side={side}\n alignItemWithTrigger={alignItemWithTrigger}\n className=\"isolate z-50\"\n >\n <SelectPrimitive.Popup\n className={cn(\n \"relative z-50 isolate max-h-(--available-height) min-w-(--anchor-width) overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md backdrop-blur-xs\",\n !alignItemWithTrigger && popoverAnimationClasses,\n className,\n )}\n {...props}\n >\n <SelectScrollUpButton className={selectScrollButtonVariants({ position: \"top\" })} />\n <SelectPrimitive.List className=\"p-1\">{children}</SelectPrimitive.List>\n <SelectScrollDownButton className={selectScrollButtonVariants({ position: \"bottom\" })} />\n </SelectPrimitive.Popup>\n </SelectPrimitive.Positioner>\n </SelectPrimitive.Portal>\n )\n}\n\nfunction SelectLabel({ className, ...props }: SelectPrimitive.GroupLabel.Props) {\n return (\n <SelectPrimitive.GroupLabel\n className={cn(\"px-2 py-1.5 text-sm font-medium\", className)}\n {...props}\n />\n )\n}\n\nfunction SelectItem({ className, children, label, ...props }: SelectPrimitive.Item.Props) {\n return (\n <SelectPrimitive.Item\n label={label ?? (typeof children === \"string\" ? children : undefined)}\n className={cn(\n `relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 ${controlTextClasses} text-secondary-foreground outline-none 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 <SelectPrimitive.ItemIndicator>\n <CheckIcon className=\"size-4\" />\n </SelectPrimitive.ItemIndicator>\n </span>\n <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n </SelectPrimitive.Item>\n )\n}\n\nfunction SelectSeparator({ className, ...props }: SelectPrimitive.Separator.Props) {\n return (\n <SelectPrimitive.Separator className={cn(\"-mx-1 my-1 h-px bg-border\", className)} {...props} />\n )\n}\n\nexport {\n Select,\n SelectGroup,\n SelectValue,\n SelectTrigger,\n SelectContent,\n SelectLabel,\n SelectItem,\n SelectSeparator,\n SelectScrollUpButton,\n SelectScrollDownButton,\n}\n"],"mappings":";;;;;;;;AAQA,MAAM,SAASA,SAAgB;AAC/B,MAAM,cAAcA,SAAgB;AACpC,MAAM,cAAcA,SAAgB;AAEpC,SAAS,cAAc,EACrB,UACA,WACA,QAAQ,MACR,QAAQ,MACR,MACA,GAAG,SACkE;CACrE,OACE,qBAACA,SAAgB,SAAjB;EACE,WAAW,GACT,cAAc;GAAE;GAAO;GAAO;EAAK,CAAC,GACpC,qCACA,SACF;EACA,GAAI;EANN,UAAA,CAQE,oBAAC,QAAD;GAAM,WAAU;GAAY;EAAe,CAAA,GAE3C,oBAACA,SAAgB,MAAjB,EACE,QAAQ,oBAAC,oBAAD,EAAoB,WAAU,kCAAmC,CAAA,EAC1E,CAAA,CACsB;;AAE7B;AAEA,SAAS,qBAAqB,EAAE,WAAW,GAAG,SAA8C;CAC1F,OACE,oBAACA,SAAgB,eAAjB;EACE,WAAW,GAAG,wDAAwD,SAAS;EAC/E,GAAI;EAEJ,UAAA,oBAAC,eAAD,CAAgB,CAAA;CACa,CAAA;AAEnC;AAEA,SAAS,uBAAuB,EAAE,WAAW,GAAG,SAAgD;CAC9F,OACE,oBAACA,SAAgB,iBAAjB;EACE,WAAW,GAAG,wDAAwD,SAAS;EAC/E,GAAI;EAEJ,UAAA,oBAAC,iBAAD,CAAkB,CAAA;CACa,CAAA;AAErC;AAEA,MAAM,6BAA6B,SAAS;CAC1C,MAAM;CACN,UAAU,EACR,UAAU;EACR,KAAK;EACL,QAAQ;CACV,EACF;AACF,CAAC;AAED,SAAS,cAAc,EACrB,WACA,UACA,OACA,MACA,aAAa,GACb,uBAAuB,OACvB,GAAG,SAKA;CACH,OACE,oBAACA,SAAgB,QAAjB,EAAA,UACE,oBAACA,SAAgB,YAAjB;EACc;EACL;EACD;EACgB;EACtB,WAAU;EAEV,UAAA,qBAACA,SAAgB,OAAjB;GACE,WAAW,GACT,2KACA,CAAC,wBAAwB,yBACzB,SACF;GACA,GAAI;GANN,UAAA;IAQE,oBAAC,sBAAD,EAAsB,WAAW,2BAA2B,EAAE,UAAU,MAAM,CAAC,EAAI,CAAA;IACnF,oBAACA,SAAgB,MAAjB;KAAsB,WAAU;KAAO;IAA+B,CAAA;IACtE,oBAAC,wBAAD,EAAwB,WAAW,2BAA2B,EAAE,UAAU,SAAS,CAAC,EAAI,CAAA;GACnE;;CACG,CAAA,EACN,CAAA;AAE5B;AAEA,SAAS,YAAY,EAAE,WAAW,GAAG,SAA2C;CAC9E,OACE,oBAACA,SAAgB,YAAjB;EACE,WAAW,GAAG,mCAAmC,SAAS;EAC1D,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,WAAW,EAAE,WAAW,UAAU,OAAO,GAAG,SAAqC;CACxF,OACE,qBAACA,SAAgB,MAAjB;EACE,OAAO,UAAU,OAAO,aAAa,WAAW,WAAW,KAAA;EAC3D,WAAW,GACT,4FAA4F,mBAAmB,kJAC/G,SACF;EACA,GAAI;EANN,UAAA,CAQE,oBAAC,QAAD;GAAM,WAAU;GACd,UAAA,oBAACA,SAAgB,eAAjB,EAAA,UACE,oBAAC,WAAD,EAAW,WAAU,SAAU,CAAA,EACF,CAAA;EAC3B,CAAA,GACN,oBAACA,SAAgB,UAAjB,EAA2B,SAAmC,CAAA,CAC1C;;AAE1B;AAEA,SAAS,gBAAgB,EAAE,WAAW,GAAG,SAA0C;CACjF,OACE,oBAACA,SAAgB,WAAjB;EAA2B,WAAW,GAAG,6BAA6B,SAAS;EAAG,GAAI;CAAQ,CAAA;AAElG"}
1
+ {"version":3,"file":"select.js","names":["SelectPrimitive"],"sources":["../../src/components/select.tsx"],"sourcesContent":["\"use client\"\n\nimport { Select as SelectPrimitive } from \"@base-ui/react/select\"\nimport { inputVariants } from \"~/components/input\"\nimport { controlTextClasses, popoverAnimationClasses } from \"~/lib/classes\"\nimport { CheckIcon, ChevronDownIcon, ChevronUpIcon, ChevronsUpDownIcon } from \"~/lib/icons\"\nimport { variants, cn, type VariantProps } from \"~/lib/variants\"\n\nconst Select = SelectPrimitive.Root\nconst SelectGroup = SelectPrimitive.Group\nconst SelectValue = SelectPrimitive.Value\n\nfunction SelectTrigger({\n children,\n className,\n hover = true,\n focus = true,\n size,\n ...props\n}: SelectPrimitive.Trigger.Props & VariantProps<typeof inputVariants>) {\n return (\n <SelectPrimitive.Trigger\n className={cn(\n inputVariants({ hover, focus, size }),\n \"flex items-center justify-between\",\n className,\n )}\n {...props}\n >\n <span className=\"truncate\">{children}</span>\n\n <SelectPrimitive.Icon\n render={<ChevronsUpDownIcon className=\"ml-1 size-4 shrink-0 opacity-50\" />}\n />\n </SelectPrimitive.Trigger>\n )\n}\n\nfunction SelectScrollUpButton({ className, ...props }: SelectPrimitive.ScrollUpArrow.Props) {\n return (\n <SelectPrimitive.ScrollUpArrow\n className={cn(\"flex cursor-pointer items-center justify-center py-1\", className)}\n {...props}\n >\n <ChevronUpIcon />\n </SelectPrimitive.ScrollUpArrow>\n )\n}\n\nfunction SelectScrollDownButton({ className, ...props }: SelectPrimitive.ScrollDownArrow.Props) {\n return (\n <SelectPrimitive.ScrollDownArrow\n className={cn(\"flex cursor-pointer items-center justify-center py-1\", className)}\n {...props}\n >\n <ChevronDownIcon />\n </SelectPrimitive.ScrollDownArrow>\n )\n}\n\nconst selectScrollButtonVariants = variants({\n base: \"absolute inset-x-0 z-10 bg-background animate-in fade-in-0 duration-300\",\n variants: {\n position: {\n top: \"top-0 mask-b-from-0\",\n bottom: \"bottom-0 mask-t-from-0\",\n },\n },\n})\n\nfunction SelectContent({\n className,\n children,\n align,\n side,\n sideOffset = 4,\n alignItemWithTrigger = false,\n ...props\n}: SelectPrimitive.Popup.Props &\n Pick<\n SelectPrimitive.Positioner.Props,\n \"align\" | \"side\" | \"sideOffset\" | \"alignItemWithTrigger\"\n >) {\n return (\n <SelectPrimitive.Portal>\n <SelectPrimitive.Positioner\n sideOffset={sideOffset}\n align={align}\n side={side}\n alignItemWithTrigger={alignItemWithTrigger}\n className=\"isolate z-50\"\n >\n <SelectPrimitive.Popup\n className={cn(\n \"relative z-50 isolate max-h-(--available-height) min-w-(--anchor-width) overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md backdrop-blur-xs\",\n !alignItemWithTrigger && popoverAnimationClasses,\n className,\n )}\n {...props}\n >\n <SelectScrollUpButton className={selectScrollButtonVariants({ position: \"top\" })} />\n {/* The list is the scroller (not the popup), so the scroll arrows can overlay it. */}\n <SelectPrimitive.List className=\"max-h-(--available-height) overflow-y-auto p-1\">\n {children}\n </SelectPrimitive.List>\n <SelectScrollDownButton className={selectScrollButtonVariants({ position: \"bottom\" })} />\n </SelectPrimitive.Popup>\n </SelectPrimitive.Positioner>\n </SelectPrimitive.Portal>\n )\n}\n\nfunction SelectLabel({ className, ...props }: SelectPrimitive.GroupLabel.Props) {\n return (\n <SelectPrimitive.GroupLabel\n className={cn(\"px-2 py-1.5 text-sm font-medium\", className)}\n {...props}\n />\n )\n}\n\nfunction SelectItem({ className, children, label, ...props }: SelectPrimitive.Item.Props) {\n return (\n <SelectPrimitive.Item\n label={label ?? (typeof children === \"string\" ? children : undefined)}\n className={cn(\n `relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 pointer-coarse:min-h-10 ${controlTextClasses} text-secondary-foreground outline-none 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 <SelectPrimitive.ItemIndicator>\n <CheckIcon className=\"size-4\" />\n </SelectPrimitive.ItemIndicator>\n </span>\n <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>\n </SelectPrimitive.Item>\n )\n}\n\nfunction SelectSeparator({ className, ...props }: SelectPrimitive.Separator.Props) {\n return (\n <SelectPrimitive.Separator className={cn(\"-mx-1 my-1 h-px bg-border\", className)} {...props} />\n )\n}\n\nexport {\n Select,\n SelectGroup,\n SelectValue,\n SelectTrigger,\n SelectContent,\n SelectLabel,\n SelectItem,\n SelectSeparator,\n SelectScrollUpButton,\n SelectScrollDownButton,\n}\n"],"mappings":";;;;;;;;AAQA,MAAM,SAASA,SAAgB;AAC/B,MAAM,cAAcA,SAAgB;AACpC,MAAM,cAAcA,SAAgB;AAEpC,SAAS,cAAc,EACrB,UACA,WACA,QAAQ,MACR,QAAQ,MACR,MACA,GAAG,SACkE;CACrE,OACE,qBAACA,SAAgB,SAAjB;EACE,WAAW,GACT,cAAc;GAAE;GAAO;GAAO;EAAK,CAAC,GACpC,qCACA,SACF;EACA,GAAI;EANN,UAAA,CAQE,oBAAC,QAAD;GAAM,WAAU;GAAY;EAAe,CAAA,GAE3C,oBAACA,SAAgB,MAAjB,EACE,QAAQ,oBAAC,oBAAD,EAAoB,WAAU,kCAAmC,CAAA,EAC1E,CAAA,CACsB;;AAE7B;AAEA,SAAS,qBAAqB,EAAE,WAAW,GAAG,SAA8C;CAC1F,OACE,oBAACA,SAAgB,eAAjB;EACE,WAAW,GAAG,wDAAwD,SAAS;EAC/E,GAAI;EAEJ,UAAA,oBAAC,eAAD,CAAgB,CAAA;CACa,CAAA;AAEnC;AAEA,SAAS,uBAAuB,EAAE,WAAW,GAAG,SAAgD;CAC9F,OACE,oBAACA,SAAgB,iBAAjB;EACE,WAAW,GAAG,wDAAwD,SAAS;EAC/E,GAAI;EAEJ,UAAA,oBAAC,iBAAD,CAAkB,CAAA;CACa,CAAA;AAErC;AAEA,MAAM,6BAA6B,SAAS;CAC1C,MAAM;CACN,UAAU,EACR,UAAU;EACR,KAAK;EACL,QAAQ;CACV,EACF;AACF,CAAC;AAED,SAAS,cAAc,EACrB,WACA,UACA,OACA,MACA,aAAa,GACb,uBAAuB,OACvB,GAAG,SAKA;CACH,OACE,oBAACA,SAAgB,QAAjB,EAAA,UACE,oBAACA,SAAgB,YAAjB;EACc;EACL;EACD;EACgB;EACtB,WAAU;EAEV,UAAA,qBAACA,SAAgB,OAAjB;GACE,WAAW,GACT,2KACA,CAAC,wBAAwB,yBACzB,SACF;GACA,GAAI;GANN,UAAA;IAQE,oBAAC,sBAAD,EAAsB,WAAW,2BAA2B,EAAE,UAAU,MAAM,CAAC,EAAI,CAAA;IAEnF,oBAACA,SAAgB,MAAjB;KAAsB,WAAU;KAC7B;IACmB,CAAA;IACtB,oBAAC,wBAAD,EAAwB,WAAW,2BAA2B,EAAE,UAAU,SAAS,CAAC,EAAI,CAAA;GACnE;;CACG,CAAA,EACN,CAAA;AAE5B;AAEA,SAAS,YAAY,EAAE,WAAW,GAAG,SAA2C;CAC9E,OACE,oBAACA,SAAgB,YAAjB;EACE,WAAW,GAAG,mCAAmC,SAAS;EAC1D,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,WAAW,EAAE,WAAW,UAAU,OAAO,GAAG,SAAqC;CACxF,OACE,qBAACA,SAAgB,MAAjB;EACE,OAAO,UAAU,OAAO,aAAa,WAAW,WAAW,KAAA;EAC3D,WAAW,GACT,oHAAoH,mBAAmB,kJACvI,SACF;EACA,GAAI;EANN,UAAA,CAQE,oBAAC,QAAD;GAAM,WAAU;GACd,UAAA,oBAACA,SAAgB,eAAjB,EAAA,UACE,oBAAC,WAAD,EAAW,WAAU,SAAU,CAAA,EACF,CAAA;EAC3B,CAAA,GACN,oBAACA,SAAgB,UAAjB,EAA2B,SAAmC,CAAA,CAC1C;;AAE1B;AAEA,SAAS,gBAAgB,EAAE,WAAW,GAAG,SAA0C;CACjF,OACE,oBAACA,SAAgB,WAAjB;EAA2B,WAAW,GAAG,6BAA6B,SAAS;EAAG,GAAI;CAAQ,CAAA;AAElG"}
@@ -1,7 +1,7 @@
1
1
  import { VariantProps } from "../lib/variants.js";
2
2
  import { Format } from "@number-flow/react";
3
3
  import { useRender } from "@base-ui/react/use-render";
4
- import { ComponentProps } from "react";
4
+ import { ComponentProps, ReactNode } from "react";
5
5
  //#region src/components/stat-strip.d.ts
6
6
  declare const stripVariants: import("tailwind-variants").TVReturnType<{
7
7
  variant: {
@@ -23,7 +23,11 @@ type StatStripProps = ComponentProps<"div"> & VariantProps<typeof stripVariants>
23
23
  declare function StatStrip({ className, variant, ...props }: StatStripProps): import("react").JSX.Element;
24
24
  type StatStripItemProps = Omit<useRender.ComponentProps<"div">, "children"> & {
25
25
  label: string;
26
- value: string | number;
26
+ /**
27
+ * A number (or a string with a `format`) spins through `AnimatedNumber`; any other node,
28
+ * e.g. two `AnimatedNumber`s composing "1m 38s", renders as is.
29
+ */
30
+ value: ReactNode;
27
31
  format?: Format;
28
32
  hint?: string;
29
33
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"stat-strip.d.ts","names":[],"sources":["../../src/components/stat-strip.tsx"],"mappings":";;;;;cAOM,2CAAa;;;;;;;;;;;;;;;;KAmBd,iBAAiB,wBAAwB,oBAAoB;iBAEzD,YAAY,WAAW,YAAY,SAAS,iCAAc,IAAA;KAU9D,qBAAqB,KAAK,UAAU;EACvC;EACA;EACA,SAAS;EACT;;;;;EAKA;;iBAGO,gBACP,OACA,OACA,QACA,MACA,UACA,QACA,cACG,SACF,qCAAkB,+CAAA"}
1
+ {"version":3,"file":"stat-strip.d.ts","names":[],"sources":["../../src/components/stat-strip.tsx"],"mappings":";;;;;cAOM,2CAAa;;;;;;;;;;;;;;;;KAmBd,iBAAiB,wBAAwB,oBAAoB;iBAEzD,YAAY,WAAW,YAAY,SAAS,iCAAc,IAAA;KAU9D,qBAAqB,KAAK,UAAU;EACvC;;;;;EAKA,OAAO;EACP,SAAS;EACT;;;;;EAKA;;iBAGO,gBACP,OACA,OACA,QACA,MACA,UACA,QACA,cACG,SACF,qCAAkB,+CAAA"}
@@ -3,6 +3,7 @@ import { cn, variants } from "../lib/variants.js";
3
3
  import { cardSurfaceClasses } from "./card.js";
4
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
5
  import { useRender } from "@base-ui/react/use-render";
6
+ import { isValidElement } from "react";
6
7
  //#region src/components/stat-strip.tsx
7
8
  const stripVariants = variants({
8
9
  base: "group/strip grid grid-cols-2 gap-y-1 max-sm:[&>*:last-child:nth-child(odd)]:col-span-2 sm:flex sm:flex-wrap sm:gap-y-0",
@@ -31,19 +32,19 @@ function StatStripItem({ label, value, format, hint, selected, render, className
31
32
  className: cn("relative min-w-0 flex-1 rounded-lg border border-transparent px-4 py-3 text-start", "before:absolute before:inset-y-2 before:-left-px before:w-px before:bg-border first:before:hidden max-sm:before:hidden", "group-data-[variant=plain]/strip:data-selected:before:hidden group-data-[variant=plain]/strip:[[data-selected]+&]:before:hidden", "group-data-[variant=track]/strip:before:-left-[8px]", "[&:is(button)]:hover:bg-foreground/3", "group-data-[variant=plain]/strip:rounded-none group-data-[variant=plain]/strip:px-5 group-data-[variant=plain]/strip:py-4 group-data-[variant=plain]/strip:before:inset-y-0 group-data-[variant=plain]/strip:before:left-0", "group-data-[variant=plain]/strip:data-selected:bg-muted", "group-data-[variant=track]/strip:data-selected:border-border group-data-[variant=track]/strip:data-selected:bg-card group-data-[variant=track]/strip:data-selected:shadow-xs", "group-data-[variant=track]/strip:not-data-selected:[&_[data-value]]:text-muted-foreground", "group-data-[variant=track]/strip:[&:is(button)]:hover:not-data-selected:bg-card/60", className),
32
33
  children: /* @__PURE__ */ jsxs(Fragment, { children: [
33
34
  /* @__PURE__ */ jsx("p", {
34
- className: "truncate text-muted-foreground text-sm",
35
+ className: "text-muted-foreground text-sm sm:truncate",
35
36
  children: label
36
37
  }),
37
38
  /* @__PURE__ */ jsx("p", {
38
39
  "data-value": true,
39
- className: "mt-0.5 truncate font-display font-semibold text-2xl tabular-nums",
40
- children: /* @__PURE__ */ jsx(AnimatedNumber, {
40
+ className: "mt-0.5 truncate font-display font-semibold text-xl leading-8 tabular-nums sm:text-2xl sm:leading-9",
41
+ children: isValidElement(value) || typeof value !== "number" && typeof value !== "string" ? value : /* @__PURE__ */ jsx(AnimatedNumber, {
41
42
  value,
42
43
  format
43
44
  })
44
45
  }),
45
46
  hint && /* @__PURE__ */ jsx("p", {
46
- className: "truncate text-muted-foreground text-xs",
47
+ className: "text-muted-foreground text-xs sm:truncate",
47
48
  children: hint
48
49
  })
49
50
  ] }),
@@ -1 +1 @@
1
- {"version":3,"file":"stat-strip.js","names":[],"sources":["../../src/components/stat-strip.tsx"],"sourcesContent":["import { useRender } from \"@base-ui/react/use-render\"\nimport type { Format } from \"@number-flow/react\"\nimport type { ComponentProps } from \"react\"\nimport { AnimatedNumber } from \"~/components/animated-number\"\nimport { cardSurfaceClasses } from \"~/components/card\"\nimport { cn, variants, type VariantProps } from \"~/lib/variants\"\n\nconst stripVariants = variants({\n base: \"group/strip grid grid-cols-2 gap-y-1 max-sm:[&>*:last-child:nth-child(odd)]:col-span-2 sm:flex sm:flex-wrap sm:gap-y-0\",\n\n variants: {\n variant: {\n // A white card of figures, divided by full-height hairlines.\n plain: cardSurfaceClasses,\n // A canvas-toned track where the selected figure sits on a raised card, like a\n // segmented control; for strips that drive the panel beside them.\n // 13px gap: a 1px divider then sits 6px from the card on either side, matching the 6px inset.\n track: \"rounded-xl bg-background p-1.5 sm:gap-x-[13px]\",\n },\n },\n\n defaultVariants: {\n variant: \"plain\",\n },\n})\n\ntype StatStripProps = ComponentProps<\"div\"> & VariantProps<typeof stripVariants>\n\nfunction StatStrip({ className, variant, ...props }: StatStripProps) {\n return (\n <div\n data-variant={variant ?? \"plain\"}\n className={cn(stripVariants({ variant, className }))}\n {...props}\n />\n )\n}\n\ntype StatStripItemProps = Omit<useRender.ComponentProps<\"div\">, \"children\"> & {\n label: string\n value: string | number\n format?: Format\n hint?: string\n /**\n * Marks the item as the one driving the panel beside it. Pass `render={<button ... />}`\n * to make it selectable.\n */\n selected?: boolean\n}\n\nfunction StatStripItem({\n label,\n value,\n format,\n hint,\n selected,\n render,\n className,\n ...props\n}: StatStripItemProps) {\n return useRender({\n render,\n defaultTagName: \"div\",\n props: {\n \"data-selected\": selected || undefined,\n className: cn(\n // The border is always laid out and only colored when selected, so toggling\n // never shifts the row.\n \"relative min-w-0 flex-1 rounded-lg border border-transparent px-4 py-3 text-start\",\n // Hairline divider between neighbours. The plain strip hides it around the\n // selected fill; the track keeps it, centered in the gap between cards.\n \"before:absolute before:inset-y-2 before:-left-px before:w-px before:bg-border first:before:hidden max-sm:before:hidden\",\n \"group-data-[variant=plain]/strip:data-selected:before:hidden group-data-[variant=plain]/strip:[[data-selected]+&]:before:hidden\",\n // Offsets are measured from the padding box, so the 1px border is added to land\n // exactly halfway across the 13px gap.\n \"group-data-[variant=track]/strip:before:-left-[8px]\",\n \"[&:is(button)]:hover:bg-foreground/3\",\n // Plain card: roomier cells, full-height dividers, selected item on a soft fill.\n \"group-data-[variant=plain]/strip:rounded-none group-data-[variant=plain]/strip:px-5 group-data-[variant=plain]/strip:py-4 group-data-[variant=plain]/strip:before:inset-y-0 group-data-[variant=plain]/strip:before:left-0\",\n \"group-data-[variant=plain]/strip:data-selected:bg-muted\",\n // Track: the selected item is a raised card; unselected figures recede.\n \"group-data-[variant=track]/strip:data-selected:border-border group-data-[variant=track]/strip:data-selected:bg-card group-data-[variant=track]/strip:data-selected:shadow-xs\",\n \"group-data-[variant=track]/strip:not-data-selected:[&_[data-value]]:text-muted-foreground\",\n \"group-data-[variant=track]/strip:[&:is(button)]:hover:not-data-selected:bg-card/60\",\n className,\n ),\n children: (\n <>\n <p className=\"truncate text-muted-foreground text-sm\">{label}</p>\n\n <p\n data-value\n className=\"mt-0.5 truncate font-display font-semibold text-2xl tabular-nums\"\n >\n <AnimatedNumber value={value} format={format} />\n </p>\n\n {hint && <p className=\"truncate text-muted-foreground text-xs\">{hint}</p>}\n </>\n ),\n ...props,\n },\n })\n}\n\nexport { StatStrip, StatStripItem }\n"],"mappings":";;;;;;AAOA,MAAM,gBAAgB,SAAS;CAC7B,MAAM;CAEN,UAAU,EACR,SAAS;EAEP,OAAO;EAIP,OAAO;CACT,EACF;CAEA,iBAAiB,EACf,SAAS,QACX;AACF,CAAC;AAID,SAAS,UAAU,EAAE,WAAW,SAAS,GAAG,SAAyB;CACnE,OACE,oBAAC,OAAD;EACE,gBAAc,WAAW;EACzB,WAAW,GAAG,cAAc;GAAE;GAAS;EAAU,CAAC,CAAC;EACnD,GAAI;CACL,CAAA;AAEL;AAcA,SAAS,cAAc,EACrB,OACA,OACA,QACA,MACA,UACA,QACA,WACA,GAAG,SACkB;CACrB,OAAO,UAAU;EACf;EACA,gBAAgB;EAChB,OAAO;GACL,iBAAiB,YAAY,KAAA;GAC7B,WAAW,GAGT,qFAGA,0HACA,mIAGA,uDACA,wCAEA,8NACA,2DAEA,gLACA,6FACA,sFACA,SACF;GACA,UACE,qBAAA,UAAA,EAAA,UAAA;IACE,oBAAC,KAAD;KAAG,WAAU;KAA0C,UAAA;IAAS,CAAA;IAEhE,oBAAC,KAAD;KACE,cAAA;KACA,WAAU;KAEV,UAAA,oBAAC,gBAAD;MAAuB;MAAe;KAAS,CAAA;IAC9C,CAAA;IAEF,QAAQ,oBAAC,KAAD;KAAG,WAAU;KAA0C,UAAA;IAAQ,CAAA;GACxE,EAAA,CAAA;GAEJ,GAAG;EACL;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"stat-strip.js","names":[],"sources":["../../src/components/stat-strip.tsx"],"sourcesContent":["import { useRender } from \"@base-ui/react/use-render\"\nimport type { Format } from \"@number-flow/react\"\nimport { type ComponentProps, isValidElement, type ReactNode } from \"react\"\nimport { AnimatedNumber } from \"~/components/animated-number\"\nimport { cardSurfaceClasses } from \"~/components/card\"\nimport { cn, variants, type VariantProps } from \"~/lib/variants\"\n\nconst stripVariants = variants({\n base: \"group/strip grid grid-cols-2 gap-y-1 max-sm:[&>*:last-child:nth-child(odd)]:col-span-2 sm:flex sm:flex-wrap sm:gap-y-0\",\n\n variants: {\n variant: {\n // A white card of figures, divided by full-height hairlines.\n plain: cardSurfaceClasses,\n // A canvas-toned track where the selected figure sits on a raised card, like a\n // segmented control; for strips that drive the panel beside them.\n // 13px gap: a 1px divider then sits 6px from the card on either side, matching the 6px inset.\n track: \"rounded-xl bg-background p-1.5 sm:gap-x-[13px]\",\n },\n },\n\n defaultVariants: {\n variant: \"plain\",\n },\n})\n\ntype StatStripProps = ComponentProps<\"div\"> & VariantProps<typeof stripVariants>\n\nfunction StatStrip({ className, variant, ...props }: StatStripProps) {\n return (\n <div\n data-variant={variant ?? \"plain\"}\n className={cn(stripVariants({ variant, className }))}\n {...props}\n />\n )\n}\n\ntype StatStripItemProps = Omit<useRender.ComponentProps<\"div\">, \"children\"> & {\n label: string\n /**\n * A number (or a string with a `format`) spins through `AnimatedNumber`; any other node,\n * e.g. two `AnimatedNumber`s composing \"1m 38s\", renders as is.\n */\n value: ReactNode\n format?: Format\n hint?: string\n /**\n * Marks the item as the one driving the panel beside it. Pass `render={<button ... />}`\n * to make it selectable.\n */\n selected?: boolean\n}\n\nfunction StatStripItem({\n label,\n value,\n format,\n hint,\n selected,\n render,\n className,\n ...props\n}: StatStripItemProps) {\n return useRender({\n render,\n defaultTagName: \"div\",\n props: {\n \"data-selected\": selected || undefined,\n className: cn(\n // The border is always laid out and only colored when selected, so toggling\n // never shifts the row.\n \"relative min-w-0 flex-1 rounded-lg border border-transparent px-4 py-3 text-start\",\n // Hairline divider between neighbours. The plain strip hides it around the\n // selected fill; the track keeps it, centered in the gap between cards.\n \"before:absolute before:inset-y-2 before:-left-px before:w-px before:bg-border first:before:hidden max-sm:before:hidden\",\n \"group-data-[variant=plain]/strip:data-selected:before:hidden group-data-[variant=plain]/strip:[[data-selected]+&]:before:hidden\",\n // Offsets are measured from the padding box, so the 1px border is added to land\n // exactly halfway across the 13px gap.\n \"group-data-[variant=track]/strip:before:-left-[8px]\",\n \"[&:is(button)]:hover:bg-foreground/3\",\n // Plain card: roomier cells, full-height dividers, selected item on a soft fill.\n \"group-data-[variant=plain]/strip:rounded-none group-data-[variant=plain]/strip:px-5 group-data-[variant=plain]/strip:py-4 group-data-[variant=plain]/strip:before:inset-y-0 group-data-[variant=plain]/strip:before:left-0\",\n \"group-data-[variant=plain]/strip:data-selected:bg-muted\",\n // Track: the selected item is a raised card; unselected figures recede.\n \"group-data-[variant=track]/strip:data-selected:border-border group-data-[variant=track]/strip:data-selected:bg-card group-data-[variant=track]/strip:data-selected:shadow-xs\",\n \"group-data-[variant=track]/strip:not-data-selected:[&_[data-value]]:text-muted-foreground\",\n \"group-data-[variant=track]/strip:[&:is(button)]:hover:not-data-selected:bg-card/60\",\n className,\n ),\n children: (\n <>\n <p className=\"text-muted-foreground text-sm sm:truncate\">{label}</p>\n\n {/* Fixed line heights (32px / 36px) equal NumberFlow's rendered box at text-xl / text-2xl,\n so a string value (\"—\", \"0:00\") sits at exactly the same height as an animated one. */}\n <p\n data-value\n className=\"mt-0.5 truncate font-display font-semibold text-xl leading-8 tabular-nums sm:text-2xl sm:leading-9\"\n >\n {isValidElement(value) || (typeof value !== \"number\" && typeof value !== \"string\") ? (\n value\n ) : (\n <AnimatedNumber value={value} format={format} />\n )}\n </p>\n\n {hint && <p className=\"text-muted-foreground text-xs sm:truncate\">{hint}</p>}\n </>\n ),\n ...props,\n },\n })\n}\n\nexport { StatStrip, StatStripItem }\n"],"mappings":";;;;;;;AAOA,MAAM,gBAAgB,SAAS;CAC7B,MAAM;CAEN,UAAU,EACR,SAAS;EAEP,OAAO;EAIP,OAAO;CACT,EACF;CAEA,iBAAiB,EACf,SAAS,QACX;AACF,CAAC;AAID,SAAS,UAAU,EAAE,WAAW,SAAS,GAAG,SAAyB;CACnE,OACE,oBAAC,OAAD;EACE,gBAAc,WAAW;EACzB,WAAW,GAAG,cAAc;GAAE;GAAS;EAAU,CAAC,CAAC;EACnD,GAAI;CACL,CAAA;AAEL;AAkBA,SAAS,cAAc,EACrB,OACA,OACA,QACA,MACA,UACA,QACA,WACA,GAAG,SACkB;CACrB,OAAO,UAAU;EACf;EACA,gBAAgB;EAChB,OAAO;GACL,iBAAiB,YAAY,KAAA;GAC7B,WAAW,GAGT,qFAGA,0HACA,mIAGA,uDACA,wCAEA,8NACA,2DAEA,gLACA,6FACA,sFACA,SACF;GACA,UACE,qBAAA,UAAA,EAAA,UAAA;IACE,oBAAC,KAAD;KAAG,WAAU;KAA6C,UAAA;IAAS,CAAA;IAInE,oBAAC,KAAD;KACE,cAAA;KACA,WAAU;KAET,UAAA,eAAe,KAAK,KAAM,OAAO,UAAU,YAAY,OAAO,UAAU,WACvE,QAEA,oBAAC,gBAAD;MAAuB;MAAe;KAAS,CAAA;IAEhD,CAAA;IAEF,QAAQ,oBAAC,KAAD;KAAG,WAAU;KAA6C,UAAA;IAAQ,CAAA;GAC3E,EAAA,CAAA;GAEJ,GAAG;EACL;CACF,CAAC;AACH"}
@@ -0,0 +1,25 @@
1
+ import { Tabs } from "./tabs.js";
2
+ import { ComponentProps } from "react";
3
+ //#region src/components/theme.d.ts
4
+ export type Theme = "system" | "light" | "dark";
5
+ /**
6
+ * The current theme and its setter. Persists to `localStorage["theme"]`, stays in sync across
7
+ * tabs, and drives the stylesheet's `light-dark()` tokens by setting `data-theme` on `<html>`
8
+ * (`system` removes it so the OS decides). SSR-safe: the server renders `system`.
9
+ *
10
+ * The hook only applies the stored theme once the first subscriber mounts. To avoid a flash of
11
+ * the wrong scheme, apply it before first paint with this inline script in `<head>`:
12
+ *
13
+ * ```html
14
+ * <script>try{var t=localStorage.theme;if(t==="light"||t==="dark")document.documentElement.dataset.theme=t}catch(e){}</script>
15
+ * ```
16
+ */
17
+ export declare function useTheme(): [Theme, (theme: Theme) => void];
18
+ type ThemeSelectProps = Omit<ComponentProps<typeof Tabs>, "value" | "defaultValue" | "onValueChange">;
19
+ /**
20
+ * Segmented System / Light / Dark control bound to `useTheme`, for a settings page or an
21
+ * account menu.
22
+ */
23
+ export declare function ThemeSelect({ className, ...props }: ThemeSelectProps): import("react").JSX.Element;
24
+ //#endregion
25
+ //# sourceMappingURL=theme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.d.ts","names":[],"sources":["../../src/components/theme.tsx"],"mappings":";;;YAKY;;;;;;;;;;;;;wBAyGI,aAAa,QAAQ,OAAO;KAMvC,mBAAmB,KACtB,sBAAsB;;;;;wBAQR,cAAc,cAAc,SAAS,mCAAgB,IAAA"}
@@ -0,0 +1,128 @@
1
+ "use client";
2
+ import { Tabs, TabsList, TabsTrigger } from "./tabs.js";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { useSyncExternalStore } from "react";
5
+ //#region src/components/theme.tsx
6
+ const STORAGE_KEY = "theme";
7
+ const THEMES = [
8
+ "system",
9
+ "light",
10
+ "dark"
11
+ ];
12
+ /**
13
+ * Tiny module store behind `useTheme`. One value shared by every subscriber, kept in step with
14
+ * `localStorage` (and with other tabs through the `storage` event) and mirrored onto
15
+ * `<html data-theme>`, which is what the stylesheet's `color-scheme` rules key on.
16
+ */
17
+ const listeners = /* @__PURE__ */ new Set();
18
+ let current;
19
+ function isTheme(value) {
20
+ return typeof value === "string" && THEMES.includes(value);
21
+ }
22
+ function readStored() {
23
+ try {
24
+ const stored = localStorage.getItem(STORAGE_KEY);
25
+ return isTheme(stored) ? stored : "system";
26
+ } catch {
27
+ return "system";
28
+ }
29
+ }
30
+ function writeStored(theme) {
31
+ try {
32
+ if (theme === "system") localStorage.removeItem(STORAGE_KEY);
33
+ else localStorage.setItem(STORAGE_KEY, theme);
34
+ } catch {}
35
+ }
36
+ /**
37
+ * `system` clears the attribute so `color-scheme: light dark` follows the OS; anything else
38
+ * pins it.
39
+ */
40
+ function applyTheme(theme) {
41
+ const root = document.documentElement;
42
+ if (theme === "system") delete root.dataset.theme;
43
+ else root.dataset.theme = theme;
44
+ }
45
+ function emit() {
46
+ for (const listener of listeners) listener();
47
+ }
48
+ function onStorage(event) {
49
+ if (event.key !== null && event.key !== STORAGE_KEY) return;
50
+ current = readStored();
51
+ applyTheme(current);
52
+ emit();
53
+ }
54
+ function getSnapshot() {
55
+ current ??= readStored();
56
+ return current;
57
+ }
58
+ function getServerSnapshot() {
59
+ return "system";
60
+ }
61
+ function subscribe(listener) {
62
+ if (listeners.size === 0) {
63
+ window.addEventListener("storage", onStorage);
64
+ applyTheme(getSnapshot());
65
+ }
66
+ listeners.add(listener);
67
+ return () => {
68
+ listeners.delete(listener);
69
+ if (listeners.size === 0) window.removeEventListener("storage", onStorage);
70
+ };
71
+ }
72
+ function setTheme(theme) {
73
+ if (theme === current) return;
74
+ current = theme;
75
+ writeStored(theme);
76
+ applyTheme(theme);
77
+ emit();
78
+ }
79
+ /**
80
+ * The current theme and its setter. Persists to `localStorage["theme"]`, stays in sync across
81
+ * tabs, and drives the stylesheet's `light-dark()` tokens by setting `data-theme` on `<html>`
82
+ * (`system` removes it so the OS decides). SSR-safe: the server renders `system`.
83
+ *
84
+ * The hook only applies the stored theme once the first subscriber mounts. To avoid a flash of
85
+ * the wrong scheme, apply it before first paint with this inline script in `<head>`:
86
+ *
87
+ * ```html
88
+ * <script>try{var t=localStorage.theme;if(t==="light"||t==="dark")document.documentElement.dataset.theme=t}catch(e){}<\/script>
89
+ * ```
90
+ */
91
+ function useTheme() {
92
+ return [useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot), setTheme];
93
+ }
94
+ /**
95
+ * Segmented System / Light / Dark control bound to `useTheme`, for a settings page or an
96
+ * account menu.
97
+ */
98
+ function ThemeSelect({ className, ...props }) {
99
+ const [theme, setTheme] = useTheme();
100
+ return /* @__PURE__ */ jsx(Tabs, {
101
+ value: theme,
102
+ onValueChange: (next) => setTheme(next),
103
+ className,
104
+ ...props,
105
+ children: /* @__PURE__ */ jsxs(TabsList, {
106
+ variant: "segmented",
107
+ "aria-label": "Theme",
108
+ children: [
109
+ /* @__PURE__ */ jsx(TabsTrigger, {
110
+ value: "system",
111
+ children: "System"
112
+ }),
113
+ /* @__PURE__ */ jsx(TabsTrigger, {
114
+ value: "light",
115
+ children: "Light"
116
+ }),
117
+ /* @__PURE__ */ jsx(TabsTrigger, {
118
+ value: "dark",
119
+ children: "Dark"
120
+ })
121
+ ]
122
+ })
123
+ });
124
+ }
125
+ //#endregion
126
+ export { ThemeSelect, useTheme };
127
+
128
+ //# sourceMappingURL=theme.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.js","names":[],"sources":["../../src/components/theme.tsx"],"sourcesContent":["\"use client\"\n\nimport { type ComponentProps, useSyncExternalStore } from \"react\"\nimport { Tabs, TabsList, TabsTrigger } from \"~/components/tabs\"\n\nexport type Theme = \"system\" | \"light\" | \"dark\"\n\nconst STORAGE_KEY = \"theme\"\nconst THEMES: readonly Theme[] = [\"system\", \"light\", \"dark\"]\n\n/**\n * Tiny module store behind `useTheme`. One value shared by every subscriber, kept in step with\n * `localStorage` (and with other tabs through the `storage` event) and mirrored onto\n * `<html data-theme>`, which is what the stylesheet's `color-scheme` rules key on.\n */\nconst listeners = new Set<() => void>()\nlet current: Theme | undefined\n\nfunction isTheme(value: unknown): value is Theme {\n return typeof value === \"string\" && THEMES.includes(value as Theme)\n}\n\nfunction readStored(): Theme {\n try {\n const stored = localStorage.getItem(STORAGE_KEY)\n return isTheme(stored) ? stored : \"system\"\n } catch {\n // Storage can be unavailable (privacy mode, sandboxed iframe) — fall back to the OS.\n return \"system\"\n }\n}\n\nfunction writeStored(theme: Theme) {\n try {\n if (theme === \"system\") localStorage.removeItem(STORAGE_KEY)\n else localStorage.setItem(STORAGE_KEY, theme)\n } catch {\n // Storage being unavailable only costs persistence; the in-memory value still applies.\n }\n}\n\n/**\n * `system` clears the attribute so `color-scheme: light dark` follows the OS; anything else\n * pins it.\n */\nfunction applyTheme(theme: Theme) {\n const root = document.documentElement\n\n if (theme === \"system\") delete root.dataset.theme\n else root.dataset.theme = theme\n}\n\nfunction emit() {\n for (const listener of listeners) listener()\n}\n\nfunction onStorage(event: StorageEvent) {\n // `key === null` is a `clear()` from another tab; treat it as a reset.\n if (event.key !== null && event.key !== STORAGE_KEY) return\n\n current = readStored()\n applyTheme(current)\n emit()\n}\n\nfunction getSnapshot(): Theme {\n current ??= readStored()\n return current\n}\n\nfunction getServerSnapshot(): Theme {\n return \"system\"\n}\n\nfunction subscribe(listener: () => void) {\n if (listeners.size === 0) {\n window.addEventListener(\"storage\", onStorage)\n // Apps that skip the inline script still land on the stored theme, one paint late.\n applyTheme(getSnapshot())\n }\n\n listeners.add(listener)\n\n return () => {\n listeners.delete(listener)\n if (listeners.size === 0) window.removeEventListener(\"storage\", onStorage)\n }\n}\n\nfunction setTheme(theme: Theme) {\n if (theme === current) return\n\n current = theme\n writeStored(theme)\n applyTheme(theme)\n emit()\n}\n\n/**\n * The current theme and its setter. Persists to `localStorage[\"theme\"]`, stays in sync across\n * tabs, and drives the stylesheet's `light-dark()` tokens by setting `data-theme` on `<html>`\n * (`system` removes it so the OS decides). SSR-safe: the server renders `system`.\n *\n * The hook only applies the stored theme once the first subscriber mounts. To avoid a flash of\n * the wrong scheme, apply it before first paint with this inline script in `<head>`:\n *\n * ```html\n * <script>try{var t=localStorage.theme;if(t===\"light\"||t===\"dark\")document.documentElement.dataset.theme=t}catch(e){}</script>\n * ```\n */\nexport function useTheme(): [Theme, (theme: Theme) => void] {\n const theme = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)\n\n return [theme, setTheme]\n}\n\ntype ThemeSelectProps = Omit<\n ComponentProps<typeof Tabs>,\n \"value\" | \"defaultValue\" | \"onValueChange\"\n>\n\n/**\n * Segmented System / Light / Dark control bound to `useTheme`, for a settings page or an\n * account menu.\n */\nexport function ThemeSelect({ className, ...props }: ThemeSelectProps) {\n const [theme, setTheme] = useTheme()\n\n return (\n <Tabs\n value={theme}\n onValueChange={(next: Theme) => setTheme(next)}\n className={className}\n {...props}\n >\n <TabsList variant=\"segmented\" aria-label=\"Theme\">\n <TabsTrigger value=\"system\">System</TabsTrigger>\n <TabsTrigger value=\"light\">Light</TabsTrigger>\n <TabsTrigger value=\"dark\">Dark</TabsTrigger>\n </TabsList>\n </Tabs>\n )\n}\n"],"mappings":";;;;;AAOA,MAAM,cAAc;AACpB,MAAM,SAA2B;CAAC;CAAU;CAAS;AAAM;;;;;;AAO3D,MAAM,4BAAY,IAAI,IAAgB;AACtC,IAAI;AAEJ,SAAS,QAAQ,OAAgC;CAC/C,OAAO,OAAO,UAAU,YAAY,OAAO,SAAS,KAAc;AACpE;AAEA,SAAS,aAAoB;CAC3B,IAAI;EACF,MAAM,SAAS,aAAa,QAAQ,WAAW;EAC/C,OAAO,QAAQ,MAAM,IAAI,SAAS;CACpC,QAAQ;EAEN,OAAO;CACT;AACF;AAEA,SAAS,YAAY,OAAc;CACjC,IAAI;EACF,IAAI,UAAU,UAAU,aAAa,WAAW,WAAW;OACtD,aAAa,QAAQ,aAAa,KAAK;CAC9C,QAAQ,CAER;AACF;;;;;AAMA,SAAS,WAAW,OAAc;CAChC,MAAM,OAAO,SAAS;CAEtB,IAAI,UAAU,UAAU,OAAO,KAAK,QAAQ;MACvC,KAAK,QAAQ,QAAQ;AAC5B;AAEA,SAAS,OAAO;CACd,KAAK,MAAM,YAAY,WAAW,SAAS;AAC7C;AAEA,SAAS,UAAU,OAAqB;CAEtC,IAAI,MAAM,QAAQ,QAAQ,MAAM,QAAQ,aAAa;CAErD,UAAU,WAAW;CACrB,WAAW,OAAO;CAClB,KAAK;AACP;AAEA,SAAS,cAAqB;CAC5B,YAAY,WAAW;CACvB,OAAO;AACT;AAEA,SAAS,oBAA2B;CAClC,OAAO;AACT;AAEA,SAAS,UAAU,UAAsB;CACvC,IAAI,UAAU,SAAS,GAAG;EACxB,OAAO,iBAAiB,WAAW,SAAS;EAE5C,WAAW,YAAY,CAAC;CAC1B;CAEA,UAAU,IAAI,QAAQ;CAEtB,aAAa;EACX,UAAU,OAAO,QAAQ;EACzB,IAAI,UAAU,SAAS,GAAG,OAAO,oBAAoB,WAAW,SAAS;CAC3E;AACF;AAEA,SAAS,SAAS,OAAc;CAC9B,IAAI,UAAU,SAAS;CAEvB,UAAU;CACV,YAAY,KAAK;CACjB,WAAW,KAAK;CAChB,KAAK;AACP;;;;;;;;;;;;;AAcA,SAAgB,WAA4C;CAG1D,OAAO,CAFO,qBAAqB,WAAW,aAAa,iBAE/C,GAAG,QAAQ;AACzB;;;;;AAWA,SAAgB,YAAY,EAAE,WAAW,GAAG,SAA2B;CACrE,MAAM,CAAC,OAAO,YAAY,SAAS;CAEnC,OACE,oBAAC,MAAD;EACE,OAAO;EACP,gBAAgB,SAAgB,SAAS,IAAI;EAClC;EACX,GAAI;EAEJ,UAAA,qBAAC,UAAD;GAAU,SAAQ;GAAY,cAAW;GAAzC,UAAA;IACE,oBAAC,aAAD;KAAa,OAAM;KAAS,UAAA;IAAmB,CAAA;IAC/C,oBAAC,aAAD;KAAa,OAAM;KAAQ,UAAA;IAAkB,CAAA;IAC7C,oBAAC,aAAD;KAAa,OAAM;KAAO,UAAA;IAAiB,CAAA;GACnC;;CACN,CAAA;AAEV"}
@@ -1,6 +1,6 @@
1
1
  import { cn } from "../lib/variants.js";
2
- import { controlTextClasses } from "../lib/classes.js";
3
2
  import { CircleCheckIcon, CircleHelpIcon, CircleXIcon, LoaderIcon } from "../lib/icons.js";
3
+ import { controlTextClasses } from "../lib/classes.js";
4
4
  import { jsx } from "react/jsx-runtime";
5
5
  import { Toaster as Toaster$1 } from "sonner";
6
6
  //#region src/components/toaster.tsx
@@ -10,6 +10,11 @@ function Toaster({ className, ...props }) {
10
10
  return /* @__PURE__ */ jsx(Toaster$1, {
11
11
  className: "pointer-events-auto",
12
12
  offset: 16,
13
+ mobileOffset: {
14
+ bottom: "calc(16px + env(safe-area-inset-bottom))",
15
+ left: 16,
16
+ right: 16
17
+ },
13
18
  toastOptions: {
14
19
  unstyled: true,
15
20
  classNames: {
@@ -1 +1 @@
1
- {"version":3,"file":"toaster.js","names":["Sonner"],"sources":["../../src/components/toaster.tsx"],"sourcesContent":["import type { ComponentProps } from \"react\"\nimport { Toaster as Sonner } from \"sonner\"\nimport { controlTextClasses } from \"~/lib/classes\"\nimport { CircleCheckIcon, CircleHelpIcon, CircleXIcon, LoaderIcon } from \"~/lib/icons\"\nimport { cn } from \"~/lib/variants\"\n\n/** The halo that lifts a solid-colored toast off whatever it floats over. */\nconst toastRingClasses = \"ring ring-background/50\"\n\nexport function Toaster({ className, ...props }: ComponentProps<typeof Sonner>) {\n return (\n <Sonner\n className=\"pointer-events-auto\"\n offset={16}\n toastOptions={{\n unstyled: true,\n classNames: {\n /*\n * Sonner joins `toast` with exactly one type key per toast (`default` for untyped\n * ones) as a plain string, so tailwind-merge never sees the pair. Anything that\n * differs per type — background, text, halo — therefore lives on the type keys\n * only, and never gets cancelled here.\n */\n toast: cn(\n `flex items-start gap-2 p-4 ${controlTextClasses} font-medium rounded-lg shadow-sm`,\n className,\n ),\n default: \"bg-background border border-border text-foreground\",\n info: cn(\"bg-foreground text-background\", toastRingClasses),\n success: cn(\"bg-success text-white\", toastRingClasses),\n error: cn(\"bg-danger text-white\", toastRingClasses),\n content: \"w-full\",\n description: \"font-normal opacity-80\",\n icon: \"mt-0.5\",\n },\n }}\n icons={{\n info: <CircleHelpIcon className=\"size-4\" />,\n success: <CircleCheckIcon className=\"size-4\" />,\n error: <CircleXIcon className=\"size-4\" />,\n loading: <LoaderIcon className=\"size-4 animate-spin\" />,\n }}\n {...props}\n />\n )\n}\n"],"mappings":";;;;;;;AAOA,MAAM,mBAAmB;AAEzB,SAAgB,QAAQ,EAAE,WAAW,GAAG,SAAwC;CAC9E,OACE,oBAACA,WAAD;EACE,WAAU;EACV,QAAQ;EACR,cAAc;GACZ,UAAU;GACV,YAAY;IAOV,OAAO,GACL,8BAA8B,mBAAmB,oCACjD,SACF;IACA,SAAS;IACT,MAAM,GAAG,iCAAiC,gBAAgB;IAC1D,SAAS,GAAG,yBAAyB,gBAAgB;IACrD,OAAO,GAAG,wBAAwB,gBAAgB;IAClD,SAAS;IACT,aAAa;IACb,MAAM;GACR;EACF;EACA,OAAO;GACL,MAAM,oBAAC,gBAAD,EAAgB,WAAU,SAAU,CAAA;GAC1C,SAAS,oBAAC,iBAAD,EAAiB,WAAU,SAAU,CAAA;GAC9C,OAAO,oBAAC,aAAD,EAAa,WAAU,SAAU,CAAA;GACxC,SAAS,oBAAC,YAAD,EAAY,WAAU,sBAAuB,CAAA;EACxD;EACA,GAAI;CACL,CAAA;AAEL"}
1
+ {"version":3,"file":"toaster.js","names":["Sonner"],"sources":["../../src/components/toaster.tsx"],"sourcesContent":["import type { ComponentProps } from \"react\"\nimport { Toaster as Sonner } from \"sonner\"\nimport { controlTextClasses } from \"~/lib/classes\"\nimport { CircleCheckIcon, CircleHelpIcon, CircleXIcon, LoaderIcon } from \"~/lib/icons\"\nimport { cn } from \"~/lib/variants\"\n\n/** The halo that lifts a solid-colored toast off whatever it floats over. */\nconst toastRingClasses = \"ring ring-background/50\"\n\nexport function Toaster({ className, ...props }: ComponentProps<typeof Sonner>) {\n return (\n <Sonner\n className=\"pointer-events-auto\"\n offset={16}\n mobileOffset={{ bottom: \"calc(16px + env(safe-area-inset-bottom))\", left: 16, right: 16 }}\n toastOptions={{\n unstyled: true,\n classNames: {\n /*\n * Sonner joins `toast` with exactly one type key per toast (`default` for untyped\n * ones) as a plain string, so tailwind-merge never sees the pair. Anything that\n * differs per type — background, text, halo — therefore lives on the type keys\n * only, and never gets cancelled here.\n */\n toast: cn(\n `flex items-start gap-2 p-4 ${controlTextClasses} font-medium rounded-lg shadow-sm`,\n className,\n ),\n default: \"bg-background border border-border text-foreground\",\n info: cn(\"bg-foreground text-background\", toastRingClasses),\n success: cn(\"bg-success text-white\", toastRingClasses),\n error: cn(\"bg-danger text-white\", toastRingClasses),\n content: \"w-full\",\n description: \"font-normal opacity-80\",\n icon: \"mt-0.5\",\n },\n }}\n icons={{\n info: <CircleHelpIcon className=\"size-4\" />,\n success: <CircleCheckIcon className=\"size-4\" />,\n error: <CircleXIcon className=\"size-4\" />,\n loading: <LoaderIcon className=\"size-4 animate-spin\" />,\n }}\n {...props}\n />\n )\n}\n"],"mappings":";;;;;;;AAOA,MAAM,mBAAmB;AAEzB,SAAgB,QAAQ,EAAE,WAAW,GAAG,SAAwC;CAC9E,OACE,oBAACA,WAAD;EACE,WAAU;EACV,QAAQ;EACR,cAAc;GAAE,QAAQ;GAA4C,MAAM;GAAI,OAAO;EAAG;EACxF,cAAc;GACZ,UAAU;GACV,YAAY;IAOV,OAAO,GACL,8BAA8B,mBAAmB,oCACjD,SACF;IACA,SAAS;IACT,MAAM,GAAG,iCAAiC,gBAAgB;IAC1D,SAAS,GAAG,yBAAyB,gBAAgB;IACrD,OAAO,GAAG,wBAAwB,gBAAgB;IAClD,SAAS;IACT,aAAa;IACb,MAAM;GACR;EACF;EACA,OAAO;GACL,MAAM,oBAAC,gBAAD,EAAgB,WAAU,SAAU,CAAA;GAC1C,SAAS,oBAAC,iBAAD,EAAiB,WAAU,SAAU,CAAA;GAC9C,OAAO,oBAAC,aAAD,EAAa,WAAU,SAAU,CAAA;GACxC,SAAS,oBAAC,YAAD,EAAY,WAAU,sBAAuB,CAAA;EACxD;EACA,GAAI;CACL,CAAA;AAEL"}
@@ -0,0 +1,26 @@
1
+ import { Tabs, TabsList } from "./tabs.js";
2
+ import { ComponentProps } from "react";
3
+ //#region src/components/view-tabs.d.ts
4
+ export type ViewTabsProps<T extends string> = Omit<ComponentProps<typeof Tabs>, "value" | "defaultValue" | "onValueChange" | "onChange"> & {
5
+ value: T;
6
+ /**
7
+ * The view that maps to no `?view=` param, so the URL stays clean on the default tab.
8
+ */
9
+ defaultValue: T;
10
+ options: {
11
+ value: T;
12
+ label: string;
13
+ }[];
14
+ onChange: (view: T | undefined) => void;
15
+ /**
16
+ * Forwarded to `TabsList`: `segmented` (default) for a header, `plain` inside a card.
17
+ */
18
+ variant?: ComponentProps<typeof TabsList>["variant"];
19
+ };
20
+ /**
21
+ * Tab switcher bound to a route's `view` search param. Emits `undefined` for the default view
22
+ * so the route can drop it from the URL instead of writing `?view=<default>`.
23
+ */
24
+ export declare function ViewTabs<T extends string>({ value, defaultValue, options, onChange, variant, className, ...props }: ViewTabsProps<T>): import("react").JSX.Element;
25
+ //#endregion
26
+ //# sourceMappingURL=view-tabs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view-tabs.d.ts","names":[],"sources":["../../src/components/view-tabs.tsx"],"mappings":";;;YAKY,cAAc,oBAAoB,KAC5C,sBAAsB;EAGtB,OAAO;;;;EAIP,cAAc;EACd;IAAW,OAAO;IAAG;;EACrB,WAAW,MAAM;;;;EAIjB,UAAU,sBAAsB;;;;;;wBAOlB,SAAS,oBACvB,OACA,cACA,SACA,UACA,SACA,cACG,SACF,cAAc,qBAAE,IAAA"}
@@ -0,0 +1,27 @@
1
+ "use client";
2
+ import { Tabs, TabsList, TabsTrigger } from "./tabs.js";
3
+ import { jsx } from "react/jsx-runtime";
4
+ //#region src/components/view-tabs.tsx
5
+ /**
6
+ * Tab switcher bound to a route's `view` search param. Emits `undefined` for the default view
7
+ * so the route can drop it from the URL instead of writing `?view=<default>`.
8
+ */
9
+ function ViewTabs({ value, defaultValue, options, onChange, variant, className, ...props }) {
10
+ return /* @__PURE__ */ jsx(Tabs, {
11
+ value,
12
+ onValueChange: (next) => onChange(next === defaultValue ? void 0 : next),
13
+ className,
14
+ ...props,
15
+ children: /* @__PURE__ */ jsx(TabsList, {
16
+ variant,
17
+ children: options.map((option) => /* @__PURE__ */ jsx(TabsTrigger, {
18
+ value: option.value,
19
+ children: option.label
20
+ }, option.value))
21
+ })
22
+ });
23
+ }
24
+ //#endregion
25
+ export { ViewTabs };
26
+
27
+ //# sourceMappingURL=view-tabs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view-tabs.js","names":[],"sources":["../../src/components/view-tabs.tsx"],"sourcesContent":["\"use client\"\n\nimport type { ComponentProps } from \"react\"\nimport { Tabs, TabsList, TabsTrigger } from \"~/components/tabs\"\n\nexport type ViewTabsProps<T extends string> = Omit<\n ComponentProps<typeof Tabs>,\n \"value\" | \"defaultValue\" | \"onValueChange\" | \"onChange\"\n> & {\n value: T\n /**\n * The view that maps to no `?view=` param, so the URL stays clean on the default tab.\n */\n defaultValue: T\n options: { value: T; label: string }[]\n onChange: (view: T | undefined) => void\n /**\n * Forwarded to `TabsList`: `segmented` (default) for a header, `plain` inside a card.\n */\n variant?: ComponentProps<typeof TabsList>[\"variant\"]\n}\n\n/**\n * Tab switcher bound to a route's `view` search param. Emits `undefined` for the default view\n * so the route can drop it from the URL instead of writing `?view=<default>`.\n */\nexport function ViewTabs<T extends string>({\n value,\n defaultValue,\n options,\n onChange,\n variant,\n className,\n ...props\n}: ViewTabsProps<T>) {\n return (\n <Tabs\n value={value}\n onValueChange={(next: T) => onChange(next === defaultValue ? undefined : next)}\n className={className}\n {...props}\n >\n <TabsList variant={variant}>\n {options.map(option => (\n <TabsTrigger key={option.value} value={option.value}>\n {option.label}\n </TabsTrigger>\n ))}\n </TabsList>\n </Tabs>\n )\n}\n"],"mappings":";;;;;;;;AA0BA,SAAgB,SAA2B,EACzC,OACA,cACA,SACA,UACA,SACA,WACA,GAAG,SACgB;CACnB,OACE,oBAAC,MAAD;EACS;EACP,gBAAgB,SAAY,SAAS,SAAS,eAAe,KAAA,IAAY,IAAI;EAClE;EACX,GAAI;EAEJ,UAAA,oBAAC,UAAD;GAAmB;GAChB,UAAA,QAAQ,KAAI,WACX,oBAAC,aAAD;IAAgC,OAAO,OAAO;IAC3C,UAAA,OAAO;GACG,GAFK,OAAO,KAEZ,CACd;EACO,CAAA;CACN,CAAA;AAEV"}
@@ -3,6 +3,7 @@ import { HugeiconsProps } from "@hugeicons/react";
3
3
  //#region src/lib/icons.d.ts
4
4
  export type IconProps = Omit<HugeiconsProps, "icon" | "altIcon" | "showAlt">;
5
5
  export type IconComponent = ComponentType<IconProps>;
6
+ export declare const ArrowUpRightIcon: IconComponent;
6
7
  export declare const CheckIcon: IconComponent;
7
8
  export declare const ChevronDownIcon: IconComponent;
8
9
  export declare const ChevronLeftIcon: IconComponent;
@@ -1 +1 @@
1
- {"version":3,"file":"icons.d.ts","names":[],"sources":["../../src/lib/icons.tsx"],"mappings":";;;YAmBY,YAAY,KAAK;YACjB,gBAAgB,cAAc;qBAqB7B,WAAS;qBACT,iBAAe;qBACf,iBAAe;qBACf,kBAAgB;qBAChB,eAAa;qBACb,oBAAkB;qBAClB,iBAAe;qBACf,gBAAc;qBACd,YAAU;qBACV,aAAW;qBACX,UAAQ;qBACR,YAAU;qBACV,YAAU;qBACV,OAAK"}
1
+ {"version":3,"file":"icons.d.ts","names":[],"sources":["../../src/lib/icons.tsx"],"mappings":";;;YAoBY,YAAY,KAAK;YACjB,gBAAgB,cAAc;qBAqB7B,kBAAgB;qBAChB,WAAS;qBACT,iBAAe;qBACf,iBAAe;qBACf,kBAAgB;qBAChB,eAAa;qBACb,oBAAkB;qBAClB,iBAAe;qBACf,gBAAc;qBACd,YAAU;qBACV,aAAW;qBACX,UAAQ;qBACR,YAAU;qBACV,YAAU;qBACV,OAAK"}
package/dist/lib/icons.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
- import { ArrowDown01Icon, ArrowLeft01Icon, ArrowRight01Icon, ArrowUp01Icon, Cancel01Icon, CancelCircleIcon, CheckmarkCircle02Icon, CircleIcon as CircleIcon$1, Copy01Icon, HelpCircleIcon, Loading03Icon, Search01Icon, Tick02Icon, UnfoldMoreIcon } from "@hugeicons/core-free-icons";
2
+ import { ArrowDown01Icon, ArrowLeft01Icon, ArrowRight01Icon, ArrowUp01Icon, ArrowUpRight01Icon, Cancel01Icon, CancelCircleIcon, CheckmarkCircle02Icon, CircleIcon as CircleIcon$1, Copy01Icon, HelpCircleIcon, Loading03Icon, Search01Icon, Tick02Icon, UnfoldMoreIcon } from "@hugeicons/core-free-icons";
3
3
  import { HugeiconsIcon } from "@hugeicons/react";
4
4
  //#region src/lib/icons.tsx
5
5
  /**
@@ -23,6 +23,7 @@ function icon(def, name) {
23
23
  Icon.displayName = name;
24
24
  return Icon;
25
25
  }
26
+ const ArrowUpRightIcon = icon(ArrowUpRight01Icon, "ArrowUpRightIcon");
26
27
  const CheckIcon = icon(Tick02Icon, "CheckIcon");
27
28
  const ChevronDownIcon = icon(ArrowDown01Icon, "ChevronDownIcon");
28
29
  const ChevronLeftIcon = icon(ArrowLeft01Icon, "ChevronLeftIcon");
@@ -38,6 +39,6 @@ const LoaderIcon = icon(Loading03Icon, "LoaderIcon");
38
39
  const SearchIcon = icon(Search01Icon, "SearchIcon");
39
40
  const XIcon = icon(Cancel01Icon, "XIcon");
40
41
  //#endregion
41
- export { CheckIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon, ChevronsUpDownIcon, CircleCheckIcon, CircleHelpIcon, CircleIcon, CircleXIcon, CopyIcon, LoaderIcon, SearchIcon, XIcon };
42
+ export { ArrowUpRightIcon, CheckIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, ChevronUpIcon, ChevronsUpDownIcon, CircleCheckIcon, CircleHelpIcon, CircleIcon, CircleXIcon, CopyIcon, LoaderIcon, SearchIcon, XIcon };
42
43
 
43
44
  //# sourceMappingURL=icons.js.map