@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
@@ -0,0 +1,12 @@
1
+ import * as React from "react";
2
+ declare const inputBaseClassName: string;
3
+ /**
4
+ * Single-line text field, controlled-first (coerces value to empty string).
5
+ *
6
+ * Use for: react-hook-form fields via FormControl, and standalone controlled
7
+ * fields like the delivery email. inputBaseClassName lets lookalike inputs
8
+ * (PlaceAutocomplete) match without duplicating classes.
9
+ * Avoid when: capturing an enumerable choice; use OptionCard groups.
10
+ */
11
+ declare function Input({ className, type, value, ...props }: React.ComponentProps<"input">): React.JSX.Element;
12
+ export { Input, inputBaseClassName };
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cn } from "../cn.js";
3
+ // Shared so other inputs (e.g. PlaceAutocomplete) can match the shadcn look
4
+ // without duplicating the class string.
5
+ const inputBaseClassName = cn("file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input min-h-[48px] w-full min-w-0 rounded-md border bg-card px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50", "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");
6
+ /**
7
+ * Single-line text field, controlled-first (coerces value to empty string).
8
+ *
9
+ * Use for: react-hook-form fields via FormControl, and standalone controlled
10
+ * fields like the delivery email. inputBaseClassName lets lookalike inputs
11
+ * (PlaceAutocomplete) match without duplicating classes.
12
+ * Avoid when: capturing an enumerable choice; use OptionCard groups.
13
+ */
14
+ function Input({ className, type, value, ...props }) {
15
+ return (_jsx("input", { type: type, "data-slot": "input",
16
+ // Coerce undefined/null to '' to prevent React uncontrolled-to-controlled warnings
17
+ value: value ?? '', className: cn(inputBaseClassName, className), ...props }));
18
+ }
19
+ export { Input, inputBaseClassName };
@@ -0,0 +1,11 @@
1
+ import * as React from "react";
2
+ import { Label as LabelPrimitive } from "radix-ui";
3
+ /**
4
+ * Form field label with peer-disabled handling.
5
+ *
6
+ * Use for: labelling standalone controlled fields outside react-hook-form.
7
+ * Avoid when: inside a FormField; use FormLabel, which wires error state
8
+ * and htmlFor automatically.
9
+ */
10
+ declare function Label({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>): React.JSX.Element;
11
+ export { Label };
@@ -0,0 +1,15 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { Label as LabelPrimitive } from "radix-ui";
4
+ import { cn } from "../cn.js";
5
+ /**
6
+ * Form field label with peer-disabled handling.
7
+ *
8
+ * Use for: labelling standalone controlled fields outside react-hook-form.
9
+ * Avoid when: inside a FormField; use FormLabel, which wires error state
10
+ * and htmlFor automatically.
11
+ */
12
+ function Label({ className, ...props }) {
13
+ return (_jsx(LabelPrimitive.Root, { "data-slot": "label", className: cn("flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50", className), ...props }));
14
+ }
15
+ export { Label };
@@ -0,0 +1,17 @@
1
+ import * as React from "react";
2
+ import { Popover as PopoverPrimitive } from "radix-ui";
3
+ /**
4
+ * Anchored floating panel (Radix), non-modal.
5
+ *
6
+ * Use for: attaching transient UI to a control, like the PlaceAutocomplete
7
+ * suggestion list (anchor on the input so focus never moves).
8
+ * Avoid when: the content is a task or announcement; use Dialog or Sheet.
9
+ */
10
+ declare function Popover({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Root>): React.JSX.Element;
11
+ declare function PopoverTrigger({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Trigger>): React.JSX.Element;
12
+ declare function PopoverContent({ className, align, sideOffset, ...props }: React.ComponentProps<typeof PopoverPrimitive.Content>): React.JSX.Element;
13
+ declare function PopoverAnchor({ ...props }: React.ComponentProps<typeof PopoverPrimitive.Anchor>): React.JSX.Element;
14
+ declare function PopoverHeader({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
15
+ declare function PopoverTitle({ className, ...props }: React.ComponentProps<"h2">): React.JSX.Element;
16
+ declare function PopoverDescription({ className, ...props }: React.ComponentProps<"p">): React.JSX.Element;
17
+ export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor, PopoverHeader, PopoverTitle, PopoverDescription, };
@@ -0,0 +1,33 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { Popover as PopoverPrimitive } from "radix-ui";
4
+ import { cn } from "../cn.js";
5
+ /**
6
+ * Anchored floating panel (Radix), non-modal.
7
+ *
8
+ * Use for: attaching transient UI to a control, like the PlaceAutocomplete
9
+ * suggestion list (anchor on the input so focus never moves).
10
+ * Avoid when: the content is a task or announcement; use Dialog or Sheet.
11
+ */
12
+ function Popover({ ...props }) {
13
+ return _jsx(PopoverPrimitive.Root, { "data-slot": "popover", ...props });
14
+ }
15
+ function PopoverTrigger({ ...props }) {
16
+ return _jsx(PopoverPrimitive.Trigger, { "data-slot": "popover-trigger", ...props });
17
+ }
18
+ function PopoverContent({ className, align = "center", sideOffset = 4, ...props }) {
19
+ return (_jsx(PopoverPrimitive.Portal, { children: _jsx(PopoverPrimitive.Content, { "data-slot": "popover-content", align: align, sideOffset: sideOffset, className: cn("bg-popover text-popover-foreground 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 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden", className), ...props }) }));
20
+ }
21
+ function PopoverAnchor({ ...props }) {
22
+ return _jsx(PopoverPrimitive.Anchor, { "data-slot": "popover-anchor", ...props });
23
+ }
24
+ function PopoverHeader({ className, ...props }) {
25
+ return (_jsx("div", { "data-slot": "popover-header", className: cn("flex flex-col gap-1 text-sm", className), ...props }));
26
+ }
27
+ function PopoverTitle({ className, ...props }) {
28
+ return (_jsx("div", { "data-slot": "popover-title", className: cn("font-medium", className), ...props }));
29
+ }
30
+ function PopoverDescription({ className, ...props }) {
31
+ return (_jsx("p", { "data-slot": "popover-description", className: cn("text-muted-foreground", className), ...props }));
32
+ }
33
+ export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor, PopoverHeader, PopoverTitle, PopoverDescription, };
@@ -0,0 +1,11 @@
1
+ import * as React from "react";
2
+ import { Progress as ProgressPrimitive } from "radix-ui";
3
+ /**
4
+ * Determinate progress bar, flat primary fill.
5
+ *
6
+ * Use for: simple completion indicators in future utilitarian UI.
7
+ * Avoid when: the bar is a brand moment; the questionnaire's gradient
8
+ * progress bars are bespoke for that reason.
9
+ */
10
+ declare function Progress({ className, value, ...props }: React.ComponentProps<typeof ProgressPrimitive.Root>): React.JSX.Element;
11
+ export { Progress };
@@ -0,0 +1,15 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { Progress as ProgressPrimitive } from "radix-ui";
4
+ import { cn } from "../cn.js";
5
+ /**
6
+ * Determinate progress bar, flat primary fill.
7
+ *
8
+ * Use for: simple completion indicators in future utilitarian UI.
9
+ * Avoid when: the bar is a brand moment; the questionnaire's gradient
10
+ * progress bars are bespoke for that reason.
11
+ */
12
+ function Progress({ className, value, ...props }) {
13
+ return (_jsx(ProgressPrimitive.Root, { "data-slot": "progress", className: cn("bg-primary/20 relative h-2 w-full overflow-hidden rounded-full", className), ...props, children: _jsx(ProgressPrimitive.Indicator, { "data-slot": "progress-indicator", className: "bg-primary h-full w-full flex-1 transition-transform", style: { transform: `translateX(-${100 - (value || 0)}%)` } }) }));
14
+ }
15
+ export { Progress };
@@ -0,0 +1,12 @@
1
+ import * as React from "react";
2
+ import { RadioGroup as RadioGroupPrimitive } from "radix-ui";
3
+ /**
4
+ * Standard radio group (Radix) for compact single choice.
5
+ *
6
+ * Use for: short enumerations in dense or utilitarian UI.
7
+ * Avoid when: asking birth plan questions; the house pattern is
8
+ * SingleSelectCardGroup icon cards, not radios.
9
+ */
10
+ declare function RadioGroup({ className, ...props }: React.ComponentProps<typeof RadioGroupPrimitive.Root>): React.JSX.Element;
11
+ declare function RadioGroupItem({ className, ...props }: React.ComponentProps<typeof RadioGroupPrimitive.Item>): React.JSX.Element;
12
+ export { RadioGroup, RadioGroupItem };
@@ -0,0 +1,19 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { CircleIcon } from "lucide-react";
4
+ import { RadioGroup as RadioGroupPrimitive } from "radix-ui";
5
+ import { cn } from "../cn.js";
6
+ /**
7
+ * Standard radio group (Radix) for compact single choice.
8
+ *
9
+ * Use for: short enumerations in dense or utilitarian UI.
10
+ * Avoid when: asking birth plan questions; the house pattern is
11
+ * SingleSelectCardGroup icon cards, not radios.
12
+ */
13
+ function RadioGroup({ className, ...props }) {
14
+ return (_jsx(RadioGroupPrimitive.Root, { "data-slot": "radio-group", className: cn("grid gap-3", className), ...props }));
15
+ }
16
+ function RadioGroupItem({ className, ...props }) {
17
+ return (_jsx(RadioGroupPrimitive.Item, { "data-slot": "radio-group-item", className: cn("border-input text-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 dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50", className), ...props, children: _jsx(RadioGroupPrimitive.Indicator, { "data-slot": "radio-group-indicator", className: "relative flex items-center justify-center", children: _jsx(CircleIcon, { className: "fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" }) }) }));
18
+ }
19
+ export { RadioGroup, RadioGroupItem };
@@ -0,0 +1,23 @@
1
+ import * as React from "react";
2
+ import { Select as SelectPrimitive } from "radix-ui";
3
+ /**
4
+ * Dropdown select (Radix) for choosing one value from a longer list.
5
+ *
6
+ * Use for: long enumerations where cards or radios would sprawl,
7
+ * such as future admin or filter surfaces.
8
+ * Avoid when: the list is short and tappable; icon cards or chip toggles
9
+ * fit the product better. Date entry uses native date inputs, not Select.
10
+ */
11
+ declare function Select({ ...props }: React.ComponentProps<typeof SelectPrimitive.Root>): React.JSX.Element;
12
+ declare function SelectGroup({ ...props }: React.ComponentProps<typeof SelectPrimitive.Group>): React.JSX.Element;
13
+ declare function SelectValue({ ...props }: React.ComponentProps<typeof SelectPrimitive.Value>): React.JSX.Element;
14
+ declare function SelectTrigger({ className, size, children, ...props }: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
15
+ size?: "sm" | "default";
16
+ }): React.JSX.Element;
17
+ declare function SelectContent({ className, children, position, align, ...props }: React.ComponentProps<typeof SelectPrimitive.Content>): React.JSX.Element;
18
+ declare function SelectLabel({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Label>): React.JSX.Element;
19
+ declare function SelectItem({ className, children, ...props }: React.ComponentProps<typeof SelectPrimitive.Item>): React.JSX.Element;
20
+ declare function SelectSeparator({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Separator>): React.JSX.Element;
21
+ declare function SelectScrollUpButton({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>): React.JSX.Element;
22
+ declare function SelectScrollDownButton({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>): React.JSX.Element;
23
+ export { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, };
@@ -0,0 +1,46 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
4
+ import { Select as SelectPrimitive } from "radix-ui";
5
+ import { cn } from "../cn.js";
6
+ /**
7
+ * Dropdown select (Radix) for choosing one value from a longer list.
8
+ *
9
+ * Use for: long enumerations where cards or radios would sprawl,
10
+ * such as future admin or filter surfaces.
11
+ * Avoid when: the list is short and tappable; icon cards or chip toggles
12
+ * fit the product better. Date entry uses native date inputs, not Select.
13
+ */
14
+ function Select({ ...props }) {
15
+ return _jsx(SelectPrimitive.Root, { "data-slot": "select", ...props });
16
+ }
17
+ function SelectGroup({ ...props }) {
18
+ return _jsx(SelectPrimitive.Group, { "data-slot": "select-group", ...props });
19
+ }
20
+ function SelectValue({ ...props }) {
21
+ return _jsx(SelectPrimitive.Value, { "data-slot": "select-value", ...props });
22
+ }
23
+ function SelectTrigger({ className, size = "default", children, ...props }) {
24
+ return (_jsxs(SelectPrimitive.Trigger, { "data-slot": "select-trigger", "data-size": size, className: cn("cursor-pointer border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-card px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", className), ...props, children: [children, _jsx(SelectPrimitive.Icon, { asChild: true, children: _jsx(ChevronDownIcon, { className: "size-4 opacity-50" }) })] }));
25
+ }
26
+ function SelectContent({ className, children, position = "item-aligned", align = "center", ...props }) {
27
+ return (_jsx(SelectPrimitive.Portal, { children: _jsxs(SelectPrimitive.Content, { "data-slot": "select-content", className: cn("bg-popover text-popover-foreground 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 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md", position === "popper" &&
28
+ "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className), position: position, align: align, ...props, children: [_jsx(SelectScrollUpButton, {}), _jsx(SelectPrimitive.Viewport, { className: cn("p-1", position === "popper" &&
29
+ "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"), children: children }), _jsx(SelectScrollDownButton, {})] }) }));
30
+ }
31
+ function SelectLabel({ className, ...props }) {
32
+ return (_jsx(SelectPrimitive.Label, { "data-slot": "select-label", className: cn("text-muted-foreground px-2 py-1.5 text-xs", className), ...props }));
33
+ }
34
+ function SelectItem({ className, children, ...props }) {
35
+ return (_jsxs(SelectPrimitive.Item, { "data-slot": "select-item", className: cn("focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2", className), ...props, children: [_jsx("span", { "data-slot": "select-item-indicator", className: "absolute right-2 flex size-3.5 items-center justify-center", children: _jsx(SelectPrimitive.ItemIndicator, { children: _jsx(CheckIcon, { className: "size-4" }) }) }), _jsx(SelectPrimitive.ItemText, { children: children })] }));
36
+ }
37
+ function SelectSeparator({ className, ...props }) {
38
+ return (_jsx(SelectPrimitive.Separator, { "data-slot": "select-separator", className: cn("bg-border pointer-events-none -mx-1 my-1 h-px", className), ...props }));
39
+ }
40
+ function SelectScrollUpButton({ className, ...props }) {
41
+ return (_jsx(SelectPrimitive.ScrollUpButton, { "data-slot": "select-scroll-up-button", className: cn("flex cursor-default items-center justify-center py-1", className), ...props, children: _jsx(ChevronUpIcon, { className: "size-4" }) }));
42
+ }
43
+ function SelectScrollDownButton({ className, ...props }) {
44
+ return (_jsx(SelectPrimitive.ScrollDownButton, { "data-slot": "select-scroll-down-button", className: cn("flex cursor-default items-center justify-center py-1", className), ...props, children: _jsx(ChevronDownIcon, { className: "size-4" }) }));
45
+ }
46
+ export { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, };
@@ -0,0 +1,23 @@
1
+ import * as React from "react";
2
+ import { Dialog as SheetPrimitive } from "radix-ui";
3
+ /**
4
+ * Sliding panel (Radix). In this product it is used exclusively as a
5
+ * mobile bottom sheet.
6
+ *
7
+ * Use for: supplementary mobile content: plan preview, question info,
8
+ * did-you-know detail. Callers add rounded-t-xl and a max height.
9
+ * Avoid when: content must persist beside the page on desktop; render an
10
+ * aside instead (see DidYouKnowBar's breakpoint split).
11
+ */
12
+ declare function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>): React.JSX.Element;
13
+ declare function SheetTrigger({ ...props }: React.ComponentProps<typeof SheetPrimitive.Trigger>): React.JSX.Element;
14
+ declare function SheetClose({ ...props }: React.ComponentProps<typeof SheetPrimitive.Close>): React.JSX.Element;
15
+ declare function SheetContent({ className, children, side, showCloseButton, ...props }: React.ComponentProps<typeof SheetPrimitive.Content> & {
16
+ side?: "top" | "right" | "bottom" | "left";
17
+ showCloseButton?: boolean;
18
+ }): React.JSX.Element;
19
+ declare function SheetHeader({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
20
+ declare function SheetFooter({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
21
+ declare function SheetTitle({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Title>): React.JSX.Element;
22
+ declare function SheetDescription({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Description>): React.JSX.Element;
23
+ export { Sheet, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription, };
@@ -0,0 +1,49 @@
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 SheetPrimitive } from "radix-ui";
5
+ import { cn } from "../cn.js";
6
+ /**
7
+ * Sliding panel (Radix). In this product it is used exclusively as a
8
+ * mobile bottom sheet.
9
+ *
10
+ * Use for: supplementary mobile content: plan preview, question info,
11
+ * did-you-know detail. Callers add rounded-t-xl and a max height.
12
+ * Avoid when: content must persist beside the page on desktop; render an
13
+ * aside instead (see DidYouKnowBar's breakpoint split).
14
+ */
15
+ function Sheet({ ...props }) {
16
+ return _jsx(SheetPrimitive.Root, { "data-slot": "sheet", ...props });
17
+ }
18
+ function SheetTrigger({ ...props }) {
19
+ return _jsx(SheetPrimitive.Trigger, { "data-slot": "sheet-trigger", ...props });
20
+ }
21
+ function SheetClose({ ...props }) {
22
+ return _jsx(SheetPrimitive.Close, { "data-slot": "sheet-close", ...props });
23
+ }
24
+ function SheetPortal({ ...props }) {
25
+ return _jsx(SheetPrimitive.Portal, { "data-slot": "sheet-portal", ...props });
26
+ }
27
+ function SheetOverlay({ className, ...props }) {
28
+ return (_jsx(SheetPrimitive.Overlay, { "data-slot": "sheet-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 }));
29
+ }
30
+ function SheetContent({ className, children, side = "right", showCloseButton = true, ...props }) {
31
+ return (_jsxs(SheetPortal, { children: [_jsx(SheetOverlay, {}), _jsxs(SheetPrimitive.Content, { "data-slot": "sheet-content", className: cn("bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500", side === "right" &&
32
+ "data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm", side === "left" &&
33
+ "data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm", side === "top" &&
34
+ "data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b", side === "bottom" &&
35
+ "data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t", className), ...props, children: [children, showCloseButton && (_jsxs(SheetPrimitive.Close, { className: "ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none", children: [_jsx(XIcon, { className: "size-4" }), _jsx("span", { className: "sr-only", children: "Close" })] }))] })] }));
36
+ }
37
+ function SheetHeader({ className, ...props }) {
38
+ return (_jsx("div", { "data-slot": "sheet-header", className: cn("flex flex-col gap-1.5 p-4", className), ...props }));
39
+ }
40
+ function SheetFooter({ className, ...props }) {
41
+ return (_jsx("div", { "data-slot": "sheet-footer", className: cn("mt-auto flex flex-col gap-2 p-4", className), ...props }));
42
+ }
43
+ function SheetTitle({ className, ...props }) {
44
+ return (_jsx(SheetPrimitive.Title, { "data-slot": "sheet-title", className: cn("text-foreground font-semibold", className), ...props }));
45
+ }
46
+ function SheetDescription({ className, ...props }) {
47
+ return (_jsx(SheetPrimitive.Description, { "data-slot": "sheet-description", className: cn("text-muted-foreground text-sm", className), ...props }));
48
+ }
49
+ export { Sheet, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription, };
@@ -0,0 +1,26 @@
1
+ export interface SortableItem {
2
+ id: string;
3
+ label: string;
4
+ }
5
+ export interface SortableListProps {
6
+ /** All available items (unranked pool) */
7
+ available: SortableItem[];
8
+ /** Currently ranked items (in order) */
9
+ ranked: SortableItem[];
10
+ /** Maximum items in the ranked list */
11
+ maxRanked: number;
12
+ /** Called when ranked list changes */
13
+ onRankedChange: (ranked: SortableItem[]) => void;
14
+ /** Label for the ranked zone */
15
+ rankedLabel?: string;
16
+ /** Label for the available zone */
17
+ availableLabel?: string;
18
+ }
19
+ /**
20
+ * Drag-to-rank with two zones: a capped ranked list and a tap-to-add pool.
21
+ * Touch, mouse and keyboard support; drag is scoped to the grip handle.
22
+ *
23
+ * Use for: ranking a small capped set, like the Top 5 priorities editor.
24
+ * Avoid when: order does not matter; use MultiSelectCardGroup.
25
+ */
26
+ export declare function SortableList({ available, ranked, maxRanked, onRankedChange, rankedLabel, availableLabel, }: SortableListProps): import("react").JSX.Element;
@@ -0,0 +1,92 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ // =============================================================================
4
+ // SortableList
5
+ //
6
+ // Reusable drag-to-rank component for ranking a small capped set.
7
+ // Two zones: "Ranked" (sortable via @dnd-kit, max N items) and
8
+ // "Available" (tap to add). Touch + mouse + keyboard support.
9
+ //
10
+ // Drag listeners are scoped to the grip handle only so that taps on
11
+ // the remove button and item body work normally on touch devices.
12
+ // =============================================================================
13
+ import { useCallback, useId } from 'react';
14
+ import { DndContext, closestCenter, KeyboardSensor, PointerSensor, TouchSensor, useSensor, useSensors, } from '@dnd-kit/core';
15
+ import { SortableContext, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy, arrayMove, } from '@dnd-kit/sortable';
16
+ import { CSS } from '@dnd-kit/utilities';
17
+ import { cn } from '../cn.js';
18
+ // -----------------------------------------------------------------------------
19
+ // Drag Handle (visual grip icon; only this element receives drag listeners)
20
+ // -----------------------------------------------------------------------------
21
+ function DragHandle({ listeners, attributes, }) {
22
+ return (_jsx("button", { type: "button", className: "flex h-8 w-8 shrink-0 cursor-grab items-center justify-center rounded text-muted-foreground touch-none hover:text-foreground active:cursor-grabbing", "aria-label": "Drag to reorder", ...attributes, ...listeners, children: _jsxs("svg", { className: "h-4 w-4", viewBox: "0 0 16 16", fill: "currentColor", "aria-hidden": "true", children: [_jsx("circle", { cx: "5", cy: "3", r: "1.5" }), _jsx("circle", { cx: "11", cy: "3", r: "1.5" }), _jsx("circle", { cx: "5", cy: "8", r: "1.5" }), _jsx("circle", { cx: "11", cy: "8", r: "1.5" }), _jsx("circle", { cx: "5", cy: "13", r: "1.5" }), _jsx("circle", { cx: "11", cy: "13", r: "1.5" })] }) }));
23
+ }
24
+ // -----------------------------------------------------------------------------
25
+ // Sortable Item (ranked zone)
26
+ // -----------------------------------------------------------------------------
27
+ function SortableRankedItem({ item, rank, onRemove, }) {
28
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging, } = useSortable({ id: item.id });
29
+ const style = {
30
+ transform: CSS.Transform.toString(transform),
31
+ transition,
32
+ };
33
+ return (_jsxs("div", { ref: setNodeRef, style: style, className: cn('flex items-center gap-2 rounded-lg border bg-card p-3 select-none', isDragging
34
+ ? 'z-50 shadow-lg border-primary ring-2 ring-primary/20'
35
+ : 'border-border'), children: [_jsx("span", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground text-sm font-semibold", "aria-hidden": "true", children: rank }), _jsx(DragHandle, { listeners: listeners, attributes: attributes }), _jsx("span", { className: "flex-1 text-sm font-medium", children: item.label }), _jsx("button", { type: "button", onClick: onRemove, className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-muted-foreground hover:bg-destructive/10 hover:text-destructive transition-colors", "aria-label": `Remove ${item.label} from ranked list`, children: _jsx("svg", { className: "h-4 w-4", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "2", children: _jsx("path", { d: "M4 4l8 8M12 4l-8 8" }) }) })] }));
36
+ }
37
+ // -----------------------------------------------------------------------------
38
+ // Available Item (unranked pool)
39
+ // -----------------------------------------------------------------------------
40
+ function AvailableItem({ item, onAdd, disabled, }) {
41
+ return (_jsxs("button", { type: "button", onClick: onAdd, disabled: disabled, className: cn('flex w-full items-center gap-3 rounded-lg border border-dashed p-3 text-left transition-colors', disabled
42
+ ? 'border-border/50 text-muted-foreground/50 cursor-not-allowed'
43
+ : 'border-border text-foreground hover:border-primary hover:bg-primary/5 cursor-pointer'), "aria-label": `Add ${item.label} to ranked list`, children: [_jsx("span", { className: cn('flex h-7 w-7 shrink-0 items-center justify-center rounded-full border text-sm', disabled
44
+ ? 'border-border/50 text-muted-foreground/50'
45
+ : 'border-primary/30 text-primary'), "aria-hidden": "true", children: "+" }), _jsx("span", { className: "flex-1 text-sm", children: item.label })] }));
46
+ }
47
+ // -----------------------------------------------------------------------------
48
+ // Main Component
49
+ // -----------------------------------------------------------------------------
50
+ /**
51
+ * Drag-to-rank with two zones: a capped ranked list and a tap-to-add pool.
52
+ * Touch, mouse and keyboard support; drag is scoped to the grip handle.
53
+ *
54
+ * Use for: ranking a small capped set, like the Top 5 priorities editor.
55
+ * Avoid when: order does not matter; use MultiSelectCardGroup.
56
+ */
57
+ export function SortableList({ available, ranked, maxRanked, onRankedChange, rankedLabel = 'Your top priorities', availableLabel = 'Available priorities', }) {
58
+ const dndId = useId();
59
+ const isFull = ranked.length >= maxRanked;
60
+ // Sensors: pointer (mouse), touch, keyboard
61
+ const sensors = useSensors(useSensor(PointerSensor, {
62
+ activationConstraint: { distance: 5 },
63
+ }), useSensor(TouchSensor, {
64
+ activationConstraint: { delay: 150, tolerance: 5 },
65
+ }), useSensor(KeyboardSensor, {
66
+ coordinateGetter: sortableKeyboardCoordinates,
67
+ }));
68
+ // Unranked items = available items not already in ranked
69
+ const rankedIds = new Set(ranked.map((r) => r.id));
70
+ const unranked = available.filter((a) => !rankedIds.has(a.id));
71
+ const handleDragEnd = useCallback((event) => {
72
+ const { active, over } = event;
73
+ if (!over || active.id === over.id)
74
+ return;
75
+ const oldIndex = ranked.findIndex((r) => r.id === active.id);
76
+ const newIndex = ranked.findIndex((r) => r.id === over.id);
77
+ if (oldIndex === -1 || newIndex === -1)
78
+ return;
79
+ onRankedChange(arrayMove(ranked, oldIndex, newIndex));
80
+ }, [ranked, onRankedChange]);
81
+ const handleAdd = useCallback((item) => {
82
+ if (isFull)
83
+ return;
84
+ onRankedChange([...ranked, item]);
85
+ }, [ranked, isFull, onRankedChange]);
86
+ const handleRemove = useCallback((id) => {
87
+ onRankedChange(ranked.filter((r) => r.id !== id));
88
+ }, [ranked, onRankedChange]);
89
+ return (_jsxs("div", { className: "space-y-6", children: [_jsxs("div", { children: [_jsxs("div", { className: "flex items-center justify-between mb-2", children: [_jsx("h3", { className: "text-sm font-medium text-foreground", children: rankedLabel }), _jsxs("span", { className: "text-xs text-muted-foreground tabular-nums", children: [ranked.length, "/", maxRanked] })] }), ranked.length === 0 ? (_jsx("div", { className: "rounded-lg border-2 border-dashed border-border/60 p-6 text-center", children: _jsxs("p", { className: "text-sm text-muted-foreground", children: ["Tap items below to add your top ", maxRanked] }) })) : (_jsx(DndContext, { id: dndId, sensors: sensors, collisionDetection: closestCenter, onDragEnd: handleDragEnd, children: _jsx(SortableContext, { items: ranked.map((r) => r.id), strategy: verticalListSortingStrategy, children: _jsx("div", { className: "space-y-2", role: "list", "aria-label": "Ranked priorities", children: ranked.map((item, index) => (_jsx(SortableRankedItem, { item: item, rank: index + 1, onRemove: () => handleRemove(item.id) }, item.id))) }) }) }))] }), unranked.length > 0 && (_jsxs("div", { children: [_jsx("h3", { className: "text-sm font-medium text-muted-foreground mb-1", children: availableLabel }), _jsx("p", { className: "text-xs text-muted-foreground mb-2", children: isFull
90
+ ? `You\u2019ve chosen ${maxRanked}. Remove one above to swap in another.`
91
+ : `Choose up to ${maxRanked}. ${maxRanked - ranked.length} remaining.` }), _jsx("div", { className: "space-y-2", role: "list", "aria-label": "Available priorities", children: unranked.map((item) => (_jsx(AvailableItem, { item: item, onAdd: () => handleAdd(item), disabled: isFull }, item.id))) })] }))] }));
92
+ }
@@ -0,0 +1,17 @@
1
+ import * as React from "react";
2
+ import { Tabs as TabsPrimitive } from "radix-ui";
3
+ /**
4
+ * Segmented tab switcher (Radix) for swapping between views of the same thing.
5
+ *
6
+ * Use for: two or three peer views a reader flips between (overview against a
7
+ * detailed comparison), where every view is worth the same weight.
8
+ * Avoid when: the second view is secondary detail (use Accordion), or the
9
+ * content must exist for search engines; inactive panels are unmounted.
10
+ * Variants: none. The list renders as a pill-track segmented control; the
11
+ * active trigger lifts onto the card surface.
12
+ */
13
+ declare function Tabs({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Root>): React.JSX.Element;
14
+ declare function TabsList({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.List>): React.JSX.Element;
15
+ declare function TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Trigger>): React.JSX.Element;
16
+ declare function TabsContent({ className, ...props }: React.ComponentProps<typeof TabsPrimitive.Content>): React.JSX.Element;
17
+ export { Tabs, TabsList, TabsTrigger, TabsContent };
@@ -0,0 +1,31 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { Tabs as TabsPrimitive } from "radix-ui";
4
+ import { cn } from "../cn.js";
5
+ /**
6
+ * Segmented tab switcher (Radix) for swapping between views of the same thing.
7
+ *
8
+ * Use for: two or three peer views a reader flips between (overview against a
9
+ * detailed comparison), where every view is worth the same weight.
10
+ * Avoid when: the second view is secondary detail (use Accordion), or the
11
+ * content must exist for search engines; inactive panels are unmounted.
12
+ * Variants: none. The list renders as a pill-track segmented control; the
13
+ * active trigger lifts onto the card surface.
14
+ */
15
+ function Tabs({ className, ...props }) {
16
+ return (_jsx(TabsPrimitive.Root, { "data-slot": "tabs", className: cn("flex flex-col gap-6", className), ...props }));
17
+ }
18
+ function TabsList({ className, ...props }) {
19
+ return (_jsx(TabsPrimitive.List, { "data-slot": "tabs-list", className: cn(
20
+ // p-1 on a rounded-20 track puts the triggers on rounded-2xl, per the
21
+ // concentric rule (md + p-2.5 is the documented neighbour; this pairing
22
+ // holds the same relationship one step in).
23
+ "inline-flex w-fit items-center gap-1 rounded-20 bg-secondary p-1", className), ...props }));
24
+ }
25
+ function TabsTrigger({ className, ...props }) {
26
+ return (_jsx(TabsPrimitive.Trigger, { "data-slot": "tabs-trigger", className: cn("cursor-pointer inline-flex items-center justify-center rounded-2xl px-4 py-2 text-[14.5px] font-medium whitespace-nowrap", "text-muted-foreground transition-[background-color,color,box-shadow]", "hover:text-foreground", "focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50", "disabled:pointer-events-none disabled:opacity-50", "data-[state=active]:bg-card data-[state=active]:text-foreground data-[state=active]:shadow-warm-sm", className), ...props }));
27
+ }
28
+ function TabsContent({ className, ...props }) {
29
+ return (_jsx(TabsPrimitive.Content, { "data-slot": "tabs-content", className: cn("outline-none", className), ...props }));
30
+ }
31
+ export { Tabs, TabsList, TabsTrigger, TabsContent };
@@ -0,0 +1,10 @@
1
+ import * as React from "react";
2
+ /**
3
+ * Multi-line free text with content-based auto-grow.
4
+ *
5
+ * Use for: long-form answers ("anything else your team should know").
6
+ * Avoid when: the answer is one line; use Input, which keeps mobile
7
+ * keyboards and validation simpler.
8
+ */
9
+ declare function Textarea({ className, ...props }: React.ComponentProps<"textarea">): React.JSX.Element;
10
+ export { Textarea };
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cn } from "../cn.js";
3
+ /**
4
+ * Multi-line free text with content-based auto-grow.
5
+ *
6
+ * Use for: long-form answers ("anything else your team should know").
7
+ * Avoid when: the answer is one line; use Input, which keeps mobile
8
+ * keyboards and validation simpler.
9
+ */
10
+ function Textarea({ className, ...props }) {
11
+ return (_jsx("textarea", { "data-slot": "textarea", className: cn("border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-card px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", className), ...props }));
12
+ }
13
+ export { Textarea };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@fracazo/design-system",
3
- "version": "0.1.0",
4
- "description": "Roles, brand contract and guardrails for a warm, evidence-led product design system. Each product supplies a brand file; the system stays the same.",
3
+ "version": "0.2.0",
4
+ "description": "Roles, brand contract, guardrails and shadcn-based components for a warm, evidence-led product design system. Each product supplies a brand file; the system stays the same.",
5
5
  "license": "MIT",
6
6
  "author": "Alex Fracazo",
7
7
  "repository": {
@@ -20,6 +20,14 @@
20
20
  "DESIGN.md"
21
21
  ],
22
22
  "exports": {
23
+ ".": {
24
+ "types": "./dist/src/index.d.ts",
25
+ "default": "./dist/src/index.js"
26
+ },
27
+ "./ui/*": {
28
+ "types": "./dist/src/ui/*.d.ts",
29
+ "default": "./dist/src/ui/*.js"
30
+ },
23
31
  "./roles.css": "./css/roles.css",
24
32
  "./eslint": {
25
33
  "types": "./dist/guardrails/eslint.d.ts",
@@ -32,12 +40,38 @@
32
40
  "ds-build-brand-css": "dist/guardrails/build-brand-css.js"
33
41
  },
34
42
  "peerDependencies": {
43
+ "@dnd-kit/core": "^6.3.1",
44
+ "@dnd-kit/sortable": "^10.0.0",
45
+ "@dnd-kit/utilities": "^3.2.2",
46
+ "class-variance-authority": "^0.7.1",
47
+ "clsx": "^2.1.1",
48
+ "lucide-react": "^0.564.0",
49
+ "radix-ui": "^1.4.3",
50
+ "react": "^19",
51
+ "react-day-picker": "^9.13.2",
52
+ "react-hook-form": "^7.71.1",
53
+ "tailwind-merge": "^3.4.1",
35
54
  "tailwindcss": "^4"
36
55
  },
37
56
  "devDependencies": {
57
+ "@dnd-kit/core": "^6.3.1",
58
+ "@dnd-kit/sortable": "^10.0.0",
59
+ "@dnd-kit/utilities": "^3.2.2",
38
60
  "@types/node": "^22",
61
+ "@types/react": "^19",
62
+ "class-variance-authority": "^0.7.1",
63
+ "clsx": "^2.1.1",
64
+ "lucide-react": "^0.564.0",
65
+ "radix-ui": "^1.4.3",
66
+ "react": "^19",
67
+ "react-day-picker": "^9.13.2",
68
+ "react-hook-form": "^7.71.1",
69
+ "tailwind-merge": "^3.4.1",
39
70
  "typescript": "^5"
40
71
  },
72
+ "sideEffects": [
73
+ "*.css"
74
+ ],
41
75
  "scripts": {
42
76
  "build": "tsc -p tsconfig.json",
43
77
  "check": "tsc -p tsconfig.json --noEmit"