@drivenets/design-system 0.7.3 → 0.8.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 +25 -0
- package/dist/index.cjs +4445 -3769
- package/dist/index.d.cts +1224 -945
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +1224 -945
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4446 -3776
- package/dist/index.js.map +1 -1
- package/dist/index.min.css +1 -1
- package/package.json +7 -8
- package/src/components/ds-autocomplete/ds-autocomplete.module.scss +1 -1
- 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-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-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 +19 -0
- package/src/components/ds-date-picker/ds-date-picker.tsx +209 -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-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 +5 -0
- 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-status-badge/ds-status-badge.module.scss +1 -1
- package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.tsx +4 -2
- 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.tsx +158 -0
- package/src/components/ds-time-picker/ds-time-picker.types.ts +48 -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 +4 -0
- package/src/styles/_root_updated.scss +593 -0
- package/src/styles/shared/_dropdown.scss +4 -1
- 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 { DateRangeValue, 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<DateRangeValue>(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';
|
|
@@ -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
|
|
|
@@ -95,7 +97,10 @@ const DsFormControl = ({
|
|
|
95
97
|
DsFormControl.TextInput = controlify(DsTextInput);
|
|
96
98
|
DsFormControl.NumberInput = controlify(DsNumberInput);
|
|
97
99
|
DsFormControl.PasswordInput = controlify(DsPasswordInput);
|
|
100
|
+
/** @deprecated DsDateInput is deprecated. Use DsDatePicker or DsDateRangePicker instead. */
|
|
98
101
|
DsFormControl.DateInput = controlify(DsDateInput);
|
|
102
|
+
DsFormControl.DatePicker = controlify(DsDatePicker);
|
|
103
|
+
DsFormControl.TimePicker = controlify(DsTimePicker);
|
|
99
104
|
DsFormControl.Textarea = controlify(DsTextarea);
|
|
100
105
|
DsFormControl.Select = controlify(DsSelect);
|
|
101
106
|
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
|
);
|
package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.tsx
CHANGED
|
@@ -4,7 +4,7 @@ import { useVirtualizer, type Virtualizer } from '@tanstack/react-virtual';
|
|
|
4
4
|
import styles from './ds-table-body-virtualized.module.scss';
|
|
5
5
|
import { DsTableRowVirtualized } from '../ds-table-row-virtualized';
|
|
6
6
|
import type { DsTableBodyVirtualizedProps } from './ds-table-body-virtualized.types';
|
|
7
|
-
import { TableBody } from '../core-table';
|
|
7
|
+
import { TableBody, TableRow, TableCell } from '../core-table';
|
|
8
8
|
import { EMPTY_TABLE_STATE_TEXT } from '../../utils/constants';
|
|
9
9
|
|
|
10
10
|
export const DsTableBodyVirtualized = <TData,>({
|
|
@@ -138,7 +138,9 @@ function DsTableBody<TData>({
|
|
|
138
138
|
);
|
|
139
139
|
})
|
|
140
140
|
) : (
|
|
141
|
-
<
|
|
141
|
+
<TableRow>
|
|
142
|
+
<TableCell className={styles.emptyState}>{emptyState || EMPTY_TABLE_STATE_TEXT}</TableCell>
|
|
143
|
+
</TableRow>
|
|
142
144
|
)}
|
|
143
145
|
</TableBody>
|
|
144
146
|
);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './time-item';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
$time-item-height: 32px;
|
|
2
|
+
$time-item-width: 48px;
|
|
3
|
+
|
|
4
|
+
.container {
|
|
5
|
+
display: flex;
|
|
6
|
+
width: $time-item-width;
|
|
7
|
+
height: $time-item-height;
|
|
8
|
+
flex-shrink: 0;
|
|
9
|
+
flex-direction: column;
|
|
10
|
+
justify-content: center;
|
|
11
|
+
align-items: center;
|
|
12
|
+
border-radius: 8px;
|
|
13
|
+
background: transparent;
|
|
14
|
+
color: var(--color-font-main);
|
|
15
|
+
|
|
16
|
+
&.selected {
|
|
17
|
+
background: var(--color-background-selected);
|
|
18
|
+
color: var(--color-font-on-action);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
&.disabled {
|
|
22
|
+
color: var(--color-font-disabled);
|
|
23
|
+
pointer-events: none;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
&:not(.selected, .disabled) {
|
|
27
|
+
&:hover {
|
|
28
|
+
border: 1px solid var(--color-border);
|
|
29
|
+
background: var(--color-background-action-hover-weak);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
&:focus-visible {
|
|
33
|
+
border: 2px solid var(--color-border-action-secondary);
|
|
34
|
+
background: var(--color-background-action-hover-weak);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import type { TimeItemProps } from './time-item.types';
|
|
3
|
+
import styles from './time-item.module.scss';
|
|
4
|
+
import { DsTypography } from '../../../ds-typography';
|
|
5
|
+
|
|
6
|
+
export const TimeItem = ({ ref, className, selected, disabled, onClick, label }: TimeItemProps) => {
|
|
7
|
+
return (
|
|
8
|
+
<button
|
|
9
|
+
ref={ref}
|
|
10
|
+
type="button"
|
|
11
|
+
role="option"
|
|
12
|
+
aria-selected={selected}
|
|
13
|
+
aria-disabled={disabled || undefined}
|
|
14
|
+
disabled={disabled}
|
|
15
|
+
className={classNames(
|
|
16
|
+
styles.container,
|
|
17
|
+
selected && styles.selected,
|
|
18
|
+
disabled && styles.disabled,
|
|
19
|
+
className,
|
|
20
|
+
)}
|
|
21
|
+
onClick={onClick}
|
|
22
|
+
>
|
|
23
|
+
<DsTypography variant="body-md-md">{label}</DsTypography>
|
|
24
|
+
</button>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
$column-width: 56px;
|
|
2
|
+
$column-max-height: 280px;
|
|
3
|
+
|
|
4
|
+
.container {
|
|
5
|
+
display: flex;
|
|
6
|
+
padding: var(--spacing-xs);
|
|
7
|
+
background: var(--color-background);
|
|
8
|
+
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.22);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.column {
|
|
12
|
+
display: flex;
|
|
13
|
+
flex-direction: column;
|
|
14
|
+
align-items: center;
|
|
15
|
+
width: $column-width;
|
|
16
|
+
gap: var(--spacing-xs);
|
|
17
|
+
max-height: $column-max-height;
|
|
18
|
+
overflow-y: auto;
|
|
19
|
+
scroll-snap-type: y mandatory;
|
|
20
|
+
|
|
21
|
+
scrollbar-width: none;
|
|
22
|
+
&::-webkit-scrollbar {
|
|
23
|
+
display: none;
|
|
24
|
+
}
|
|
25
|
+
}
|