@syscore/ui-library 1.27.3 → 2.0.0-beta-publish-workflow-beta.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 (169) hide show
  1. package/client/App.tsx +0 -2
  2. package/client/assets/alert/icon-clear.svg +4 -0
  3. package/client/assets/alert/icon-done.svg +3 -0
  4. package/client/assets/avatar-identity/photo.png +0 -0
  5. package/client/assets/avatar-identity/placeholder-icon.svg +8 -0
  6. package/client/assets/breadcrumb/icon-ellipsis.svg +5 -0
  7. package/client/assets/breadcrumb/icon-home.svg +3 -0
  8. package/client/assets/breadcrumb/icon-separator.svg +3 -0
  9. package/client/assets/button-v01/icon-back.svg +4 -0
  10. package/client/assets/button-v01/icon-check.svg +3 -0
  11. package/client/assets/button-v01/icon-close.svg +4 -0
  12. package/client/assets/button-v01/icon-delete.svg +3 -0
  13. package/client/assets/button-v01/icon-device.svg +3 -0
  14. package/client/assets/dropdown/icon-carrot-contract.svg +3 -0
  15. package/client/assets/dropdown/icon-carrot-expand.svg +3 -0
  16. package/client/assets/dropdown/icon-clear.svg +4 -0
  17. package/client/assets/dropdown/icon-enter.svg +3 -0
  18. package/client/components/icons/UtilityChangeType.tsx +21 -0
  19. package/client/components/ui/alert-dialog-panel.tsx +246 -0
  20. package/client/components/ui/alert-v02.tsx +214 -0
  21. package/client/components/ui/app-bar.tsx +299 -0
  22. package/client/components/ui/avatar-identity.tsx +122 -0
  23. package/client/components/ui/banner.tsx +108 -0
  24. package/client/components/ui/bottom-navigation.tsx +223 -0
  25. package/client/components/ui/breadcrumb-trail.tsx +170 -0
  26. package/client/components/ui/button-v01.tsx +238 -0
  27. package/client/components/ui/button.tsx +2 -0
  28. package/client/components/ui/card.tsx +108 -26
  29. package/client/components/ui/code-badge.tsx +62 -0
  30. package/client/components/ui/date-picker.tsx +188 -0
  31. package/client/components/ui/dropdown.tsx +448 -0
  32. package/client/components/ui/empty-state.tsx +197 -0
  33. package/client/components/ui/file-upload.tsx +214 -0
  34. package/client/components/ui/footer.tsx +179 -0
  35. package/client/components/ui/layout.tsx +1182 -0
  36. package/client/components/ui/pagination-v01.tsx +157 -0
  37. package/client/components/ui/password-field-v01.tsx +67 -0
  38. package/client/components/ui/search-field-v01.tsx +192 -0
  39. package/client/components/ui/section-heading.tsx +49 -0
  40. package/client/components/ui/sidebar.tsx +2 -3
  41. package/client/components/ui/standard-navigation.tsx +8 -2
  42. package/client/components/ui/stepper.tsx +148 -0
  43. package/client/components/ui/switch-v01.tsx +71 -0
  44. package/client/components/ui/system-bar.tsx +119 -0
  45. package/client/components/ui/table-v01.tsx +230 -0
  46. package/client/components/ui/tabs-v01.tsx +99 -0
  47. package/client/components/ui/tag-v02.tsx +113 -0
  48. package/client/components/ui/text-area-v01.tsx +33 -0
  49. package/client/components/ui/text-field-v01.tsx +108 -0
  50. package/client/components/ui/timeline.tsx +181 -0
  51. package/client/components/ui/toast-v01.tsx +206 -0
  52. package/client/components/ui/tooltip.tsx +264 -71
  53. package/client/global.css +9565 -4378
  54. package/client/storybook.css +125 -0
  55. package/client/ui/Accordion.stories.tsx +1 -1
  56. package/client/ui/AlertDialogPanel/AlertDialogPanel.stories.tsx +150 -0
  57. package/client/ui/AlertV02/AlertV02.stories.tsx +188 -0
  58. package/client/ui/AppBar/app-bar.stories.tsx +280 -0
  59. package/client/ui/AspectRatio.stories.tsx +1 -1
  60. package/client/ui/AvatarIdentity/AvatarIdentity.stories.tsx +77 -0
  61. package/client/ui/Banner/banner.stories.tsx +238 -0
  62. package/client/ui/BottomNavigation/bottom-navigation.stories.tsx +285 -0
  63. package/client/ui/BreadcrumbTrail/BreadcrumbTrail.stories.tsx +232 -0
  64. package/client/ui/Button.stories.tsx +1 -1
  65. package/client/ui/ButtonV01/ButtonV01.stories.tsx +417 -0
  66. package/client/ui/Calendar/Calendar.stories.tsx +1 -1
  67. package/client/ui/Card.stories.tsx +285 -168
  68. package/client/ui/Carousel/Carousel.stories.tsx +1 -1
  69. package/client/ui/Chart/Chart.stories.tsx +1 -1
  70. package/client/ui/Checkbox.stories.tsx +1 -1
  71. package/client/ui/Collapsible/Collapsible.stories.tsx +1 -1
  72. package/client/ui/Colors.stories.tsx +1 -1
  73. package/client/ui/Command/Command.stories.tsx +1 -1
  74. package/client/ui/ContextMenu/ContextMenu.stories.tsx +1 -1
  75. package/client/ui/DatePicker/DatePicker.stories.tsx +293 -0
  76. package/client/ui/Dialog.stories.tsx +1 -1
  77. package/client/ui/Drawer/Drawer.stories.tsx +1 -1
  78. package/client/ui/Dropdown/Dropdown.stories.tsx +205 -0
  79. package/client/ui/EmptyState/empty-state.stories.tsx +251 -0
  80. package/client/ui/FileUpload/FileUpload.stories.tsx +218 -0
  81. package/client/ui/Footer/footer.stories.tsx +194 -0
  82. package/client/ui/Form/Form.stories.tsx +1 -1
  83. package/client/ui/Hero.stories.tsx +1 -1
  84. package/client/ui/HoverCard/HoverCard.stories.tsx +1 -1
  85. package/client/ui/Icons.stories.tsx +1 -1
  86. package/client/ui/Input.stories.tsx +1 -1
  87. package/client/ui/InputOTP/InputOTP.stories.tsx +1 -1
  88. package/client/ui/Label.stories.tsx +1 -1
  89. package/client/ui/Layout/Center.stories.tsx +100 -0
  90. package/client/ui/Layout/Columns.stories.tsx +136 -0
  91. package/client/ui/Layout/Container.stories.tsx +104 -0
  92. package/client/ui/Layout/FormLayout.stories.tsx +144 -0
  93. package/client/ui/Layout/Grid.stories.tsx +158 -0
  94. package/client/ui/Layout/GridSpan.stories.tsx +75 -0
  95. package/client/ui/Layout/HStack.stories.tsx +128 -0
  96. package/client/ui/Layout/Layout.stories.tsx +287 -0
  97. package/client/ui/Layout/LayoutContent.stories.tsx +91 -0
  98. package/client/ui/Layout/LayoutFooter.stories.tsx +77 -0
  99. package/client/ui/Layout/LayoutHeader.stories.tsx +104 -0
  100. package/client/ui/Layout/LayoutPanel.stories.tsx +143 -0
  101. package/client/ui/Layout/Positioned.stories.tsx +113 -0
  102. package/client/ui/Layout/Section.stories.tsx +84 -0
  103. package/client/ui/Layout/ShowHide.stories.tsx +99 -0
  104. package/client/ui/Layout/Spacer.stories.tsx +149 -0
  105. package/client/ui/Layout/Stack.stories.tsx +69 -0
  106. package/client/ui/Layout/StackItem.stories.tsx +124 -0
  107. package/client/ui/Layout/VStack.stories.tsx +101 -0
  108. package/client/ui/Layout/controls.ts +33 -0
  109. package/client/ui/Layout/demo.tsx +37 -0
  110. package/client/ui/Menubar/Menubar.stories.tsx +1 -1
  111. package/client/ui/MobileNav.stories.tsx +1 -1
  112. package/client/ui/Navigation.stories.tsx +1 -1
  113. package/client/ui/NavigationMenu/NavigationMenu.stories.tsx +1 -1
  114. package/client/ui/PageHeader.stories.tsx +13 -13
  115. package/client/ui/Pagination/Pagination.stories.tsx +178 -98
  116. package/client/ui/Pagination/PaginationLegacy.stories.tsx +121 -0
  117. package/client/ui/Panel.stories.tsx +1 -1
  118. package/client/ui/Popover/Popover.stories.tsx +1 -1
  119. package/client/ui/Progress/Progress.stories.tsx +1 -1
  120. package/client/ui/RadioGroup/RadioGroup.stories.tsx +1 -1
  121. package/client/ui/Resizable/Resizable.stories.tsx +1 -1
  122. package/client/ui/ScrollArea/ScrollArea.stories.tsx +1 -1
  123. package/client/ui/SearchField/SearchField.stories.tsx +153 -0
  124. package/client/ui/{SearchField.stories.tsx → SearchField/SearchFieldLegacy.stories.tsx} +2 -2
  125. package/client/ui/SectionBadge.stories.tsx +1 -1
  126. package/client/ui/Select.stories.tsx +1 -1
  127. package/client/ui/Separator/Separator.stories.tsx +1 -1
  128. package/client/ui/Sheet/Sheet.stories.tsx +1 -1
  129. package/client/ui/Sidebar/Sidebar.stories.tsx +57 -8
  130. package/client/ui/Skeleton/Skeleton.stories.tsx +1 -1
  131. package/client/ui/Slider/Slider.stories.tsx +1 -1
  132. package/client/ui/StandardNavigation.stories.tsx +1 -1
  133. package/client/ui/Stepper/Stepper.stories.tsx +344 -0
  134. package/client/ui/Switch.stories.tsx +1 -1
  135. package/client/ui/SwitchV01/SwitchV01.stories.tsx +152 -0
  136. package/client/ui/SystemBar/system-bar.stories.tsx +179 -0
  137. package/client/ui/Table/TableV01.stories.tsx +256 -0
  138. package/client/ui/Tabs.stories.tsx +1 -1
  139. package/client/ui/TabsV01/TabsV01.stories.tsx +144 -0
  140. package/client/ui/Tag.stories.tsx +1 -1
  141. package/client/ui/TagV02/TagStatusV02.stories.tsx +132 -0
  142. package/client/ui/TagV02/TagV02.stories.tsx +142 -0
  143. package/client/ui/TextAreaV01/TextAreaV01.stories.tsx +146 -0
  144. package/client/ui/TextFieldV01/PasswordFieldV01.stories.tsx +161 -0
  145. package/client/ui/TextFieldV01/TextFieldV01.stories.tsx +160 -0
  146. package/client/ui/Textarea.stories.tsx +1 -1
  147. package/client/ui/Timeline/timeline.stories.tsx +404 -0
  148. package/client/ui/Toast/Toast.stories.tsx +1 -1
  149. package/client/ui/ToastV01/ToastV01.stories.tsx +201 -0
  150. package/client/ui/Toaster/Toaster.stories.tsx +1 -1
  151. package/client/ui/Toggle.stories.tsx +1 -1
  152. package/client/ui/Tooltip.stories.tsx +287 -114
  153. package/client/ui/Typography.stories.tsx +1 -1
  154. package/dist/index.cjs.js +1 -1
  155. package/dist/index.d.ts +649 -13
  156. package/dist/index.es.js +4405 -653
  157. package/package.json +1 -1
  158. package/client/components/ui/badge.tsx +0 -33
  159. package/client/components/ui/sonner.tsx +0 -26
  160. package/client/components/ui/toggle-group.tsx +0 -76
  161. package/client/ui/Alert/Alert.stories.tsx +0 -82
  162. package/client/ui/AlertDialog/AlertDialog.stories.tsx +0 -106
  163. package/client/ui/Avatar/Avatar.stories.tsx +0 -94
  164. package/client/ui/Badge/Badge.stories.tsx +0 -60
  165. package/client/ui/Breadcrumb/Breadcrumb.stories.tsx +0 -97
  166. package/client/ui/DropdownMenu/DropdownMenu.stories.tsx +0 -139
  167. package/client/ui/Sonner/Sonner.stories.tsx +0 -48
  168. package/client/ui/Table/Table.stories.tsx +0 -135
  169. package/client/ui/ToggleGroup/ToggleGroup.stories.tsx +0 -88
