@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.
Files changed (117) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/index.cjs +5730 -4569
  3. package/dist/index.d.cts +1768 -1065
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.ts +1768 -1065
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +5718 -4577
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.min.css +1 -1
  10. package/package.json +14 -14
  11. package/src/components/ds-autocomplete/ds-autocomplete.module.scss +1 -2
  12. package/src/components/ds-autocomplete/ds-autocomplete.tsx +13 -8
  13. package/src/components/ds-autocomplete/ds-autocomplete.types.ts +2 -1
  14. package/src/components/ds-button/versions/ds-button-new/ds-button-new.module.scss +25 -9
  15. package/src/components/ds-button/versions/ds-button-new/ds-button-new.tsx +5 -5
  16. package/src/components/ds-button/versions/ds-button-new/ds-button-new.types.ts +11 -1
  17. package/src/components/ds-button/versions/ds-button-new/index.ts +4 -1
  18. package/src/components/ds-button-v3/ds-button-v3.module.scss +391 -0
  19. package/src/components/ds-button-v3/ds-button-v3.tsx +55 -0
  20. package/src/components/ds-button-v3/ds-button-v3.types.ts +49 -0
  21. package/src/components/ds-button-v3/index.ts +2 -0
  22. package/src/components/ds-comment-bubble/ds-comment-bubble.module.scss +6 -2
  23. package/src/components/ds-comment-bubble/ds-comment-bubble.tsx +22 -15
  24. package/src/components/ds-comment-bubble/ds-comment-bubble.types.ts +1 -0
  25. package/src/components/ds-comments-drawer/components/comments-filter-modal/comments-filter-modal.tsx +1 -1
  26. package/src/components/ds-comments-drawer/ds-comments-drawer.tsx +3 -9
  27. package/src/components/ds-date-input/ds-date-input.tsx +3 -4
  28. package/src/components/ds-date-input/ds-date-input.types.ts +3 -1
  29. package/src/components/ds-date-input/index.ts +10 -0
  30. package/src/components/ds-date-picker/components/calendar-table.module.scss +93 -0
  31. package/src/components/ds-date-picker/components/calendar-view-control/calendar-view-control.module.scss +48 -0
  32. package/src/components/ds-date-picker/components/calendar-view-control/calendar-view-control.tsx +20 -0
  33. package/src/components/ds-date-picker/components/calendar-view-control/index.ts +1 -0
  34. package/src/components/ds-date-picker/components/day-view/day-view.module.scss +12 -0
  35. package/src/components/ds-date-picker/components/day-view/day-view.tsx +45 -0
  36. package/src/components/ds-date-picker/components/day-view/index.ts +1 -0
  37. package/src/components/ds-date-picker/components/month-view/index.ts +1 -0
  38. package/src/components/ds-date-picker/components/month-view/month-view.module.scss +4 -0
  39. package/src/components/ds-date-picker/components/month-view/month-view.tsx +35 -0
  40. package/src/components/ds-date-picker/components/year-view/index.ts +1 -0
  41. package/src/components/ds-date-picker/components/year-view/year-view.module.scss +4 -0
  42. package/src/components/ds-date-picker/components/year-view/year-view.tsx +35 -0
  43. package/src/components/ds-date-picker/ds-date-picker.module.scss +29 -0
  44. package/src/components/ds-date-picker/ds-date-picker.tsx +211 -0
  45. package/src/components/ds-date-picker/ds-date-picker.types.ts +94 -0
  46. package/src/components/ds-date-picker/ds-date-picker.utils.ts +87 -0
  47. package/src/components/ds-date-picker/ds-date-picker.utils.unit.test.ts +226 -0
  48. package/src/components/ds-date-picker/index.ts +2 -0
  49. package/src/components/ds-date-range-picker/ds-date-range-picker.module.scss +20 -0
  50. package/src/components/ds-date-range-picker/ds-date-range-picker.tsx +101 -0
  51. package/src/components/ds-date-range-picker/ds-date-range-picker.types.ts +95 -0
  52. package/src/components/ds-date-range-picker/ds-date-range-picker.utils.ts +19 -0
  53. package/src/components/ds-date-range-picker/ds-date-range-picker.utils.unit.test.ts +48 -0
  54. package/src/components/ds-date-range-picker/index.ts +9 -0
  55. package/src/components/ds-drawer/ds-drawer.tsx +6 -9
  56. package/src/components/ds-drawer/ds-drawer.types.ts +4 -2
  57. package/src/components/ds-file-upload/ds-file-upload-api.types.ts +1 -0
  58. package/src/components/ds-file-upload/hooks/use-file-upload.ts +1 -1
  59. package/src/components/ds-file-upload/index.ts +6 -1
  60. package/src/components/ds-file-upload/utils/file-factory.ts +1 -0
  61. package/src/components/ds-file-upload/utils/file-factory.unit.test.ts +37 -0
  62. package/src/components/ds-form-control/ds-form-control.tsx +7 -3
  63. package/src/components/ds-key-value-pair/ds-key-value-pair.module.scss +72 -0
  64. package/src/components/ds-key-value-pair/ds-key-value-pair.tsx +47 -0
  65. package/src/components/ds-key-value-pair/ds-key-value-pair.types.ts +24 -0
  66. package/src/components/ds-key-value-pair/index.ts +3 -0
  67. package/src/components/ds-loader/ds-loader.tsx +1 -1
  68. package/src/components/ds-panel/ds-panel.module.scss +4 -1
  69. package/src/components/ds-panel/ds-panel.tsx +8 -3
  70. package/src/components/ds-panel/ds-panel.types.ts +8 -1
  71. package/src/components/ds-panel/index.ts +4 -1
  72. package/src/components/ds-select/ds-select.module.scss +4 -1
  73. package/src/components/ds-split-button/ds-split-button.module.scss +99 -0
  74. package/src/components/ds-split-button/ds-split-button.tsx +49 -0
  75. package/src/components/ds-split-button/ds-split-button.types.ts +25 -0
  76. package/src/components/ds-split-button/index.ts +10 -0
  77. package/src/components/ds-stack/ds-stack.module.scss +4 -0
  78. package/src/components/ds-stack/ds-stack.tsx +35 -0
  79. package/src/components/ds-stack/ds-stack.types.ts +19 -0
  80. package/src/components/ds-stack/index.ts +18 -0
  81. package/src/components/ds-status-badge/ds-status-badge.module.scss +1 -1
  82. package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.tsx +6 -4
  83. package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.module.scss +5 -8
  84. package/src/components/ds-table/ds-table.module.scss +6 -0
  85. package/src/components/ds-table/ds-table.tsx +3 -0
  86. package/src/components/ds-time-picker/components/time-item/index.ts +1 -0
  87. package/src/components/ds-time-picker/components/time-item/time-item.module.scss +37 -0
  88. package/src/components/ds-time-picker/components/time-item/time-item.tsx +26 -0
  89. package/src/components/ds-time-picker/components/time-item/time-item.types.ts +10 -0
  90. package/src/components/ds-time-picker/components/time-scroller/index.ts +2 -0
  91. package/src/components/ds-time-picker/components/time-scroller/time-scroller.module.scss +25 -0
  92. package/src/components/ds-time-picker/components/time-scroller/time-scroller.tsx +61 -0
  93. package/src/components/ds-time-picker/components/time-scroller/time-scroller.types.ts +17 -0
  94. package/src/components/ds-time-picker/components/time-scroller/time-scroller.utils.ts +44 -0
  95. package/src/components/ds-time-picker/ds-time-picker.module.scss +11 -0
  96. package/src/components/ds-time-picker/ds-time-picker.tsx +178 -0
  97. package/src/components/ds-time-picker/ds-time-picker.types.ts +54 -0
  98. package/src/components/ds-time-picker/ds-time-picker.utils.ts +176 -0
  99. package/src/components/ds-time-picker/ds-time-picker.utils.unit.test.ts +316 -0
  100. package/src/components/ds-time-picker/index.ts +3 -0
  101. package/src/components/ds-tooltip/ds-tooltip.module.scss +1 -7
  102. package/src/components/ds-tooltip/ds-tooltip.tsx +9 -9
  103. package/src/components/ds-tooltip/ds-tooltip.types.ts +7 -1
  104. package/src/index.ts +8 -0
  105. package/src/styles/_breakpoints.module.scss +5 -0
  106. package/src/styles/_responsive.scss +13 -0
  107. package/src/styles/_root_updated.scss +593 -0
  108. package/src/styles/_variables.scss +2 -0
  109. package/src/styles/shared/_button.scss +2 -0
  110. package/src/styles/shared/_dropdown.scss +4 -1
  111. package/src/styles/shared/_input.scss +3 -40
  112. package/src/utils/responsive.browser.test.ts +94 -0
  113. package/src/utils/responsive.ts +79 -0
  114. package/src/utils/responsive.unit.test.ts +54 -0
  115. package/src/utils/type-utils.ts +1 -0
  116. package/src/utils/use-controlled.browser.test.ts +79 -0
  117. 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
