@drivenets/design-system 0.7.3 → 0.9.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/CHANGELOG.md +47 -0
- package/dist/index.cjs +5730 -4569
- package/dist/index.d.cts +1768 -1065
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +1768 -1065
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5718 -4577
- package/dist/index.js.map +1 -1
- package/dist/index.min.css +1 -1
- package/package.json +14 -14
- package/src/components/ds-autocomplete/ds-autocomplete.module.scss +1 -2
- package/src/components/ds-autocomplete/ds-autocomplete.tsx +13 -8
- package/src/components/ds-autocomplete/ds-autocomplete.types.ts +2 -1
- package/src/components/ds-button/versions/ds-button-new/ds-button-new.module.scss +25 -9
- package/src/components/ds-button/versions/ds-button-new/ds-button-new.tsx +5 -5
- package/src/components/ds-button/versions/ds-button-new/ds-button-new.types.ts +11 -1
- package/src/components/ds-button/versions/ds-button-new/index.ts +4 -1
- package/src/components/ds-button-v3/ds-button-v3.module.scss +391 -0
- package/src/components/ds-button-v3/ds-button-v3.tsx +55 -0
- package/src/components/ds-button-v3/ds-button-v3.types.ts +49 -0
- package/src/components/ds-button-v3/index.ts +2 -0
- package/src/components/ds-comment-bubble/ds-comment-bubble.module.scss +6 -2
- package/src/components/ds-comment-bubble/ds-comment-bubble.tsx +22 -15
- package/src/components/ds-comment-bubble/ds-comment-bubble.types.ts +1 -0
- package/src/components/ds-comments-drawer/components/comments-filter-modal/comments-filter-modal.tsx +1 -1
- package/src/components/ds-comments-drawer/ds-comments-drawer.tsx +3 -9
- package/src/components/ds-date-input/ds-date-input.tsx +3 -4
- package/src/components/ds-date-input/ds-date-input.types.ts +3 -1
- package/src/components/ds-date-input/index.ts +10 -0
- package/src/components/ds-date-picker/components/calendar-table.module.scss +93 -0
- package/src/components/ds-date-picker/components/calendar-view-control/calendar-view-control.module.scss +48 -0
- package/src/components/ds-date-picker/components/calendar-view-control/calendar-view-control.tsx +20 -0
- package/src/components/ds-date-picker/components/calendar-view-control/index.ts +1 -0
- package/src/components/ds-date-picker/components/day-view/day-view.module.scss +12 -0
- package/src/components/ds-date-picker/components/day-view/day-view.tsx +45 -0
- package/src/components/ds-date-picker/components/day-view/index.ts +1 -0
- package/src/components/ds-date-picker/components/month-view/index.ts +1 -0
- package/src/components/ds-date-picker/components/month-view/month-view.module.scss +4 -0
- package/src/components/ds-date-picker/components/month-view/month-view.tsx +35 -0
- package/src/components/ds-date-picker/components/year-view/index.ts +1 -0
- package/src/components/ds-date-picker/components/year-view/year-view.module.scss +4 -0
- package/src/components/ds-date-picker/components/year-view/year-view.tsx +35 -0
- package/src/components/ds-date-picker/ds-date-picker.module.scss +29 -0
- package/src/components/ds-date-picker/ds-date-picker.tsx +211 -0
- package/src/components/ds-date-picker/ds-date-picker.types.ts +94 -0
- package/src/components/ds-date-picker/ds-date-picker.utils.ts +87 -0
- package/src/components/ds-date-picker/ds-date-picker.utils.unit.test.ts +226 -0
- package/src/components/ds-date-picker/index.ts +2 -0
- package/src/components/ds-date-range-picker/ds-date-range-picker.module.scss +20 -0
- package/src/components/ds-date-range-picker/ds-date-range-picker.tsx +101 -0
- package/src/components/ds-date-range-picker/ds-date-range-picker.types.ts +95 -0
- package/src/components/ds-date-range-picker/ds-date-range-picker.utils.ts +19 -0
- package/src/components/ds-date-range-picker/ds-date-range-picker.utils.unit.test.ts +48 -0
- package/src/components/ds-date-range-picker/index.ts +9 -0
- package/src/components/ds-drawer/ds-drawer.tsx +6 -9
- package/src/components/ds-drawer/ds-drawer.types.ts +4 -2
- package/src/components/ds-file-upload/ds-file-upload-api.types.ts +1 -0
- package/src/components/ds-file-upload/hooks/use-file-upload.ts +1 -1
- package/src/components/ds-file-upload/index.ts +6 -1
- package/src/components/ds-file-upload/utils/file-factory.ts +1 -0
- package/src/components/ds-file-upload/utils/file-factory.unit.test.ts +37 -0
- package/src/components/ds-form-control/ds-form-control.tsx +7 -3
- package/src/components/ds-key-value-pair/ds-key-value-pair.module.scss +72 -0
- package/src/components/ds-key-value-pair/ds-key-value-pair.tsx +47 -0
- package/src/components/ds-key-value-pair/ds-key-value-pair.types.ts +24 -0
- package/src/components/ds-key-value-pair/index.ts +3 -0
- package/src/components/ds-loader/ds-loader.tsx +1 -1
- package/src/components/ds-panel/ds-panel.module.scss +4 -1
- package/src/components/ds-panel/ds-panel.tsx +8 -3
- package/src/components/ds-panel/ds-panel.types.ts +8 -1
- package/src/components/ds-panel/index.ts +4 -1
- package/src/components/ds-select/ds-select.module.scss +4 -1
- package/src/components/ds-split-button/ds-split-button.module.scss +99 -0
- package/src/components/ds-split-button/ds-split-button.tsx +49 -0
- package/src/components/ds-split-button/ds-split-button.types.ts +25 -0
- package/src/components/ds-split-button/index.ts +10 -0
- package/src/components/ds-stack/ds-stack.module.scss +4 -0
- package/src/components/ds-stack/ds-stack.tsx +35 -0
- package/src/components/ds-stack/ds-stack.types.ts +19 -0
- package/src/components/ds-stack/index.ts +18 -0
- package/src/components/ds-status-badge/ds-status-badge.module.scss +1 -1
- package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.tsx +6 -4
- package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.module.scss +5 -8
- package/src/components/ds-table/ds-table.module.scss +6 -0
- package/src/components/ds-table/ds-table.tsx +3 -0
- package/src/components/ds-time-picker/components/time-item/index.ts +1 -0
- package/src/components/ds-time-picker/components/time-item/time-item.module.scss +37 -0
- package/src/components/ds-time-picker/components/time-item/time-item.tsx +26 -0
- package/src/components/ds-time-picker/components/time-item/time-item.types.ts +10 -0
- package/src/components/ds-time-picker/components/time-scroller/index.ts +2 -0
- package/src/components/ds-time-picker/components/time-scroller/time-scroller.module.scss +25 -0
- package/src/components/ds-time-picker/components/time-scroller/time-scroller.tsx +61 -0
- package/src/components/ds-time-picker/components/time-scroller/time-scroller.types.ts +17 -0
- package/src/components/ds-time-picker/components/time-scroller/time-scroller.utils.ts +44 -0
- package/src/components/ds-time-picker/ds-time-picker.module.scss +11 -0
- package/src/components/ds-time-picker/ds-time-picker.tsx +178 -0
- package/src/components/ds-time-picker/ds-time-picker.types.ts +54 -0
- package/src/components/ds-time-picker/ds-time-picker.utils.ts +176 -0
- package/src/components/ds-time-picker/ds-time-picker.utils.unit.test.ts +316 -0
- package/src/components/ds-time-picker/index.ts +3 -0
- package/src/components/ds-tooltip/ds-tooltip.module.scss +1 -7
- package/src/components/ds-tooltip/ds-tooltip.tsx +9 -9
- package/src/components/ds-tooltip/ds-tooltip.types.ts +7 -1
- package/src/index.ts +8 -0
- package/src/styles/_breakpoints.module.scss +5 -0
- package/src/styles/_responsive.scss +13 -0
- package/src/styles/_root_updated.scss +593 -0
- package/src/styles/_variables.scss +2 -0
- package/src/styles/shared/_button.scss +2 -0
- package/src/styles/shared/_dropdown.scss +4 -1
- package/src/styles/shared/_input.scss +3 -40
- package/src/utils/responsive.browser.test.ts +94 -0
- package/src/utils/responsive.ts +79 -0
- package/src/utils/responsive.unit.test.ts +54 -0
- package/src/utils/type-utils.ts +1 -0
- package/src/utils/use-controlled.browser.test.ts +79 -0
- package/src/utils/use-controlled.ts +18 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import { DsFormControl } from '../ds-form-control';
|
|
3
|
+
import { DsButton } from '../ds-button';
|
|
4
|
+
import { DsIcon } from '../ds-icon';
|
|
5
|
+
import { useControlled } from '../../utils/use-controlled';
|
|
6
|
+
import type { DsDateRangePickerProps } from './ds-date-range-picker.types';
|
|
7
|
+
import styles from './ds-date-range-picker.module.scss';
|
|
8
|
+
import { earlierDate, laterDate } from './ds-date-range-picker.utils';
|
|
9
|
+
|
|
10
|
+
const DsDateRangePicker = ({
|
|
11
|
+
value: valueProp,
|
|
12
|
+
defaultValue = [null, null],
|
|
13
|
+
onChange,
|
|
14
|
+
min,
|
|
15
|
+
max,
|
|
16
|
+
withTime = false,
|
|
17
|
+
orientation = 'horizontal',
|
|
18
|
+
hideClearAll = false,
|
|
19
|
+
closeOnSelect = true,
|
|
20
|
+
disabled,
|
|
21
|
+
readOnly,
|
|
22
|
+
className,
|
|
23
|
+
disablePortal,
|
|
24
|
+
locale,
|
|
25
|
+
slotProps,
|
|
26
|
+
}: DsDateRangePickerProps) => {
|
|
27
|
+
const [value, setValue] = useControlled(valueProp, onChange, defaultValue);
|
|
28
|
+
|
|
29
|
+
const [startDate, endDate] = value;
|
|
30
|
+
|
|
31
|
+
const handleStartChange = (date: Date | null) => {
|
|
32
|
+
setValue([date, endDate]);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const handleEndChange = (date: Date | null) => {
|
|
36
|
+
setValue([startDate, date]);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const handleClearAll = () => {
|
|
40
|
+
setValue([null, null]);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// max allowed date for the "Start date" is the earliest of "max" and the selected "endDate"
|
|
44
|
+
const effectiveStartMax = earlierDate(max, endDate ?? undefined);
|
|
45
|
+
|
|
46
|
+
// min allowed date for the "End date" is the later of "min" and the selected "startDate"
|
|
47
|
+
const effectiveEndMin = laterDate(min, startDate ?? undefined);
|
|
48
|
+
|
|
49
|
+
const showClearAll = !hideClearAll && !disabled && !readOnly && (!!startDate || !!endDate);
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div className={classNames(styles.root, styles[orientation], className)}>
|
|
53
|
+
<DsFormControl
|
|
54
|
+
label="Start date"
|
|
55
|
+
{...slotProps?.startDateFormControl}
|
|
56
|
+
className={classNames(styles.formControl, slotProps?.startDateFormControl?.className)}
|
|
57
|
+
>
|
|
58
|
+
<DsFormControl.DatePicker
|
|
59
|
+
value={startDate}
|
|
60
|
+
onChange={handleStartChange}
|
|
61
|
+
min={min}
|
|
62
|
+
max={effectiveStartMax}
|
|
63
|
+
withTime={withTime}
|
|
64
|
+
closeOnSelect={closeOnSelect}
|
|
65
|
+
disabled={disabled}
|
|
66
|
+
readOnly={readOnly}
|
|
67
|
+
disablePortal={disablePortal}
|
|
68
|
+
{...slotProps?.startDatePicker}
|
|
69
|
+
/>
|
|
70
|
+
</DsFormControl>
|
|
71
|
+
|
|
72
|
+
<DsFormControl
|
|
73
|
+
label="End date"
|
|
74
|
+
{...slotProps?.endDateFormControl}
|
|
75
|
+
className={classNames(styles.formControl, slotProps?.startDateFormControl?.className)}
|
|
76
|
+
>
|
|
77
|
+
<DsFormControl.DatePicker
|
|
78
|
+
value={endDate}
|
|
79
|
+
onChange={handleEndChange}
|
|
80
|
+
min={effectiveEndMin}
|
|
81
|
+
max={max}
|
|
82
|
+
withTime={withTime}
|
|
83
|
+
closeOnSelect={closeOnSelect}
|
|
84
|
+
disabled={disabled}
|
|
85
|
+
readOnly={readOnly}
|
|
86
|
+
disablePortal={disablePortal}
|
|
87
|
+
{...slotProps?.endDatePicker}
|
|
88
|
+
/>
|
|
89
|
+
</DsFormControl>
|
|
90
|
+
|
|
91
|
+
{showClearAll && (
|
|
92
|
+
<DsButton design="v1.2" buttonType="tertiary" className={styles.clearButton} onClick={handleClearAll}>
|
|
93
|
+
<DsIcon icon="close" size="small" />
|
|
94
|
+
{locale?.clearAllLabel ?? 'Clear all'}
|
|
95
|
+
</DsButton>
|
|
96
|
+
)}
|
|
97
|
+
</div>
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export default DsDateRangePicker;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { DsDatePickerProps } from '../ds-date-picker';
|
|
2
|
+
import type { DsFormControlProps } from '../ds-form-control';
|
|
3
|
+
|
|
4
|
+
export type DateRangeValue = [Date | null, Date | null];
|
|
5
|
+
|
|
6
|
+
export const dateRangePickerOrientations = ['horizontal', 'vertical'] as const;
|
|
7
|
+
export type DateRangePickerOrientation = (typeof dateRangePickerOrientations)[number];
|
|
8
|
+
|
|
9
|
+
export interface DsDateRangePickerLocale {
|
|
10
|
+
clearAllLabel?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type DateRangeFormControlSlotProps = Partial<Omit<DsFormControlProps, 'children'>>;
|
|
14
|
+
|
|
15
|
+
export interface DsDateRangePickerSlotProps {
|
|
16
|
+
startDatePicker?: DsDatePickerProps;
|
|
17
|
+
endDatePicker?: DsDatePickerProps;
|
|
18
|
+
startDateFormControl?: DateRangeFormControlSlotProps;
|
|
19
|
+
endDateFormControl?: DateRangeFormControlSlotProps;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface DsDateRangePickerProps {
|
|
23
|
+
/**
|
|
24
|
+
* Controlled value as a tuple of [startDate, endDate].
|
|
25
|
+
* `null` for either element means that date is empty.
|
|
26
|
+
* `undefined` means the component is uncontrolled.
|
|
27
|
+
*/
|
|
28
|
+
value?: DateRangeValue;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Default value for uncontrolled mode
|
|
32
|
+
*/
|
|
33
|
+
defaultValue?: DateRangeValue;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Callback when either date changes
|
|
37
|
+
*/
|
|
38
|
+
onChange?: (value: DateRangeValue) => void;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Minimum allowed datetime for both pickers
|
|
42
|
+
*/
|
|
43
|
+
min?: Date;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Maximum allowed datetime for both pickers
|
|
47
|
+
*/
|
|
48
|
+
max?: Date;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Whether to show date pickers with time
|
|
52
|
+
* @default false
|
|
53
|
+
*/
|
|
54
|
+
withTime?: boolean;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Layout orientation of the two pickers
|
|
58
|
+
* @default 'horizontal'
|
|
59
|
+
*/
|
|
60
|
+
orientation?: DateRangePickerOrientation;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Whether to hide the "Clear all" button
|
|
64
|
+
* @default false
|
|
65
|
+
*/
|
|
66
|
+
hideClearAll?: boolean;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Whether to close the calendar when a date is selected. This is ignored when withTime is true.
|
|
70
|
+
* @default true
|
|
71
|
+
*/
|
|
72
|
+
closeOnSelect?: boolean;
|
|
73
|
+
|
|
74
|
+
disabled?: boolean;
|
|
75
|
+
|
|
76
|
+
readOnly?: boolean;
|
|
77
|
+
|
|
78
|
+
className?: string;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Whether to disable the portal for both calendar popups
|
|
82
|
+
* @default false
|
|
83
|
+
*/
|
|
84
|
+
disablePortal?: boolean;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Localizable labels and text
|
|
88
|
+
*/
|
|
89
|
+
locale?: DsDateRangePickerLocale;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Props forwarded to sub-component slots
|
|
93
|
+
*/
|
|
94
|
+
slotProps?: DsDateRangePickerSlotProps;
|
|
95
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export const earlierDate = (a: Date | undefined, b: Date | undefined): Date | undefined => {
|
|
2
|
+
if (!a) {
|
|
3
|
+
return b;
|
|
4
|
+
}
|
|
5
|
+
if (!b) {
|
|
6
|
+
return a;
|
|
7
|
+
}
|
|
8
|
+
return a < b ? a : b;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const laterDate = (a: Date | undefined, b: Date | undefined): Date | undefined => {
|
|
12
|
+
if (!a) {
|
|
13
|
+
return b;
|
|
14
|
+
}
|
|
15
|
+
if (!b) {
|
|
16
|
+
return a;
|
|
17
|
+
}
|
|
18
|
+
return a > b ? a : b;
|
|
19
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { earlierDate, laterDate } from './ds-date-range-picker.utils';
|
|
3
|
+
|
|
4
|
+
const createDate = (year: number, month: number, day: number) => new Date(year, month - 1, day);
|
|
5
|
+
|
|
6
|
+
describe('Date Range Picker Utils', () => {
|
|
7
|
+
describe('earlierDate', () => {
|
|
8
|
+
it('should return the earlier of two dates', () => {
|
|
9
|
+
const jan = createDate(2024, 1, 1);
|
|
10
|
+
const mar = createDate(2024, 3, 15);
|
|
11
|
+
|
|
12
|
+
expect(earlierDate(jan, mar)).toBe(jan);
|
|
13
|
+
expect(earlierDate(mar, jan)).toBe(jan);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should return the other date when one is undefined', () => {
|
|
17
|
+
const date = createDate(2024, 6, 1);
|
|
18
|
+
|
|
19
|
+
expect(earlierDate(undefined, date)).toBe(date);
|
|
20
|
+
expect(earlierDate(date, undefined)).toBe(date);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should return undefined when both are undefined', () => {
|
|
24
|
+
expect(earlierDate(undefined, undefined)).toBeUndefined();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('laterDate', () => {
|
|
29
|
+
it('should return the later of two dates', () => {
|
|
30
|
+
const jan = createDate(2024, 1, 1);
|
|
31
|
+
const mar = createDate(2024, 3, 15);
|
|
32
|
+
|
|
33
|
+
expect(laterDate(jan, mar)).toBe(mar);
|
|
34
|
+
expect(laterDate(mar, jan)).toBe(mar);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should return the other date when one is undefined', () => {
|
|
38
|
+
const date = createDate(2024, 6, 1);
|
|
39
|
+
|
|
40
|
+
expect(laterDate(undefined, date)).toBe(date);
|
|
41
|
+
expect(laterDate(date, undefined)).toBe(date);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should return undefined when both are undefined', () => {
|
|
45
|
+
expect(laterDate(undefined, undefined)).toBeUndefined();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { default as DsDateRangePicker } from './ds-date-range-picker';
|
|
2
|
+
export type {
|
|
3
|
+
DsDateRangePickerProps,
|
|
4
|
+
DsDateRangePickerLocale,
|
|
5
|
+
DsDateRangePickerSlotProps,
|
|
6
|
+
DateRangeFormControlSlotProps,
|
|
7
|
+
DateRangeValue,
|
|
8
|
+
DateRangePickerOrientation,
|
|
9
|
+
} from './ds-date-range-picker.types';
|
|
@@ -6,12 +6,8 @@ import type { DsDrawerProps } from './ds-drawer.types';
|
|
|
6
6
|
import styles from './ds-drawer.module.scss';
|
|
7
7
|
import { DsIcon } from '../ds-icon';
|
|
8
8
|
import { DsTypography } from '../ds-typography';
|
|
9
|
+
import { useResponsiveValue } from '../../utils/responsive';
|
|
9
10
|
|
|
10
|
-
/**
|
|
11
|
-
* Composable drawer component.
|
|
12
|
-
* Supports grid-based sizing, flexible positioning, and compound components.
|
|
13
|
-
* Use columns prop to control drawer width (1-12 grid columns).
|
|
14
|
-
*/
|
|
15
11
|
const DsDrawer = ({
|
|
16
12
|
open,
|
|
17
13
|
onOpenChange,
|
|
@@ -25,9 +21,12 @@ const DsDrawer = ({
|
|
|
25
21
|
className,
|
|
26
22
|
children,
|
|
27
23
|
}: DsDrawerProps) => {
|
|
24
|
+
const resolvedColumns = useResponsiveValue(columns);
|
|
25
|
+
|
|
28
26
|
const handleOpenChange = (details: { open: boolean }) => {
|
|
29
27
|
onOpenChange(details.open);
|
|
30
28
|
};
|
|
29
|
+
|
|
31
30
|
const Wrapper = portal ? Portal : Fragment;
|
|
32
31
|
|
|
33
32
|
return (
|
|
@@ -48,10 +47,8 @@ const DsDrawer = ({
|
|
|
48
47
|
styles.drawer,
|
|
49
48
|
styles[`position-${position}`],
|
|
50
49
|
className,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
54
|
-
styles[`cols-${String(columns)}`],
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
51
|
+
styles[`cols-${resolvedColumns}`],
|
|
55
52
|
)}
|
|
56
53
|
>
|
|
57
54
|
<div className={styles.content}>{children}</div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CSSProperties, ReactNode } from 'react';
|
|
2
2
|
import type { UseDialogProps as DialogProps } from '@ark-ui/react/dialog';
|
|
3
|
+
import type { ResponsiveValue } from '../../utils/responsive';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Available positions for the drawer
|
|
@@ -20,10 +21,11 @@ export interface DsDrawerProps extends Pick<DialogProps, 'closeOnEscape' | 'clos
|
|
|
20
21
|
*/
|
|
21
22
|
onOpenChange: (open: boolean) => void;
|
|
22
23
|
/**
|
|
23
|
-
* Number of grid columns the drawer should span (1-12)
|
|
24
|
+
* Number of grid columns the drawer should span (1-12).
|
|
25
|
+
* Accepts a responsive value, e.g. `{ lg: 4, md: 6 }`.
|
|
24
26
|
* @default 4
|
|
25
27
|
*/
|
|
26
|
-
columns?: DsDrawerColumns
|
|
28
|
+
columns?: ResponsiveValue<DsDrawerColumns>;
|
|
27
29
|
/**
|
|
28
30
|
* Position of the drawer
|
|
29
31
|
* @default 'end'
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
export { default as DsFileUpload } from './ds-file-upload';
|
|
2
2
|
export { FileUpload } from './components/file-upload';
|
|
3
3
|
export * from './ds-file-upload.types';
|
|
4
|
-
export type {
|
|
4
|
+
export type {
|
|
5
|
+
FileUploadAdapter,
|
|
6
|
+
FileUploadOptions,
|
|
7
|
+
FileUploadResult,
|
|
8
|
+
UploadedFile,
|
|
9
|
+
} from './ds-file-upload-api.types';
|
|
5
10
|
export * from './hooks';
|
|
6
11
|
export { FileUploadError, RetryableFileUploadError, FatalFileUploadError } from './errors/file-upload-errors';
|
|
7
12
|
export type { AcceptedFileType, FileExtension } from './types/accept-types';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { createUploadedFile } from './file-factory';
|
|
3
|
+
|
|
4
|
+
describe('createUploadedFile', () => {
|
|
5
|
+
const makeFile = (name = 'test.txt', content = 'hello') =>
|
|
6
|
+
new File([content], name, { type: 'text/plain' });
|
|
7
|
+
|
|
8
|
+
it('should preserve originalFile reference', () => {
|
|
9
|
+
const mockFile = makeFile();
|
|
10
|
+
const uploaded = createUploadedFile(mockFile, 'pending');
|
|
11
|
+
|
|
12
|
+
expect(uploaded.originalFile).toBe(mockFile);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should preserve originalFile through object spread (retry scenario)', () => {
|
|
16
|
+
const mockFile = makeFile('data.bin', 'bytes');
|
|
17
|
+
const uploaded = createUploadedFile(mockFile, 'pending');
|
|
18
|
+
const data = { ...uploaded, status: 'uploading' as const };
|
|
19
|
+
|
|
20
|
+
expect(data.originalFile).toBe(mockFile);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should set upload metadata correctly', () => {
|
|
24
|
+
const name = 'test.txt';
|
|
25
|
+
const content = 'hello';
|
|
26
|
+
const mockFile = makeFile(name, content);
|
|
27
|
+
const uploaded = createUploadedFile(mockFile, 'error', ['TOO_LARGE']);
|
|
28
|
+
|
|
29
|
+
expect(uploaded.status).toBe('error');
|
|
30
|
+
expect(uploaded.progress).toBe(0);
|
|
31
|
+
expect(uploaded.errors).toEqual(['TOO_LARGE']);
|
|
32
|
+
expect(uploaded.name).toBe(name);
|
|
33
|
+
expect(uploaded.size).toBe(content.length);
|
|
34
|
+
expect(uploaded.type).toBe('text/plain');
|
|
35
|
+
expect(uploaded.id).toMatch(new RegExp(`^${name}-`));
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -10,6 +10,8 @@ import { DsPasswordInput } from '../ds-password-input';
|
|
|
10
10
|
import type { DsFormControlDescriptionProps, DsFormControlProps } from './ds-form-control.types';
|
|
11
11
|
import styles from './ds-form-control.module.scss';
|
|
12
12
|
import { DsDateInput } from '../ds-date-input';
|
|
13
|
+
import { DsDatePicker } from '../ds-date-picker';
|
|
14
|
+
import { DsTimePicker } from '../ds-time-picker';
|
|
13
15
|
|
|
14
16
|
const FormControlContext = createContext<{ controlId: string } | null>(null);
|
|
15
17
|
|
|
@@ -61,9 +63,8 @@ const DsFormControl = ({
|
|
|
61
63
|
styles.container,
|
|
62
64
|
className,
|
|
63
65
|
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
status && message && styles[status],
|
|
66
|
+
// Casting because `info` doesn't have its own classname
|
|
67
|
+
status && message && styles[status as keyof typeof styles],
|
|
67
68
|
)}
|
|
68
69
|
style={style}
|
|
69
70
|
>
|
|
@@ -95,7 +96,10 @@ const DsFormControl = ({
|
|
|
95
96
|
DsFormControl.TextInput = controlify(DsTextInput);
|
|
96
97
|
DsFormControl.NumberInput = controlify(DsNumberInput);
|
|
97
98
|
DsFormControl.PasswordInput = controlify(DsPasswordInput);
|
|
99
|
+
/** @deprecated DsDateInput is deprecated. Use DsDatePicker or DsDateRangePicker instead. */
|
|
98
100
|
DsFormControl.DateInput = controlify(DsDateInput);
|
|
101
|
+
DsFormControl.DatePicker = controlify(DsDatePicker);
|
|
102
|
+
DsFormControl.TimePicker = controlify(DsTimePicker);
|
|
99
103
|
DsFormControl.Textarea = controlify(DsTextarea);
|
|
100
104
|
DsFormControl.Select = controlify(DsSelect);
|
|
101
105
|
DsFormControl.Description = DsFormControlDescription;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
.root {
|
|
2
|
+
display: flex;
|
|
3
|
+
align-items: flex-start;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.root[data-orientation='vertical'] {
|
|
7
|
+
flex-direction: column;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.root[data-orientation='horizontal'] {
|
|
11
|
+
display: grid;
|
|
12
|
+
grid-template-columns: auto 1fr;
|
|
13
|
+
column-gap: var(--spacing-xs);
|
|
14
|
+
align-items: start;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.label {
|
|
18
|
+
padding-bottom: var(--spacing-4xs);
|
|
19
|
+
padding-left: var(--spacing-sm);
|
|
20
|
+
color: var(--color-font-main);
|
|
21
|
+
white-space: nowrap;
|
|
22
|
+
flex-shrink: 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.root[data-orientation='horizontal'] .label {
|
|
26
|
+
padding-bottom: 0;
|
|
27
|
+
padding-top: var(--spacing-3xs);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.valueContainer {
|
|
31
|
+
display: grid;
|
|
32
|
+
align-items: center;
|
|
33
|
+
width: 100%;
|
|
34
|
+
outline: none;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.root[data-orientation='horizontal'] .valueContainer {
|
|
38
|
+
justify-items: end;
|
|
39
|
+
min-width: 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.valueDisplay {
|
|
43
|
+
width: 100%;
|
|
44
|
+
min-width: 0;
|
|
45
|
+
min-height: 28px;
|
|
46
|
+
padding: var(--spacing-3xs) var(--spacing-sm);
|
|
47
|
+
color: var(--color-font-secondary);
|
|
48
|
+
text-overflow: ellipsis;
|
|
49
|
+
white-space: nowrap;
|
|
50
|
+
overflow: hidden;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.root[data-orientation='horizontal'] .valueDisplay {
|
|
54
|
+
text-align: end;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.editorSlot {
|
|
58
|
+
display: none;
|
|
59
|
+
min-width: 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.valueContainer[data-editable]:hover .valueDisplay,
|
|
63
|
+
.valueContainer[data-editable]:focus-within .valueDisplay,
|
|
64
|
+
.valueContainer[data-editable]:has([data-state='open']) .valueDisplay {
|
|
65
|
+
display: none;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.valueContainer[data-editable]:hover .editorSlot,
|
|
69
|
+
.valueContainer[data-editable]:focus-within .editorSlot,
|
|
70
|
+
.valueContainer[data-editable]:has([data-state='open']) .editorSlot {
|
|
71
|
+
display: block;
|
|
72
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
|
|
3
|
+
import { DsTypography } from '../ds-typography';
|
|
4
|
+
import styles from './ds-key-value-pair.module.scss';
|
|
5
|
+
import type { DsKeyValuePairProps } from './ds-key-value-pair.types';
|
|
6
|
+
|
|
7
|
+
const DsKeyValuePair = ({
|
|
8
|
+
ref,
|
|
9
|
+
keyLabel,
|
|
10
|
+
value,
|
|
11
|
+
readOnly = false,
|
|
12
|
+
orientation = 'vertical',
|
|
13
|
+
editInput,
|
|
14
|
+
className,
|
|
15
|
+
style,
|
|
16
|
+
}: DsKeyValuePairProps) => {
|
|
17
|
+
const editable = !readOnly && !!editInput;
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div
|
|
21
|
+
ref={ref}
|
|
22
|
+
className={classNames(styles.root, className)}
|
|
23
|
+
data-orientation={orientation}
|
|
24
|
+
data-readonly={readOnly || undefined}
|
|
25
|
+
style={style}
|
|
26
|
+
>
|
|
27
|
+
<DsTypography variant="body-sm-md" className={styles.label}>
|
|
28
|
+
{keyLabel}
|
|
29
|
+
</DsTypography>
|
|
30
|
+
|
|
31
|
+
<div
|
|
32
|
+
className={styles.valueContainer}
|
|
33
|
+
/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex -- tabIndex enables keyboard focus which triggers :focus-within to reveal the editor */
|
|
34
|
+
tabIndex={editable ? 0 : undefined}
|
|
35
|
+
data-editable={editable || undefined}
|
|
36
|
+
>
|
|
37
|
+
<DsTypography variant="body-sm-reg" asChild>
|
|
38
|
+
<div className={styles.valueDisplay}>{value}</div>
|
|
39
|
+
</DsTypography>
|
|
40
|
+
|
|
41
|
+
{editable && <div className={styles.editorSlot}>{editInput}</div>}
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export default DsKeyValuePair;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { CSSProperties, ReactNode, Ref } from 'react';
|
|
2
|
+
|
|
3
|
+
export const dsKeyValuePairOrientations = ['vertical', 'horizontal'] as const;
|
|
4
|
+
export type DsKeyValuePairOrientation = (typeof dsKeyValuePairOrientations)[number];
|
|
5
|
+
|
|
6
|
+
export interface DsKeyValuePairProps {
|
|
7
|
+
keyLabel: ReactNode;
|
|
8
|
+
|
|
9
|
+
/** Shown when the editor is not active (read-only or not hovered). */
|
|
10
|
+
value?: ReactNode;
|
|
11
|
+
|
|
12
|
+
/** @default false */
|
|
13
|
+
readOnly?: boolean;
|
|
14
|
+
|
|
15
|
+
/** @default 'vertical' */
|
|
16
|
+
orientation?: DsKeyValuePairOrientation;
|
|
17
|
+
|
|
18
|
+
/** Editor content revealed on hover/focus when not read-only. */
|
|
19
|
+
editInput?: ReactNode;
|
|
20
|
+
|
|
21
|
+
ref?: Ref<HTMLDivElement>;
|
|
22
|
+
className?: string;
|
|
23
|
+
style?: CSSProperties;
|
|
24
|
+
}
|
|
@@ -9,7 +9,7 @@ import SpinnerIcon from './spinner-icon';
|
|
|
9
9
|
*/
|
|
10
10
|
const DsLoader = ({ variant = 'spinner', className, ...props }: DsLoaderProps) => {
|
|
11
11
|
return (
|
|
12
|
-
<div className={classNames(styles.loaderContainer, className)} {...props}>
|
|
12
|
+
<div className={classNames(styles.loaderContainer, className)} role="progressbar" {...props}>
|
|
13
13
|
{variant === 'pulsing' ? <PulsingIcon /> : <SpinnerIcon />}
|
|
14
14
|
</div>
|
|
15
15
|
);
|
|
@@ -3,7 +3,7 @@ import * as Collapsible from '@radix-ui/react-collapsible';
|
|
|
3
3
|
import classnames from 'classnames';
|
|
4
4
|
import styles from './ds-panel.module.scss';
|
|
5
5
|
import { DsIcon } from '../ds-icon';
|
|
6
|
-
import type {
|
|
6
|
+
import type { DsPanelBaseProps, DsPanelCollapseButtonProps } from './ds-panel.types';
|
|
7
7
|
|
|
8
8
|
function useDraggable(enabled: boolean) {
|
|
9
9
|
const rootRef = useRef<HTMLDivElement>(null);
|
|
@@ -84,20 +84,24 @@ function useDraggable(enabled: boolean) {
|
|
|
84
84
|
return rootRef;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
export function
|
|
87
|
+
export function DsPanelBase({
|
|
88
88
|
open,
|
|
89
89
|
onOpenChange,
|
|
90
90
|
children,
|
|
91
91
|
className,
|
|
92
|
+
style,
|
|
92
93
|
slotProps,
|
|
93
94
|
variant = 'docked',
|
|
95
|
+
width,
|
|
94
96
|
draggable = false,
|
|
95
97
|
disablePadding = false,
|
|
96
98
|
...props
|
|
97
|
-
}:
|
|
99
|
+
}: DsPanelBaseProps) {
|
|
98
100
|
const isDraggable = variant === 'floating' && draggable;
|
|
99
101
|
const rootRef = useDraggable(isDraggable);
|
|
100
102
|
|
|
103
|
+
const rootStyle = width ? { ...style, width } : style;
|
|
104
|
+
|
|
101
105
|
return (
|
|
102
106
|
<Collapsible.Root
|
|
103
107
|
ref={rootRef}
|
|
@@ -108,6 +112,7 @@ export function DsPanel({
|
|
|
108
112
|
[styles.variantFloating]: variant === 'floating',
|
|
109
113
|
[styles.disablePadding]: disablePadding,
|
|
110
114
|
})}
|
|
115
|
+
style={rootStyle}
|
|
111
116
|
{...props}
|
|
112
117
|
>
|
|
113
118
|
<DsPanelCollapseButton {...slotProps?.collapseButton} />
|