@shipfox/react-ui 0.11.0 → 0.13.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 (115) hide show
  1. package/.turbo/turbo-build.log +6 -6
  2. package/.turbo/turbo-check.log +2 -2
  3. package/.turbo/turbo-type.log +1 -1
  4. package/CHANGELOG.md +12 -0
  5. package/dist/components/badge/index.d.ts +4 -4
  6. package/dist/components/badge/index.d.ts.map +1 -1
  7. package/dist/components/badge/index.js +4 -4
  8. package/dist/components/badge/index.js.map +1 -1
  9. package/dist/components/calendar/calendar.d.ts +5 -0
  10. package/dist/components/calendar/calendar.d.ts.map +1 -0
  11. package/dist/components/calendar/calendar.js +46 -0
  12. package/dist/components/calendar/calendar.js.map +1 -0
  13. package/dist/components/calendar/index.d.ts +2 -0
  14. package/dist/components/calendar/index.d.ts.map +1 -0
  15. package/dist/components/calendar/index.js +3 -0
  16. package/dist/components/calendar/index.js.map +1 -0
  17. package/dist/components/date-picker/date-picker.d.ts +19 -0
  18. package/dist/components/date-picker/date-picker.d.ts.map +1 -0
  19. package/dist/components/date-picker/date-picker.js +114 -0
  20. package/dist/components/date-picker/date-picker.js.map +1 -0
  21. package/dist/components/date-picker/date-picker.stories.js +333 -0
  22. package/dist/components/date-picker/date-picker.stories.js.map +1 -0
  23. package/dist/components/date-picker/index.d.ts +2 -0
  24. package/dist/components/date-picker/index.d.ts.map +1 -0
  25. package/dist/components/date-picker/index.js +3 -0
  26. package/dist/components/date-picker/index.js.map +1 -0
  27. package/dist/components/date-time-range-picker/date-time-range-picker.d.ts +24 -0
  28. package/dist/components/date-time-range-picker/date-time-range-picker.d.ts.map +1 -0
  29. package/dist/components/date-time-range-picker/date-time-range-picker.js +130 -0
  30. package/dist/components/date-time-range-picker/date-time-range-picker.js.map +1 -0
  31. package/dist/components/date-time-range-picker/index.d.ts +2 -0
  32. package/dist/components/date-time-range-picker/index.d.ts.map +1 -0
  33. package/dist/components/date-time-range-picker/index.js +3 -0
  34. package/dist/components/date-time-range-picker/index.js.map +1 -0
  35. package/dist/components/dropdown-menu/index.d.ts +1 -2
  36. package/dist/components/dropdown-menu/index.d.ts.map +1 -1
  37. package/dist/components/dropdown-menu/index.js +1 -1
  38. package/dist/components/dropdown-menu/index.js.map +1 -1
  39. package/dist/components/index.d.ts +6 -0
  40. package/dist/components/index.d.ts.map +1 -1
  41. package/dist/components/index.js +6 -0
  42. package/dist/components/index.js.map +1 -1
  43. package/dist/components/inline-tips/inline-tips.stories.js +5 -0
  44. package/dist/components/inline-tips/inline-tips.stories.js.map +1 -1
  45. package/dist/components/item/item.stories.js +15 -8
  46. package/dist/components/item/item.stories.js.map +1 -1
  47. package/dist/components/modal/index.d.ts +1 -2
  48. package/dist/components/modal/index.d.ts.map +1 -1
  49. package/dist/components/modal/index.js +1 -1
  50. package/dist/components/modal/index.js.map +1 -1
  51. package/dist/components/modal/modal.d.ts +2 -1
  52. package/dist/components/modal/modal.d.ts.map +1 -1
  53. package/dist/components/modal/modal.js +5 -3
  54. package/dist/components/modal/modal.js.map +1 -1
  55. package/dist/components/modal/modal.stories.js +16 -6
  56. package/dist/components/modal/modal.stories.js.map +1 -1
  57. package/dist/components/popover/index.d.ts +2 -0
  58. package/dist/components/popover/index.d.ts.map +1 -0
  59. package/dist/components/popover/index.js +3 -0
  60. package/dist/components/popover/index.js.map +1 -0
  61. package/dist/components/popover/popover.d.ts +10 -0
  62. package/dist/components/popover/popover.d.ts.map +1 -0
  63. package/dist/components/popover/popover.js +47 -0
  64. package/dist/components/popover/popover.js.map +1 -0
  65. package/dist/components/tabs/index.d.ts +2 -0
  66. package/dist/components/tabs/index.d.ts.map +1 -0
  67. package/dist/components/tabs/index.js +3 -0
  68. package/dist/components/tabs/index.js.map +1 -0
  69. package/dist/components/tabs/tabs.d.ts +50 -0
  70. package/dist/components/tabs/tabs.d.ts.map +1 -0
  71. package/dist/components/tabs/tabs.js +243 -0
  72. package/dist/components/tabs/tabs.js.map +1 -0
  73. package/dist/components/tabs/tabs.stories.js +179 -0
  74. package/dist/components/tabs/tabs.stories.js.map +1 -0
  75. package/dist/components/textarea/textarea.stories.js +8 -2
  76. package/dist/components/textarea/textarea.stories.js.map +1 -1
  77. package/dist/components/toast/index.d.ts +2 -2
  78. package/dist/components/toast/index.d.ts.map +1 -1
  79. package/dist/components/toast/index.js +2 -2
  80. package/dist/components/toast/index.js.map +1 -1
  81. package/dist/styles.css +1 -1
  82. package/dist/utils/debounce.d.ts +2 -0
  83. package/dist/utils/debounce.d.ts.map +1 -0
  84. package/dist/utils/debounce.js +13 -0
  85. package/dist/utils/debounce.js.map +1 -0
  86. package/dist/utils/index.d.ts +1 -0
  87. package/dist/utils/index.d.ts.map +1 -1
  88. package/dist/utils/index.js +1 -0
  89. package/dist/utils/index.js.map +1 -1
  90. package/index.css +3 -0
  91. package/package.json +1 -1
  92. package/src/components/badge/index.ts +4 -4
  93. package/src/components/calendar/calendar.tsx +90 -0
  94. package/src/components/calendar/index.ts +1 -0
  95. package/src/components/date-picker/date-picker.stories.tsx +230 -0
  96. package/src/components/date-picker/date-picker.tsx +179 -0
  97. package/src/components/date-picker/index.ts +1 -0
  98. package/src/components/date-time-range-picker/date-time-range-picker.tsx +211 -0
  99. package/src/components/date-time-range-picker/index.ts +1 -0
  100. package/src/components/dropdown-menu/index.ts +1 -29
  101. package/src/components/index.ts +6 -0
  102. package/src/components/inline-tips/inline-tips.stories.tsx +5 -0
  103. package/src/components/item/item.stories.tsx +65 -56
  104. package/src/components/modal/index.ts +1 -23
  105. package/src/components/modal/modal.stories.tsx +18 -8
  106. package/src/components/modal/modal.tsx +4 -2
  107. package/src/components/popover/index.ts +1 -0
  108. package/src/components/popover/popover.tsx +60 -0
  109. package/src/components/tabs/index.ts +1 -0
  110. package/src/components/tabs/tabs.stories.tsx +100 -0
  111. package/src/components/tabs/tabs.tsx +380 -0
  112. package/src/components/textarea/textarea.stories.tsx +8 -2
  113. package/src/components/toast/index.ts +2 -2
  114. package/src/utils/debounce.ts +15 -0
  115. package/src/utils/index.ts +1 -0
