@fracazo/design-system 0.1.0 → 0.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 (40) hide show
  1. package/README.md +24 -3
  2. package/dist/src/cn.d.ts +8 -0
  3. package/dist/src/cn.js +11 -0
  4. package/dist/src/index.d.ts +26 -0
  5. package/dist/src/index.js +26 -0
  6. package/dist/src/ui/accordion.d.ts +14 -0
  7. package/dist/src/ui/accordion.js +25 -0
  8. package/dist/src/ui/button.d.ts +27 -0
  9. package/dist/src/ui/button.js +67 -0
  10. package/dist/src/ui/calendar.d.ts +16 -0
  11. package/dist/src/ui/calendar.js +84 -0
  12. package/dist/src/ui/card.d.ts +17 -0
  13. package/dist/src/ui/card.js +32 -0
  14. package/dist/src/ui/checkbox.d.ts +12 -0
  15. package/dist/src/ui/checkbox.js +17 -0
  16. package/dist/src/ui/dialog.d.ts +23 -0
  17. package/dist/src/ui/dialog.js +44 -0
  18. package/dist/src/ui/form.d.ts +33 -0
  19. package/dist/src/ui/form.js +68 -0
  20. package/dist/src/ui/input.d.ts +12 -0
  21. package/dist/src/ui/input.js +19 -0
  22. package/dist/src/ui/label.d.ts +11 -0
  23. package/dist/src/ui/label.js +15 -0
  24. package/dist/src/ui/popover.d.ts +17 -0
  25. package/dist/src/ui/popover.js +33 -0
  26. package/dist/src/ui/progress.d.ts +11 -0
  27. package/dist/src/ui/progress.js +15 -0
  28. package/dist/src/ui/radio-group.d.ts +12 -0
  29. package/dist/src/ui/radio-group.js +19 -0
  30. package/dist/src/ui/select.d.ts +23 -0
  31. package/dist/src/ui/select.js +46 -0
  32. package/dist/src/ui/sheet.d.ts +23 -0
  33. package/dist/src/ui/sheet.js +49 -0
  34. package/dist/src/ui/sortable-list.d.ts +26 -0
  35. package/dist/src/ui/sortable-list.js +92 -0
  36. package/dist/src/ui/tabs.d.ts +17 -0
  37. package/dist/src/ui/tabs.js +31 -0
  38. package/dist/src/ui/textarea.d.ts +10 -0
  39. package/dist/src/ui/textarea.js +13 -0
  40. package/package.json +36 -2
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @fracazo/design-system
2
2
 
3
- Roles, a brand contract and guardrails for a warm, evidence-led product
4
- design system. The package ships the half of the system that is the same
3
+ Roles, a brand contract, guardrails and components for a warm, evidence-led
4
+ product design system. The package ships the half of the system that is the same
5
5
  for every product; each product supplies one brand file with its values.
6
6
  Change the brand file and the whole product re-skins; the roles never move.
7
7
 
@@ -16,6 +16,7 @@ product from.
16
16
  | `css/roles.css` | The system: the dark variant, the Tailwind v4 `@theme` mapping, the radius ramp, fluid type roles, band rhythm, the eight aliasing semantics, and the **brand contract** at the top |
17
17
  | `ds-check-brand` | Holds a brand file to the contract: nothing missing, nothing extra |
18
18
  | `ds-build-brand-css` | Composes the plain-CSS token file a product serves publicly (e.g. `/brand.css`) |
19
+ | `@fracazo/design-system` and `./ui/*` | `cn` and seventeen shadcn-based components (button, card, dialog, form, select, sortable-list and the rest), each with intent JSDoc: use for, avoid when, variants |
19
20
  | `@fracazo/design-system/eslint` | Two guardrails: no raw colours and no arbitrary fluid type sizes in a `className` |
20
21
  | `demo/index.html` | A showcase page that renders the roles in both modes off a served `/brand.css` |
21
22
  | `DESIGN.md` | The written authority: judgment, composition, the rejection list. Authored next |
@@ -25,7 +26,11 @@ them honest.
25
26
 
26
27
  ## Consume it
27
28
 
28
- Install (Tailwind v4 is a peer dependency):
29
+ Install. Tailwind v4 is a peer dependency; so are the libraries the
30
+ components sit on (react, radix-ui, lucide-react, react-hook-form,
31
+ react-day-picker, the three `@dnd-kit` packages, clsx, tailwind-merge and
32
+ class-variance-authority). A product that only wants the roles and
33
+ guardrails can leave the component peers uninstalled.
29
34
 
30
35
  ```bash
31
36
  pnpm add @fracazo/design-system
@@ -40,6 +45,22 @@ stylesheet. Order matters: roles first.
40
45
  @import "./system/brands/my-product.css";
41
46
  ```
42
47
 