- // @ts-expect-error: these classnames are generated dynamically in scss
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'
@@ -9,6 +9,7 @@ export interface UploadedFile extends File {
9
9
  progress: number;
10
10
  status: UploadFileStatus;
11
11
  errors?: FileError[];
12
+ originalFile: File;
12
13
  }
13
14
 
14
15
  export interface FileUploadOptions {
@@ -211,7 +211,7 @@ export function useFileUpload({
211
211
 
212
212
  try {
213
213
  const result = await adapter.upload({
214
- file,
214
+ file: file.originalFile,
215
215
  fileId,
216
216
  metadata,
217
217
  signal: abortController.signal,
@@ -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 { FileUploadAdapter, FileUploadOptions, FileUploadResult } from './ds-file-upload-api.types';
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';
@@ -14,5 +14,6 @@ export function createUploadedFile(file: File, status: UploadFileStatus, errors?
14
14
  progress: 0,
15
15
  status,
16
16
  errors,
17
+ originalFile: file,
17
18
  } satisfies UploadedFile;
18
19
  }
@@ -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
- // @ts-expect-error: `info` doesn't have its own classname
65
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
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
+ }
@@ -0,0 +1,3 @@
1
+ export { default as DsKeyValuePair } from './ds-key-value-pair';
2
+ export type { DsKeyValuePairProps, DsKeyValuePairOrientation } from './ds-key-value-pair.types';
3
+ export { dsKeyValuePairOrientations } from './ds-key-value-pair.types';
@@ -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
  );
@@ -2,7 +2,10 @@
2
2
  position: relative;
3
3
  background: var(--white);
4
4
  padding: var(--spacing-6);
5
- width: 340px;
5
+ width: 20vw;
6
+ min-width: 240px;
7
+ max-width: 480px;
8
+ height: 100vh;
6
9
  border-inline-end: 1px solid transparent;
7
10
 
8
11
  &[data-state='open']:hover,
@@ -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 { DsPanelCollapseButtonProps, DsPanelProps } from './ds-panel.types';
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 DsPanel({
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
- }: DsPanelProps) {
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} />