package/index.css CHANGED
@@ -193,6 +193,7 @@
193
193
  --background-contrast-base: var(--color-neutral-900);
194
194
  --background-neutral-background: var(--color-neutral-50);
195
195
  --background-backdrop-backdrop: var(--color-alpha-black-64);
196
+ --background-modal-overlay: var(--color-alpha-black-88);
196
197
 
197
198
  /* Button Background */
198
199
  --background-button-transparent-default: var(--color-alpha-white-0);
@@ -398,6 +399,7 @@
398
399
  --background-contrast-base: var(--color-neutral-800);
399
400
  --background-neutral-background: var(--color-neutral-1000);
400
401
  --background-backdrop-backdrop: var(--color-alpha-black-64);
402
+ --background-modal-overlay: var(--color-alpha-black-88);
401
403
 
402
404
  /* Button Background */
403
405
  --background-button-transparent-default: var(--color-alpha-white-0);
@@ -743,6 +745,7 @@
743
745
  --color-background-contrast-base: var(--background-contrast-base);
744
746
  --color-background-neutral-background: var(--background-neutral-background);
745
747
  --color-background-backdrop-backdrop: var(--background-backdrop-backdrop);
748
+ --color-background-modal-overlay: var(--background-modal-overlay);
746
749
 
747
750
  /* Theme tokens - button background */
