@drivenets/design-system 0.7.2 → 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.
Files changed (87) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/index.cjs +4489 -3807
  3. package/dist/index.d.cts +1269 -990
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.ts +1269 -990
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +4490 -3814
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.min.css +1 -1
  10. package/package.json +19 -20
  11. package/src/components/ds-autocomplete/ds-autocomplete.module.scss +1 -1
  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-comment-bubble/ds-comment-bubble.module.scss +6 -2
  15. package/src/components/ds-comment-bubble/ds-comment-bubble.tsx +22 -15
  16. package/src/components/ds-comment-bubble/ds-comment-bubble.types.ts +1 -0
  17. package/src/components/ds-date-input/ds-date-input.tsx +3 -4
  18. package/src/components/ds-date-input/ds-date-input.types.ts +3 -1
  19. package/src/components/ds-date-input/index.ts +10 -0
  20. package/src/components/ds-date-picker/components/calendar-table.module.scss +93 -0
  21. package/src/components/ds-date-picker/components/calendar-view-control/calendar-view-control.module.scss +48 -0
  22. package/src/components/ds-date-picker/components/calendar-view-control/calendar-view-control.tsx +20 -0
  23. package/src/components/ds-date-picker/components/calendar-view-control/index.ts +1 -0
  24. package/src/components/ds-date-picker/components/day-view/day-view.module.scss +12 -0
  25. package/src/components/ds-date-picker/components/day-view/day-view.tsx +45 -0
  26. package/src/components/ds-date-picker/components/day-view/index.ts +1 -0
  27. package/src/components/ds-date-picker/components/month-view/index.ts +1 -0
  28. package/src/components/ds-date-picker/components/month-view/month-view.module.scss +4 -0
  29. package/src/components/ds-date-picker/components/month-view/month-view.tsx +35 -0
  30. package/src/components/ds-date-picker/components/year-view/index.ts +1 -0
  31. package/src/components/ds-date-picker/components/year-view/year-view.module.scss +4 -0
  32. package/src/components/ds-date-picker/components/year-view/year-view.tsx +35 -0
  33. package/src/components/ds-date-picker/ds-date-picker.module.scss +19 -0
  34. package/src/components/ds-date-picker/ds-date-picker.tsx +209 -0
  35. package/src/components/ds-date-picker/ds-date-picker.types.ts +94 -0
  36. package/src/components/ds-date-picker/ds-date-picker.utils.ts +87 -0
  37. package/src/components/ds-date-picker/ds-date-picker.utils.unit.test.ts +226 -0
  38. package/src/components/ds-date-picker/index.ts +2 -0
  39. package/src/components/ds-date-range-picker/ds-date-range-picker.module.scss +20 -0
  40. package/src/components/ds-date-range-picker/ds-date-range-picker.tsx +101 -0
  41. package/src/components/ds-date-range-picker/ds-date-range-picker.types.ts +95 -0
  42. package/src/components/ds-date-range-picker/ds-date-range-picker.utils.ts +19 -0
  43. package/src/components/ds-date-range-picker/ds-date-range-picker.utils.unit.test.ts +48 -0
  44. package/src/components/ds-date-range-picker/index.ts +9 -0
  45. package/src/components/ds-file-upload/ds-file-upload-api.types.ts +1 -0
  46. package/src/components/ds-file-upload/hooks/use-file-upload.ts +1 -1
  47. package/src/components/ds-file-upload/index.ts +6 -1
  48. package/src/components/ds-file-upload/utils/file-factory.ts +1 -0
  49. package/src/components/ds-file-upload/utils/file-factory.unit.test.ts +37 -0
  50. package/src/components/ds-form-control/ds-form-control.tsx +5 -0
  51. package/src/components/ds-key-value-pair/ds-key-value-pair.module.scss +72 -0
  52. package/src/components/ds-key-value-pair/ds-key-value-pair.tsx +47 -0
  53. package/src/components/ds-key-value-pair/ds-key-value-pair.types.ts +24 -0
  54. package/src/components/ds-key-value-pair/index.ts +3 -0
  55. package/src/components/ds-loader/ds-loader.tsx +1 -1
  56. package/src/components/ds-status-badge/ds-status-badge.module.scss +1 -1
  57. package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.tsx +14 -7
  58. package/src/components/ds-table/components/ds-table-body-virtualized/ds-table-body-virtualized.types.ts +5 -3
  59. package/src/components/ds-table/components/ds-table-row/ds-table-row.tsx +5 -3
  60. package/src/components/ds-table/components/ds-table-row/ds-table-row.types.ts +4 -0
  61. package/src/components/ds-table/components/ds-table-row-selectable-cell/ds-table-row-selectable-cell.tsx +2 -1
  62. package/src/components/ds-table/components/ds-table-row-selectable-cell/ds-table-row-selectable-cell.types.ts +1 -0
  63. package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.tsx +5 -2
  64. package/src/components/ds-table/components/ds-table-row-virtualized/ds-table-row-virtualized.types.ts +4 -2
  65. package/src/components/ds-table/ds-table.tsx +4 -1
  66. package/src/components/ds-time-picker/components/time-item/index.ts +1 -0
  67. package/src/components/ds-time-picker/components/time-item/time-item.module.scss +37 -0
  68. package/src/components/ds-time-picker/components/time-item/time-item.tsx +26 -0
  69. package/src/components/ds-time-picker/components/time-item/time-item.types.ts +10 -0
  70. package/src/components/ds-time-picker/components/time-scroller/index.ts +2 -0
  71. package/src/components/ds-time-picker/components/time-scroller/time-scroller.module.scss +25 -0
  72. package/src/components/ds-time-picker/components/time-scroller/time-scroller.tsx +61 -0
  73. package/src/components/ds-time-picker/components/time-scroller/time-scroller.types.ts +17 -0
  74. package/src/components/ds-time-picker/components/time-scroller/time-scroller.utils.ts +44 -0
  75. package/src/components/ds-time-picker/ds-time-picker.tsx +158 -0
  76. package/src/components/ds-time-picker/ds-time-picker.types.ts +48 -0
  77. package/src/components/ds-time-picker/ds-time-picker.utils.ts +176 -0
  78. package/src/components/ds-time-picker/ds-time-picker.utils.unit.test.ts +316 -0
  79. package/src/components/ds-time-picker/index.ts +3 -0
  80. package/src/components/ds-tooltip/ds-tooltip.module.scss +1 -7
  81. package/src/components/ds-tooltip/ds-tooltip.tsx +9 -9
  82. package/src/components/ds-tooltip/ds-tooltip.types.ts +7 -1
  83. package/src/index.ts +4 -0
  84. package/src/styles/_root_updated.scss +593 -0
  85. package/src/styles/shared/_dropdown.scss +4 -1
  86. package/src/utils/use-controlled.browser.test.ts +79 -0
  87. package/src/utils/use-controlled.ts +18 -0
