@ponti-studios/ui 0.1.2

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 (108) hide show
  1. package/README.md +27 -0
  2. package/dist/index.js +2118 -0
  3. package/dist/index.js.map +1 -0
  4. package/package.json +130 -0
  5. package/src/animations.css +437 -0
  6. package/src/components/data-display/accordion.stories.tsx +57 -0
  7. package/src/components/data-display/accordion.tsx +65 -0
  8. package/src/components/data-display/filter-chip.tsx +41 -0
  9. package/src/components/data-display/index.ts +13 -0
  10. package/src/components/data-display/table.stories.tsx +75 -0
  11. package/src/components/data-display/table.tsx +84 -0
  12. package/src/components/feedback/alert.stories.tsx +51 -0
  13. package/src/components/feedback/alert.tsx +60 -0
  14. package/src/components/feedback/empty-state.tsx +66 -0
  15. package/src/components/feedback/index.ts +6 -0
  16. package/src/components/feedback/progress.stories.tsx +37 -0
  17. package/src/components/feedback/progress.tsx +30 -0
  18. package/src/components/feedback/spinner.stories.tsx +52 -0
  19. package/src/components/feedback/spinner.tsx +48 -0
  20. package/src/components/forms/calendar.stories.tsx +30 -0
  21. package/src/components/forms/calendar.tsx +67 -0
  22. package/src/components/forms/date-picker.stories.tsx +48 -0
  23. package/src/components/forms/date-picker.tsx +60 -0
  24. package/src/components/forms/field.stories.tsx +62 -0
  25. package/src/components/forms/field.tsx +65 -0
  26. package/src/components/forms/field.types.ts +7 -0
  27. package/src/components/forms/index.ts +24 -0
  28. package/src/components/forms/input.stories.tsx +47 -0
  29. package/src/components/forms/input.tsx +20 -0
  30. package/src/components/forms/radio-group.stories.tsx +48 -0
  31. package/src/components/forms/radio-group.tsx +38 -0
  32. package/src/components/forms/select.stories.tsx +88 -0
  33. package/src/components/forms/select.tsx +147 -0
  34. package/src/components/forms/slider.stories.tsx +67 -0
  35. package/src/components/forms/slider.tsx +25 -0
  36. package/src/components/forms/stepper.stories.tsx +111 -0
  37. package/src/components/forms/stepper.tsx +66 -0
  38. package/src/components/forms/switch.stories.tsx +144 -0
  39. package/src/components/forms/switch.tsx +36 -0
  40. package/src/components/forms/text-field.stories.tsx +87 -0
  41. package/src/components/forms/text-field.tsx +40 -0
  42. package/src/components/forms/text-field.types.ts +9 -0
  43. package/src/components/forms/textarea.stories.tsx +150 -0
  44. package/src/components/forms/textarea.tsx +18 -0
  45. package/src/components/layout/index.ts +2 -0
  46. package/src/components/layout/scroll-area.stories.tsx +83 -0
  47. package/src/components/layout/scroll-area.tsx +40 -0
  48. package/src/components/navigation/app-navigation.stories.tsx +81 -0
  49. package/src/components/navigation/app-navigation.tsx +155 -0
  50. package/src/components/navigation/index.ts +3 -0
  51. package/src/components/navigation/tabs.stories.tsx +69 -0
  52. package/src/components/navigation/tabs.tsx +49 -0
  53. package/src/components/overlays/alert-dialog.stories.tsx +45 -0
  54. package/src/components/overlays/alert-dialog.tsx +119 -0
  55. package/src/components/overlays/command.stories.tsx +65 -0
  56. package/src/components/overlays/command.tsx +130 -0
  57. package/src/components/overlays/dialog.stories.tsx +111 -0
  58. package/src/components/overlays/dialog.tsx +162 -0
  59. package/src/components/overlays/dropdown-menu.stories.tsx +79 -0
  60. package/src/components/overlays/dropdown-menu.tsx +214 -0
  61. package/src/components/overlays/index.ts +65 -0
  62. package/src/components/overlays/popover.stories.tsx +39 -0
  63. package/src/components/overlays/popover.tsx +40 -0
  64. package/src/components/overlays/sheet.stories.tsx +68 -0
  65. package/src/components/overlays/sheet.tsx +98 -0
  66. package/src/components/primitives/avatar.stories.tsx +71 -0
  67. package/src/components/primitives/avatar.tsx +132 -0
  68. package/src/components/primitives/badge.stories.tsx +33 -0
  69. package/src/components/primitives/badge.tsx +44 -0
  70. package/src/components/primitives/button.stories.tsx +113 -0
  71. package/src/components/primitives/button.tsx +95 -0
  72. package/src/components/primitives/button.types.ts +10 -0
  73. package/src/components/primitives/card.stories.tsx +59 -0
  74. package/src/components/primitives/card.tsx +69 -0
  75. package/src/components/primitives/index.ts +13 -0
  76. package/src/components/primitives/label.stories.tsx +47 -0
  77. package/src/components/primitives/label.tsx +21 -0
  78. package/src/constants/chart-colors.ts +39 -0
  79. package/src/env.d.ts +9 -0
  80. package/src/hooks/index.ts +6 -0
  81. package/src/hooks/sort.types.ts +12 -0
  82. package/src/hooks/use-api-client.ts +122 -0
  83. package/src/hooks/use-debounced-value.ts +12 -0
  84. package/src/hooks/use-filter-state.ts +68 -0
  85. package/src/hooks/use-media-query.ts +65 -0
  86. package/src/hooks/use-mobile.tsx +19 -0
  87. package/src/index.ts +25 -0
  88. package/src/lib/auth.ts +5 -0
  89. package/src/lib/clipboard.ts +8 -0
  90. package/src/lib/create-memory-storage.ts +24 -0
  91. package/src/lib/index.ts +4 -0
  92. package/src/lib/utils.ts +6 -0
  93. package/src/storybook/controls.ts +117 -0
  94. package/src/storybook/options.ts +52 -0
  95. package/src/styles.css +1052 -0
  96. package/src/tokens/color-systems.stories.tsx +133 -0
  97. package/src/tokens/colors.ts +156 -0
  98. package/src/tokens/foundations.stories.tsx +151 -0
  99. package/src/tokens/index.ts +16 -0
  100. package/src/tokens/layout.stories.tsx +118 -0
  101. package/src/tokens/motion.ts +51 -0
  102. package/src/tokens/radii.ts +10 -0
  103. package/src/tokens/shadows.ts +34 -0
  104. package/src/tokens/spacing.ts +30 -0
  105. package/src/tokens/typography.ts +42 -0
  106. package/src/translations/en.ts +34 -0
  107. package/src/translations/index.ts +32 -0
  108. package/src/types/upload.ts +42 -0
