@ponti-studios/ui 1.6.0 → 1.7.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 (77) hide show
  1. package/README.md +7 -11
  2. package/package.json +6 -31
  3. package/src/components/feedback/alert.tsx +8 -4
  4. package/src/components/feedback/index.ts +0 -4
  5. package/src/components/feedback/progress.stories.tsx +15 -0
  6. package/src/components/feedback/progress.tsx +14 -3
  7. package/src/components/feedback/update-guard.stories.tsx +4 -8
  8. package/src/components/feedback/update-guard.tsx +41 -272
  9. package/src/components/filters/filter-select.tsx +3 -5
  10. package/src/components/forms/date-picker.stories.tsx +158 -18
  11. package/src/components/forms/date-picker.tsx +286 -64
  12. package/src/components/forms/date-utils.ts +7 -2
  13. package/src/components/forms/drop-zone.tsx +11 -12
  14. package/src/components/forms/index.ts +2 -6
  15. package/src/components/forms/passkey-management.stories.tsx +8 -4
  16. package/src/components/forms/passkey-management.tsx +78 -42
  17. package/src/components/forms/radio-group.stories.tsx +3 -5
  18. package/src/components/forms/radio-group.tsx +2 -2
  19. package/src/components/forms/select.stories.tsx +16 -13
  20. package/src/components/forms/textarea.stories.tsx +1 -1
  21. package/src/components/layout/index.ts +2 -0
  22. package/src/components/layout/particle-background.stories.tsx +38 -0
  23. package/src/components/layout/particle-background.tsx +378 -0
  24. package/src/components/layout/scroll-area.stories.tsx +12 -4
  25. package/src/components/layout/scroll-area.tsx +41 -4
  26. package/src/components/navigation/tabs.stories.tsx +0 -21
  27. package/src/components/overlays/dialog.stories.tsx +38 -1
  28. package/src/components/overlays/dialog.tsx +53 -12
  29. package/src/components/overlays/index.ts +0 -11
  30. package/src/components/primitives/avatar-placeholder.svg +13 -0
  31. package/src/components/primitives/avatar.stories.tsx +2 -1
  32. package/src/components/primitives/badge.stories.tsx +13 -3
  33. package/src/components/primitives/badge.tsx +1 -1
  34. package/src/components/primitives/button.tsx +1 -1
  35. package/src/components/primitives/card.tsx +6 -2
  36. package/src/components/primitives/index.ts +0 -3
  37. package/src/index.ts +1 -3
  38. package/src/lib/index.ts +2 -3
  39. package/src/styles/animations.css +62 -0
  40. package/src/styles/index.css +27 -0
  41. package/src/styles/tokens/generated/dark.colors.generated.ts +15 -12
  42. package/src/styles/tokens/generated/dark.css +15 -12
  43. package/src/styles/tokens/generated/light.colors.generated.ts +14 -11
  44. package/src/styles/tokens/generated/light.css +14 -11
  45. package/src/styles/tokens/index.ts +0 -3
  46. package/src/styles/tokens/source/dark.tokens.json +24 -12
  47. package/src/styles/tokens/source/light.tokens.json +23 -11
  48. package/src/styles/tokens/typography.stories.tsx +157 -0
  49. package/src/components/feedback/file-upload-status.stories.tsx +0 -57
  50. package/src/components/feedback/file-upload-status.tsx +0 -160
  51. package/src/components/feedback/progress-bar.stories.tsx +0 -39
  52. package/src/components/feedback/progress-bar.tsx +0 -22
  53. package/src/components/forms/calendar.stories.tsx +0 -30
  54. package/src/components/forms/calendar.tsx +0 -75
  55. package/src/components/forms/date-field.stories.tsx +0 -202
  56. package/src/components/forms/date-field.tsx +0 -179
  57. package/src/components/overlays/alert-dialog.stories.tsx +0 -45
  58. package/src/components/overlays/alert-dialog.tsx +0 -116
  59. package/src/components/primitives/label.stories.tsx +0 -29
  60. package/src/components/primitives/label.tsx +0 -20
  61. package/src/components/primitives/status-badge.stories.tsx +0 -52
  62. package/src/components/primitives/status-badge.tsx +0 -33
  63. package/src/lib/create-memory-storage.ts +0 -24
  64. package/src/native/card.tsx +0 -36
  65. package/src/native/icon-button.tsx +0 -41
  66. package/src/native/index.ts +0 -38
  67. package/src/native/list-row.tsx +0 -67
  68. package/src/native/make-styles.ts +0 -32
  69. package/src/native/shadows.ts +0 -38
  70. package/src/native/text-field.tsx +0 -34
  71. package/src/native/uniwind.d.ts +0 -1
  72. package/src/native/use-theme-colors.ts +0 -19
  73. package/src/styles/tokens/foundations.stories.tsx +0 -109
  74. package/src/styles/tokens/generated/native.generated.ts +0 -266
  75. package/src/styles/tokens/normalize-native.ts +0 -88
  76. package/src/styles/tokens/source/native.tokens.json +0 -204
  77. package/src/styles/tokens/style-dictionary.native.mjs +0 -26