@@ -8,13 +8,14 @@ import styles from './ds-table-row-selectable-cell.module.scss';
8
8
 
9
9
  export const DsTableRowSelectableCell = <TData,>({
10
10
  row,
11
+ isSelected,
11
12
  className,
12
13
  }: DsTableRowSelectableCellProps<TData>) => {
13
14
  return (
14
15
  <TableCell className={classnames(styles.cell, className)}>
15
16
  <DsCheckbox
16
17
  className={stylesShared.checkboxContainer}
17
- checked={row.getIsSelected()}
18
+ checked={isSelected}
18
19
  disabled={!row.getCanSelect()}
19
20
  onClick={(e) => {
20
21
  e.stopPropagation();
@@ -2,5 +2,6 @@ import type { Row } from '@tanstack/react-table';
2
2
 
3
3
  export interface DsTableRowSelectableCellProps<TData> {
4
4
  row: Row<TData>;
5
+ isSelected: boolean;
5
6
  className?: string;
6
7
  }
@@ -10,6 +10,7 @@ import { DsTableCell } from '../ds-table-cell';
10
10
 
11
11
  export const DsTableRowVirtualized = <TData,>({
12
12
  row,
13
+ isSelected,
13
14
  rowsMapRef,
14
15
  rowHeightsMapRef,
15
16
  rowVirtualizer,
@@ -47,7 +48,7 @@ export const DsTableRowVirtualized = <TData,>({
47
48
  rowsMapRef.current.delete(key);
48
49
  }
49
50
  }}
50
- data-state={isActive ? 'active' : row.getIsSelected() ? 'selected' : undefined}
51
+ data-state={isActive ? 'active' : isSelected ? 'selected' : undefined}
51
52
  className={classnames(
52
53
  styles.row,
53
54
  {
@@ -67,7 +68,9 @@ export const DsTableRowVirtualized = <TData,>({
67
68
  <TableCell>{renderExpandedRow?.(row.original)}</TableCell>
68
69
  ) : (
69
70
  <>
70
- {selectable && <DsTableRowSelectableCell row={row} className={styles.selectableCell} />}
71
+ {selectable && (
72
+ <DsTableRowSelectableCell row={row} isSelected={isSelected} className={styles.selectableCell} />
73
+ )}
71
74
  {expandable && <DsTableRowExpandableCell row={row} className={styles.expandableCell} />}
72
75
  {row.getVisibleCells().map((cell, idx) => {
73
76
  const isLastColumn = idx === row.getVisibleCells().length - 1;
@@ -1,10 +1,12 @@
1
+ import { type RefObject } from 'react';
1
2
  import { type Row } from '@tanstack/react-table';
2
3
  import { type VirtualItem, type Virtualizer } from '@tanstack/react-virtual';
3
4
 
4
5
  export interface DsTableRowVirtualizedProps<TData> {
5
6
  row: Row<TData>;
6
- rowsMapRef: React.RefObject<Map<string, HTMLTableRowElement>>;
7
- rowHeightsMapRef: React.RefObject<Map<string, number>>;
7
+ isSelected: boolean;
8
+ rowsMapRef: RefObject<Map<string, HTMLTableRowElement>>;
9
+ rowHeightsMapRef: RefObject<Map<string, number>>;
8
10
  rowVirtualizer: Virtualizer<HTMLDivElement, HTMLTableRowElement>;
9
11
  virtualRow: VirtualItem;
10
12
  isExpandedRowContent?: boolean;
@@ -250,12 +250,15 @@ const DsTable = <TData extends { id: string }, TValue>({
250
250
  estimateSize={virtualizedOptions?.estimateSize || ROW_SIZE_HEIGHT_MAP[rowSize]}
251
251
  overscan={virtualizedOptions?.overscan}
252
252
  onScroll={onScroll}
253
+ rowSelection={rowSelection}
253
254
  />
254
255
  ) : (
255
256
  <TableBody>
256
257
  <SortableWrapper>
257
258
  {rows.length
258
- ? rows.map((row) => <DsTableRow key={row.id} row={row} />)
259
+ ? rows.map((row) => (
260
+ <DsTableRow key={row.id} row={row} isSelected={!!rowSelection[row.id]} />
261
+ ))
259
262
  : renderEmptyState()}
260
263
  </SortableWrapper>
261
264
  </TableBody>
@@ -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,10 @@
1
+ import type { RefObject } from 'react';
2
+
3
+ export interface TimeItemProps {
4
+ ref?: RefObject<HTMLButtonElement | null>;
5
+ className?: string;
6
+ label: string;
7
+ selected: boolean;
8
+ disabled?: boolean;
9
+ onClick: () => void;
10
+ }
@@ -0,0 +1,2 @@
1
+ export type { TimePeriod, TimeScrollerProps } from './time-scroller.types';
2
+ export * from './time-scroller';
@@ -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
+ }
@@ -0,0 +1,61 @@
1
+ import classNames from 'classnames';
2
+ import { TimeItem } from '../time-item';
3
+ import type { TimePeriod, TimeScrollerProps } from './time-scroller.types';
4
+ import styles from './time-scroller.module.scss';
5
+ import { useScrollToSelected } from './time-scroller.utils';
6
+
7
+ /**
8
+ * In AM/PM mode, hours start from 12 and then go from to 1 to 11.
9
+ * e.g. 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
10
+ */
11
+ const HOURS = [12, ...Array.from({ length: 11 }, (_, i) => i + 1)];
12
+
13
+ const MINUTES = Array.from({ length: 60 }, (_, i) => i);
14
+ const PERIODS: TimePeriod[] = ['AM', 'PM'];
15
+
16
+ export const TimeScroller = ({ open, slotProps: { hour, minute, period }, className }: TimeScrollerProps) => {
17
+ const hourScroll = useScrollToSelected(hour.value, open);
18
+ const minuteScroll = useScrollToSelected(minute.value, open);
19
+
20
+ return (
21
+ <div className={classNames(styles.container, className)}>
22
+ <div ref={hourScroll.columnRef} className={styles.column} role="listbox" aria-label="Hour">
23
+ {HOURS.map((h) => (
24
+ <TimeItem
25
+ key={h}
26
+ ref={h === hour.value ? hourScroll.selectedRef : undefined}
27
+ label={String(h).padStart(2, '0')}
28
+ selected={h === hour.value}
29
+ disabled={hour.isDisabled?.(h)}
30
+ onClick={() => hour.onChange?.(h)}
31
+ />
32
+ ))}
33
+ </div>
34
+
35
+ <div ref={minuteScroll.columnRef} className={styles.column} role="listbox" aria-label="Minute">
36
+ {MINUTES.map((m) => (
37
+ <TimeItem
38
+ key={m}
39
+ ref={m === minute.value ? minuteScroll.selectedRef : undefined}
40
+ label={String(m).padStart(2, '0')}
41
+ selected={m === minute.value}
42
+ disabled={minute.isDisabled?.(m)}
43
+ onClick={() => minute.onChange?.(m)}
44
+ />
45
+ ))}
46
+ </div>
47
+
48
+ <div className={styles.column} role="listbox" aria-label="AM/PM">
49
+ {PERIODS.map((p) => (
50
+ <TimeItem
51
+ key={p}
52
+ label={p}
53
+ selected={p === period.value}
54
+ disabled={period.isDisabled?.(p)}
55
+ onClick={() => period.onChange?.(p)}
56
+ />
57
+ ))}
58
+ </div>
59
+ </div>
60
+ );
61
+ };
@@ -0,0 +1,17 @@
1
+ export type TimePeriod = 'AM' | 'PM';
2
+
3
+ export interface TimeScrollerProps {
4
+ className?: string;
5
+ open: boolean;
6
+ slotProps: {
7
+ hour: TimeScrollerSlotProps<number>;
8
+ minute: TimeScrollerSlotProps<number>;
9
+ period: TimeScrollerSlotProps<TimePeriod>;
10
+ };
11
+ }
12
+
13
+ export interface TimeScrollerSlotProps<TValue> {
14
+ value?: TValue;
15
+ onChange?: (value: TValue) => void;
16
+ isDisabled?: (value: TValue) => boolean;
17
+ }
@@ -0,0 +1,44 @@
1
+ import { useLayoutEffect, useEffect, useRef } from 'react';
2
+
3
+ export const useScrollToSelected = (value: unknown, open: boolean) => {
4
+ const columnRef = useRef<HTMLDivElement | null>(null);
5
+ const selectedRef = useRef<HTMLButtonElement | null>(null);
6
+
7
+ useLayoutEffect(() => {
8
+ const column = columnRef.current;
9
+ const selected = selectedRef.current;
10
+
11
+ if (!column || !selected) {
12
+ return;
13
+ }
14
+
15
+ scrollToCenter(column, selected, 'smooth');
16
+ }, [value]);
17
+
18
+ useEffect(() => {
19
+ if (!open) {
20
+ return;
21
+ }
22
+
23
+ // Ark UI removes `display: none` from Popover.Content after React's commit phase,
24
+ // so layout measurements are only valid after the browser has painted.
25
+ requestAnimationFrame(() => {
26
+ const column = columnRef.current;
27
+ const selected = selectedRef.current;
28
+
29
+ if (!column || !selected) {
30
+ return;
31
+ }
32
+
33
+ scrollToCenter(column, selected, 'instant');
34
+ });
35
+ }, [open]);
36
+
37
+ return { columnRef, selectedRef };
38
+ };
39
+
40
+ const scrollToCenter = (column: HTMLDivElement, selected: HTMLButtonElement, behavior: ScrollBehavior) => {
41
+ const columnCenter = column.offsetHeight / 2;
42
+ const itemCenter = selected.offsetTop + selected.offsetHeight / 2;
43
+ column.scrollTo({ top: itemCenter - columnCenter, behavior });
44
+ };
@@ -0,0 +1,158 @@
1
+ import { type ChangeEvent, Fragment, useEffect, useRef, useState } from 'react';
2
+ import { Popover } from '@ark-ui/react/popover';
3
+ import { Portal } from '@ark-ui/react/portal';
4
+ import type { DsTimePickerProps } from './ds-time-picker.types';
5
+ import { clampTime, formatTime, parseTime, timeScrollerAdapter } from './ds-time-picker.utils';
6
+ import { TimeScroller } from './components/time-scroller/time-scroller';
7
+ import { DsIcon } from '../ds-icon';
8
+ import { DsButton } from '../ds-button';
9
+ import { DsTextInput } from '../ds-text-input';
10
+ import { useControlled } from '../../utils/use-controlled';
11
+
12
+ const DsTimePicker = (props: DsTimePickerProps) => {
13
+ const {
14
+ id,
15
+ ref,
16
+ className,
17
+ placeholder,
18
+ min,
19
+ max,
20
+ disabled,
21
+ readOnly,
22
+ disablePortal = false,
23
+ locale,
24
+ slotProps,
25
+ } = props;
26
+ const [value, setValue] = useControlled(props.value, props.onChange, props.defaultValue ?? null);
27
+ const [isOpen, setIsOpen] = useControlled(props.open, props.onOpenChange, props.defaultOpen ?? false);
28
+ const [isFocused, setIsFocused] = useState(false);
29
+
30
+ const inputRef = useRef<HTMLInputElement>(null);
31
+
32
+ const formattedValue = formatTime(value);
33
+
34
+ // If the time is before the min or after the max, immediately clamp it to the min or max
35
+ if (value) {
36
+ const clamped = clampTime(value, min, max);
37
+
38
+ if (clamped !== value) {
39
+ setValue(clamped);
40
+ }
41
+ }
42
+
43
+ // Reformat on value change, but skip while focused to avoid disrupting typing
44
+ useEffect(() => {
45
+ if (!isFocused && inputRef.current) {
46
+ inputRef.current.value = formattedValue;
47
+ }
48
+ }, [isFocused, formattedValue]);
49
+
50
+ const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
51
+ const inputVal = e.target.value;
52
+
53
+ if (!inputVal) {
54
+ setValue(null);
55
+ return;
56
+ }
57
+
58
+ const parsed = parseTime(inputVal);
59
+
60
+ if (parsed) {
61
+ const newDate = value ? new Date(value) : new Date();
62
+ newDate.setHours(parsed.hours, parsed.minutes, 0, 0);
63
+ setValue(newDate);
64
+ }
65
+ };
66
+
67
+ const handleFocus = () => {
68
+ setIsFocused(true);
69
+ };
70
+
71
+ const resetInput = () => {
72
+ if (inputRef.current) {
73
+ inputRef.current.value = value ? formattedValue : '';
74
+ }
75
+ };
76
+
77
+ const handleBlur = () => {
78
+ setIsFocused(false);
79
+
80
+ const inputVal = inputRef.current?.value ?? '';
81
+
82
+ if (!inputVal) {
83
+ setValue(null);
84
+ return;
85
+ }
86
+
87
+ const parsed = parseTime(inputVal);
88
+
89
+ if (parsed) {
90
+ const newDate = value ? new Date(value) : new Date();
91
+ newDate.setHours(parsed.hours, parsed.minutes, 0, 0);
92
+ setValue(newDate);
93
+ } else {
94
+ resetInput();
95
+ }
96
+ };
97
+
98
+ const handleOpenChange = (details: Popover.OpenChangeDetails) => {
99
+ if (disabled || readOnly) {
100
+ return;
101
+ }
102
+
103
+ setIsOpen(details.open);
104
+ };
105
+
106
+ const Wrapper = disablePortal ? Fragment : Portal;
107
+
108
+ return (
109
+ <Popover.Root
110
+ open={isOpen}
111
+ onOpenChange={handleOpenChange}
112
+ positioning={{ placement: 'bottom-start', gutter: 4 }}
113
+ >
114
+ <Popover.Anchor asChild>
115
+ <div ref={ref} className={className}>
116
+ <DsTextInput
117
+ ref={inputRef}
118
+ id={id}
119
+ placeholder={placeholder ?? 'hh:mm AM/PM'}
120
+ defaultValue={formattedValue}
121
+ onChange={handleInputChange}
122
+ onFocus={handleFocus}
123
+ onBlur={handleBlur}
124
+ readOnly={readOnly}
125
+ disabled={disabled}
126
+ {...slotProps?.input}
127
+ slots={{
128
+ endAdornment: slotProps?.input?.slots?.endAdornment ?? (
129
+ <Popover.Trigger asChild>
130
+ <DsButton
131
+ design="v1.2"
132
+ size="tiny"
133
+ buttonType="tertiary"
134
+ disabled={disabled || readOnly}
135
+ aria-label={locale?.openLabel ?? 'Open time picker'}
136
+ >
137
+ <DsIcon icon="schedule" variant="outlined" size="tiny" />
138
+ </DsButton>
139
+ </Popover.Trigger>
140
+ ),
141
+ ...slotProps?.input?.slots,
142
+ }}
143
+ />
144
+ </div>
145
+ </Popover.Anchor>
146
+
147
+ <Wrapper>
148
+ <Popover.Positioner>
149
+ <Popover.Content {...slotProps?.popover}>
150
+ <TimeScroller open={isOpen} {...timeScrollerAdapter(value, setValue, min, max)} />
151
+ </Popover.Content>
152
+ </Popover.Positioner>
153
+ </Wrapper>
154
+ </Popover.Root>
155
+ );
156
+ };
157
+
158
+ export default DsTimePicker;
@@ -0,0 +1,48 @@
1
+ import type { RefObject } from 'react';
2
+ import type { DsTextInputProps } from '../ds-text-input';
3
+ import type { PopoverContentProps } from '@ark-ui/react/popover';
4
+
5
+ export interface DsTimePickerProps {
6
+ id?: string;
7
+ ref?: RefObject<HTMLDivElement>;
8
+ className?: string;
9
+
10
+ value?: Date | null;
11
+ defaultValue?: Date;
12
+
13
+ open?: boolean;
14
+ defaultOpen?: boolean;
15
+
16
+ placeholder?: string;
17
+
18
+ min?: Date;
19
+ max?: Date;
20
+
21
+ disabled?: boolean;
22
+ readOnly?: boolean;
23
+
24
+ /**
25
+ * Whether to disable the portal for the popover content
26
+ * @default false
27
+ */
28
+ disablePortal?: boolean;
29
+
30
+ locale?: DsTimePickerLocale;
31
+ slotProps?: DsTimePickerSlotProps;
32
+
33
+ onChange?: (value: Date | null) => void;
34
+ onOpenChange?: (open: boolean) => void;
35
+ }
36
+
37
+ export interface DsTimePickerLocale {
38
+ openLabel?: string;
39
+ clearLabel?: string;
40
+ hourLabel?: string;
41
+ minuteLabel?: string;
42
+ periodLabel?: string;
43
+ }
44
+
45
+ export interface DsTimePickerSlotProps {
46
+ input?: DsTextInputProps;
47
+ popover?: PopoverContentProps;
48
+ }