@@ -1,18 +1,37 @@
1
1
  import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
2
3
 
3
4
  import { cn } from "@/lib/utils";
4
5
  import { Text } from "./typography";
5
6
 
6
- const Card = React.forwardRef<
7
- HTMLDivElement,
8
- React.HTMLAttributes<HTMLDivElement>
9
- >(({ className, ...props }, ref) => (
10
- <div
11
- ref={ref}
12
- className={cn("card", className)}
13
- {...props}
14
- />
15
- ));
7
+ const cardVariants = cva("card", {
8
+ variants: {
9
+ variant: {
10
+ default: "card--default",
11
+ outlined: "card--outlined",
12
+ elevated: "card--elevated",
13
+ filled: "card--filled",
14
+ interactive: "card--interactive",
15
+ },
16
+ },
17
+ defaultVariants: {
18
+ variant: "default",
19
+ },
20
+ });
21
+
22
+ interface CardProps
23
+ extends React.HTMLAttributes<HTMLDivElement>,
24
+ VariantProps<typeof cardVariants> {}
25
+
26
+ const Card = React.forwardRef<HTMLDivElement, CardProps>(
27
+ ({ className, variant, ...props }, ref) => (
28
+ <div
29
+ ref={ref}
30
+ className={cn(cardVariants({ variant }), className)}
31
+ {...props}
32
+ />
33
+ ),
34
+ );
16
35
  Card.displayName = "Card";