@@ -1,202 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
- import { expect, screen, userEvent, within } from "storybook/test";
3
-
4
- import { DateField } from "./date-field";
5
-
6
- const meta = {
7
- title: "Forms/DateField",
8
- component: DateField,
9
- tags: ["autodocs"],
10
- } satisfies Meta<typeof DateField>;
11
-
12
- export default meta;
13
- type Story = StoryObj<typeof meta>;
14
-
15
- function hiddenValue(canvasElement: HTMLElement, name: string): string | null {
16
- const input = canvasElement.querySelector(`input[name="${name}"]`) as HTMLInputElement | null;
17
- return input?.value ?? null;
18
- }
19
-
20
- async function nextMonth(dialog: HTMLElement) {
21
- await userEvent.click(within(dialog).getByRole("button", { name: "Go to the Next Month" }));
22
- }
23
-
24
- // The trigger button's accessible name comes from its <label> (htmlFor), so
25
- // stories query the button by label and assert the visible date text separately.
26
- export const WithStringDefaultValue: Story = {
27
- args: {
28
- name: "startDate",
29
- defaultValue: "2020-01-15",
30
- label: "Start date",
31
- },
32
- play: async ({ canvasElement }) => {
33
- const canvas = within(canvasElement);
34
-
35
- // The hidden input carries the local "YYYY-MM-DD" value, and the trigger
36
- // shows the formatted date.
37
- await expect(hiddenValue(canvasElement, "startDate")).toBe("2020-01-15");
38
- const trigger = canvas.getByRole("button", { name: "Start date" });
39
- await expect(trigger).toHaveTextContent("Jan 15, 2020");
40
-
41
- // The calendar opens on the selected month, not the current one.
42
- await userEvent.click(trigger);
43
- const dialog = await screen.findByRole("dialog");
44
- await expect(within(dialog).getByText("January 2020")).toBeInTheDocument();
45
-
46
- // Real click on the Next chevron navigates the month.
47
- await nextMonth(dialog);
48
- await expect(within(dialog).getByText("February 2020")).toBeInTheDocument();
49
-
50
- // Picking a day updates the trigger and the hidden input.
51
- await userEvent.click(within(dialog).getByText("15"));
52
- await expect(canvas.getByRole("button", { name: "Start date" })).toHaveTextContent(
53
- "Feb 15, 2020",
54
- );
55
- await expect(hiddenValue(canvasElement, "startDate")).toBe("2020-02-15");
56
- },
57
- };
58
-
59
- export const WithDateDefaultValue: Story = {
60
- args: {
61
- name: "endDate",
62
- defaultValue: new Date(2020, 0, 15),
63
- label: "End date",
64
- },
65
- play: async ({ canvasElement }) => {
66
- const canvas = within(canvasElement);
67
-
68
- await expect(hiddenValue(canvasElement, "endDate")).toBe("2020-01-15");
69
- await expect(canvas.getByRole("button", { name: "End date" })).toHaveTextContent(
70
- "Jan 15, 2020",
71
- );
72
- },
73
- };
74
-
75
- export const Empty: Story = {
76
- args: {
77
- name: "startDate",
78
- label: "Start date",
79
- },
80
- play: async ({ canvasElement }) => {
81
- const canvas = within(canvasElement);
82
-
83
- await expect(hiddenValue(canvasElement, "startDate")).toBe("");
84
- await expect(canvas.getByRole("button", { name: "Start date" })).toHaveTextContent(
85
- "Pick a date",
86
- );
87
- },
88
- };
89
-
90
- export const Disabled: Story = {
91
- args: {
92
- name: "startDate",
93
- defaultValue: "2020-01-15",
94
- label: "Start date",
95
- disabled: true,
96
- },
97
- play: async ({ canvasElement }) => {
98
- const canvas = within(canvasElement);
99
-
100
- await expect(canvas.getByRole("button", { name: "Start date" })).toBeDisabled();
101
- await expect(hiddenValue(canvasElement, "startDate")).toBe("2020-01-15");
102
- },
103
- };
104
-
105
- // No play function — this one is for manually driving the interaction
106
- // (e.g. screen recordings), not automated assertions.
107
- export const DropdownEmpty: Story = {
108
- args: {
109
- name: "startDate",
110
- label: "Start date",
111
- captionLayout: "dropdown",
112
- },
113
- };
114
-
115
- export const WithDropdownCaption: Story = {
116
- args: {
117
- mode: "range",
118
- startName: "startDate",
119
- endName: "endDate",
120
- defaultValue: { from: "2018-03-01" },
121
- label: "Employment period",
122
- captionLayout: "dropdown",
123
- },
124
- play: async ({ canvasElement }) => {
125
- const canvas = within(canvasElement);
126
-
127
- // Jumping back to a date years ago is a month/year select, not repeated
128
- // prev-month clicks.
129
- const trigger = canvas.getByRole("button", { name: "Employment period" });
130
- await userEvent.click(trigger);
131
- const dialog = await screen.findByRole("dialog");
132
-
133
- const yearSelect = within(dialog).getByRole("combobox", { name: /year/i });
134
- await userEvent.selectOptions(yearSelect, "2009");
135
- const monthSelect = within(dialog).getByRole("combobox", { name: /month/i });
136
- await userEvent.selectOptions(monthSelect, "May");
137
-
138
- await userEvent.click(within(dialog).getByText("12"));
139
- await expect(hiddenValue(canvasElement, "startDate")).toBe("2009-05-12");
140
- },
141
- };
142
-
143
- export const WithRangeDefaultValue: Story = {
144
- args: {
145
- mode: "range",
146
- startName: "startDate",
147
- endName: "endDate",
148
- defaultValue: { from: "2020-01-15", to: "2020-06-30" },
149
- label: "Active period",
150
- },
151
- play: async ({ canvasElement }) => {
152
- const canvas = within(canvasElement);
153
-
154
- // Both hidden inputs carry local date strings; the trigger shows the range.
155
- await expect(hiddenValue(canvasElement, "startDate")).toBe("2020-01-15");
156
- await expect(hiddenValue(canvasElement, "endDate")).toBe("2020-06-30");
157
- const trigger = canvas.getByRole("button", { name: "Active period" });
158
- await expect(trigger).toHaveTextContent("Jan 15, 2020");
159
- await expect(trigger).toHaveTextContent("Jun 30, 2020");
160
-
161
- // The range calendar opens on the start month.
162
- await userEvent.click(trigger);
163
- const dialog = await screen.findByRole("dialog");
164
- await expect(within(dialog).getByText("January 2020")).toBeInTheDocument();
165
-
166
- // Real-click the Next chevron, then pick a new from date.
167
- await nextMonth(dialog);
168
- await expect(within(dialog).getByText("February 2020")).toBeInTheDocument();
169
- await userEvent.click(within(dialog).getByText("20"));
170
- await expect(canvas.getByRole("button", { name: "Active period" })).toHaveTextContent(
171
- "From Feb 20, 2020",
172
- );
173
- await expect(hiddenValue(canvasElement, "startDate")).toBe("2020-02-20");
174
- await expect(hiddenValue(canvasElement, "endDate")).toBe("");
175
-
176
- // Picking an end date fills the second hidden input.
177
- await userEvent.click(within(dialog).getByText("25"));
178
- await expect(canvas.getByRole("button", { name: "Active period" })).toHaveTextContent(
179
- "Feb 20, 2020 – Feb 25, 2020",
180
- );
181
- await expect(hiddenValue(canvasElement, "startDate")).toBe("2020-02-20");
182
- await expect(hiddenValue(canvasElement, "endDate")).toBe("2020-02-25");
183
- },
184
- };
185
-
186
- export const EmptyRange: Story = {
187
- args: {
188
- mode: "range",
189
- startName: "startDate",
190
- endName: "endDate",
191
- label: "Active period",
192
- },
193
- play: async ({ canvasElement }) => {
194
- const canvas = within(canvasElement);
195
-
196
- await expect(hiddenValue(canvasElement, "startDate")).toBe("");
197
- await expect(hiddenValue(canvasElement, "endDate")).toBe("");
198
- await expect(canvas.getByRole("button", { name: "Active period" })).toHaveTextContent(
199
- "Pick a date range",
200
- );
201
- },
202
- };
@@ -1,179 +0,0 @@
1
- import { format } from "date-fns";
2
- import { CalendarIcon } from "lucide-react";
3
- import { useId, useState } from "react";
4
-
5
- import { Popover, PopoverContent, PopoverTrigger } from "../overlays/popover";
6
- import { Button } from "../primitives/button";
7
- import { Calendar } from "./calendar";
8
- import { formatDateInput, parseDateInput } from "./date-utils";
9
- import { DatePicker, type DatePickerProps } from "./date-picker";
10
-
11
- export interface DateRangeValue {
12
- from?: Date | string | null;
13
- to?: Date | string | null;
14
- }
15
-
16
- interface DateFieldCommonProps {
17
- id?: string;
18
- label?: string;
19
- showLabel?: boolean;
20
- placeholder?: string;
21
- dateFormat?: string;
22
- disabled?: boolean;
23
- containerClassName?: string;
24
- popoverAlign?: DatePickerProps["popoverAlign"];
25
- variant?: DatePickerProps["variant"];
26
- captionLayout?: DatePickerProps["captionLayout"];
27
- }
28
-
29
- export interface SingleDateFieldProps extends DateFieldCommonProps {
30
- mode?: "single";
31
- name: string;
32
- defaultValue?: Date | string | null;
33
- }
34
-
35
- export interface RangeDateFieldProps extends DateFieldCommonProps {
36
- mode: "range";
37
- startName: string;
38
- endName: string;
39
- defaultValue?: DateRangeValue;
40
- }
41
-
42
- export type DateFieldProps = SingleDateFieldProps | RangeDateFieldProps;
43
-
44
- const TRIGGER_CLASSES =
45
- "border-border bg-background text-foreground placeholder:text-muted-foreground min-h-10 max-h-10 w-full justify-start rounded-l-none rounded-r-xl border-l-0 text-left font-normal";
46
-
47
- function SingleDateField({
48
- name,
49
- defaultValue,
50
- id,
51
- label,
52
- showLabel,
53
- placeholder,
54
- dateFormat,
55
- disabled,
56
- containerClassName,
57
- popoverAlign,
58
- variant,
59
- captionLayout,
60
- }: SingleDateFieldProps) {
61
- const generatedId = useId();
62
- const inputId = id ?? generatedId;
63
- const [value, setValue] = useState<Date | undefined>(() => parseDateInput(defaultValue));
64
-
65
- return (
66
- <div>
67
- <DatePicker
68
- id={inputId}
69
- label={label}
70
- showLabel={showLabel}
71
- placeholder={placeholder}
72
- dateFormat={dateFormat}
73
- disabled={disabled}
74
- containerClassName={containerClassName}
75
- popoverAlign={popoverAlign}
76
- variant={variant}
77
- captionLayout={captionLayout}
78
- value={value}
79
- onSelect={setValue}
80
- />
81
- <input type="hidden" name={name} value={value ? formatDateInput(value) : ""} />
82
- </div>
83
- );
84
- }
85
-
86
- function RangeDateField({
87
- startName,
88
- endName,
89
- defaultValue,
90
- id,
91
- label,
92
- showLabel = true,
93
- placeholder = "Pick a date range",
94
- dateFormat = "MMM d, yyyy",
95
- disabled = false,
96
- containerClassName = "flex-1 min-w-0",
97
- popoverAlign = "start",
98
- variant = "outline",
99
- captionLayout = "label",
100
- }: RangeDateFieldProps) {
101
- const generatedId = useId();
102
- const inputId = id ?? generatedId;
103
- const [range, setRange] = useState<{ from?: Date; to?: Date }>(() => ({
104
- from: parseDateInput(defaultValue?.from),
105
- to: parseDateInput(defaultValue?.to),
106
- }));
107
-
108
- const fromLabel = range.from ? format(range.from, dateFormat) : null;
109
- const toLabel = range.to ? format(range.to, dateFormat) : null;
110
- const triggerLabel = fromLabel
111
- ? toLabel
112
- ? `${fromLabel} – ${toLabel}`
113
- : `From ${fromLabel}`
114
- : toLabel
115
- ? `Until ${toLabel}`
116
- : placeholder;
117
-
118
- return (
119
- <div className={containerClassName}>
120
- {showLabel && label ? (
121
- <label htmlFor={inputId} className="text-foreground mb-2 block text-sm font-medium">
122
- {label}
123
- </label>
124
- ) : null}
125
- <Popover>
126
- <div className="flex w-full items-center">
127
- <div className="border-border bg-background text-foreground flex size-10 shrink-0 items-center justify-center rounded-l-xl border">
128
- <CalendarIcon className="size-4" aria-hidden />
129
- </div>
130
- <PopoverTrigger asChild>
131
- <Button id={inputId} variant={variant} disabled={disabled} className={TRIGGER_CLASSES}>
132
- {triggerLabel}
133
- </Button>
134
- </PopoverTrigger>
135
- </div>
136
- <PopoverContent
137
- className="bg-popover w-auto p-0"
138
- align={popoverAlign}
139
- aria-label={label ?? "Choose a date range"}
140
- >
141
- <Calendar
142
- mode="range"
143
- selected={range.from ? { from: range.from, to: range.to } : undefined}
144
- defaultMonth={range.from ?? range.to}
145
- resetOnSelect
146
- onSelect={(next) => setRange({ from: next?.from, to: next?.to })}
147
- captionLayout={captionLayout}
148
- />
149
- </PopoverContent>
150
- </Popover>
151
- <input type="hidden" name={startName} value={range.from ? formatDateInput(range.from) : ""} />
152
- <input type="hidden" name={endName} value={range.to ? formatDateInput(range.to) : ""} />
153
- </div>
154
- );
155
- }
156
-
157
- /**
158
- * DateField — calendar picker backed by hidden inputs that submit "YYYY-MM-DD"
159
- * local date strings, so forms never receive UTC-shifted dates. Supports both
160
- * a single date (`name`) and a range (`startName` + `endName`).
161
- *
162
- * @example
163
- * <DateField name="startDate" defaultValue="2020-01-15" label="Start date" />
164
- * <DateField
165
- * mode="range"
166
- * startName="startDate"
167
- * endName="endDate"
168
- * defaultValue={{ from: "2020-01-15", to: "2020-06-30" }}
169
- * label="Active period"
170
- * />
171
- */
172
- function DateField(props: DateFieldProps) {
173
- if (props.mode === "range") {
174
- return <RangeDateField {...props} />;
175
- }
176
- return <SingleDateField {...props} />;
177
- }
178
-
179
- export { DateField };
@@ -1,45 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
-
3
- import {
4
- AlertDialog,
5
- AlertDialogAction,
6
- AlertDialogCancel,
7
- AlertDialogContent,
8
- AlertDialogDescription,
9
- AlertDialogFooter,
10
- AlertDialogHeader,
11
- AlertDialogTitle,
12
- AlertDialogTrigger,
13
- } from "./alert-dialog";
14
-
15
- const meta = {
16
- title: "Overlays/AlertDialog",
17
- component: AlertDialog,
18
- tags: ["autodocs"],
19
- } satisfies Meta<typeof AlertDialog>;
20
-
21
- export default meta;
22
- type Story = StoryObj<typeof meta>;
23
-
24
- export const Default: Story = {
25
- render: () => (
26
- <AlertDialog>
27
- <AlertDialogTrigger className="rounded border px-4 py-2 text-sm">
28
- Open Dialog
29
- </AlertDialogTrigger>
30
- <AlertDialogContent>
31
- <AlertDialogHeader>
32
- <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
33
- <AlertDialogDescription>
34
- This action cannot be undone. This will permanently delete your account and remove your
35
- data from our servers.
36
- </AlertDialogDescription>
37
- </AlertDialogHeader>
38
- <AlertDialogFooter>
39
- <AlertDialogCancel>Cancel</AlertDialogCancel>
40
- <AlertDialogAction>Continue</AlertDialogAction>
41
- </AlertDialogFooter>
42
- </AlertDialogContent>
43
- </AlertDialog>
44
- ),
45
- };
@@ -1,116 +0,0 @@
1
- import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog";
2
- import * as React from "react";
3
-
4
- import { cn } from "../../lib/utils";
5
- import { buttonVariants } from "../primitives/button";
6
-
7
- const AlertDialog = AlertDialogPrimitive.Root;
8
- const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
9
-
10
- const AlertDialogPortal = ({
11
- children,
12
- ...props
13
- }: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) => (
14
- <AlertDialogPrimitive.Portal {...props}>
15
- <div className="fixed inset-0 z-50 flex items-end justify-center sm:items-center">
16
- {children}
17
- </div>
18
- </AlertDialogPrimitive.Portal>
19
- );
20
-
21
- const AlertDialogOverlay = React.forwardRef<
22
- React.ComponentRef<typeof AlertDialogPrimitive.Backdrop>,
23
- React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Backdrop>
24
- >(({ className, ...props }, ref) => (
25
- <AlertDialogPrimitive.Backdrop
26
- ref={ref}
27
- className={cn(
28
- "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 z-50 bg-black/80 transition-opacity",
29
- className,
30
- )}
31
- {...props}
32
- />
33
- ));
34
-
35
- const AlertDialogContent = React.forwardRef<
36
- React.ComponentRef<typeof AlertDialogPrimitive.Popup>,
37
- React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Popup>
38
- >(({ className, ...props }, ref) => (
39
- <AlertDialogPortal>
40
- <AlertDialogOverlay />
41
- <AlertDialogPrimitive.Popup
42
- ref={ref}
43
- className={cn(
44
- "bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 sm:data-open:slide-in-from-bottom-0 fixed bottom-0 z-50 grid w-full max-w-lg gap-6 rounded-t-xl border p-4 outline-none sm:top-[50%] sm:right-auto sm:bottom-auto sm:left-[50%] sm:-translate-x-1/2 sm:-translate-y-1/2 sm:rounded-xl sm:p-6",
45
- className,
46
- )}
47
- {...props}
48
- />
49
- </AlertDialogPortal>
50
- ));
51
-
52
- function AlertDialogHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
53
- return <div className={cn("flex flex-col gap-2 text-left", className)} {...props} />;
54
- }
55
-
56
- function AlertDialogFooter({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
57
- return (
58
- <div
59
- className={cn("flex flex-col-reverse gap-3 sm:flex-row sm:justify-end", className)}
60
- {...props}
61
- />
62
- );
63
- }
64
-
65
- const AlertDialogTitle = React.forwardRef<
66
- React.ComponentRef<typeof AlertDialogPrimitive.Title>,
67
- React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
68
- >(({ className, ...props }, ref) => (
69
- <AlertDialogPrimitive.Title
70
- ref={ref}
71
- className={cn("text-lg font-semibold tracking-tight", className)}
72
- {...props}
73
- />
74
- ));
75
-
76
- const AlertDialogDescription = React.forwardRef<
77
- React.ComponentRef<typeof AlertDialogPrimitive.Description>,
78
- React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
79
- >(({ className, ...props }, ref) => (
80
- <AlertDialogPrimitive.Description
81
- ref={ref}
82
- className={cn("text-muted-foreground max-w-prose text-sm", className)}
83
- {...props}
84
- />
85
- ));
86
-
87
- const AlertDialogAction = React.forwardRef<
88
- React.ComponentRef<typeof AlertDialogPrimitive.Close>,
89
- React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Close>
90
- >(({ className, ...props }, ref) => (
91
- <AlertDialogPrimitive.Close ref={ref} className={cn(buttonVariants(), className)} {...props} />
92
- ));
93
-
94
- const AlertDialogCancel = React.forwardRef<
95
- React.ComponentRef<typeof AlertDialogPrimitive.Close>,
96
- React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Close>
97
- >(({ className, ...props }, ref) => (
98
- <AlertDialogPrimitive.Close
99
- ref={ref}
100
- className={cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className)}
101
- {...props}
102
- />
103
- ));
104
-
105
- export {
106
- AlertDialog,
107
- AlertDialogAction,
108
- AlertDialogCancel,
109
- AlertDialogContent,
110
- AlertDialogDescription,
111
- AlertDialogFooter,
112
- AlertDialogHeader,
113
- AlertDialogOverlay,
114
- AlertDialogTitle,
115
- AlertDialogTrigger,
116
- };
@@ -1,29 +0,0 @@
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
- };
@@ -1,20 +0,0 @@
1
- import * as React from "react";
2
-
3
- import { cn } from "../../lib/utils";
4
-
5
- const Label = React.forwardRef<HTMLLabelElement, React.LabelHTMLAttributes<HTMLLabelElement>>(
6
- ({ className, ...props }, ref) => (
7
- <label
8
- htmlFor={props.htmlFor}
9
- ref={ref}
10
- className={cn(
11
- "text-sm font-medium leading-none text-foreground peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
12
- className,
13
- )}
14
- {...props}
15
- />
16
- ),
17
- );
18
- Label.displayName = "Label";
19
-
20
- export { Label };
@@ -1,52 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
- import { CheckCircle, Clock, XCircle } from "lucide-react";
3
-
4
- import { StatusBadge, type StatusBadgeConfig } from "./status-badge";
5
-
6
- type ConnectionStatus = "active" | "error" | "pending" | "revoked";
7
-
8
- const connectionStatusConfig: Record<ConnectionStatus, StatusBadgeConfig> = {
9
- active: { label: "Active", variant: "default", icon: <CheckCircle className="mr-1 size-3" /> },
10
- error: { label: "Error", variant: "destructive", icon: <XCircle className="mr-1 size-3" /> },
11
- pending: { label: "Pending", variant: "secondary", icon: <Clock className="mr-1 size-3" /> },
12
- revoked: { label: "Revoked", variant: "outline" },
13
- };
14
-
15
- const meta = {
16
- title: "Primitives/StatusBadge",
17
- component: StatusBadge<ConnectionStatus>,
18
- tags: ["autodocs"],
19
- } satisfies Meta<typeof StatusBadge<ConnectionStatus>>;
20
-
21
- export default meta;
22
- type Story = StoryObj<typeof meta>;
23
-
24
- export const Default: Story = {
25
- args: {
26
- status: "active",
27
- config: connectionStatusConfig,
28
- },
29
- };
30
-
31
- export const AllStatuses: Story = {
32
- args: {
33
- status: "active",
34
- config: connectionStatusConfig,
35
- },
36
- render: () => (
37
- <div className="flex flex-wrap gap-2">
38
- <StatusBadge status="active" config={connectionStatusConfig} />
39
- <StatusBadge status="error" config={connectionStatusConfig} />
40
- <StatusBadge status="pending" config={connectionStatusConfig} />
41
- <StatusBadge status="revoked" config={connectionStatusConfig} />
42
- </div>
43
- ),
44
- };
45
-
46
- export const WithFallback: Story = {
47
- args: {
48
- status: null,
49
- config: connectionStatusConfig,
50
- fallback: { label: "Unknown", variant: "outline" },
51
- },
52
- };
@@ -1,33 +0,0 @@
1
- import type { ReactNode } from "react";
2
-
3
- import { Badge, type badgeVariants } from "./badge";
4
- import type { VariantProps } from "class-variance-authority";
5
-
6
- export interface StatusBadgeConfig {
7
- label: string;
8
- variant?: VariantProps<typeof badgeVariants>["variant"];
9
- className?: string;
10
- icon?: ReactNode;
11
- }
12
-
13
- export interface StatusBadgeProps<T extends string> {
14
- status: T | null | undefined;
15
- config: Record<T, StatusBadgeConfig>;
16
- fallback?: StatusBadgeConfig;
17
- }
18
-
19
- /** Renders a `Badge` from a status → { label, variant, icon } config map. */
20
- export function StatusBadge<T extends string>({ status, config, fallback }: StatusBadgeProps<T>) {
21
- const entry = (status != null ? config[status] : undefined) ?? fallback;
22
-
23
- if (!entry) {
24
- return null;
25
- }
26
-
27
- return (
28
- <Badge variant={entry.variant ?? "outline"} className={entry.className}>
29
- {entry.icon}
30
- {entry.label}
31
- </Badge>
32
- );
33
- }