748
751
  --color-background-button-transparent-default: var(--background-button-transparent-default);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shipfox/react-ui",
3
3
  "license": "MIT",
4
- "version": "0.11.0",
4
+ "version": "0.13.0",
5
5
  "private": false,
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -1,4 +1,4 @@
1
- export {Badge, type BadgeProps, type BadgeVariant, badgeVariants} from './badge';
2
- export {IconBadge, type IconBadgeProps, type IconBadgeVariant} from './icon-badge';
3
- export {StatusBadge, type StatusBadgeProps} from './status-badge';
4
- export {UserBadge, type UserBadgeProps} from './user-badge';
1
+ export * from './badge';
2
+ export * from './icon-badge';
3
+ export * from './status-badge';
4
+ export * from './user-badge';
@@ -0,0 +1,90 @@
1
+ import {Icon} from 'components/icon';
2
+ import type {ComponentProps} from 'react';
3
+ import {DayPicker} from 'react-day-picker';
4
+ import {cn} from 'utils/cn';
5
+
6
+ export type CalendarProps = ComponentProps<typeof DayPicker>;
7
+
8
+ export function Calendar({className, classNames, showOutsideDays = true, ...props}: CalendarProps) {
9
+ return (
10
+ <DayPicker
11
+ showOutsideDays={showOutsideDays}
12
+ className={cn('p-16 transition-colors', className)}
13
+ classNames={{
14
+ months: 'flex flex-col sm:flex-row gap-24',
15
+ month: 'space-y-16 relative',
16
+ month_caption: 'flex items-center justify-center mb-8 px-4 relative h-32',
17
+ caption_label: 'text-sm font-medium text-foreground-neutral-base',
18
+ nav: 'flex items-center gap-4 fixed left-0 top-16 w-full z-10',
19
+ button_previous: cn(
20
+ 'size-32 bg-transparent p-0 absolute left-16 top-0',
21
+ 'inline-flex items-center justify-center rounded-6',
22
+ 'text-foreground-neutral-base',
23
+ 'hover:bg-background-button-transparent-hover',
24
+ 'active:bg-background-button-transparent-pressed',
25
+ 'transition-colors outline-none',
26
+ 'focus-visible:shadow-border-interactive-with-active',
27
+ 'disabled:pointer-events-none disabled:opacity-50',
28
+ ),
29
+ button_next: cn(
30
+ 'size-32 bg-transparent p-0 absolute right-16 top-0',
31
+ 'inline-flex items-center justify-center rounded-6',
32
+ 'text-foreground-neutral-base',
33
+ 'hover:bg-background-button-transparent-hover',
34
+ 'active:bg-background-button-transparent-pressed',
35
+ 'transition-colors outline-none',
36
+ 'focus-visible:shadow-border-interactive-with-active',
37
+ 'disabled:pointer-events-none disabled:opacity-50',
38
+ ),
39
+ month_grid: 'w-full border-collapse mt-8',
40
+ weekdays: 'flex mb-8',
41
+ weekday:
42
+ 'text-foreground-neutral-subtle text-xs font-medium w-36 h-32 flex items-center justify-center',
43
+ week: 'flex mt-4',
44
+ day: cn(
45
+ 'relative text-center size-36 p-0 text-sm font-normal [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none',
46
+ '[&:last-child[data-selected=true]_button]:rounded-r-6',
47
+ props.showWeekNumber
48
+ ? '[&:nth-child(2)[data-selected=true]_button]:rounded-l-6'
49
+ : '[&:first-child[data-selected=true]_button]:rounded-l-6',
50
+ ),
51
+ day_button: cn(
52
+ 'size-36 p-0 text-sm font-normal rounded-6',
53
+ 'inline-flex items-center justify-center',
54
+ 'hover:bg-background-button-transparent-hover',
55
+ 'focus-visible:shadow-border-interactive-with-active',
56
+ 'transition-colors outline-none',
57
+ 'aria-selected:opacity-100',
58
+ ),
59
+ range_start: 'day-range-start rounded-6',
60
+ range_end: 'day-range-end rounded-6',
61
+ selected: cn(
62
+ 'bg-foreground-highlight-interactive/80 !text-foreground-neutral-base font-medium rounded-6',
63
+ 'hover:bg-foreground-highlight-interactive-hover/80',
64
+ 'focus:bg-foreground-highlight-interactive/80',
65
+ ),
66
+ today: cn(
67
+ 'bg-background-field-base text-foreground-neutral-base font-medium rounded-6',
68
+ 'border border-border-neutral-base',
69
+ ),
70
+ outside: 'day-outside text-foreground-neutral-muted',
71
+ disabled: 'text-foreground-neutral-disabled opacity-30 cursor-not-allowed',
72
+ range_middle: cn(
73
+ 'aria-selected:bg-foreground-highlight-interactive/10 aria-selected:text-foreground-neutral-base',
74
+ 'rounded-none',
75
+ 'first:rounded-l-6 first:rounded-r-none',
76
+ 'last:rounded-r-6 last:rounded-l-none',
77
+ ),
78
+ hidden: 'invisible',
79
+ ...classNames,
80
+ }}
81
+ components={{
82
+ Chevron: ({orientation}) => {
83
+ const iconName = orientation === 'left' ? 'arrowLeftSLine' : 'arrowRightSLine';
84
+ return <Icon name={iconName} className="size-20" />;
85
+ },
86
+ }}
87
+ {...props}
88
+ />
89
+ );
90
+ }
@@ -0,0 +1 @@
1
+ export * from './calendar';
@@ -0,0 +1,230 @@
1
+ import {argosScreenshot} from '@argos-ci/storybook/vitest';
2
+ import type {Meta, StoryObj} from '@storybook/react';
3
+ import {within} from '@testing-library/react';
4
+ import userEvent from '@testing-library/user-event';
5
+ import {subDays} from 'date-fns';
6
+ import {useState} from 'react';
7
+ import {
8
+ type DateRange,
9
+ DateTimeRangePicker,
10
+ } from '../date-time-range-picker/date-time-range-picker';
11
+ import {DatePicker} from './date-picker';
12
+
13
+ const meta: Meta<typeof DatePicker> = {
14
+ title: 'Components/DatePicker',
15
+ component: DatePicker,
16
+ parameters: {
17
+ layout: 'centered',
18
+ },
19
+ };
20
+ export default meta;
21
+
22
+ type Story = StoryObj<typeof DatePicker>;
23
+
24
+ const OPEN_CALENDAR_REGEX = /open calendar/i;
25
+
26
+ const isTestEnvironment = () => typeof navigator !== 'undefined' && navigator.webdriver === true;
27
+
28
+ type StoryContext = Parameters<NonNullable<Story['play']>>[0];
29
+
30
+ async function openCalendarAndScreenshot(ctx: StoryContext, screenshotName: string): Promise<void> {
31
+ const {canvasElement, step} = ctx;
32
+ const canvas = within(canvasElement);
33
+ const user = userEvent.setup();
34
+
35
+ let triggerButton: HTMLElement | null = null;
36
+
37
+ await step('Open the calendar popover', async () => {
38
+ triggerButton = canvas.getByRole('button', {name: OPEN_CALENDAR_REGEX});
39
+ await user.click(triggerButton);
40
+ });
41
+
42
+ await step('Wait for calendar to appear and render', async () => {
43
+ await new Promise((resolve) => setTimeout(resolve, 300));
44
+
45
+ if (isTestEnvironment() && triggerButton instanceof HTMLElement) {
46
+ triggerButton.style.display = 'none';
47
+ }
48
+ await new Promise((resolve) => setTimeout(resolve, 100));
49
+ });
50
+
51
+ await argosScreenshot(ctx, screenshotName);
52
+ }
53
+
54
+ export const DatePickerStory: Story = {
55
+ play: (ctx) => openCalendarAndScreenshot(ctx, 'DatePicker Calendar Open'),
56
+ render: () => {
57
+ const [date, setDate] = useState<Date | undefined>(new Date());
58
+ return (
59
+ <div className="relative flex h-600 w-500 items-center justify-center rounded-16 bg-background-subtle-base shadow-tooltip overflow-visible">
60
+ <DatePicker
61
+ date={date}
62
+ onDateSelect={setDate}
63
+ onClear={() => setDate(undefined)}
64
+ placeholder="DD/MM/YYYY"
65
+ />
66
+ </div>
67
+ );
68
+ },
69
+ };
70
+
71
+ export const DateRangePickerStory: Story = {
72
+ play: (ctx) => openCalendarAndScreenshot(ctx, 'DateRangePicker Calendar Open'),
73
+ render: () => {
74
+ const [dateRange, setDateRange] = useState<DateRange | undefined>({
75
+ start: new Date(),
76
+ end: subDays(new Date(), -30),
77
+ });
78
+ return (
79
+ <div className="relative flex h-600 w-800 items-center justify-center rounded-16 bg-background-subtle-base shadow-tooltip overflow-visible">
80
+ <DateTimeRangePicker
81
+ dateRange={dateRange}
82
+ onDateRangeSelect={setDateRange}
83
+ onClear={() => setDateRange(undefined)}
84
+ placeholder="DD/MM/YYYY - DD/MM/YYYY"
85
+ className="min-w-280"
86
+ />
87
+ </div>
88
+ );
89
+ },
90
+ };
91
+
92
+ export const AllStates: Story = {
93
+ render: () => {
94
+ const now = new Date();
95
+ const past = subDays(now, 30);
96
+ return (
97
+ <div className="flex flex-col gap-32 p-32 min-w-350">
98
+ <div>
99
+ <h3 className="text-lg font-semibold mb-16 text-foreground-neutral-base">
100
+ Single Date Picker
101
+ </h3>
102
+ <div className="flex flex-col gap-16">
103
+ <div>
104
+ <p className="text-xs text-foreground-neutral-subtle mb-8 font-mono">DEFAULT</p>
105
+ <DatePicker placeholder="DD/MM/YYYY" />
106
+ </div>
107
+ <div>
108
+ <p className="text-xs text-foreground-neutral-subtle mb-8 font-mono">FILLED</p>
109
+ <DatePicker
110
+ date={new Date()}
111
+ onClear={() => {
112
+ /* noop for demo */
113
+ }}
114
+ placeholder="DD/MM/YYYY"
115
+ />
116
+ </div>
117
+ <div>
118
+ <p className="text-xs text-foreground-neutral-subtle mb-8 font-mono">ERROR</p>
119
+ <DatePicker
120
+ date={new Date()}
121
+ onClear={() => {
122
+ /* noop for demo */
123
+ }}
124
+ state="error"
125
+ placeholder="DD/MM/YYYY"
126
+ />
127
+ </div>
128
+ <div>
129
+ <p className="text-xs text-foreground-neutral-subtle mb-8 font-mono">DISABLED</p>
130
+ <DatePicker placeholder="DD/MM/YYYY" state="disabled" />
131
+ </div>
132
+ <div>
133
+ <p className="text-xs text-foreground-neutral-subtle mb-8 font-mono">SMALL</p>
134
+ <DatePicker size="small" placeholder="DD/MM/YYYY" />
135
+ </div>
136
+ <div>
137
+ <p className="text-xs text-foreground-neutral-subtle mb-8 font-mono">
138
+ COMPONENT VARIANT
139
+ </p>
140
+ <DatePicker variant="component" placeholder="DD/MM/YYYY" />
141
+ </div>
142
+ </div>
143
+ </div>
144
+
145
+ <div>
146
+ <h3 className="text-lg font-semibold mb-16 text-foreground-neutral-base">
147
+ Date Range Picker
148
+ </h3>
149
+ <div className="flex flex-col gap-16">
150
+ <div>
151
+ <p className="text-xs text-foreground-neutral-subtle mb-8 font-mono">DEFAULT</p>
152
+ <DateTimeRangePicker placeholder="DD/MM/YYYY - DD/MM/YYYY" />
153
+ </div>
154
+ <div>
155
+ <p className="text-xs text-foreground-neutral-subtle mb-8 font-mono">FILLED</p>
156
+ <DateTimeRangePicker
157
+ dateRange={{start: past, end: now}}
158
+ onClear={() => {
159
+ /* noop for demo */
160
+ }}
161
+ placeholder="DD/MM/YYYY - DD/MM/YYYY"
162
+ />
163
+ </div>
164
+ <div>
165
+ <p className="text-xs text-foreground-neutral-subtle mb-8 font-mono">ERROR</p>
166
+ <DateTimeRangePicker
167
+ dateRange={{start: past, end: now}}
168
+ onClear={() => {
169
+ /* noop for demo */
170
+ }}
171
+ state="error"
172
+ placeholder="DD/MM/YYYY - DD/MM/YYYY"
173
+ />
174
+ </div>
175
+ <div>
176
+ <p className="text-xs text-foreground-neutral-subtle mb-8 font-mono">DISABLED</p>
177
+ <DateTimeRangePicker placeholder="DD/MM/YYYY - DD/MM/YYYY" state="disabled" />
178
+ </div>
179
+ <div>
180
+ <p className="text-xs text-foreground-neutral-subtle mb-8 font-mono">SMALL</p>
181
+ <DateTimeRangePicker size="small" placeholder="DD/MM/YYYY - DD/MM/YYYY" />
182
+ </div>
183
+ <div>
184
+ <p className="text-xs text-foreground-neutral-subtle mb-8 font-mono">
185
+ COMPONENT VARIANT
186
+ </p>
187
+ <DateTimeRangePicker variant="component" placeholder="DD/MM/YYYY - DD/MM/YYYY" />
188
+ </div>
189
+ </div>
190
+ </div>
191
+ </div>
192
+ );
193
+ },
194
+ };
195
+
196
+ export const DateFormats: Story = {
197
+ render: () => {
198
+ const [date, setDate] = useState<Date | undefined>(new Date());
199
+ const formats = [
200
+ {format: 'MMMM dd, yyyy', placeholder: 'Month DD, YYYY', label: 'MMMM dd, yyyy'},
201
+ {format: 'MMMM d, yyyy', placeholder: 'Month D, YYYY', label: 'MMMM d, yyyy'},
202
+ {format: 'MMM dd, yyyy', placeholder: 'Mon DD, YYYY', label: 'MMM dd, yyyy'},
203
+ {format: 'dd/MM/yyyy', placeholder: 'DD/MM/YYYY', label: 'dd/MM/yyyy'},
204
+ {format: 'MM/dd/yyyy', placeholder: 'MM/DD/YYYY', label: 'MM/dd/yyyy'},
205
+ {format: 'yyyy-MM-dd', placeholder: 'YYYY-MM-DD', label: 'yyyy-MM-dd'},
206
+ ];
207
+
208
+ return (
209
+ <div className="flex flex-col gap-32 p-32 min-w-350">
210
+ <h3 className="text-lg font-semibold mb-16 text-foreground-neutral-base">
211
+ Date Format Examples
212
+ </h3>
213
+ <div className="flex flex-col gap-16">
214
+ {formats.map(({format, placeholder, label}) => (
215
+ <div key={format}>
216
+ <p className="text-xs text-foreground-neutral-subtle mb-8 font-mono">{label}</p>
217
+ <DatePicker
218
+ date={date}
219
+ onDateSelect={setDate}
220
+ onClear={() => setDate(undefined)}
221
+ dateFormat={format}
222
+ placeholder={placeholder}
223
+ />
224
+ </div>
225
+ ))}
226
+ </div>
227
+ </div>
228
+ );
229
+ },
230
+ };
@@ -0,0 +1,179 @@
1
+ import {cva, type VariantProps} from 'class-variance-authority';
2
+ import {Calendar} from 'components/calendar';
3
+ import {Icon} from 'components/icon';
4
+ import {Popover, PopoverContent, PopoverTrigger} from 'components/popover';
5
+ import {format} from 'date-fns';
6
+ import type {ComponentProps, ReactNode} from 'react';
7
+ import {forwardRef, useState} from 'react';
8
+ import {cn} from 'utils/cn';
9
+
10
+ export const datePickerVariants = cva(
11
+ 'relative flex items-center rounded-6 shadow-button-neutral transition-[background-color,box-shadow] outline-none',
12
+ {
13
+ variants: {
14
+ variant: {
15
+ base: 'bg-background-field-base hover:bg-background-field-hover',
16
+ component: 'bg-background-field-component hover:bg-background-field-component-hover',
17
+ },
18
+ size: {
19
+ base: 'h-32',
20
+ small: 'h-28',
21
+ },
22
+ state: {
23
+ default: '',
24
+ error: 'shadow-border-error',
25
+ disabled:
26
+ 'bg-background-neutral-disabled shadow-none pointer-events-none cursor-not-allowed',
27
+ },
28
+ },
29
+ defaultVariants: {
30
+ variant: 'base',
31
+ size: 'base',
32
+ state: 'default',
33
+ },
34
+ },
35
+ );
36
+
37
+ export type DatePickerProps = Omit<ComponentProps<'input'>, 'size' | 'type'> &
38
+ VariantProps<typeof datePickerVariants> & {
39
+ date?: Date;
40
+ onDateSelect?: (date: Date | undefined) => void;
41
+ placeholder?: string;
42
+ dateFormat?: string;
43
+ leftIcon?: ReactNode;
44
+ rightIcon?: ReactNode;
45
+ onClear?: () => void;
46
+ closeOnSelect?: boolean;
47
+ };
48
+
49
+ export const DatePicker = forwardRef<HTMLInputElement, DatePickerProps>(
50
+ (
51
+ {
52
+ className,
53
+ variant,
54
+ size,
55
+ state,
56
+ date,
57
+ onDateSelect,
58
+ placeholder = 'DD/MM/YYYY',
59
+ dateFormat = 'dd/MM/yyyy',
60
+ leftIcon,
61
+ rightIcon,
62
+ onClear,
63
+ disabled,
64
+ closeOnSelect = false,
65
+ ...props
66
+ },
67
+ ref,
68
+ ) => {
69
+ const [open, setOpen] = useState(false);
70
+ const isDisabled = disabled || state === 'disabled';
71
+ const displayValue = date ? format(date, dateFormat) : '';
72
+
73
+ const handleSelect = (selectedDate: Date | undefined) => {
74
+ onDateSelect?.(selectedDate);
75
+ if (closeOnSelect) {
76
+ setOpen(false);
77
+ }
78
+ };
79
+
80
+ const handleClear = (e: React.MouseEvent) => {
81
+ e.stopPropagation();
82
+ onClear?.();
83
+ setOpen(false);
84
+ };
85
+
86
+ return (
87
+ <Popover open={open} onOpenChange={setOpen}>
88
+ <div
89
+ className={cn(
90
+ open && 'shadow-border-interactive-with-active',
91
+ datePickerVariants({variant, size, state: isDisabled ? 'disabled' : state}),
92
+ className,
93
+ )}
94
+ >
95
+ {/* Calendar Icon Button */}
96
+ <PopoverTrigger asChild>
97
+ <button
98
+ type="button"
99
+ disabled={isDisabled}
100
+ className={cn(
101
+ 'flex items-center justify-center shrink-0 transition-colors',
102
+ size === 'small' ? 'size-28' : 'size-32',
103
+ isDisabled && 'text-foreground-neutral-disabled',
104
+ )}
105
+ aria-label="Open calendar"
106
+ >
107
+ {leftIcon || (
108
+ <Icon
109
+ name="calendar2Line"
110
+ className={cn(
111
+ 'size-16 text-foreground-neutral-muted',
112
+ isDisabled && 'text-foreground-neutral-disabled',
113
+ )}
114
+ />
115
+ )}
116
+ </button>
117
+ </PopoverTrigger>
118
+
119
+ {/* Divider */}
120
+ <div className="h-full w-px bg-border-neutral-base shrink-0" />
121
+
122
+ {/* Input Field */}
123
+ <input
124
+ ref={ref}
125
+ type="text"
126
+ disabled={isDisabled}
127
+ placeholder={placeholder}
128
+ value={displayValue}
129
+ readOnly
130
+ className={cn(
131
+ 'flex-1 min-w-0 px-8 text-sm leading-20 bg-transparent outline-none border-none cursor-pointer',
132
+ 'placeholder:text-foreground-neutral-muted',
133
+ 'text-foreground-neutral-base',
134
+ 'disabled:text-foreground-neutral-disabled disabled:cursor-not-allowed',
135
+ size === 'small' ? 'py-4' : 'py-6',
136
+ )}
137
+ onClick={() => !isDisabled && setOpen(true)}
138
+ {...props}
139
+ />
140
+
141
+ {/* Clear Button (shown when date is selected) */}
142
+ <button
143
+ type="button"
144
+ onClick={handleClear}
145
+ className={cn(
146
+ 'flex items-center justify-center shrink-0 cursor-pointer',
147
+ size === 'small' ? 'size-28' : 'size-32',
148
+ date && onClear && !isDisabled ? 'visible' : 'invisible',
149
+ )}
150
+ aria-label="Clear date"
151
+ >
152
+ <Icon
153
+ name="closeLine"
154
+ className="size-16 text-foreground-neutral-muted hover:text-foreground-neutral-subtle transition-colors"
155
+ />
156
+ </button>
157
+
158
+ {/* Custom Right Icon */}
159
+ {rightIcon && !date && (
160
+ <div
161
+ className={cn(
162
+ 'flex items-center justify-center shrink-0',
163
+ size === 'small' ? 'size-28' : 'size-32',
164
+ )}
165
+ >
166
+ {rightIcon}
167
+ </div>
168
+ )}
169
+ </div>
170
+
171
+ <PopoverContent className="w-auto p-0" align="start">
172
+ <Calendar mode="single" selected={date} onSelect={handleSelect} />
173
+ </PopoverContent>
174
+ </Popover>
175
+ );
176
+ },
177
+ );
178
+
179
+ DatePicker.displayName = 'DatePicker';
@@ -0,0 +1 @@
1
+ export * from './date-picker';