@@ -0,0 +1,132 @@
1
+ import * as AvatarPrimitive from "@radix-ui/react-avatar";
2
+ import type { ClassValue } from "clsx";
3
+ import * as React from "react";
4
+
5
+ import { cn } from "../../lib/utils";
6
+
7
+ type AvatarSize = "sm" | "default" | "lg";
8
+
9
+ const AvatarContext = React.createContext<{ size: AvatarSize }>({ size: "default" });
10
+
11
+ const avatarSize: Record<AvatarSize, ClassValue> = {
12
+ sm: "size-6",
13
+ default: "size-6",
14
+ lg: "size-10",
15
+ };
16
+
17
+ const badgeDotSize: Record<AvatarSize, ClassValue> = {
18
+ sm: "size-2",
19
+ default: "size-2.5",
20
+ lg: "size-3",
21
+ };
22
+
23
+ const fallbackText: Record<AvatarSize, ClassValue> = {
24
+ sm: "text-xs",
25
+ default: "text-sm",
26
+ lg: "text-sm",
27
+ };
28
+
29
+ const groupCountSvg: Record<AvatarSize, ClassValue> = {
30
+ sm: "[&>svg]:size-3",
31
+ default: "[&>svg]:size-4",
32
+ lg: "[&>svg]:size-5",
33
+ };
34
+
35
+ function Avatar({
36
+ className,
37
+ size = "default",
38
+ statusBadge = false,
39
+ children,
40
+ ...props
41
+ }: React.ComponentProps<typeof AvatarPrimitive.Root> & {
42
+ size?: AvatarSize;
43
+ statusBadge?: boolean;
44
+ }) {
45
+ return (
46
+ <AvatarContext.Provider value={{ size }}>
47
+ <AvatarPrimitive.Root
48
+ data-slot="avatar"
49
+ className={cn("relative shrink-0 rounded-full select-none", avatarSize[size], className)}
50
+ {...props}
51
+ >
52
+ {children}
53
+ {statusBadge ? (
54
+ <span
55
+ data-slot="avatar-badge"
56
+ className={cn(
57
+ "bg-accent ring-background absolute right-0 bottom-0 z-10 rounded-full ring-2",
58
+ badgeDotSize[size],
59
+ )}
60
+ />
61
+ ) : null}
62
+ </AvatarPrimitive.Root>
63
+ </AvatarContext.Provider>
64
+ );
65
+ }
66
+
67
+ function AvatarImage({ className, ...props }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
68
+ return (
69
+ <AvatarPrimitive.Image
70
+ data-slot="avatar-image"
71
+ className={cn("absolute inset-0 size-full rounded-full object-cover", className)}
72
+ {...props}
73
+ />
74
+ );
75
+ }
76
+
77
+ function AvatarFallback({
78
+ className,
79
+ ...props
80
+ }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
81
+ const { size } = React.useContext(AvatarContext);
82
+
83
+ return (
84
+ <AvatarPrimitive.Fallback
85
+ data-slot="avatar-fallback"
86
+ className={cn(
87
+ "bg-inset text-secondary absolute inset-0 flex items-center justify-center rounded-full",
88
+ fallbackText[size],
89
+ className,
90
+ )}
91
+ {...props}
92
+ />
93
+ );
94
+ }
95
+
96
+ function AvatarGroup({
97
+ className,
98
+ size = "default",
99
+ ...props
100
+ }: React.ComponentProps<"div"> & { size?: AvatarSize }) {
101
+ return (
102
+ <AvatarContext.Provider value={{ size }}>
103
+ <div
104
+ data-slot="avatar-group"
105
+ className={cn(
106
+ "*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:ring-2",
107
+ className,
108
+ )}
109
+ {...props}
110
+ />
111
+ </AvatarContext.Provider>
112
+ );
113
+ }
114
+
115
+ function AvatarGroupCount({ className, ...props }: React.ComponentProps<"div">) {
116
+ const { size } = React.useContext(AvatarContext);
117
+
118
+ return (
119
+ <div
120
+ data-slot="avatar-group-count"
121
+ className={cn(
122
+ "bg-inset text-secondary ring-background relative flex shrink-0 items-center justify-center rounded-full text-sm ring-2",
123
+ avatarSize[size],
124
+ groupCountSvg[size],
125
+ className,
126
+ )}
127
+ {...props}
128
+ />
129
+ );
130
+ }
131
+
132
+ export { Avatar, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage };
@@ -0,0 +1,33 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+
3
+ import { Badge } from "./badge";
4
+
5
+ const meta = {
6
+ title: "Primitives/Badge",
7
+ component: Badge,
8
+ tags: ["autodocs"],
9
+ argTypes: {
10
+ variant: {
11
+ control: "select",
12
+ options: ["default", "secondary", "destructive", "outline"],
13
+ },
14
+ },
15
+ } satisfies Meta<typeof Badge>;
16
+
17
+ export default meta;
18
+ type Story = StoryObj<typeof meta>;
19
+
20
+ export const Default: Story = {
21
+ args: { children: "Badge" },
22
+ };
23
+
24
+ export const Variants: Story = {
25
+ render: () => (
26
+ <div className="flex flex-wrap gap-3">
27
+ <Badge variant="default">Default</Badge>
28
+ <Badge variant="secondary">Secondary</Badge>
29
+ <Badge variant="destructive">Destructive</Badge>
30
+ <Badge variant="outline">Outline</Badge>
31
+ </div>
32
+ ),
33
+ };
@@ -0,0 +1,44 @@
1
+ import { Slot } from "@radix-ui/react-slot";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import * as React from "react";
4
+
5
+ import { cn } from "../../lib/utils";
6
+
7
+ const badgeVariants = cva(
8
+ "inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden whitespace-nowrap rounded border border-transparent px-1.5 py-px text-xs font-medium [&>svg]:size-3 [&>svg]:pointer-events-none aria-invalid:border-destructive aria-invalid:ring-destructive/20",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-accent text-on-accent [a&]:[a&]: [a&]: [a&]:",
13
+ secondary: "bg-panel text-primary [a&]:[a&]: [a&]: [a&]:",
14
+ destructive: "bg-destructive text-on-destructive [a&]:[a&]: [a&]: [a&]:",
15
+ outline: "border-default text-primary [a&]:[a&]: [a&]: [a&]:",
16
+ ghost: "[a&]:[a&]: [a&]: [a&]:",
17
+ link: "text-primary underline-offset-4 [a&]:[a&]:hover:underline [a&]: [a&]: [a&]:",
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ variant: "default",
22
+ },
23
+ },
24
+ );
25
+
26
+ function Badge({
27
+ className,
28
+ variant = "default",
29
+ asChild = false,
30
+ ...props
31
+ }: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
32
+ const Comp = asChild ? Slot : "span";
33
+
34
+ return (
35
+ <Comp
36
+ data-slot="badge"
37
+ data-variant={variant}
38
+ className={cn(badgeVariants({ variant }), className)}
39
+ {...props}
40
+ />
41
+ );
42
+ }
43
+
44
+ export { Badge, badgeVariants };
@@ -0,0 +1,113 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { Plus, Trash2 } from "lucide-react";
3
+ import { expect, userEvent, within } from "storybook/test";
4
+
5
+ import { booleanControl, hiddenControl, selectControl } from "../../storybook/controls";
6
+ import { buttonVariantOptions } from "../../storybook/options";
7
+ import { Button } from "./button";
8
+
9
+ const meta: Meta = {
10
+ title: "Primitives/Button",
11
+ component: Button,
12
+ tags: ["autodocs"],
13
+ argTypes: {
14
+ variant: selectControl(buttonVariantOptions, "Visual style variant of the button", {
15
+ defaultValue: "default",
16
+ }),
17
+ disabled: booleanControl("Prevents user interaction and applies disabled styling", false),
18
+ asChild: hiddenControl,
19
+ },
20
+ };
21
+
22
+ export default meta;
23
+ type Story = StoryObj<typeof meta>;
24
+
25
+ export const Default: Story = {
26
+ args: { children: "Button" },
27
+ play: async ({ canvasElement }) => {
28
+ const canvas = within(canvasElement);
29
+ const button = canvas.getByRole("button", { name: "Button" });
30
+
31
+ await userEvent.hover(button);
32
+ await userEvent.click(button);
33
+
34
+ await expect(button).toHaveFocus();
35
+ await expect(button).toBeEnabled();
36
+ },
37
+ };
38
+
39
+ export const Variants: Story = {
40
+ render: () => (
41
+ <div className="flex flex-wrap gap-3">
42
+ <Button variant="default">Default</Button>
43
+ <Button variant="secondary">Secondary</Button>
44
+ <Button variant="ghost">Ghost</Button>
45
+ <Button variant="destructive">Destructive</Button>
46
+ <Button variant="ghost" size="icon" aria-label="Add">
47
+ <Plus className="size-4" />
48
+ </Button>
49
+ <Button variant="link">Link</Button>
50
+ </div>
51
+ ),
52
+ };
53
+
54
+ export const Icon: Story = {
55
+ render: () => (
56
+ <div className="flex items-center gap-3">
57
+ <Button>Standard</Button>
58
+ <Button size="icon" aria-label="Add">
59
+ <Plus className="size-4" />
60
+ </Button>
61
+ </div>
62
+ ),
63
+ };
64
+
65
+ export const WithIcon: Story = {
66
+ render: () => (
67
+ <div className="flex gap-3">
68
+ <Button>
69
+ <Plus className="size-4" />
70
+ Create
71
+ </Button>
72
+ <Button variant="destructive">
73
+ <Trash2 className="size-4" />
74
+ Delete
75
+ </Button>
76
+ </div>
77
+ ),
78
+ };
79
+
80
+ export const Disabled: Story = {
81
+ render: () => (
82
+ <div className="flex gap-3">
83
+ <Button disabled>Default</Button>
84
+ <Button variant="outline" disabled>
85
+ Outline
86
+ </Button>
87
+ <Button variant="destructive" disabled>
88
+ Destructive
89
+ </Button>
90
+ </div>
91
+ ),
92
+ play: async ({ canvasElement }) => {
93
+ const canvas = within(canvasElement);
94
+
95
+ await expect(canvas.getByRole("button", { name: "Default" })).toBeDisabled();
96
+ await expect(canvas.getByRole("button", { name: "Outline" })).toBeDisabled();
97
+ await expect(canvas.getByRole("button", { name: "Destructive" })).toBeDisabled();
98
+ },
99
+ };
100
+
101
+ export const Loading: Story = {
102
+ render: () => (
103
+ <Button isLoading loadingLabel="Saving">
104
+ Save
105
+ </Button>
106
+ ),
107
+ play: async ({ canvasElement }) => {
108
+ const canvas = within(canvasElement);
109
+ const button = canvas.getByRole("button", { name: "Saving" });
110
+
111
+ await expect(button).toBeDisabled();
112
+ },
113
+ };
@@ -0,0 +1,95 @@
1
+ import { Slot } from "@radix-ui/react-slot";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import * as React from "react";
4
+
5
+ import { cn } from "../../lib/utils";
6
+ import { Spinner } from "../feedback/spinner";
7
+
8
+ const buttonVariants = cva(
9
+ "inline-flex min-h-11 !cursor-pointer items-center justify-center gap-1.5 whitespace-nowrap rounded-md border px-3 py-1.5 text-sm font-medium leading-5 transition-colors duration-150 focus-visible:ring-2 focus-visible:ring-focus focus-visible:ring-offset-2 focus-visible:outline-none disabled:pointer-events-none disabled:!cursor-default [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
10
+ {
11
+ variants: {
12
+ variant: {
13
+ default:
14
+ "border-transparent bg-accent text-on-accent hover:bg-accent-hover active:bg-accent-pressed disabled:border-subtle disabled:bg-panel disabled:text-disabled",
15
+ destructive:
16
+ "border-transparent bg-destructive text-on-destructive hover:bg-destructive-hover active:bg-destructive-pressed disabled:border-subtle disabled:bg-panel disabled:text-disabled",
17
+ outline:
18
+ "border-default bg-transparent text-primary hover:bg-surface-hover active:bg-surface-pressed disabled:border-subtle disabled:text-disabled",
19
+ secondary:
20
+ "border-default bg-panel text-primary hover:bg-surface-hover active:bg-surface-pressed disabled:border-subtle disabled:bg-panel disabled:text-disabled",
21
+ ghost:
22
+ "border-transparent bg-transparent text-secondary hover:bg-surface-hover hover:text-primary active:bg-surface-pressed disabled:text-disabled",
23
+ link: "border-transparent bg-transparent px-0 text-accent hover:text-accent-hover disabled:text-disabled underline-offset-4 hover:underline",
24
+ },
25
+ size: {
26
+ sm: "min-h-11 px-2.5 text-sm",
27
+ md: "min-h-11 px-3 text-sm",
28
+ lg: "min-h-12 px-4 text-base",
29
+ default: "",
30
+ icon: "size-11 rounded-md p-0",
31
+ },
32
+ },
33
+ defaultVariants: {
34
+ variant: "default",
35
+ size: "md",
36
+ },
37
+ },
38
+ );
39
+
40
+ interface ButtonProps
41
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
42
+ asChild?: boolean;
43
+ isLoading?: boolean;
44
+ loadingLabel?: string;
45
+ }
46
+
47
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
48
+ (
49
+ {
50
+ children,
51
+ className,
52
+ disabled,
53
+ isLoading = false,
54
+ loadingLabel = "Loading",
55
+ variant,
56
+ size,
57
+ asChild = false,
58
+ "aria-label": ariaLabel,
59
+ ...props
60
+ },
61
+ ref,
62
+ ) => {
63
+ const Comp = asChild ? Slot : "button";
64
+ return (
65
+ <Comp
66
+ className={cn(buttonVariants({ variant, size, className }), isLoading && "relative")}
67
+ style={{
68
+ ...props.style,
69
+ cursor: disabled || isLoading ? "default" : "pointer",
70
+ }}
71
+ type="button"
72
+ ref={ref}
73
+ disabled={disabled || isLoading}
74
+ aria-busy={isLoading}
75
+ aria-label={isLoading ? loadingLabel : ariaLabel}
76
+ {...props}
77
+ >
78
+ {isLoading ? (
79
+ <>
80
+ <span className="opacity-0">{children}</span>
81
+ <span className="absolute inset-0 inline-flex items-center justify-center">
82
+ <Spinner size="sm" aria-hidden="true" />
83
+ </span>
84
+ <span className="sr-only">{loadingLabel}</span>
85
+ </>
86
+ ) : (
87
+ children
88
+ )}
89
+ </Comp>
90
+ );
91
+ },
92
+ );
93
+ Button.displayName = "Button";
94
+
95
+ export { Button, buttonVariants };
@@ -0,0 +1,10 @@
1
+ export type ButtonVariant = "default" | "destructive" | "ghost" | "link" | "outline" | "secondary";
2
+
3
+ export type ButtonSize = "sm" | "md" | "lg" | "default" | "icon";
4
+
5
+ export interface ButtonBaseProps {
6
+ isLoading?: boolean | undefined;
7
+ size?: ButtonSize;
8
+ title?: string | undefined;
9
+ variant?: ButtonVariant;
10
+ }
@@ -0,0 +1,59 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { expect, userEvent, within } from "storybook/test";
3
+
4
+ import { Button } from "./button";
5
+ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "./card";
6
+
7
+ const meta = {
8
+ title: "Primitives/Card",
9
+ tags: ["autodocs"],
10
+ component: Card,
11
+ } satisfies Meta<typeof Card>;
12
+
13
+ export default meta;
14
+ type Story = StoryObj<typeof meta>;
15
+
16
+ export const Default: Story = {
17
+ render: () => (
18
+ <Card>
19
+ <CardHeader>
20
+ <CardTitle>Card Title</CardTitle>
21
+ <CardDescription>A short description of the card's content.</CardDescription>
22
+ </CardHeader>
23
+ <CardContent>
24
+ <p className="body-2 text-secondary">
25
+ Card body content goes here. It can be any React node.
26
+ </p>
27
+ </CardContent>
28
+ <CardFooter className="flex justify-end gap-2">
29
+ <Button variant="outline">Cancel</Button>
30
+ <Button>Confirm</Button>
31
+ </CardFooter>
32
+ </Card>
33
+ ),
34
+ play: async ({ canvasElement }) => {
35
+ const canvas = within(canvasElement);
36
+ const cancelButton = canvas.getByRole("button", { name: "Cancel" });
37
+
38
+ await userEvent.tab();
39
+
40
+ await expect(canvas.getByText("Card Title")).toBeInTheDocument();
41
+ await expect(cancelButton).toHaveFocus();
42
+ await expect(canvas.getByRole("button", { name: "Confirm" })).toBeInTheDocument();
43
+ },
44
+ };
45
+
46
+ export const Minimal: Story = {
47
+ render: () => (
48
+ <Card className="w-80">
49
+ <CardContent>
50
+ <p className="body-2 text-secondary">Minimal card with only content.</p>
51
+ </CardContent>
52
+ </Card>
53
+ ),
54
+ play: async ({ canvasElement }) => {
55
+ const canvas = within(canvasElement);
56
+
57
+ await expect(canvas.getByText("Minimal card with only content.")).toBeInTheDocument();
58
+ },
59
+ };
@@ -0,0 +1,69 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "../../lib/utils";
4
+
5
+ function Card({ className, ...props }: React.ComponentProps<"div">) {
6
+ return (
7
+ <div
8
+ data-slot="card"
9
+ className={cn(
10
+ "bg-panel text-primary border-default flex flex-col rounded-md border",
11
+ className,
12
+ )}
13
+ {...props}
14
+ />
15
+ );
16
+ }
17
+
18
+ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
19
+ return (
20
+ <div
21
+ data-slot="card-header"
22
+ className={cn(
23
+ "flex items-center justify-between gap-2 border-b px-4 py-3 has-data-[slot=card-action]:grid has-data-[slot=card-action]:grid-cols-[1fr_auto]",
24
+ className,
25
+ )}
26
+ {...props}
27
+ />
28
+ );
29
+ }
30
+
31
+ function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
32
+ return <div data-slot="card-title" className={cn(className)} {...props} />;
33
+ }
34
+
35
+ function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
36
+ return (
37
+ <div
38
+ data-slot="card-description"
39
+ className={cn("body-4 text-secondary", className)}
40
+ {...props}
41
+ />
42
+ );
43
+ }
44
+
45
+ function CardAction({ className, ...props }: React.ComponentProps<"div">) {
46
+ return (
47
+ <div
48
+ data-slot="card-action"
49
+ className={cn("col-start-2 self-start justify-self-end", className)}
50
+ {...props}
51
+ />
52
+ );
53
+ }
54
+
55
+ function CardContent({ className, ...props }: React.ComponentProps<"div">) {
56
+ return <div data-slot="card-content" className={cn("p-4", className)} {...props} />;
57
+ }
58
+
59
+ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
60
+ return (
61
+ <div
62
+ data-slot="card-footer"
63
+ className={cn("flex items-center border-t px-4 py-3", className)}
64
+ {...props}
65
+ />
66
+ );
67
+ }
68
+
69
+ export { Card, CardHeader, CardFooter, CardTitle, CardAction, CardDescription, CardContent };
@@ -0,0 +1,13 @@
1
+ export { Avatar, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage } from "./avatar";
2
+ export { Badge, badgeVariants } from "./badge";
3
+ export { Button, buttonVariants } from "./button";
4
+ export {
5
+ Card,
6
+ CardAction,
7
+ CardContent,
8
+ CardDescription,
9
+ CardFooter,
10
+ CardHeader,
11
+ CardTitle,
12
+ } from "./card";
13
+ export { Label } from "./label";
@@ -0,0 +1,47 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import * as React from "react";
3
+
4
+ import { Input } from "../forms/input";
5
+ import { Label } from "../primitives/label";
6
+
7
+ const meta: Meta<typeof Label> = {
8
+ title: "Primitives/Label",
9
+ component: Label,
10
+ tags: ["autodocs"],
11
+ };
12
+
13
+ export default meta;
14
+ type Story = StoryObj<typeof meta>;
15
+
16
+ export const Default: Story = {
17
+ args: {
18
+ children: "Email address",
19
+ },
20
+ };
21
+
22
+ export const WithInput: Story = {
23
+ render: () => (
24
+ <div className="flex w-full max-w-sm flex-col gap-2">
25
+ <Label htmlFor="email">Email address</Label>
26
+ <Input id="email" type="email" placeholder="you@example.com" />
27
+ </div>
28
+ ),
29
+ };
30
+
31
+ export const Disabled: Story = {
32
+ render: () => (
33
+ <div className="flex w-full max-w-sm flex-col gap-2">
34
+ <Label htmlFor="disabled-field">Subscription plan</Label>
35
+ <div className="flex items-center gap-3">
36
+ <Input id="disabled-field" className="peer" disabled placeholder="Enterprise" />
37
+ <Label htmlFor="disabled-field" className="shrink-0">
38
+ (locked)
39
+ </Label>
40
+ </div>
41
+ <p className="text-secondary text-xs">
42
+ The <code className="font-mono">peer-disabled</code> variant mutes the label when its paired
43
+ input is disabled.
44
+ </p>
45
+ </div>
46
+ ),
47
+ };
@@ -0,0 +1,21 @@
1
+ "use client";
2
+
3
+ import * as LabelPrimitive from "@radix-ui/react-label";
4
+ import { cva, type VariantProps } from "class-variance-authority";
5
+ import * as React from "react";
6
+
7
+ import { cn } from "../../lib/utils";
8
+
9
+ const labelVariants = cva(
10
+ "text-sm font-medium leading-none text-primary peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
11
+ );
12
+
13
+ const Label = React.forwardRef<
14
+ React.ComponentRef<typeof LabelPrimitive.Root>,
15
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>
16
+ >(({ className, ...props }, ref) => (
17
+ <LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
18
+ ));
19
+ Label.displayName = LabelPrimitive.Root.displayName;
20
+
21
+ export { Label };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Chart colors are CSS-variable-backed so they stay in sync with the active
3
+ * color system and mode at runtime.
4
+ */
5
+
6
+ export const CHART_COLORS = {
7
+ chart1: "var(--color-chart-1)",
8
+ chart2: "var(--color-chart-2)",
9
+ chart3: "var(--color-chart-3)",
10
+ chart4: "var(--color-chart-4)",
11
+ chart5: "var(--color-chart-5)",
12
+
13
+ positive: "var(--color-chart-positive)",
14
+ negative: "var(--color-chart-negative)",
15
+ neutral: "var(--color-chart-neutral)",
16
+
17
+ background: "var(--color-surface-canvas)",
18
+ grid: "var(--color-border-subtle)",
19
+
20
+ axis: "var(--color-text-tertiary)",
21
+ label: "var(--color-text-secondary)",
22
+
23
+ tooltip: {
24
+ background: "var(--color-surface-panel)",
25
+ text: "var(--color-text-primary)",
26
+ border: "var(--color-border-default)",
27
+ },
28
+ } as const;
29
+
30
+ export const CHART_CSS_VARS = {
31
+ positive: "var(--color-chart-positive)",
32
+ negative: "var(--color-chart-negative)",
33
+ neutral: "var(--color-chart-neutral)",
34
+ chart1: "var(--color-chart-1)",
35
+ chart2: "var(--color-chart-2)",
36
+ chart3: "var(--color-chart-3)",
37
+ chart4: "var(--color-chart-4)",
38
+ chart5: "var(--color-chart-5)",
39
+ } as const;