@shipfox/react-ui 0.11.0 → 0.12.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 (66) 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 +6 -0
  5. package/dist/components/calendar/calendar.d.ts +5 -0
  6. package/dist/components/calendar/calendar.d.ts.map +1 -0
  7. package/dist/components/calendar/calendar.js +46 -0
  8. package/dist/components/calendar/calendar.js.map +1 -0
  9. package/dist/components/calendar/index.d.ts +2 -0
  10. package/dist/components/calendar/index.d.ts.map +1 -0
  11. package/dist/components/calendar/index.js +3 -0
  12. package/dist/components/calendar/index.js.map +1 -0
  13. package/dist/components/date-picker/date-picker.d.ts +19 -0
  14. package/dist/components/date-picker/date-picker.d.ts.map +1 -0
  15. package/dist/components/date-picker/date-picker.js +114 -0
  16. package/dist/components/date-picker/date-picker.js.map +1 -0
  17. package/dist/components/date-picker/date-picker.stories.js +333 -0
  18. package/dist/components/date-picker/date-picker.stories.js.map +1 -0
  19. package/dist/components/date-picker/index.d.ts +2 -0
  20. package/dist/components/date-picker/index.d.ts.map +1 -0
  21. package/dist/components/date-picker/index.js +3 -0
  22. package/dist/components/date-picker/index.js.map +1 -0
  23. package/dist/components/date-time-range-picker/date-time-range-picker.d.ts +24 -0
  24. package/dist/components/date-time-range-picker/date-time-range-picker.d.ts.map +1 -0
  25. package/dist/components/date-time-range-picker/date-time-range-picker.js +130 -0
  26. package/dist/components/date-time-range-picker/date-time-range-picker.js.map +1 -0
  27. package/dist/components/date-time-range-picker/index.d.ts +2 -0
  28. package/dist/components/date-time-range-picker/index.d.ts.map +1 -0
  29. package/dist/components/date-time-range-picker/index.js +3 -0
  30. package/dist/components/date-time-range-picker/index.js.map +1 -0
  31. package/dist/components/index.d.ts +4 -0
  32. package/dist/components/index.d.ts.map +1 -1
  33. package/dist/components/index.js +4 -0
  34. package/dist/components/index.js.map +1 -1
  35. package/dist/components/inline-tips/inline-tips.stories.js +5 -0
  36. package/dist/components/inline-tips/inline-tips.stories.js.map +1 -1
  37. package/dist/components/item/item.stories.js +15 -8
  38. package/dist/components/item/item.stories.js.map +1 -1
  39. package/dist/components/modal/modal.stories.js +14 -6
  40. package/dist/components/modal/modal.stories.js.map +1 -1
  41. package/dist/components/popover/index.d.ts +2 -0
  42. package/dist/components/popover/index.d.ts.map +1 -0
  43. package/dist/components/popover/index.js +3 -0
  44. package/dist/components/popover/index.js.map +1 -0
  45. package/dist/components/popover/popover.d.ts +10 -0
  46. package/dist/components/popover/popover.d.ts.map +1 -0
  47. package/dist/components/popover/popover.js +47 -0
  48. package/dist/components/popover/popover.js.map +1 -0
  49. package/dist/components/textarea/textarea.stories.js +8 -2
  50. package/dist/components/textarea/textarea.stories.js.map +1 -1
  51. package/dist/styles.css +1 -1
  52. package/package.json +1 -1
  53. package/src/components/calendar/calendar.tsx +90 -0
  54. package/src/components/calendar/index.ts +1 -0
  55. package/src/components/date-picker/date-picker.stories.tsx +230 -0
  56. package/src/components/date-picker/date-picker.tsx +179 -0
  57. package/src/components/date-picker/index.ts +1 -0
  58. package/src/components/date-time-range-picker/date-time-range-picker.tsx +211 -0
  59. package/src/components/date-time-range-picker/index.ts +1 -0
  60. package/src/components/index.ts +4 -0
  61. package/src/components/inline-tips/inline-tips.stories.tsx +5 -0
  62. package/src/components/item/item.stories.tsx +65 -56
  63. package/src/components/modal/modal.stories.tsx +16 -6
  64. package/src/components/popover/index.ts +1 -0
  65. package/src/components/popover/popover.tsx +60 -0
  66. package/src/components/textarea/textarea.stories.tsx +8 -2