17
36
 
18
37
  const CardHeader = React.forwardRef<
@@ -71,6 +90,65 @@ const CardFooter = React.forwardRef<
71
90
  ));
72
91
  CardFooter.displayName = "CardFooter";
73
92
 
93
+ const CardMedia = React.forwardRef<
94
+ HTMLDivElement,
95
+ React.HTMLAttributes<HTMLDivElement> & { src?: string; alt?: string; height?: number }
96
+ >(({ className, src, alt = "", height = 200, children, ...props }, ref) => (
97
+ <div ref={ref} className={cn("card-media", className)} style={{ height }} {...props}>
98
+ {src ? (
99
+ <img src={src} alt={alt} className="card-media__img" />
100
+ ) : (
101
+ children
102
+ )}
103
+ </div>
104
+ ));
105
+ CardMedia.displayName = "CardMedia";
106
+
107
+ interface CardBadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
108
+ color?: "default" | "success" | "warning" | "error" | "info";
109
+ }
110
+
111
+ const CardBadge = React.forwardRef<HTMLSpanElement, CardBadgeProps>(
112
+ ({ className, color = "default", ...props }, ref) => (
113
+ <span
114
+ ref={ref}
115
+ className={cn("card-badge", `card-badge--${color}`, className)}
116
+ {...props}
117
+ />
118
+ ),
119
+ );
120
+ CardBadge.displayName = "CardBadge";
121
+
122
+ const CardActions = React.forwardRef<
123
+ HTMLDivElement,
124
+ React.HTMLAttributes<HTMLDivElement>
125
+ >(({ className, ...props }, ref) => (
126
+ <div ref={ref} className={cn("card-actions", className)} {...props} />
127
+ ));
128
+ CardActions.displayName = "CardActions";
129
+
130
+ interface CardMetricProps extends React.HTMLAttributes<HTMLDivElement> {
131
+ value: string | number;
132
+ label: string;
133
+ trend?: "up" | "down" | "neutral";
134
+ trendValue?: string;
135
+ }
136
+
137
+ const CardMetric = React.forwardRef<HTMLDivElement, CardMetricProps>(
138
+ ({ className, value, label, trend, trendValue, ...props }, ref) => (
139
+ <div ref={ref} className={cn("card-metric", className)} {...props}>
140
+ <span className="card-metric__value">{value}</span>
141
+ <span className="card-metric__label">{label}</span>
142
+ {trend && trendValue && (
143
+ <span className={cn("card-metric__trend", `card-metric__trend--${trend}`)}>
144
+ {trend === "up" ? "↑" : trend === "down" ? "↓" : "→"} {trendValue}
145
+ </span>
146
+ )}
147
+ </div>
148
+ ),
149
+ );
150
+ CardMetric.displayName = "CardMetric";
151
+
74
152
  interface CardWithIconProps {
75
153
  icon?: React.ComponentType<React.SVGProps<SVGSVGElement> | { className?: string }>;
76
154
  title: string;
@@ -78,22 +156,22 @@ interface CardWithIconProps {
78
156
  onClick?: React.MouseEventHandler<HTMLDivElement>;
79
157
  }
80
158
 
81
- const CardWithIcon = React.forwardRef<
82
- HTMLDivElement,
83
- CardWithIconProps
84
- >(({ icon: Icon, title, description, onClick }, ref) => (
85
- <Card
86
- ref={ref}
87
- className="card-with-icon"
88
- onClick={onClick}
89
- >
90
- <div className="card-with-icon__header">
91
- {Icon && <Icon className="card-with-icon__icon" />}
92
- <Text as="h3" variant="overline-large">{title}</Text>
93
- </div>
94
- <Text as="p" variant="body-base">{description}</Text>
95
- </Card>
96
- ));
159
+ const CardWithIcon = React.forwardRef<HTMLDivElement, CardWithIconProps>(
160
+ ({ icon: Icon, title, description, onClick }, ref) => (
161
+ <Card
162
+ ref={ref}
163
+ variant="interactive"
164
+ className="card-with-icon"
165
+ onClick={onClick}
166
+ >
167
+ <div className="card-with-icon__header">
168
+ {Icon && <Icon className="card-with-icon__icon" />}
169
+ <Text as="h3" variant="overline-large">{title}</Text>
170
+ </div>
171
+ <Text as="p" variant="body-base">{description}</Text>
172
+ </Card>
173
+ ),
174
+ );
97
175
  CardWithIcon.displayName = "CardWithIcon";
98
176
 
99
177
  export {
@@ -103,5 +181,9 @@ export {
103
181
  CardTitle,
104
182
  CardDescription,
105
183
  CardContent,
184
+ CardMedia,
185
+ CardBadge,
186
+ CardActions,
187
+ CardMetric,
106
188
  CardWithIcon,
107
189
  };
@@ -0,0 +1,62 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ /** Figma `code_theme_*` / `code_strategy_*`. */
6
+ export type CodeBadgeConcept =
7
+ | "mind"
8
+ | "community"
9
+ | "movement"
10
+ | "water"
11
+ | "air"
12
+ | "light"
13
+ | "thermal-comfort"
14
+ | "nourishment"
15
+ | "sound"
16
+ | "materials"
17
+ | "inactive";
18
+
19
+ /** `theme` is the filled chip, `strategy` the tinted one. */
20
+ export type CodeBadgeVariant = "theme" | "strategy";
21
+
22
+ /**
23
+ * Strategy chips are tinted at different strengths depending on where they
24
+ * sit. Breadcrumbs use 8%/16%; tables use 12%/24% (which is also what
25
+ * client/lib/concept-colors.ts records). Figma specifies both — neither is a
26
+ * mistake, so the strength is a variant rather than a constant.
27
+ */
28
+ export type CodeBadgeTone = "trail" | "table";
29
+
30
+ export interface CodeBadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
31
+ concept?: CodeBadgeConcept;
32
+ variant?: CodeBadgeVariant;
33
+ tone?: CodeBadgeTone;
34
+ }
35
+
36
+ const CodeBadge = React.forwardRef<HTMLSpanElement, CodeBadgeProps>(
37
+ (
38
+ {
39
+ className,
40
+ variant = "theme",
41
+ concept = "community",
42
+ tone = "trail",
43
+ ...props
44
+ },
45
+ ref,
46
+ ) => (
47
+ <span
48
+ ref={ref}
49
+ className={cn(
50
+ "code-badge number-small font-semibold",
51
+ variant === "theme" ? "code-badge--theme" : "code-badge--strategy",
52
+ `code-badge--${concept}`,
53
+ tone === "table" && "code-badge--tone-table",
54
+ className,
55
+ )}
56
+ {...props}
57
+ />
58
+ ),
59
+ );
60
+ CodeBadge.displayName = "CodeBadge";
61
+
62
+ export { CodeBadge };
@@ -0,0 +1,188 @@
1
+ import * as React from "react";
2
+ import { format } from "date-fns";
3
+ import { Calendar } from "@/components/ui/calendar";
4
+ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
5
+ import { cn } from "@/lib/utils";
6
+
7
+ export interface DatePickerProps {
8
+ value?: Date;
9
+ onChange?: (date: Date | undefined) => void;
10
+ placeholder?: string;
11
+ disabled?: boolean;
12
+ disabledDates?: (date: Date) => boolean;
13
+ fromDate?: Date;
14
+ toDate?: Date;
15
+ dateFormat?: string;
16
+ className?: string;
17
+ align?: "start" | "center" | "end";
18
+ }
19
+
20
+ const DatePicker = React.forwardRef<HTMLButtonElement, DatePickerProps>(
21
+ (
22
+ {
23
+ value,
24
+ onChange,
25
+ placeholder = "Select date",
26
+ disabled = false,
27
+ disabledDates,
28
+ fromDate,
29
+ toDate,
30
+ dateFormat = "MMM d, yyyy",
31
+ className,
32
+ align = "start",
33
+ },
34
+ ref,
35
+ ) => {
36
+ const [open, setOpen] = React.useState(false);
37
+
38
+ const handleSelect = (date: Date | undefined) => {
39
+ onChange?.(date);
40
+ setOpen(false);
41
+ };
42
+
43
+ return (
44
+ <Popover open={open} onOpenChange={setOpen}>
45
+ <PopoverTrigger asChild>
46
+ <button
47
+ ref={ref}
48
+ type="button"
49
+ disabled={disabled}
50
+ aria-haspopup="dialog"
51
+ aria-expanded={open}
52
+ className={cn(
53
+ "date-picker-trigger",
54
+ !value && "date-picker-trigger--placeholder",
55
+ disabled && "date-picker-trigger--disabled",
56
+ className,
57
+ )}
58
+ >
59
+ <span className="date-picker-icon" aria-hidden="true">
60
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
61
+ <rect x="1" y="3" width="14" height="12" rx="2" stroke="currentColor" strokeWidth="1.5" />
62
+ <path d="M1 7h14" stroke="currentColor" strokeWidth="1.5" />
63
+ <path d="M5 1v4M11 1v4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
64
+ </svg>
65
+ </span>
66
+ <span className="date-picker-value">
67
+ {value ? format(value, dateFormat) : placeholder}
68
+ </span>
69
+ <span className="date-picker-chevron" aria-hidden="true">
70
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none">
71
+ <path d="M2 4l4 4 4-4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
72
+ </svg>
73
+ </span>
74
+ </button>
75
+ </PopoverTrigger>
76
+ <PopoverContent
77
+ className="date-picker-popover"
78
+ align={align}
79
+ sideOffset={6}
80
+ >
81
+ <Calendar
82
+ mode="single"
83
+ selected={value}
84
+ onSelect={handleSelect}
85
+ disabled={disabledDates}
86
+ fromDate={fromDate}
87
+ toDate={toDate}
88
+ initialFocus
89
+ />
90
+ </PopoverContent>
91
+ </Popover>
92
+ );
93
+ },
94
+ );
95
+
96
+ DatePicker.displayName = "DatePicker";
97
+
98
+ // ---------------------------------------------------------------------------
99
+ // DateRangePicker
100
+ // ---------------------------------------------------------------------------
101
+ export interface DateRange {
102
+ from: Date | undefined;
103
+ to?: Date | undefined;
104
+ }
105
+
106
+ export interface DateRangePickerProps {
107
+ value?: DateRange;
108
+ onChange?: (range: DateRange | undefined) => void;
109
+ placeholder?: string;
110
+ disabled?: boolean;
111
+ dateFormat?: string;
112
+ className?: string;
113
+ align?: "start" | "center" | "end";
114
+ }
115
+
116
+ const DateRangePicker = React.forwardRef<HTMLButtonElement, DateRangePickerProps>(
117
+ (
118
+ {
119
+ value,
120
+ onChange,
121
+ placeholder = "Select date range",
122
+ disabled = false,
123
+ dateFormat = "MMM d, yyyy",
124
+ className,
125
+ align = "start",
126
+ },
127
+ ref,
128
+ ) => {
129
+ const [open, setOpen] = React.useState(false);
130
+
131
+ const label = React.useMemo(() => {
132
+ if (!value?.from) return null;
133
+ if (!value.to) return format(value.from, dateFormat);
134
+ return `${format(value.from, dateFormat)} — ${format(value.to, dateFormat)}`;
135
+ }, [value, dateFormat]);
136
+
137
+ return (
138
+ <Popover open={open} onOpenChange={setOpen}>
139
+ <PopoverTrigger asChild>
140
+ <button
141
+ ref={ref}
142
+ type="button"
143
+ disabled={disabled}
144
+ aria-haspopup="dialog"
145
+ aria-expanded={open}
146
+ className={cn(
147
+ "date-picker-trigger",
148
+ !label && "date-picker-trigger--placeholder",
149
+ disabled && "date-picker-trigger--disabled",
150
+ className,
151
+ )}
152
+ >
153
+ <span className="date-picker-icon" aria-hidden="true">
154
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
155
+ <rect x="1" y="3" width="14" height="12" rx="2" stroke="currentColor" strokeWidth="1.5" />
156
+ <path d="M1 7h14" stroke="currentColor" strokeWidth="1.5" />
157
+ <path d="M5 1v4M11 1v4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
158
+ </svg>
159
+ </span>
160
+ <span className="date-picker-value">{label ?? placeholder}</span>
161
+ <span className="date-picker-chevron" aria-hidden="true">
162
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none">
163
+ <path d="M2 4l4 4 4-4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
164
+ </svg>
165
+ </span>
166
+ </button>
167
+ </PopoverTrigger>
168
+ <PopoverContent
169
+ className="date-picker-popover"
170
+ align={align}
171
+ sideOffset={6}
172
+ >
173
+ <Calendar
174
+ mode="range"
175
+ selected={value}
176
+ onSelect={onChange as (range: DateRange | undefined) => void}
177
+ numberOfMonths={2}
178
+ initialFocus
179
+ />
180
+ </PopoverContent>
181
+ </Popover>
182
+ );
183
+ },
184
+ );
185
+
186
+ DateRangePicker.displayName = "DateRangePicker";
187
+
188
+ export { DatePicker, DateRangePicker };