@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.
- package/.turbo/turbo-build.log +6 -6
- package/.turbo/turbo-check.log +2 -2
- package/.turbo/turbo-type.log +1 -1
- package/CHANGELOG.md +12 -0
- package/dist/components/badge/index.d.ts +4 -4
- package/dist/components/badge/index.d.ts.map +1 -1
- package/dist/components/badge/index.js +4 -4
- package/dist/components/badge/index.js.map +1 -1
- package/dist/components/calendar/calendar.d.ts +5 -0
- package/dist/components/calendar/calendar.d.ts.map +1 -0
- package/dist/components/calendar/calendar.js +46 -0
- package/dist/components/calendar/calendar.js.map +1 -0
- package/dist/components/calendar/index.d.ts +2 -0
- package/dist/components/calendar/index.d.ts.map +1 -0
- package/dist/components/calendar/index.js +3 -0
- package/dist/components/calendar/index.js.map +1 -0
- package/dist/components/date-picker/date-picker.d.ts +19 -0
- package/dist/components/date-picker/date-picker.d.ts.map +1 -0
- package/dist/components/date-picker/date-picker.js +114 -0
- package/dist/components/date-picker/date-picker.js.map +1 -0
- package/dist/components/date-picker/date-picker.stories.js +333 -0
- package/dist/components/date-picker/date-picker.stories.js.map +1 -0
- package/dist/components/date-picker/index.d.ts +2 -0
- package/dist/components/date-picker/index.d.ts.map +1 -0
- package/dist/components/date-picker/index.js +3 -0
- package/dist/components/date-picker/index.js.map +1 -0
- package/dist/components/date-time-range-picker/date-time-range-picker.d.ts +24 -0
- package/dist/components/date-time-range-picker/date-time-range-picker.d.ts.map +1 -0
- package/dist/components/date-time-range-picker/date-time-range-picker.js +130 -0
- package/dist/components/date-time-range-picker/date-time-range-picker.js.map +1 -0
- package/dist/components/date-time-range-picker/index.d.ts +2 -0
- package/dist/components/date-time-range-picker/index.d.ts.map +1 -0
- package/dist/components/date-time-range-picker/index.js +3 -0
- package/dist/components/date-time-range-picker/index.js.map +1 -0
- package/dist/components/dropdown-menu/index.d.ts +1 -2
- package/dist/components/dropdown-menu/index.d.ts.map +1 -1
- package/dist/components/dropdown-menu/index.js +1 -1
- package/dist/components/dropdown-menu/index.js.map +1 -1
- package/dist/components/index.d.ts +6 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +6 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/inline-tips/inline-tips.stories.js +5 -0
- package/dist/components/inline-tips/inline-tips.stories.js.map +1 -1
- package/dist/components/item/item.stories.js +15 -8
- package/dist/components/item/item.stories.js.map +1 -1
- package/dist/components/modal/index.d.ts +1 -2
- package/dist/components/modal/index.d.ts.map +1 -1
- package/dist/components/modal/index.js +1 -1
- package/dist/components/modal/index.js.map +1 -1
- package/dist/components/modal/modal.d.ts +2 -1
- package/dist/components/modal/modal.d.ts.map +1 -1
- package/dist/components/modal/modal.js +5 -3
- package/dist/components/modal/modal.js.map +1 -1
- package/dist/components/modal/modal.stories.js +16 -6
- package/dist/components/modal/modal.stories.js.map +1 -1
- package/dist/components/popover/index.d.ts +2 -0
- package/dist/components/popover/index.d.ts.map +1 -0
- package/dist/components/popover/index.js +3 -0
- package/dist/components/popover/index.js.map +1 -0
- package/dist/components/popover/popover.d.ts +10 -0
- package/dist/components/popover/popover.d.ts.map +1 -0
- package/dist/components/popover/popover.js +47 -0
- package/dist/components/popover/popover.js.map +1 -0
- package/dist/components/tabs/index.d.ts +2 -0
- package/dist/components/tabs/index.d.ts.map +1 -0
- package/dist/components/tabs/index.js +3 -0
- package/dist/components/tabs/index.js.map +1 -0
- package/dist/components/tabs/tabs.d.ts +50 -0
- package/dist/components/tabs/tabs.d.ts.map +1 -0
- package/dist/components/tabs/tabs.js +243 -0
- package/dist/components/tabs/tabs.js.map +1 -0
- package/dist/components/tabs/tabs.stories.js +179 -0
- package/dist/components/tabs/tabs.stories.js.map +1 -0
- package/dist/components/textarea/textarea.stories.js +8 -2
- package/dist/components/textarea/textarea.stories.js.map +1 -1
- package/dist/components/toast/index.d.ts +2 -2
- package/dist/components/toast/index.d.ts.map +1 -1
- package/dist/components/toast/index.js +2 -2
- package/dist/components/toast/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/utils/debounce.d.ts +2 -0
- package/dist/utils/debounce.d.ts.map +1 -0
- package/dist/utils/debounce.js +13 -0
- package/dist/utils/debounce.js.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/index.css +3 -0
- package/package.json +1 -1
- package/src/components/badge/index.ts +4 -4
- package/src/components/calendar/calendar.tsx +90 -0
- package/src/components/calendar/index.ts +1 -0
- package/src/components/date-picker/date-picker.stories.tsx +230 -0
- package/src/components/date-picker/date-picker.tsx +179 -0
- package/src/components/date-picker/index.ts +1 -0
- package/src/components/date-time-range-picker/date-time-range-picker.tsx +211 -0
- package/src/components/date-time-range-picker/index.ts +1 -0
- package/src/components/dropdown-menu/index.ts +1 -29
- package/src/components/index.ts +6 -0
- package/src/components/inline-tips/inline-tips.stories.tsx +5 -0
- package/src/components/item/item.stories.tsx +65 -56
- package/src/components/modal/index.ts +1 -23
- package/src/components/modal/modal.stories.tsx +18 -8
- package/src/components/modal/modal.tsx +4 -2
- package/src/components/popover/index.ts +1 -0
- package/src/components/popover/popover.tsx +60 -0
- package/src/components/tabs/index.ts +1 -0
- package/src/components/tabs/tabs.stories.tsx +100 -0
- package/src/components/tabs/tabs.tsx +380 -0
- package/src/components/textarea/textarea.stories.tsx +8 -2
- package/src/components/toast/index.ts +2 -2
- package/src/utils/debounce.ts +15 -0
- 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,4 +1,4 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export
|
|
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';
|