@@ -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';
@@ -0,0 +1,211 @@
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 type {DateRange as DayPickerDateRange} from 'react-day-picker';
9
+ import {cn} from 'utils/cn';
10
+
11
+ export const dateTimeRangePickerVariants = cva(
12
+ 'min-w-240 relative flex items-center rounded-6 shadow-button-neutral transition-[background-color,box-shadow] outline-none',
13
+ {
14
+ variants: {
15
+ variant: {
16
+ base: 'bg-background-field-base hover:bg-background-field-hover',
17
+ component: 'bg-background-field-component hover:bg-background-field-component-hover',
18
+ },
19
+ size: {
20
+ base: 'h-32',
21
+ small: 'h-28',
22
+ },
23
+ state: {
24
+ default: '',
25
+ error: 'shadow-border-error',
26
+ disabled:
27
+ 'bg-background-neutral-disabled shadow-none pointer-events-none cursor-not-allowed',
28
+ },
29
+ },
30
+ defaultVariants: {
31
+ variant: 'base',
32
+ size: 'base',
33
+ state: 'default',
34
+ },
35
+ },
36
+ );
37
+
38
+ export type DateRange = {
39
+ start?: Date;
40
+ end?: Date;
41
+ };
42
+
43
+ export type DateTimeRangePickerProps = Omit<ComponentProps<'input'>, 'size' | 'type'> &
44
+ VariantProps<typeof dateTimeRangePickerVariants> & {
45
+ dateRange?: DateRange;
46
+ onDateRangeSelect?: (range: DateRange | undefined) => void;
47
+ placeholder?: string;
48
+ dateFormat?: string;
49
+ leftIcon?: ReactNode;
50
+ rightIcon?: ReactNode;
51
+ onClear?: () => void;
52
+ numberOfMonths?: number;
53
+ closeOnSelect?: boolean;
54
+ };
55
+
56
+ export const DateTimeRangePicker = forwardRef<HTMLInputElement, DateTimeRangePickerProps>(
57
+ (
58
+ {
59
+ className,
60
+ variant,
61
+ size,
62
+ state,
63
+ dateRange,
64
+ onDateRangeSelect,
65
+ placeholder = 'DD/MM/YYYY - DD/MM/YYYY',
66
+ dateFormat = 'dd/MM/yyyy',
67
+ leftIcon,
68
+ rightIcon,
69
+ onClear,
70
+ disabled,
71
+ numberOfMonths = 2,
72
+ closeOnSelect = false,
73
+ ...props
74
+ },
75
+ ref,
76
+ ) => {
77
+ const [open, setOpen] = useState(false);
78
+ const isDisabled = disabled || state === 'disabled';
79
+ const hasRange = dateRange?.start && dateRange?.end;
80
+ const displayValue =
81
+ hasRange && dateRange.start && dateRange.end
82
+ ? `${format(dateRange.start, dateFormat)} - ${format(dateRange.end, dateFormat)}`
83
+ : '';
84
+
85
+ // Convert our DateRange to react-day-picker's DateRange format
86
+ const dayPickerRange: DayPickerDateRange | undefined =
87
+ dateRange?.start || dateRange?.end
88
+ ? {
89
+ from: dateRange?.start,
90
+ to: dateRange?.end,
91
+ }
92
+ : undefined;
93
+
94
+ const handleSelect = (selectedRange: DayPickerDateRange | undefined) => {
95
+ if (selectedRange) {
96
+ onDateRangeSelect?.({
97
+ start: selectedRange.from,
98
+ end: selectedRange.to,
99
+ });
100
+ // Only close if both dates are selected and closeOnSelect is true
101
+ if (closeOnSelect && selectedRange.from && selectedRange.to) {
102
+ setOpen(false);
103
+ }
104
+ } else {
105
+ onDateRangeSelect?.(undefined);
106
+ }
107
+ };
108
+
109
+ const handleClear = (e: React.MouseEvent) => {
110
+ e.stopPropagation();
111
+ onClear?.();
112
+ setOpen(false);
113
+ };
114
+
115
+ return (
116
+ <Popover open={open} onOpenChange={setOpen}>
117
+ <div
118
+ className={cn(
119
+ open && 'shadow-border-interactive-with-active',
120
+ dateTimeRangePickerVariants({variant, size, state: isDisabled ? 'disabled' : state}),
121
+ className,
122
+ )}
123
+ >
124
+ {/* Calendar Icon Button */}
125
+ <PopoverTrigger asChild>
126
+ <button
127
+ type="button"
128
+ disabled={isDisabled}
129
+ className={cn(
130
+ 'flex items-center justify-center shrink-0 transition-colors',
131
+ size === 'small' ? 'size-28' : 'size-32',
132
+ isDisabled && 'text-foreground-neutral-disabled',
133
+ )}
134
+ aria-label="Open calendar"
135
+ >
136
+ {leftIcon || (
137
+ <Icon
138
+ name="calendar2Line"
139
+ className={cn(
140
+ 'size-16 text-foreground-neutral-muted',
141
+ isDisabled && 'text-foreground-neutral-disabled',
142
+ )}
143
+ />
144
+ )}
145
+ </button>
146
+ </PopoverTrigger>
147
+
148
+ {/* Divider */}
149
+ <div className="h-full w-px bg-border-neutral-base shrink-0" />
150
+
151
+ {/* Input Field */}
152
+ <input
153
+ ref={ref}
154
+ type="text"
155
+ disabled={isDisabled}
156
+ placeholder={placeholder}
157
+ value={displayValue}
158
+ readOnly
159
+ className={cn(
160
+ 'flex-1 min-w-0 px-8 text-sm leading-20 bg-transparent outline-none border-none cursor-pointer',
161
+ 'placeholder:text-foreground-neutral-muted',
162
+ 'text-foreground-neutral-base',
163
+ 'disabled:text-foreground-neutral-disabled disabled:cursor-not-allowed',
164
+ size === 'small' ? 'py-4' : 'py-6',
165
+ )}
166
+ onClick={() => !isDisabled && setOpen(true)}
167
+ {...props}
168
+ />
169
+
170
+ {/* Clear Button (shown when date range is selected) */}
171
+ <button
172
+ type="button"
173
+ onClick={handleClear}
174
+ className={cn(
175
+ 'flex items-center justify-center shrink-0 transition-colors hover:text-foreground-neutral-base',
176
+ size === 'small' ? 'size-28' : 'size-32',
177
+ hasRange && onClear && !isDisabled ? 'visible' : 'invisible',
178
+ )}
179
+ aria-label="Clear date range"
180
+ >
181
+ <Icon name="closeLine" className="size-16 text-foreground-neutral-muted" />
182
+ </button>
183
+
184
+ {/* Custom Right Icon */}
185
+ {rightIcon && !hasRange && (
186
+ <div
187
+ className={cn(
188
+ 'flex items-center justify-center shrink-0',
189
+ size === 'small' ? 'size-28' : 'size-32',
190
+ )}
191
+ >
192
+ {rightIcon}
193
+ </div>
194
+ )}
195
+ </div>
196
+
197
+ <PopoverContent className="w-auto p-0" align="start">
198
+ <Calendar
199
+ mode="range"
200
+ defaultMonth={dayPickerRange?.from}
201
+ selected={dayPickerRange}
202
+ onSelect={handleSelect}
203
+ numberOfMonths={numberOfMonths}
204
+ />
205
+ </PopoverContent>
206
+ </Popover>
207
+ );
208
+ },
209
+ );
210
+
211
+ DateTimeRangePicker.displayName = 'DateTimeRangePicker';
@@ -0,0 +1 @@
1
+ export * from './date-time-range-picker';
@@ -2,8 +2,11 @@ export * from './alert';
2
2
  export * from './avatar';
