@arbor-education/design-system.components 0.8.1 → 0.10.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/.github/workflows/release.yml +1 -1
- package/CHANGELOG.md +22 -0
- package/dist/components/button/Button.d.ts.map +1 -1
- package/dist/components/button/Button.js +2 -2
- package/dist/components/button/Button.js.map +1 -1
- package/dist/components/combobox/Combobox.d.ts.map +1 -1
- package/dist/components/combobox/Combobox.js +10 -8
- package/dist/components/combobox/Combobox.js.map +1 -1
- package/dist/components/combobox/Combobox.stories.d.ts +1 -0
- package/dist/components/combobox/Combobox.stories.d.ts.map +1 -1
- package/dist/components/combobox/Combobox.stories.js +16 -0
- package/dist/components/combobox/Combobox.stories.js.map +1 -1
- package/dist/components/combobox/Combobox.test.js +107 -61
- package/dist/components/combobox/Combobox.test.js.map +1 -1
- package/dist/components/combobox/ComboboxButtonTrigger.d.ts +4 -2
- package/dist/components/combobox/ComboboxButtonTrigger.d.ts.map +1 -1
- package/dist/components/combobox/ComboboxButtonTrigger.js +11 -4
- package/dist/components/combobox/ComboboxButtonTrigger.js.map +1 -1
- package/dist/components/combobox/ComboboxTrigger.d.ts +3 -1
- package/dist/components/combobox/ComboboxTrigger.d.ts.map +1 -1
- package/dist/components/combobox/ComboboxTrigger.js +10 -2
- package/dist/components/combobox/ComboboxTrigger.js.map +1 -1
- package/dist/components/combobox/types.d.ts +3 -0
- package/dist/components/combobox/types.d.ts.map +1 -1
- package/dist/components/combobox/useComboboxPopoverBehavior.d.ts +3 -1
- package/dist/components/combobox/useComboboxPopoverBehavior.d.ts.map +1 -1
- package/dist/components/combobox/useComboboxPopoverBehavior.js +7 -6
- package/dist/components/combobox/useComboboxPopoverBehavior.js.map +1 -1
- package/dist/components/combobox/useComboboxState.d.ts.map +1 -1
- package/dist/components/combobox/useComboboxState.js +4 -1
- package/dist/components/combobox/useComboboxState.js.map +1 -1
- package/dist/components/datePicker/DatePicker.d.ts +4 -1
- package/dist/components/datePicker/DatePicker.d.ts.map +1 -1
- package/dist/components/datePicker/DatePicker.js +77 -37
- package/dist/components/datePicker/DatePicker.js.map +1 -1
- package/dist/components/datePicker/DatePicker.stories.d.ts +28 -3
- package/dist/components/datePicker/DatePicker.stories.d.ts.map +1 -1
- package/dist/components/datePicker/DatePicker.stories.js +62 -9
- package/dist/components/datePicker/DatePicker.stories.js.map +1 -1
- package/dist/components/datePicker/DatePicker.test.js +133 -66
- package/dist/components/datePicker/DatePicker.test.js.map +1 -1
- package/dist/components/datePicker/DatePickerCalendarHeader.d.ts +8 -0
- package/dist/components/datePicker/DatePickerCalendarHeader.d.ts.map +1 -0
- package/dist/components/datePicker/DatePickerCalendarHeader.js +36 -0
- package/dist/components/datePicker/DatePickerCalendarHeader.js.map +1 -0
- package/dist/components/datePicker/dateInputUtils.d.ts +25 -0
- package/dist/components/datePicker/dateInputUtils.d.ts.map +1 -0
- package/dist/components/datePicker/dateInputUtils.js +60 -0
- package/dist/components/datePicker/dateInputUtils.js.map +1 -0
- package/dist/components/datePicker/datePickerTestUtils.test-helpers.d.ts +2 -0
- package/dist/components/datePicker/datePickerTestUtils.test-helpers.d.ts.map +1 -0
- package/dist/components/datePicker/datePickerTestUtils.test-helpers.js +4 -0
- package/dist/components/datePicker/datePickerTestUtils.test-helpers.js.map +1 -0
- package/dist/components/dateTimePicker/DateTimePicker.d.ts +22 -0
- package/dist/components/dateTimePicker/DateTimePicker.d.ts.map +1 -0
- package/dist/components/dateTimePicker/DateTimePicker.js +132 -0
- package/dist/components/dateTimePicker/DateTimePicker.js.map +1 -0
- package/dist/components/dateTimePicker/DateTimePicker.stories.d.ts +77 -0
- package/dist/components/dateTimePicker/DateTimePicker.stories.d.ts.map +1 -0
- package/dist/components/dateTimePicker/DateTimePicker.stories.js +163 -0
- package/dist/components/dateTimePicker/DateTimePicker.stories.js.map +1 -0
- package/dist/components/dateTimePicker/DateTimePicker.test.d.ts +2 -0
- package/dist/components/dateTimePicker/DateTimePicker.test.d.ts.map +1 -0
- package/dist/components/dateTimePicker/DateTimePicker.test.js +235 -0
- package/dist/components/dateTimePicker/DateTimePicker.test.js.map +1 -0
- package/dist/components/formField/FormField.d.ts +4 -0
- package/dist/components/formField/FormField.d.ts.map +1 -1
- package/dist/components/formField/FormField.js +2 -1
- package/dist/components/formField/FormField.js.map +1 -1
- package/dist/components/formField/FormField.stories.d.ts.map +1 -1
- package/dist/components/formField/FormField.stories.js +4 -1
- package/dist/components/formField/FormField.stories.js.map +1 -1
- package/dist/components/formField/FormField.test.d.ts.map +1 -1
- package/dist/components/formField/FormField.test.js +10 -5
- package/dist/components/formField/FormField.test.js.map +1 -1
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.d.ts +1 -0
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.d.ts.map +1 -1
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.js +7 -3
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.js.map +1 -1
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.test.js +12 -0
- package/dist/components/formField/inputs/selectDropdown/SelectDropdown.test.js.map +1 -1
- package/dist/components/formField/inputs/text/TextInput.d.ts +4 -1
- package/dist/components/formField/inputs/text/TextInput.d.ts.map +1 -1
- package/dist/components/formField/inputs/text/TextInput.js +5 -4
- package/dist/components/formField/inputs/text/TextInput.js.map +1 -1
- package/dist/components/formField/inputs/text/TextInput.stories.d.ts +4 -1
- package/dist/components/formField/inputs/text/TextInput.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/time/TimeInput.d.ts +29 -0
- package/dist/components/formField/inputs/time/TimeInput.d.ts.map +1 -0
- package/dist/components/formField/inputs/time/TimeInput.js +67 -0
- package/dist/components/formField/inputs/time/TimeInput.js.map +1 -0
- package/dist/components/formField/inputs/time/TimeInput.stories.d.ts +60 -0
- package/dist/components/formField/inputs/time/TimeInput.stories.d.ts.map +1 -0
- package/dist/components/formField/inputs/time/TimeInput.stories.js +132 -0
- package/dist/components/formField/inputs/time/TimeInput.stories.js.map +1 -0
- package/dist/components/formField/inputs/time/TimeInput.test.d.ts +2 -0
- package/dist/components/formField/inputs/time/TimeInput.test.d.ts.map +1 -0
- package/dist/components/formField/inputs/time/TimeInput.test.js +58 -0
- package/dist/components/formField/inputs/time/TimeInput.test.js.map +1 -0
- package/dist/components/table/Table.d.ts.map +1 -1
- package/dist/components/table/Table.js +2 -0
- package/dist/components/table/Table.js.map +1 -1
- package/dist/components/table/Table.stories.d.ts +1 -0
- package/dist/components/table/Table.stories.d.ts.map +1 -1
- package/dist/components/table/Table.stories.js +37 -0
- package/dist/components/table/Table.stories.js.map +1 -1
- package/dist/components/table/cellRenderers/BooleanCellRenderer.d.ts +3 -0
- package/dist/components/table/cellRenderers/BooleanCellRenderer.d.ts.map +1 -0
- package/dist/components/table/cellRenderers/BooleanCellRenderer.js +15 -0
- package/dist/components/table/cellRenderers/BooleanCellRenderer.js.map +1 -0
- package/dist/components/table/cellRenderers/BooleanCellRenderer.test.d.ts +2 -0
- package/dist/components/table/cellRenderers/BooleanCellRenderer.test.d.ts.map +1 -0
- package/dist/components/table/cellRenderers/BooleanCellRenderer.test.js +31 -0
- package/dist/components/table/cellRenderers/BooleanCellRenderer.test.js.map +1 -0
- package/dist/index.css +309 -4
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/button/Button.tsx +2 -1
- package/src/components/combobox/Combobox.stories.tsx +18 -0
- package/src/components/combobox/Combobox.test.tsx +131 -61
- package/src/components/combobox/Combobox.tsx +15 -6
- package/src/components/combobox/ComboboxButtonTrigger.tsx +54 -25
- package/src/components/combobox/ComboboxTrigger.tsx +39 -15
- package/src/components/combobox/combobox.scss +18 -0
- package/src/components/combobox/types.ts +3 -0
- package/src/components/combobox/useComboboxPopoverBehavior.ts +10 -5
- package/src/components/combobox/useComboboxState.ts +4 -1
- package/src/components/datePicker/DatePicker.stories.tsx +67 -9
- package/src/components/datePicker/DatePicker.test.tsx +157 -72
- package/src/components/datePicker/DatePicker.tsx +163 -69
- package/src/components/datePicker/DatePickerCalendarHeader.tsx +82 -0
- package/src/components/datePicker/date-field-hint.scss +152 -0
- package/src/components/datePicker/dateInputUtils.ts +117 -0
- package/src/components/datePicker/datePicker.scss +53 -29
- package/src/components/datePicker/datePickerTestUtils.test-helpers.ts +6 -0
- package/src/components/dateTimePicker/DateTimePicker.stories.tsx +202 -0
- package/src/components/dateTimePicker/DateTimePicker.test.tsx +295 -0
- package/src/components/dateTimePicker/DateTimePicker.tsx +293 -0
- package/src/components/dateTimePicker/dateTimePicker.scss +17 -0
- package/src/components/formField/FormField.stories.tsx +10 -1
- package/src/components/formField/FormField.test.tsx +11 -5
- package/src/components/formField/FormField.tsx +5 -0
- package/src/components/formField/inputs/selectDropdown/SelectDropdown.test.tsx +28 -0
- package/src/components/formField/inputs/selectDropdown/SelectDropdown.tsx +8 -2
- package/src/components/formField/inputs/text/TextInput.tsx +6 -3
- package/src/components/formField/inputs/time/TimeInput.stories.tsx +170 -0
- package/src/components/formField/inputs/time/TimeInput.test.tsx +86 -0
- package/src/components/formField/inputs/time/TimeInput.tsx +168 -0
- package/src/components/formField/inputs/time/timeInput.scss +33 -0
- package/src/components/row/row.scss +2 -2
- package/src/components/table/Table.stories.tsx +48 -0
- package/src/components/table/Table.tsx +2 -0
- package/src/components/table/cellRenderers/BooleanCellRenderer.test.tsx +37 -0
- package/src/components/table/cellRenderers/BooleanCellRenderer.tsx +34 -0
- package/src/components/table/cellRenderers/booleanCellRenderer.scss +7 -0
- package/src/index.scss +3 -0
- package/src/index.ts +5 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import '@testing-library/jest-dom/vitest';
|
|
2
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
5
|
+
import { TimeInput } from './TimeInput';
|
|
6
|
+
|
|
7
|
+
describe('TimeInput', () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
globalThis.ResizeObserver = class {
|
|
10
|
+
observe() {}
|
|
11
|
+
unobserve() {}
|
|
12
|
+
disconnect() {}
|
|
13
|
+
} as unknown as typeof ResizeObserver;
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
vi.restoreAllMocks();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('renders a native time input by default', () => {
|
|
21
|
+
render(<TimeInput aria-label="Start time" />);
|
|
22
|
+
const input = screen.getByLabelText('Start time');
|
|
23
|
+
|
|
24
|
+
expect(input).toHaveAttribute('type', 'time');
|
|
25
|
+
expect(input).toHaveAttribute('step', '60');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('uses one-second stepping when granularity is second', () => {
|
|
29
|
+
render(<TimeInput aria-label="Start time" granularity="second" />);
|
|
30
|
+
expect(screen.getByLabelText('Start time')).toHaveAttribute('step', '1');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('native mode emits the string value when changed', () => {
|
|
34
|
+
const onValueChange = vi.fn();
|
|
35
|
+
render(<TimeInput aria-label="Start time" onValueChange={onValueChange} />);
|
|
36
|
+
|
|
37
|
+
fireEvent.change(screen.getByLabelText('Start time'), {
|
|
38
|
+
target: { value: '14:30' },
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
expect(onValueChange).toHaveBeenCalledWith('14:30');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('clicking the clock icon focuses the input and selects the hour segment when supported', async () => {
|
|
45
|
+
const user = userEvent.setup();
|
|
46
|
+
const setSelectionRange = vi.spyOn(HTMLInputElement.prototype, 'setSelectionRange').mockImplementation(() => {});
|
|
47
|
+
|
|
48
|
+
render(<TimeInput aria-label="Start time" />);
|
|
49
|
+
|
|
50
|
+
await user.click(screen.getByRole('button', { name: 'Select time' }));
|
|
51
|
+
|
|
52
|
+
expect(screen.getByLabelText('Start time')).toHaveFocus();
|
|
53
|
+
expect(setSelectionRange).toHaveBeenCalledWith(0, 2);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('options mode renders the combobox-backed time list with plain text selection', () => {
|
|
57
|
+
render(
|
|
58
|
+
<TimeInput
|
|
59
|
+
aria-label="Start time"
|
|
60
|
+
options={['13:00', '13:30', '14:00']}
|
|
61
|
+
value="13:30"
|
|
62
|
+
/>,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
expect(screen.getByRole('button', { name: 'Start time' })).toHaveTextContent('13:30');
|
|
66
|
+
expect(document.querySelector('.ds-tag')).not.toBeInTheDocument();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('options mode emits the selected allowed time', async () => {
|
|
70
|
+
const user = userEvent.setup();
|
|
71
|
+
const onValueChange = vi.fn();
|
|
72
|
+
|
|
73
|
+
render(
|
|
74
|
+
<TimeInput
|
|
75
|
+
aria-label="Start time"
|
|
76
|
+
options={['13:00', '13:30', '14:00']}
|
|
77
|
+
onValueChange={onValueChange}
|
|
78
|
+
/>,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
await user.click(screen.getByRole('button', { name: 'Start time' }));
|
|
82
|
+
await user.click(screen.getByText('13:30'));
|
|
83
|
+
|
|
84
|
+
expect(onValueChange).toHaveBeenCalledWith('13:30');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import { Combobox } from 'Components/combobox/Combobox';
|
|
3
|
+
import type { ComboboxOption, ComboboxSearchType } from 'Components/combobox/types';
|
|
4
|
+
import { Icon } from 'Components/icon/Icon';
|
|
5
|
+
import { forwardRef, useCallback, useMemo, useRef, useState, type ChangeEvent, type InputHTMLAttributes } from 'react';
|
|
6
|
+
|
|
7
|
+
/** Expected string `HH:MM` or `HH:MM:SS`; format-shaped versus strict time validation. */
|
|
8
|
+
export type TimeValue = `${string}:${string}` | `${string}:${string}:${string}`;
|
|
9
|
+
export type TimeGranularity = 'minute' | 'second';
|
|
10
|
+
|
|
11
|
+
export type TimeInputProps = {
|
|
12
|
+
options?: TimeValue[];
|
|
13
|
+
granularity?: TimeGranularity;
|
|
14
|
+
hasError?: boolean;
|
|
15
|
+
onValueChange?: (value: string) => void;
|
|
16
|
+
searchType?: ComboboxSearchType;
|
|
17
|
+
highlightStringMatches?: boolean;
|
|
18
|
+
} & Omit<InputHTMLAttributes<HTMLInputElement>, 'defaultValue' | 'size' | 'type' | 'value'> & {
|
|
19
|
+
value?: TimeValue | '';
|
|
20
|
+
defaultValue?: TimeValue | '';
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const clockIcon = <Icon name="clock-3" size={16} />;
|
|
24
|
+
|
|
25
|
+
/** The forwarded ref is attached in native mode but when `options` is provided, no input ref is exposed. */
|
|
26
|
+
export const TimeInput = forwardRef<HTMLInputElement, TimeInputProps>((props, ref) => {
|
|
27
|
+
const {
|
|
28
|
+
options,
|
|
29
|
+
granularity = 'minute',
|
|
30
|
+
hasError = false,
|
|
31
|
+
onValueChange,
|
|
32
|
+
searchType = 'prefix',
|
|
33
|
+
highlightStringMatches = false,
|
|
34
|
+
onChange,
|
|
35
|
+
className,
|
|
36
|
+
disabled = false,
|
|
37
|
+
id,
|
|
38
|
+
name,
|
|
39
|
+
placeholder,
|
|
40
|
+
value: controlledValue,
|
|
41
|
+
defaultValue = '',
|
|
42
|
+
'aria-describedby': ariaDescribedBy,
|
|
43
|
+
'aria-invalid': ariaInvalid,
|
|
44
|
+
'aria-label': ariaLabel,
|
|
45
|
+
...rest
|
|
46
|
+
} = props;
|
|
47
|
+
|
|
48
|
+
const isControlled = controlledValue !== undefined;
|
|
49
|
+
const [internalValue, setInternalValue] = useState<string>(defaultValue);
|
|
50
|
+
const currentValue = isControlled ? controlledValue ?? '' : internalValue;
|
|
51
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
52
|
+
|
|
53
|
+
const setRefs = useCallback((node: HTMLInputElement | null) => {
|
|
54
|
+
inputRef.current = node;
|
|
55
|
+
if (typeof ref === 'function') {
|
|
56
|
+
ref(node);
|
|
57
|
+
}
|
|
58
|
+
else if (ref) {
|
|
59
|
+
ref.current = node;
|
|
60
|
+
}
|
|
61
|
+
}, [ref]);
|
|
62
|
+
|
|
63
|
+
const updateValue = useCallback((nextValue: string, nativeEvent?: ChangeEvent<HTMLInputElement>) => {
|
|
64
|
+
if (!isControlled) {
|
|
65
|
+
setInternalValue(nextValue);
|
|
66
|
+
}
|
|
67
|
+
onValueChange?.(nextValue);
|
|
68
|
+
if (nativeEvent) {
|
|
69
|
+
onChange?.(nativeEvent);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
// Combobox-driven updates only provide compatibility with common form handlers.
|
|
73
|
+
onChange?.({
|
|
74
|
+
currentTarget: { value: nextValue } as EventTarget & HTMLInputElement,
|
|
75
|
+
target: { value: nextValue } as EventTarget & HTMLInputElement,
|
|
76
|
+
} as ChangeEvent<HTMLInputElement>);
|
|
77
|
+
}, [isControlled, onChange, onValueChange]);
|
|
78
|
+
|
|
79
|
+
const handleNativeChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
|
|
80
|
+
updateValue(event.currentTarget.value, event);
|
|
81
|
+
}, [updateValue]);
|
|
82
|
+
|
|
83
|
+
const focusAndSelectHours = useCallback(() => {
|
|
84
|
+
const input = inputRef.current;
|
|
85
|
+
if (!input) return;
|
|
86
|
+
|
|
87
|
+
input.focus();
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
input.setSelectionRange(0, 2);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// Some browsers do not expose selection APIs on time inputs.
|
|
94
|
+
}
|
|
95
|
+
}, []);
|
|
96
|
+
|
|
97
|
+
const timeOptions = useMemo<ComboboxOption[]>(
|
|
98
|
+
() => (options ?? []).map(time => ({ value: time, label: time })),
|
|
99
|
+
[options],
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const selectedTimeValue = options?.includes(currentValue as TimeValue) ? [currentValue] : [];
|
|
103
|
+
|
|
104
|
+
const handleTimeListChange = useCallback((values: string[]) => {
|
|
105
|
+
const nextValue = values[0] ?? '';
|
|
106
|
+
updateValue(nextValue);
|
|
107
|
+
}, [updateValue]);
|
|
108
|
+
|
|
109
|
+
if (options) {
|
|
110
|
+
return (
|
|
111
|
+
<div className={classNames('ds-time-input', className)}>
|
|
112
|
+
{name && <input type="hidden" name={name} value={selectedTimeValue[0] ?? ''} />}
|
|
113
|
+
<Combobox
|
|
114
|
+
id={id}
|
|
115
|
+
options={timeOptions}
|
|
116
|
+
value={selectedTimeValue}
|
|
117
|
+
onValueChange={handleTimeListChange}
|
|
118
|
+
searchType={searchType}
|
|
119
|
+
highlightStringMatches={highlightStringMatches}
|
|
120
|
+
placeholder={placeholder ?? 'Select time'}
|
|
121
|
+
disabled={disabled}
|
|
122
|
+
hasError={hasError}
|
|
123
|
+
aria-describedby={ariaDescribedBy}
|
|
124
|
+
aria-invalid={ariaInvalid}
|
|
125
|
+
aria-label={ariaLabel}
|
|
126
|
+
triggerVariant="button"
|
|
127
|
+
selectedValueDisplay="text"
|
|
128
|
+
showDropdownTrigger={false}
|
|
129
|
+
triggerEndContent={clockIcon}
|
|
130
|
+
/>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<div className={classNames('ds-time-input', className)}>
|
|
137
|
+
<input
|
|
138
|
+
{...rest}
|
|
139
|
+
ref={setRefs}
|
|
140
|
+
id={id}
|
|
141
|
+
name={name}
|
|
142
|
+
type="time"
|
|
143
|
+
value={currentValue}
|
|
144
|
+
step={granularity === 'second' ? 1 : 60}
|
|
145
|
+
disabled={disabled}
|
|
146
|
+
aria-describedby={ariaDescribedBy}
|
|
147
|
+
aria-invalid={ariaInvalid}
|
|
148
|
+
aria-label={ariaLabel}
|
|
149
|
+
placeholder={placeholder}
|
|
150
|
+
className={classNames('ds-input', 'ds-input--M', 'ds-time-input__input', {
|
|
151
|
+
'ds-input--error': hasError,
|
|
152
|
+
})}
|
|
153
|
+
onChange={handleNativeChange}
|
|
154
|
+
/>
|
|
155
|
+
<button
|
|
156
|
+
type="button"
|
|
157
|
+
className="ds-time-input__icon-button"
|
|
158
|
+
onClick={focusAndSelectHours}
|
|
159
|
+
aria-label="Select time"
|
|
160
|
+
disabled={disabled}
|
|
161
|
+
>
|
|
162
|
+
{clockIcon}
|
|
163
|
+
</button>
|
|
164
|
+
</div>
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
TimeInput.displayName = 'TimeInput';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
.ds-time-input {
|
|
2
|
+
position: relative;
|
|
3
|
+
width: 100%;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.ds-time-input__input {
|
|
7
|
+
appearance: none;
|
|
8
|
+
padding-right: calc(var(--form-field-text-medium-height) + var(--spacing-small));
|
|
9
|
+
|
|
10
|
+
&::-webkit-calendar-picker-indicator {
|
|
11
|
+
display: none;
|
|
12
|
+
appearance: none;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.ds-time-input__icon-button {
|
|
17
|
+
position: absolute;
|
|
18
|
+
inset-block: 0;
|
|
19
|
+
inset-inline-end: 0;
|
|
20
|
+
width: var(--form-field-text-medium-height);
|
|
21
|
+
display: inline-flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
justify-content: center;
|
|
24
|
+
border: none;
|
|
25
|
+
background: transparent;
|
|
26
|
+
color: var(--form-field-icon-default-color-icon);
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
|
|
29
|
+
&:disabled {
|
|
30
|
+
color: var(--form-field-icon-disabled-color-icon);
|
|
31
|
+
cursor: not-allowed;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
background-color: var(--section-list-row-default-color-background);
|
|
7
7
|
padding: var(--section-list-row-spacing-vertical) var(--section-list-row-spacing-horizontal);
|
|
8
8
|
border-radius: var(--section-list-row-radius);
|
|
9
|
+
gap: var(--spacing-small);
|
|
9
10
|
|
|
10
11
|
.ds-row__icon-click {
|
|
11
12
|
flex-shrink: 0;
|
|
12
|
-
|
|
13
|
-
stroke: var(--section-list-row-default-color-icon-arrow);
|
|
13
|
+
stroke: var(--section-list-row-default-color-icon-arrow);
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
&.ds-icon-arrow-right {
|
|
@@ -1361,4 +1361,52 @@ export const TableWithSemanticColors: Story = {
|
|
|
1361
1361
|
},
|
|
1362
1362
|
};
|
|
1363
1363
|
|
|
1364
|
+
interface BooleanRowData {
|
|
1365
|
+
id: number;
|
|
1366
|
+
name: string;
|
|
1367
|
+
hasPet: boolean | null | undefined;
|
|
1368
|
+
isEnrolled: boolean | null | undefined;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
const booleanCellRendererData: BooleanRowData[] = [
|
|
1372
|
+
{ id: 1, name: 'Alice Johnson', hasPet: true, isEnrolled: true },
|
|
1373
|
+
{ id: 2, name: 'Bob Smith', hasPet: false, isEnrolled: false },
|
|
1374
|
+
{ id: 3, name: 'Charlie Brown', hasPet: null, isEnrolled: undefined },
|
|
1375
|
+
{ id: 4, name: 'Diana Prince', hasPet: true, isEnrolled: null },
|
|
1376
|
+
{ id: 5, name: 'Ethan Hunt', hasPet: undefined, isEnrolled: true },
|
|
1377
|
+
];
|
|
1378
|
+
|
|
1379
|
+
const booleanCellRendererColumnDefs: ColDef<BooleanRowData>[] = [
|
|
1380
|
+
{ field: 'name', headerName: 'Name' },
|
|
1381
|
+
{
|
|
1382
|
+
field: 'hasPet',
|
|
1383
|
+
headerName: 'Has Pet',
|
|
1384
|
+
cellRenderer: 'dsBooleanCellRenderer',
|
|
1385
|
+
editable: false,
|
|
1386
|
+
},
|
|
1387
|
+
{
|
|
1388
|
+
field: 'isEnrolled',
|
|
1389
|
+
headerName: 'Is Enrolled',
|
|
1390
|
+
cellRenderer: 'dsBooleanCellRenderer',
|
|
1391
|
+
editable: false,
|
|
1392
|
+
},
|
|
1393
|
+
];
|
|
1394
|
+
|
|
1395
|
+
export const WithBooleanCellRenderer: Story = {
|
|
1396
|
+
parameters: {
|
|
1397
|
+
docs: {
|
|
1398
|
+
description: {
|
|
1399
|
+
story:
|
|
1400
|
+
'The BooleanCellRenderer displays a true or false icon for boolean values, and renders nothing for any other value regardless of truthiness. Only explicit true and false are accepted, anything else is treated as nullish',
|
|
1401
|
+
},
|
|
1402
|
+
},
|
|
1403
|
+
},
|
|
1404
|
+
args: {
|
|
1405
|
+
rowData: booleanCellRendererData,
|
|
1406
|
+
columnDefs: booleanCellRendererColumnDefs,
|
|
1407
|
+
defaultColDef,
|
|
1408
|
+
domLayout: 'autoHeight',
|
|
1409
|
+
},
|
|
1410
|
+
};
|
|
1411
|
+
|
|
1364
1412
|
export default meta;
|
|
@@ -24,6 +24,7 @@ import { focusFirstFocusableElement } from 'Utils/focusFirstFocusableElement';
|
|
|
24
24
|
import { BooleanFilter } from './columnFilters/BooleanFilter/BooleanFilter';
|
|
25
25
|
import { TimeFilter } from './columnFilters/TimeFilter/TimeFilter';
|
|
26
26
|
import { TableSettingsDropdown } from './TableSettingsDropdown';
|
|
27
|
+
import { BooleanCellRenderer } from './cellRenderers/BooleanCellRenderer';
|
|
27
28
|
|
|
28
29
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
30
|
type TableProps<TData = any> = {
|
|
@@ -194,6 +195,7 @@ export const Table = (props: TableProps) => {
|
|
|
194
195
|
dsSelectDropdownCellRenderer: SelectDropdownCellRenderer,
|
|
195
196
|
dsBooleanFilter: BooleanFilter,
|
|
196
197
|
dsTimeFilter: TimeFilter,
|
|
198
|
+
dsBooleanCellRenderer: BooleanCellRenderer,
|
|
197
199
|
...components,
|
|
198
200
|
}}
|
|
199
201
|
{...rest}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import '@testing-library/jest-dom/vitest';
|
|
2
|
+
import { describe, expect, test } from 'vitest';
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
|
+
import { BooleanCellRenderer } from './BooleanCellRenderer';
|
|
5
|
+
import type { CustomCellRendererProps } from 'ag-grid-react';
|
|
6
|
+
|
|
7
|
+
const renderWithValue = (value: unknown) =>
|
|
8
|
+
render(<BooleanCellRenderer {...{ value } as CustomCellRendererProps} />);
|
|
9
|
+
|
|
10
|
+
describe('BooleanCellRenderer', () => {
|
|
11
|
+
test('renders a check icon when value is true', () => {
|
|
12
|
+
renderWithValue(true);
|
|
13
|
+
expect(screen.getByRole('img', { hidden: true })).toBeInTheDocument();
|
|
14
|
+
expect(screen.getByText('true')).toBeInTheDocument();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('renders an x icon when value is false', () => {
|
|
18
|
+
renderWithValue(false);
|
|
19
|
+
expect(screen.getByRole('img', { hidden: true })).toBeInTheDocument();
|
|
20
|
+
expect(screen.getByText('false')).toBeInTheDocument();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('renders nothing when value is null', () => {
|
|
24
|
+
const { container } = renderWithValue(null);
|
|
25
|
+
expect(container.innerHTML).toBeFalsy();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('renders nothing when value is undefined', () => {
|
|
29
|
+
const { container } = renderWithValue(undefined);
|
|
30
|
+
expect(container.innerHTML).toBeFalsy();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('renders nothing when value is a non-boolean', () => {
|
|
34
|
+
const { container } = renderWithValue('some string');
|
|
35
|
+
expect(container.innerHTML).toBeFalsy();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { CustomCellRendererProps } from 'ag-grid-react';
|
|
2
|
+
import { Icon } from 'Components/icon/Icon';
|
|
3
|
+
|
|
4
|
+
export const BooleanCellRenderer = (props: CustomCellRendererProps) => {
|
|
5
|
+
const { value } = props;
|
|
6
|
+
|
|
7
|
+
if (value === true) {
|
|
8
|
+
return (
|
|
9
|
+
<div className="ds-boolean-cell-renderer">
|
|
10
|
+
<Icon
|
|
11
|
+
size={24}
|
|
12
|
+
name="check-solid"
|
|
13
|
+
color="var(--color-semantic-success-700)"
|
|
14
|
+
screenReaderText="true"
|
|
15
|
+
/>
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
else if (value === false) {
|
|
20
|
+
return (
|
|
21
|
+
<div className="ds-boolean-cell-renderer">
|
|
22
|
+
<Icon
|
|
23
|
+
size={24}
|
|
24
|
+
name="x-solid"
|
|
25
|
+
color="var(--color-semantic-destructive-700)"
|
|
26
|
+
screenReaderText="false"
|
|
27
|
+
/>
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
};
|
package/src/index.scss
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
@use "components/formField/inputs/input.scss";
|
|
12
12
|
@use "components/formField/label/label.scss";
|
|
13
13
|
@use "components/formField/inputs/number/numberInput.scss";
|
|
14
|
+
@use "components/formField/inputs/time/timeInput.scss";
|
|
14
15
|
@use "components/formField/inputs/radio/radioButtonInput.scss";
|
|
15
16
|
@use "components/formField/inputs/checkbox/checkboxInput.scss";
|
|
16
17
|
@use "components/formField/inputs/selectDropdown/selectDropdown";
|
|
@@ -36,10 +37,12 @@
|
|
|
36
37
|
@use "components/progress/progress.scss";
|
|
37
38
|
@use "components/toast/toast.scss";
|
|
38
39
|
@use "components/datePicker/datePicker.scss";
|
|
40
|
+
@use "components/dateTimePicker/dateTimePicker.scss";
|
|
39
41
|
@use "components/avatar/avatar.scss";
|
|
40
42
|
@use "components/singleUser/singleUser.scss";
|
|
41
43
|
@use "components/avatarGroup/avatarGroup.scss";
|
|
42
44
|
@use "components/userDropdown/userDropdown.scss";
|
|
45
|
+
@use "components/table/cellRenderers/booleanCellRenderer.scss";
|
|
43
46
|
@use "components/row/row.scss";
|
|
44
47
|
@use "components/combobox/combobox.scss";
|
|
45
48
|
@use "components/toggle/toggle.scss";
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,8 @@ export { Banner, BANNER_LEVEL, type BannerProps } from 'Components/banner/Banner
|
|
|
10
10
|
export { Button } from 'Components/button/Button';
|
|
11
11
|
export { Card } from 'Components/card/Card';
|
|
12
12
|
export { DatePicker } from 'Components/datePicker/DatePicker';
|
|
13
|
+
export { DateTimePicker } from 'Components/dateTimePicker/DateTimePicker';
|
|
14
|
+
export type { DateTimePickerDisplayFormat, DateTimePickerProps } from 'Components/dateTimePicker/DateTimePicker';
|
|
13
15
|
export { Dropdown } from 'Components/dropdown/Dropdown';
|
|
14
16
|
export { Tag } from 'Components/tag/Tag';
|
|
15
17
|
export type { TagProps, TagColor } from 'Components/tag/Tag';
|
|
@@ -26,6 +28,8 @@ export { ColourPickerDropdown } from 'Components/formField/inputs/colourPickerDr
|
|
|
26
28
|
export { RadioButtonInput } from 'Components/formField/inputs/radio/RadioButtonInput';
|
|
27
29
|
export { SelectDropdown } from 'Components/formField/inputs/selectDropdown/SelectDropdown';
|
|
28
30
|
export { TextInput } from 'Components/formField/inputs/text/TextInput';
|
|
31
|
+
export { TimeInput } from 'Components/formField/inputs/time/TimeInput';
|
|
32
|
+
export type { TimeGranularity, TimeInputProps, TimeValue } from 'Components/formField/inputs/time/TimeInput';
|
|
29
33
|
export { TextArea } from 'Components/formField/inputs/textArea/TextArea';
|
|
30
34
|
export { Heading } from 'Components/heading/Heading';
|
|
31
35
|
export { Icon } from 'Components/icon/Icon';
|
|
@@ -40,6 +44,7 @@ export { SingleUser, type SingleUserProps } from 'Components/singleUser/SingleUs
|
|
|
40
44
|
export { Slideover, type SlideoverProps } from 'Components/slideover/Slideover';
|
|
41
45
|
export { SlideoverManager } from 'Components/slideoverManager/SlideoverManager';
|
|
42
46
|
export { DefaultCellRenderer } from 'Components/table/cellRenderers/DefaultCellRenderer';
|
|
47
|
+
export { BooleanCellRenderer } from 'Components/table/cellRenderers/BooleanCellRenderer';
|
|
43
48
|
export { DSDefaultColDef } from 'Components/table/DSDefaultColDef';
|
|
44
49
|
export { GridApiContext } from 'Components/table/GridApiContext';
|
|
45
50
|
export { Table } from 'Components/table/Table';
|