48
+ Then tell Tailwind to scan the package, so it generates the utilities the
49
+ components use. The package ships compiled JavaScript, and the class strings
50
+ survive compilation verbatim:
51
+
52
+ ```css
53
+ @source "../../node_modules/@fracazo/design-system/dist";
54
+ ```
55
+
56
+ Import components from their subpath, which keeps each one's client
57
+ boundary where it declares it, or `cn` and everything else from the root:
58
+
59
+ ```tsx
60
+ import { Button } from '@fracazo/design-system/ui/button'
61
+ import { cn } from '@fracazo/design-system'
62
+ ```
63
+
43
64
  Write the brand file to the contract printed at the top of `roles.css`: one
44
65
  `@theme inline` block for the typeface mapping, one `:root, .force-light`
45
66
  block with every light value, one `.dark` block with every theme-varying
@@ -0,0 +1,8 @@
1
+ import { type ClassValue } from "clsx";
2
+ /**
3
+ * Merge class names with Tailwind-aware conflict resolution: later classes
4
+ * win over earlier ones for the same utility (twMerge), and falsy or nested
5
+ * inputs are flattened away (clsx). Every component in `./ui` composes its
6
+ * className through this, so a consumer's override always wins.
7
+ */
8
+ export declare function cn(...inputs: ClassValue[]): string;
package/dist/src/cn.js ADDED
@@ -0,0 +1,11 @@
1
+ import { clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+ /**
4
+ * Merge class names with Tailwind-aware conflict resolution: later classes
5
+ * win over earlier ones for the same utility (twMerge), and falsy or nested
6
+ * inputs are flattened away (clsx). Every component in `./ui` composes its
7
+ * className through this, so a consumer's override always wins.
8
+ */
9
+ export function cn(...inputs) {
10
+ return twMerge(clsx(inputs));
11
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Root entry: `cn` and every component, re-exported from `./ui/*`.
3
+ *
4
+ * Prefer the per-component subpaths (`@fracazo/design-system/ui/button`) in
5
+ * app code; they keep the client boundary at the component that declares it.
6
+ * This barrel exists for quick imports and for tooling that wants one map of
7
+ * everything the package ships.
8
+ */
9
+ export { cn } from "./cn.js";
10
+ export * from "./ui/accordion.js";
11
+ export * from "./ui/button.js";
12
+ export * from "./ui/calendar.js";
13
+ export * from "./ui/card.js";
14
+ export * from "./ui/checkbox.js";
15
+ export * from "./ui/dialog.js";
16
+ export * from "./ui/form.js";
17
+ export * from "./ui/input.js";
18
+ export * from "./ui/label.js";
19
+ export * from "./ui/popover.js";
20
+ export * from "./ui/progress.js";
21
+ export * from "./ui/radio-group.js";
22
+ export * from "./ui/select.js";
23
+ export * from "./ui/sheet.js";
24
+ export * from "./ui/sortable-list.js";
25
+ export * from "./ui/tabs.js";
26
+ export * from "./ui/textarea.js";
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Root entry: `cn` and every component, re-exported from `./ui/*`.
3
+ *
4
+ * Prefer the per-component subpaths (`@fracazo/design-system/ui/button`) in
5
+ * app code; they keep the client boundary at the component that declares it.
6
+ * This barrel exists for quick imports and for tooling that wants one map of
7
+ * everything the package ships.
8
+ */
9
+ export { cn } from "./cn.js";
10
+ export * from "./ui/accordion.js";
11
+ export * from "./ui/button.js";
12
+ export * from "./ui/calendar.js";
13
+ export * from "./ui/card.js";
14
+ export * from "./ui/checkbox.js";
15
+ export * from "./ui/dialog.js";
16
+ export * from "./ui/form.js";
17
+ export * from "./ui/input.js";
18
+ export * from "./ui/label.js";
19
+ export * from "./ui/popover.js";
20
+ export * from "./ui/progress.js";
21
+ export * from "./ui/radio-group.js";
22
+ export * from "./ui/select.js";
23
+ export * from "./ui/sheet.js";
24
+ export * from "./ui/sortable-list.js";
25
+ export * from "./ui/tabs.js";
26
+ export * from "./ui/textarea.js";
@@ -0,0 +1,14 @@
1
+ import * as React from "react";
2
+ import { Accordion as AccordionPrimitive } from "radix-ui";
3
+ /**
4
+ * Expand-collapse list (Radix) with single or multiple open items.
5
+ *
6
+ * Use for: progressive disclosure inside app surfaces.
7
+ * Avoid when: SEO matters on public content; the landing FAQ deliberately
8
+ * uses native details and summary so answers exist without JS.
9
+ */
10
+ declare function Accordion({ ...props }: React.ComponentProps<typeof AccordionPrimitive.Root>): React.JSX.Element;
11
+ declare function AccordionItem({ className, ...props }: React.ComponentProps<typeof AccordionPrimitive.Item>): React.JSX.Element;
12
+ declare function AccordionTrigger({ className, children, ...props }: React.ComponentProps<typeof AccordionPrimitive.Trigger>): React.JSX.Element;
13
+ declare function AccordionContent({ className, children, ...props }: React.ComponentProps<typeof AccordionPrimitive.Content>): React.JSX.Element;
14
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
@@ -0,0 +1,25 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { ChevronDownIcon } from "lucide-react";
4
+ import { Accordion as AccordionPrimitive } from "radix-ui";
5
+ import { cn } from "../cn.js";
6
+ /**
7
+ * Expand-collapse list (Radix) with single or multiple open items.
8
+ *
9
+ * Use for: progressive disclosure inside app surfaces.
10
+ * Avoid when: SEO matters on public content; the landing FAQ deliberately
11
+ * uses native details and summary so answers exist without JS.
12
+ */
13
+ function Accordion({ ...props }) {
14
+ return _jsx(AccordionPrimitive.Root, { "data-slot": "accordion", ...props });
15
+ }
16
+ function AccordionItem({ className, ...props }) {
17
+ return (_jsx(AccordionPrimitive.Item, { "data-slot": "accordion-item", className: cn("border-b last:border-b-0", className), ...props }));
18
+ }
19
+ function AccordionTrigger({ className, children, ...props }) {
20
+ return (_jsx(AccordionPrimitive.Header, { className: "flex", children: _jsxs(AccordionPrimitive.Trigger, { "data-slot": "accordion-trigger", className: cn("cursor-pointer focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-colors outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180", className), ...props, children: [children, _jsx(ChevronDownIcon, { className: "text-muted-foreground pointer-events-none size-4 shrink-0 translate-y-0.5 transition-transform duration-200" })] }) }));
21
+ }
22
+ function AccordionContent({ className, children, ...props }) {
23
+ return (_jsx(AccordionPrimitive.Content, { "data-slot": "accordion-content", className: "data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm", ...props, children: _jsx("div", { className: cn("pt-0 pb-4", className), children: children }) }));
24
+ }
25
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
@@ -0,0 +1,27 @@
1
+ import * as React from "react";
2
+ import { type VariantProps } from "class-variance-authority";
3
+ declare const buttonVariants: (props?: ({
4
+ variant?: "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
5
+ size?: "default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | null | undefined;
6
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
7
+ /**
8
+ * The tappable action primitive for everything except landing page pills.
9
+ *
10
+ * Use for: any action in the questionnaire, plan, checkout and download flows.
11
+ * Avoid when: styling a navigation link as text; use a plain anchor. On landing
12
+ * sections, follow the local pill pattern for now.
13
+ * Variants: default carries the step's one primary action; outline is the
14
+ * standard secondary (copy, save, discard, dismiss); ghost is back, cancel and
15
+ * edit-in-place; destructive is reserved for irreversible acts (unused today).
16
+ * Sizes: default (48px) almost always; sm in compact rows; lg only for
17
+ * money-adjacent hero actions like checkout.
18
+ * Radius: text buttons are rounded rectangles on a size-scaled corner ladder
19
+ * (xs rounded-sm, sm rounded-md, default rounded-lg, lg rounded-xl), so a
20
+ * flush full-bleed button can go concentric with its container (outer = inner
21
+ * radius + padding, e.g. default in a p-4 card resolves to rounded-4xl).
22
+ * Icon-only sizes (icon, icon-sm, icon-lg, icon-xs) stay circular.
23
+ */
24
+ declare function Button({ className, variant, size, asChild, ...props }: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & {
25
+ asChild?: boolean;
26
+ }): React.JSX.Element;
27
+ export { Button, buttonVariants };
@@ -0,0 +1,67 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { cva } from "class-variance-authority";
4
+ import { Slot } from "radix-ui";
5
+ import { cn } from "../cn.js";
6
+ const buttonVariants = cva("cursor-pointer inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg text-sm font-medium transition-[color,background-color,border-color,box-shadow,translate,scale] active:scale-[0.96] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", {
7
+ variants: {
8
+ variant: {
9
+ default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90 hover:-translate-y-0.5 active:translate-y-0",
10
+ destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
11
+ outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
12
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
13
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
14
+ link: "text-primary underline-offset-4 hover:underline",
15
+ },
16
+ size: {
17
+ // Optical alignment (SPEC_021): only the icon side tightens, so
18
+ // icon-plus-text buttons read centred. Works because Button wraps
19
+ // bare text children in a span (CSS cannot see text nodes, so
20
+ // without the wrap an only-icon-element is both :first-child and
21
+ // :last-child). Spinner-only buttons tighten both sides, which is
22
+ // the old symmetric behaviour.
23
+ default: "min-h-[48px] px-5 py-2 has-[>svg:first-child]:pl-4 has-[>svg:last-child]:pr-4",
24
+ xs: "h-6 gap-1 rounded-sm px-2 text-xs has-[>svg:first-child]:pl-1.5 has-[>svg:last-child]:pr-1.5 [&_svg:not([class*='size-'])]:size-3",
25
+ sm: "min-h-[44px] rounded-md gap-1.5 px-4 has-[>svg:first-child]:pl-3 has-[>svg:last-child]:pr-3",
26
+ lg: "min-h-[52px] rounded-xl px-8 text-[15px] has-[>svg:first-child]:pl-6 has-[>svg:last-child]:pr-6",
27
+ icon: "size-9 rounded-full",
28
+ "icon-xs": "size-6 rounded-full [&_svg:not([class*='size-'])]:size-3",
29
+ "icon-sm": "size-8 rounded-full",
30
+ "icon-lg": "size-10 rounded-full",
31
+ },
32
+ },
33
+ defaultVariants: {
34
+ variant: "default",
35
+ size: "default",
36
+ },
37
+ });
38
+ /**
39
+ * The tappable action primitive for everything except landing page pills.
40
+ *
41
+ * Use for: any action in the questionnaire, plan, checkout and download flows.
42
+ * Avoid when: styling a navigation link as text; use a plain anchor. On landing
43
+ * sections, follow the local pill pattern for now.
44
+ * Variants: default carries the step's one primary action; outline is the
45
+ * standard secondary (copy, save, discard, dismiss); ghost is back, cancel and
46
+ * edit-in-place; destructive is reserved for irreversible acts (unused today).
47
+ * Sizes: default (48px) almost always; sm in compact rows; lg only for
48
+ * money-adjacent hero actions like checkout.
49
+ * Radius: text buttons are rounded rectangles on a size-scaled corner ladder
50
+ * (xs rounded-sm, sm rounded-md, default rounded-lg, lg rounded-xl), so a
51
+ * flush full-bleed button can go concentric with its container (outer = inner
52
+ * radius + padding, e.g. default in a p-4 card resolves to rounded-4xl).
53
+ * Icon-only sizes (icon, icon-sm, icon-lg, icon-xs) stay circular.
54
+ */
55
+ function Button({ className, variant = "default", size = "default", asChild = false, ...props }) {
56
+ const Comp = asChild ? Slot.Root : "button";
57
+ // SPEC_021: wrap bare text children in a span so the directional
58
+ // has-[>svg:first-child] padding above can tell icon-leading from
59
+ // icon-trailing (text nodes are invisible to CSS child selectors).
60
+ // Skipped for asChild, where the single child element owns its content.
61
+ const { children, ...rest } = props;
62
+ const content = asChild
63
+ ? children
64
+ : React.Children.map(children, (child) => typeof child === "string" || typeof child === "number" ? (_jsx("span", { children: child })) : (child));
65
+ return (_jsx(Comp, { "data-slot": "button", "data-variant": variant, "data-size": size, className: cn(buttonVariants({ variant, size, className })), ...rest, children: content }));
66
+ }
67
+ export { Button, buttonVariants };
@@ -0,0 +1,16 @@
1
+ import * as React from "react";
2
+ import { DayPicker, type DayButton } from "react-day-picker";
3
+ import { Button } from "./button.js";
4
+ /**
5
+ * Date picker surface over react-day-picker.
6
+ *
7
+ * Use for: range selection or constrained date choice, if a surface ever
8
+ * needs more than a plain date.
9
+ * Avoid when: capturing a single date like the due date; the product uses
10
+ * native date inputs for the OS picker on mobile.
11
+ */
12
+ declare function Calendar({ className, classNames, showOutsideDays, captionLayout, buttonVariant, formatters, components, ...props }: React.ComponentProps<typeof DayPicker> & {
13
+ buttonVariant?: React.ComponentProps<typeof Button>["variant"];
14
+ }): React.JSX.Element;
15
+ declare function CalendarDayButton({ className, day, modifiers, ...props }: React.ComponentProps<typeof DayButton>): React.JSX.Element;
16
+ export { Calendar, CalendarDayButton };
@@ -0,0 +1,84 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon, } from "lucide-react";
5
+ import { DayPicker, getDefaultClassNames, } from "react-day-picker";
6
+ import { cn } from "../cn.js";
7
+ import { Button, buttonVariants } from "./button.js";
8
+ /**
9
+ * Date picker surface over react-day-picker.
10
+ *
11
+ * Use for: range selection or constrained date choice, if a surface ever
12
+ * needs more than a plain date.
13
+ * Avoid when: capturing a single date like the due date; the product uses
14
+ * native date inputs for the OS picker on mobile.
15
+ */
16
+ function Calendar({ className, classNames, showOutsideDays = true, captionLayout = "label", buttonVariant = "ghost", formatters, components, ...props }) {
17
+ const defaultClassNames = getDefaultClassNames();
18
+ return (_jsx(DayPicker, { showOutsideDays: showOutsideDays, className: cn("bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent", String.raw `rtl:**:[.rdp-button\_next>svg]:rotate-180`, String.raw `rtl:**:[.rdp-button\_previous>svg]:rotate-180`, className), captionLayout: captionLayout, formatters: {
19
+ formatMonthDropdown: (date) => date.toLocaleString("default", { month: "short" }),
20
+ ...formatters,
21
+ }, classNames: {
22
+ root: cn("w-fit", defaultClassNames.root),
23
+ months: cn("flex gap-4 flex-col md:flex-row relative", defaultClassNames.months),
24
+ month: cn("flex flex-col w-full gap-4", defaultClassNames.month),
25
+ nav: cn("flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between", defaultClassNames.nav),
26
+ button_previous: cn(buttonVariants({ variant: buttonVariant }), "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none", defaultClassNames.button_previous),
27
+ button_next: cn(buttonVariants({ variant: buttonVariant }), "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none", defaultClassNames.button_next),
28
+ month_caption: cn("flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)", defaultClassNames.month_caption),
29
+ dropdowns: cn("w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5", defaultClassNames.dropdowns),
30
+ dropdown_root: cn("relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md", defaultClassNames.dropdown_root),
31
+ dropdown: cn("absolute bg-popover inset-0 opacity-0", defaultClassNames.dropdown),
32
+ caption_label: cn("select-none font-medium", captionLayout === "label"
33
+ ? "text-sm"
34
+ : "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5", defaultClassNames.caption_label),
35
+ table: "w-full border-collapse",
36
+ weekdays: cn("flex", defaultClassNames.weekdays),
37
+ weekday: cn("text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none", defaultClassNames.weekday),
38
+ week: cn("flex w-full mt-2", defaultClassNames.week),
39
+ week_number_header: cn("select-none w-(--cell-size)", defaultClassNames.week_number_header),
40
+ week_number: cn("text-[0.8rem] select-none text-muted-foreground", defaultClassNames.week_number),
41
+ day: cn("relative w-full h-full p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none", props.showWeekNumber
42
+ ? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-md"
43
+ : "[&:first-child[data-selected=true]_button]:rounded-l-md", defaultClassNames.day),
44
+ range_start: cn("rounded-l-md bg-accent", defaultClassNames.range_start),
45
+ range_middle: cn("rounded-none", defaultClassNames.range_middle),
46
+ range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end),
47
+ today: cn("bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none", defaultClassNames.today),
48
+ outside: cn("text-muted-foreground aria-selected:text-muted-foreground", defaultClassNames.outside),
49
+ disabled: cn("text-muted-foreground opacity-50", defaultClassNames.disabled),
50
+ hidden: cn("invisible", defaultClassNames.hidden),
51
+ ...classNames,
52
+ }, components: {
53
+ Root: ({ className, rootRef, ...props }) => {
54
+ return (_jsx("div", { "data-slot": "calendar", ref: rootRef, className: cn(className), ...props }));
55
+ },
56
+ Chevron: ({ className, orientation, ...props }) => {
57
+ if (orientation === "left") {
58
+ return (_jsx(ChevronLeftIcon, { className: cn("size-4", className), ...props }));
59
+ }
60
+ if (orientation === "right") {
61
+ return (_jsx(ChevronRightIcon, { className: cn("size-4", className), ...props }));
62
+ }
63
+ return (_jsx(ChevronDownIcon, { className: cn("size-4", className), ...props }));
64
+ },
65
+ DayButton: CalendarDayButton,
66
+ WeekNumber: ({ children, ...props }) => {
67
+ return (_jsx("td", { ...props, children: _jsx("div", { className: "flex size-(--cell-size) items-center justify-center text-center", children: children }) }));
68
+ },
69
+ ...components,
70
+ }, ...props }));
71
+ }
72
+ function CalendarDayButton({ className, day, modifiers, ...props }) {
73
+ const defaultClassNames = getDefaultClassNames();
74
+ const ref = React.useRef(null);
75
+ React.useEffect(() => {
76
+ if (modifiers.focused)
77
+ ref.current?.focus();
78
+ }, [modifiers.focused]);
79
+ return (_jsx(Button, { ref: ref, variant: "ghost", size: "icon", "data-day": day.date.toLocaleDateString(), "data-selected-single": modifiers.selected &&
80
+ !modifiers.range_start &&
81
+ !modifiers.range_end &&
82
+ !modifiers.range_middle, "data-range-start": modifiers.range_start, "data-range-end": modifiers.range_end, "data-range-middle": modifiers.range_middle, className: cn("data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 dark:hover:text-accent-foreground flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md [&>span]:text-xs [&>span]:opacity-70", defaultClassNames.day, className), ...props }));
83
+ }
84
+ export { Calendar, CalendarDayButton };
@@ -0,0 +1,17 @@
1
+ import * as React from "react";
2
+ /**
3
+ * Surface family: Card with Header, Title, Description, Content, Footer.
4
+ *
5
+ * Use for: quickly composed standard surfaces where the stock look is fine.
6
+ * Avoid when: the surface must match the house card look (rounded-20 with
7
+ * warm shadows); today the app composes those from semantic utilities
8
+ * directly.
9
+ */
10
+ declare function Card({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
11
+ declare function CardHeader({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
12
+ declare function CardTitle({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
13
+ declare function CardDescription({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
14
+ declare function CardAction({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
15
+ declare function CardContent({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
16
+ declare function CardFooter({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
17
+ export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent, };
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cn } from "../cn.js";
3
+ /**
4
+ * Surface family: Card with Header, Title, Description, Content, Footer.
5
+ *
6
+ * Use for: quickly composed standard surfaces where the stock look is fine.
7
+ * Avoid when: the surface must match the house card look (rounded-20 with
8
+ * warm shadows); today the app composes those from semantic utilities
9
+ * directly.
10
+ */
11
+ function Card({ className, ...props }) {
12
+ return (_jsx("div", { "data-slot": "card", className: cn("bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm", className), ...props }));
13
+ }
14
+ function CardHeader({ className, ...props }) {
15
+ return (_jsx("div", { "data-slot": "card-header", className: cn("@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6", className), ...props }));
16
+ }
17
+ function CardTitle({ className, ...props }) {
18
+ return (_jsx("div", { "data-slot": "card-title", className: cn("leading-none font-semibold", className), ...props }));
19
+ }
20
+ function CardDescription({ className, ...props }) {
21
+ return (_jsx("div", { "data-slot": "card-description", className: cn("text-muted-foreground text-sm", className), ...props }));
22
+ }
23
+ function CardAction({ className, ...props }) {
24
+ return (_jsx("div", { "data-slot": "card-action", className: cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className), ...props }));
25
+ }
26
+ function CardContent({ className, ...props }) {
27
+ return (_jsx("div", { "data-slot": "card-content", className: cn("px-6", className), ...props }));
28
+ }
29
+ function CardFooter({ className, ...props }) {
30
+ return (_jsx("div", { "data-slot": "card-footer", className: cn("flex items-center px-6 [.border-t]:pt-6", className), ...props }));
31
+ }
32
+ export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent, };
@@ -0,0 +1,12 @@
1
+ import * as React from "react";
2
+ import { Checkbox as CheckboxPrimitive } from "radix-ui";
3
+ /**
4
+ * Standard checkbox (Radix) with the brand focus ring.
5
+ *
6
+ * Use for: dense forms and settings where a full tappable card is overkill,
7
+ * such as future account or admin surfaces.
8
+ * Avoid when: capturing questionnaire answers; the house pattern is
9
+ * MultiSelectCardGroup icon cards with 44px targets.
10
+ */
11
+ declare function Checkbox({ className, ...props }: React.ComponentProps<typeof CheckboxPrimitive.Root>): React.JSX.Element;
12
+ export { Checkbox };
@@ -0,0 +1,17 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { CheckIcon } from "lucide-react";
4
+ import { Checkbox as CheckboxPrimitive } from "radix-ui";
5
+ import { cn } from "../cn.js";
6
+ /**
7
+ * Standard checkbox (Radix) with the brand focus ring.
8
+ *
9
+ * Use for: dense forms and settings where a full tappable card is overkill,
10
+ * such as future account or admin surfaces.
11
+ * Avoid when: capturing questionnaire answers; the house pattern is
12
+ * MultiSelectCardGroup icon cards with 44px targets.
13
+ */
14
+ function Checkbox({ className, ...props }) {
15
+ return (_jsx(CheckboxPrimitive.Root, { "data-slot": "checkbox", className: cn("cursor-pointer peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50", className), ...props, children: _jsx(CheckboxPrimitive.Indicator, { "data-slot": "checkbox-indicator", className: "grid place-content-center text-current transition-none", children: _jsx(CheckIcon, { className: "size-3.5" }) }) }));
16
+ }
17
+ export { Checkbox };
@@ -0,0 +1,23 @@
1
+ import * as React from "react";
2
+ import { Dialog as DialogPrimitive } from "radix-ui";
3
+ /**
4
+ * Centred modal (Radix) for a focused task over dimmed content.
5
+ *
6
+ * Use for: previews and confirmations that need full attention, like the
7
+ * landing example-output preview.
8
+ * Avoid when: the surface is a mobile flow aid; use Sheet side bottom. The
9
+ * QR share overlay and paywall keep their custom overlays.
10
+ */
11
+ declare function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>): React.JSX.Element;
12
+ declare function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>): React.JSX.Element;
13
+ declare function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>): React.JSX.Element;
14
+ declare function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>): React.JSX.Element;
15
+ declare function DialogOverlay({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Overlay>): React.JSX.Element;
16
+ declare function DialogContent({ className, children, showCloseButton, ...props }: React.ComponentProps<typeof DialogPrimitive.Content> & {
17
+ showCloseButton?: boolean;
18
+ }): React.JSX.Element;
19
+ declare function DialogHeader({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
20
+ declare function DialogFooter({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
21
+ declare function DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>): React.JSX.Element;
22
+ declare function DialogDescription({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Description>): React.JSX.Element;
23
+ export { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, };
@@ -0,0 +1,44 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { XIcon } from "lucide-react";
4
+ import { Dialog as DialogPrimitive } from "radix-ui";
5
+ import { cn } from "../cn.js";
6
+ /**
7
+ * Centred modal (Radix) for a focused task over dimmed content.
8
+ *
9
+ * Use for: previews and confirmations that need full attention, like the
10
+ * landing example-output preview.
11
+ * Avoid when: the surface is a mobile flow aid; use Sheet side bottom. The
12
+ * QR share overlay and paywall keep their custom overlays.
13
+ */
14
+ function Dialog({ ...props }) {
15
+ return _jsx(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
16
+ }
17
+ function DialogTrigger({ ...props }) {
18
+ return _jsx(DialogPrimitive.Trigger, { "data-slot": "dialog-trigger", ...props });
19
+ }
20
+ function DialogPortal({ ...props }) {
21
+ return _jsx(DialogPrimitive.Portal, { "data-slot": "dialog-portal", ...props });
22
+ }
23
+ function DialogClose({ ...props }) {
24
+ return _jsx(DialogPrimitive.Close, { "data-slot": "dialog-close", ...props });
25
+ }
26
+ function DialogOverlay({ className, ...props }) {
27
+ return (_jsx(DialogPrimitive.Overlay, { "data-slot": "dialog-overlay", className: cn("data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50", className), ...props }));
28
+ }
29
+ function DialogContent({ className, children, showCloseButton = true, ...props }) {
30
+ return (_jsxs(DialogPortal, { children: [_jsx(DialogOverlay, {}), _jsxs(DialogPrimitive.Content, { "data-slot": "dialog-content", className: cn("bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-2xl border p-6 shadow-lg duration-200 sm:max-w-lg", className), ...props, children: [children, showCloseButton && (_jsxs(DialogPrimitive.Close, { "data-slot": "dialog-close", className: "ring-offset-background focus-visible:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 grid size-8 place-items-center rounded-full opacity-70 transition-opacity hover:opacity-100 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:pointer-events-none", children: [_jsx(XIcon, { className: "size-4" }), _jsx("span", { className: "sr-only", children: "Close" })] }))] })] }));
31
+ }
32
+ function DialogHeader({ className, ...props }) {
33
+ return (_jsx("div", { "data-slot": "dialog-header", className: cn("flex flex-col gap-1.5 text-center sm:text-left", className), ...props }));
34
+ }
35
+ function DialogFooter({ className, ...props }) {
36
+ return (_jsx("div", { "data-slot": "dialog-footer", className: cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className), ...props }));
37
+ }
38
+ function DialogTitle({ className, ...props }) {
39
+ return (_jsx(DialogPrimitive.Title, { "data-slot": "dialog-title", className: cn("text-lg leading-none font-semibold", className), ...props }));
40
+ }
41
+ function DialogDescription({ className, ...props }) {
42
+ return (_jsx(DialogPrimitive.Description, { "data-slot": "dialog-description", className: cn("text-muted-foreground text-sm", className), ...props }));
43
+ }
44
+ export { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger, };
@@ -0,0 +1,33 @@
1
+ import * as React from "react";
2
+ import type { Label as LabelPrimitive } from "radix-ui";
3
+ import { Slot } from "radix-ui";
4
+ import { type ControllerProps, type FieldPath, type FieldValues } from "react-hook-form";
5
+ declare const Form: <TFieldValues extends FieldValues, TContext = any, TTransformedValues = TFieldValues>({ children, watch, getValues, getErrors, getFieldState, setError, clearErrors, setValue, setValues, trigger, formState, resetField, reset, resetDefaultValues, handleSubmit, unregister, control, register, setFocus, subscribe, }: import("react-hook-form").FormProviderProps<TFieldValues, TContext, TTransformedValues>) => React.JSX.Element;
6
+ declare const FormField: <TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>>({ ...props }: ControllerProps<TFieldValues, TName>) => React.JSX.Element;
7
+ declare const useFormField: () => {
8
+ invalid: boolean;
9
+ isDirty: boolean;
10
+ isTouched: boolean;
11
+ isValidating: boolean;
12
+ error?: import("react-hook-form").FieldError | undefined;
13
+ id: string;
14
+ name: string;
15
+ formItemId: string;
16
+ formDescriptionId: string;
17
+ formMessageId: string;
18
+ };
19
+ /**
20
+ * react-hook-form glue: FormField, FormItem, FormLabel, FormControl,
21
+ * FormMessage, FormDescription.
22
+ *
23
+ * Use for: every RHF-driven field; one FormField per question or input.
24
+ * Avoid when: a field lives outside RHF; use Label plus Input directly.
25
+ * Note: wrap forms in FormProvider from react-hook-form itself; the Form
26
+ * alias export is unused in this app.
27
+ */
28
+ declare function FormItem({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
29
+ declare function FormLabel({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>): React.JSX.Element;
30
+ declare function FormControl({ ...props }: React.ComponentProps<typeof Slot.Root>): React.JSX.Element;
31
+ declare function FormDescription({ className, ...props }: React.ComponentProps<"p">): React.JSX.Element;
32
+ declare function FormMessage({ className, ...props }: React.ComponentProps<"p">): React.JSX.Element | null;
33
+ export { useFormField, Form, FormItem, FormLabel, FormControl, FormDescription, FormMessage, FormField, };
@@ -0,0 +1,68 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Slot } from "radix-ui";
5
+ import { Controller, FormProvider, useFormContext, useFormState, } from "react-hook-form";
6
+ import { cn } from "../cn.js";
7
+ import { Label } from "./label.js";
8
+ const Form = FormProvider;
9
+ const FormFieldContext = React.createContext({});
10
+ const FormField = ({ ...props }) => {
11
+ return (_jsx(FormFieldContext.Provider, { value: { name: props.name }, children: _jsx(Controller, { ...props }) }));
12
+ };
13
+ const useFormField = () => {
14
+ const fieldContext = React.useContext(FormFieldContext);
15
+ const itemContext = React.useContext(FormItemContext);
16
+ const { getFieldState } = useFormContext();
17
+ const formState = useFormState({ name: fieldContext.name });
18
+ const fieldState = getFieldState(fieldContext.name, formState);
19
+ if (!fieldContext) {
20
+ throw new Error("useFormField should be used within <FormField>");
21
+ }
22
+ const { id } = itemContext;
23
+ return {
24
+ id,
25
+ name: fieldContext.name,
26
+ formItemId: `${id}-form-item`,
27
+ formDescriptionId: `${id}-form-item-description`,
28
+ formMessageId: `${id}-form-item-message`,
29
+ ...fieldState,
30
+ };
31
+ };
32
+ const FormItemContext = React.createContext({});
33
+ /**
34
+ * react-hook-form glue: FormField, FormItem, FormLabel, FormControl,
35
+ * FormMessage, FormDescription.
36
+ *
37
+ * Use for: every RHF-driven field; one FormField per question or input.
38
+ * Avoid when: a field lives outside RHF; use Label plus Input directly.
39
+ * Note: wrap forms in FormProvider from react-hook-form itself; the Form
40
+ * alias export is unused in this app.
41
+ */
42
+ function FormItem({ className, ...props }) {
43
+ const id = React.useId();
44
+ return (_jsx(FormItemContext.Provider, { value: { id }, children: _jsx("div", { "data-slot": "form-item", className: cn("grid gap-2", className), ...props }) }));
45
+ }
46
+ function FormLabel({ className, ...props }) {
47
+ const { error, formItemId } = useFormField();
48
+ return (_jsx(Label, { "data-slot": "form-label", "data-error": !!error, className: cn("data-[error=true]:text-destructive", className), htmlFor: formItemId, ...props }));
49
+ }
50
+ function FormControl({ ...props }) {
51
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField();
52
+ return (_jsx(Slot.Root, { "data-slot": "form-control", id: formItemId, "aria-describedby": !error
53
+ ? `${formDescriptionId}`
54
+ : `${formDescriptionId} ${formMessageId}`, "aria-invalid": !!error, ...props }));
55
+ }
56
+ function FormDescription({ className, ...props }) {
57
+ const { formDescriptionId } = useFormField();
58
+ return (_jsx("p", { "data-slot": "form-description", id: formDescriptionId, className: cn("text-muted-foreground text-sm", className), ...props }));
59
+ }
60
+ function FormMessage({ className, ...props }) {
61
+ const { error, formMessageId } = useFormField();
62
+ const body = error ? String(error?.message ?? "") : props.children;
63
+ if (!body) {
64
+ return null;
65
+ }
66
+ return (_jsx("p", { "data-slot": "form-message", id: formMessageId, role: "alert", className: cn("text-destructive text-sm", className), ...props, children: body }));
67
+ }
68
+ export { useFormField, Form, FormItem, FormLabel, FormControl, FormDescription, FormMessage, FormField, };