3
3
  export * from './badge';
4
4
  export * from './button';
5
+ export * from './calendar';
5
6
  export * from './checkbox';
6
7
  export * from './code-block';
8
+ export * from './date-picker';
9
+ export * from './date-time-range-picker';
7
10
  export * from './dot-grid';
8
11
  export * from './dropdown-menu';
9
12
  export * from './dynamic-item';
@@ -14,6 +17,7 @@ export * from './input';
14
17
  export * from './item';
15
18
  export * from './label';
16
19
  export * from './modal';
20
+ export * from './popover';
17
21
  export * from './textarea';
18
22
  export * from './theme';
19
23
  export * from './toast';
@@ -1,3 +1,4 @@
1
+ import {argosScreenshot} from '@argos-ci/storybook/vitest';
1
2
  import type {Meta, StoryObj} from '@storybook/react';
2
3
  import {Code, Header} from 'components/typography';
3
4
  import {
@@ -37,6 +38,10 @@ const types = ['default', 'info', 'success', 'error'] as const;
37
38
  const variants = ['primary', 'secondary'] as const;
38
39
 
39
40
  export const Default: Story = {
41
+ play: async (ctx) => {
42
+ await new Promise((resolve) => setTimeout(resolve, 100));
43
+ await argosScreenshot(ctx, 'InlineTips Default');
44
+ },
40
45
  render: (args) => {
41
46
  return (
42
47
  <InlineTips {...args}>