@dashforge/tw 0.9.0-beta → 0.10.0-beta

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 (58) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/dist/index.esm.js +1634 -256
  3. package/dist/src/components/Calendar/Calendar.d.ts +14 -0
  4. package/dist/src/components/Calendar/Calendar.d.ts.map +1 -0
  5. package/dist/src/components/Calendar/calendar.types.d.ts +72 -0
  6. package/dist/src/components/Calendar/calendar.types.d.ts.map +1 -0
  7. package/dist/src/components/Calendar/calendar.variants.d.ts +99 -0
  8. package/dist/src/components/Calendar/calendar.variants.d.ts.map +1 -0
  9. package/dist/src/components/DatePicker/DatePicker.d.ts +14 -0
  10. package/dist/src/components/DatePicker/DatePicker.d.ts.map +1 -0
  11. package/dist/src/components/DatePicker/datePicker.types.d.ts +88 -0
  12. package/dist/src/components/DatePicker/datePicker.types.d.ts.map +1 -0
  13. package/dist/src/components/DatePicker/datePicker.variants.d.ts +97 -0
  14. package/dist/src/components/DatePicker/datePicker.variants.d.ts.map +1 -0
  15. package/dist/src/components/DateRangePicker/DateRangePicker.d.ts +13 -0
  16. package/dist/src/components/DateRangePicker/DateRangePicker.d.ts.map +1 -0
  17. package/dist/src/components/DateRangePicker/dateRangePicker.types.d.ts +85 -0
  18. package/dist/src/components/DateRangePicker/dateRangePicker.types.d.ts.map +1 -0
  19. package/dist/src/components/DateRangePicker/dateRangePicker.variants.d.ts +183 -0
  20. package/dist/src/components/DateRangePicker/dateRangePicker.variants.d.ts.map +1 -0
  21. package/dist/src/components/DateTimePicker/DateTimePicker.d.ts +7 -43
  22. package/dist/src/components/DateTimePicker/DateTimePicker.d.ts.map +1 -1
  23. package/dist/src/components/DateTimePicker/dateTimePicker.types.d.ts +50 -55
  24. package/dist/src/components/DateTimePicker/dateTimePicker.types.d.ts.map +1 -1
  25. package/dist/src/components/DateTimePicker/dateTimePicker.variants.d.ts +36 -94
  26. package/dist/src/components/DateTimePicker/dateTimePicker.variants.d.ts.map +1 -1
  27. package/dist/src/components/TimePicker/TimePicker.d.ts +12 -0
  28. package/dist/src/components/TimePicker/TimePicker.d.ts.map +1 -0
  29. package/dist/src/components/TimePicker/timePicker.types.d.ts +80 -0
  30. package/dist/src/components/TimePicker/timePicker.types.d.ts.map +1 -0
  31. package/dist/src/components/TimePicker/timePicker.variants.d.ts +103 -0
  32. package/dist/src/components/TimePicker/timePicker.variants.d.ts.map +1 -0
  33. package/dist/src/index.d.ts +15 -3
  34. package/dist/src/index.d.ts.map +1 -1
  35. package/package.json +4 -3
  36. package/src/components/Calendar/Calendar.test.tsx +75 -0
  37. package/src/components/Calendar/Calendar.tsx +264 -0
  38. package/src/components/Calendar/calendar.types.ts +65 -0
  39. package/src/components/Calendar/calendar.variants.ts +64 -0
  40. package/src/components/DatePicker/DatePicker.test.tsx +76 -0
  41. package/src/components/DatePicker/DatePicker.tsx +305 -0
  42. package/src/components/DatePicker/datePicker.types.ts +77 -0
  43. package/src/components/DatePicker/datePicker.variants.ts +50 -0
  44. package/src/components/DateRangePicker/DateRangePicker.test.tsx +92 -0
  45. package/src/components/DateRangePicker/DateRangePicker.tsx +510 -0
  46. package/src/components/DateRangePicker/dateRangePicker.types.ts +74 -0
  47. package/src/components/DateRangePicker/dateRangePicker.variants.ts +101 -0
  48. package/src/components/DateTimePicker/DateTimePicker.test.tsx +60 -186
  49. package/src/components/DateTimePicker/DateTimePicker.tsx +209 -181
  50. package/src/components/DateTimePicker/dateTimePicker.types.ts +50 -54
  51. package/src/components/DateTimePicker/dateTimePicker.variants.ts +22 -51
  52. package/src/components/TimePicker/TimePicker.test.tsx +65 -0
  53. package/src/components/TimePicker/TimePicker.tsx +274 -0
  54. package/src/components/TimePicker/timePicker.types.ts +69 -0
  55. package/src/components/TimePicker/timePicker.variants.ts +53 -0
  56. package/src/index.ts +38 -6
  57. package/tsconfig.lib.json +7 -4
  58. package/src/components/DateTimePicker/DateTimePicker.perf.test.tsx +0 -153
@@ -1,204 +1,78 @@
1
1
  // @vitest-environment jsdom
2
- import { describe, it, expect, vi } from 'vitest';
2
+ import { describe, it, expect, vi, beforeAll } from 'vitest';
3
3
  import { render, screen, fireEvent } from '@testing-library/react';
4
- import { DashFormProvider } from '@dashforge/forms';
5
- import { DateTimePicker, isoToInputValue } from './DateTimePicker.js';
6
4
 
7
- describe('isoToInputValue (pure helper)', () => {
8
- it('returns "" for null / undefined / empty', () => {
9
- expect(isoToInputValue('date', null)).toBe('');
10
- expect(isoToInputValue('date', undefined)).toBe('');
11
- expect(isoToInputValue('date', '')).toBe('');
12
- });
13
-
14
- it('passes through canonical `YYYY-MM-DD` for date mode', () => {
15
- expect(isoToInputValue('date', '2026-05-16')).toBe('2026-05-16');
16
- });
17
-
18
- it('passes through canonical `HH:mm` for time mode', () => {
19
- expect(isoToInputValue('time', '09:42')).toBe('09:42');
20
- });
21
-
22
- it('passes through canonical `HH:mm:ss` for time mode', () => {
23
- expect(isoToInputValue('time', '09:42:15')).toBe('09:42:15');
24
- });
5
+ import { DateTimePicker } from './DateTimePicker.js';
6
+
7
+ beforeAll(() => {
8
+ // Radix Popover (Floating UI) needs ResizeObserver — jsdom lacks it.
9
+ if (!('ResizeObserver' in globalThis)) {
10
+ class ResizeObserverStub {
11
+ observe(): void {
12
+ /* no-op */
13
+ }
14
+ unobserve(): void {
15
+ /* no-op */
16
+ }
17
+ disconnect(): void {
18
+ /* no-op */
19
+ }
20
+ }
21
+ (globalThis as { ResizeObserver?: unknown }).ResizeObserver =
22
+ ResizeObserverStub;
23
+ }
24
+ });
25
25
 
26
- it('passes through canonical `YYYY-MM-DDTHH:mm` for datetime mode', () => {
27
- expect(isoToInputValue('datetime', '2026-05-16T09:42')).toBe(
28
- '2026-05-16T09:42'
26
+ describe('DateTimePicker', () => {
27
+ it('renders a labelled trigger with the placeholder', () => {
28
+ render(
29
+ <DateTimePicker name="appt" label="Appointment" placeholder="Pick one" />,
29
30
  );
31
+ expect(screen.queryByText('Appointment')).not.toBeNull();
32
+ expect(screen.getByRole('button').textContent).toContain('Pick one');
30
33
  });
31
34
 
32
- it('truncates a rich ISO datetime to the date mode prefix', () => {
33
- expect(isoToInputValue('date', '2026-05-16T09:42:15.000Z')).toBe(
34
- '2026-05-16'
35
+ it('shows a formatted controlled value', () => {
36
+ render(<DateTimePicker name="d" value="2026-05-20T14:30" />);
37
+ expect(screen.getByRole('button').textContent).toContain(
38
+ 'May 20, 2026, 14:30',
35
39
  );
36
40
  });
37
41
 
38
- it('returns "" for an unparseable string', () => {
39
- expect(isoToInputValue('date', 'not-a-date')).toBe('');
40
- });
41
- });
42
-
43
- describe('<DateTimePicker>', () => {
44
- describe('standalone (no DashForm)', () => {
45
- it('renders a native date input by default', () => {
46
- render(<DateTimePicker name="dob" label="DOB" />);
47
- const input = screen.getByLabelText('DOB') as HTMLInputElement;
48
- expect(input.type).toBe('date');
49
- });
50
-
51
- it('renders a time input when mode="time"', () => {
52
- render(<DateTimePicker name="t" label="Time" mode="time" />);
53
- const input = screen.getByLabelText('Time') as HTMLInputElement;
54
- expect(input.type).toBe('time');
55
- });
56
-
57
- it('renders a datetime-local input when mode="datetime"', () => {
58
- render(<DateTimePicker name="dt" label="When" mode="datetime" />);
59
- const input = screen.getByLabelText('When') as HTMLInputElement;
60
- expect(input.type).toBe('datetime-local');
61
- });
62
-
63
- it('seeds `defaultValue` (uncontrolled)', () => {
64
- render(
65
- <DateTimePicker name="dob" label="DOB" defaultValue="2026-05-16" />
66
- );
67
- const input = screen.getByLabelText('DOB') as HTMLInputElement;
68
- expect(input.defaultValue).toBe('2026-05-16');
69
- });
70
-
71
- it('reflects controlled `value`', () => {
72
- render(
73
- <DateTimePicker name="dob" label="DOB" value="2026-05-16" />
74
- );
75
- const input = screen.getByLabelText('DOB') as HTMLInputElement;
76
- expect(input.value).toBe('2026-05-16');
77
- });
78
-
79
- it('truncates a controlled rich ISO to the date mode prefix', () => {
80
- render(
81
- <DateTimePicker
82
- name="dob"
83
- label="DOB"
84
- value="2026-05-16T09:42:15.000Z"
85
- />
86
- );
87
- const input = screen.getByLabelText('DOB') as HTMLInputElement;
88
- expect(input.value).toBe('2026-05-16');
89
- });
90
-
91
- it('forwards min/max/step to the native input', () => {
92
- render(
93
- <DateTimePicker
94
- name="t"
95
- label="Time"
96
- mode="time"
97
- min="08:00"
98
- max="18:00"
99
- step={300}
100
- />
101
- );
102
- const input = screen.getByLabelText('Time') as HTMLInputElement;
103
- expect(input.min).toBe('08:00');
104
- expect(input.max).toBe('18:00');
105
- expect(input.step).toBe('300');
106
- });
107
-
108
- it('renders a `*` for required fields and sets the native attr', () => {
109
- render(<DateTimePicker name="dob" label="DOB" required />);
110
- const input = screen.getByLabelText(/DOB/) as HTMLInputElement;
111
- expect(input.required).toBe(true);
112
- expect(screen.getByText('*')).toBeTruthy();
113
- });
114
-
115
- it('renders helperText when not in error', () => {
116
- render(
117
- <DateTimePicker
118
- name="dob"
119
- label="DOB"
120
- helperText="Pick a date in the past."
121
- />
122
- );
123
- expect(screen.getByText('Pick a date in the past.')).toBeTruthy();
124
- });
125
-
126
- it('shows the helperText in the error slot when `error` is true', () => {
127
- render(
128
- <DateTimePicker
129
- name="dob"
130
- label="DOB"
131
- error
132
- helperText="Required"
133
- />
134
- );
135
- const p = screen.getByText('Required');
136
- // The error slot has the `text-danger-600` class per the variants.
137
- expect(p.className).toMatch(/danger/);
138
- });
139
-
140
- it('disables the input', () => {
141
- render(<DateTimePicker name="dob" label="DOB" disabled />);
142
- const input = screen.getByLabelText('DOB') as HTMLInputElement;
143
- expect(input.disabled).toBe(true);
144
- });
42
+ it('opens a popover with a calendar and a time list', () => {
43
+ render(<DateTimePicker name="d" defaultValue="2026-05-20T10:00" />);
44
+ expect(screen.queryByRole('dialog')).toBeNull();
45
+ fireEvent.click(screen.getByRole('button'));
46
+ expect(screen.queryByRole('dialog')).not.toBeNull();
47
+ expect(screen.getByRole('grid')).not.toBeNull();
48
+ expect(screen.getByRole('listbox', { name: 'Time options' })).not.toBeNull();
145
49
  });
146
50
 
147
- describe('event wiring', () => {
148
- it('fires onValueChange with the typed value (date)', () => {
149
- const onValueChange = vi.fn();
150
- render(
151
- <DateTimePicker
152
- name="dob"
153
- label="DOB"
154
- onValueChange={onValueChange}
155
- />
156
- );
157
- const input = screen.getByLabelText('DOB') as HTMLInputElement;
158
- fireEvent.change(input, { target: { value: '2026-12-25' } });
159
- expect(onValueChange).toHaveBeenCalledWith('2026-12-25');
160
- });
161
-
162
- it('fires onValueChange with `null` when the input is cleared', () => {
163
- const onValueChange = vi.fn();
164
- render(
165
- <DateTimePicker
166
- name="dob"
167
- label="DOB"
168
- value="2026-05-16"
169
- onValueChange={onValueChange}
170
- />
171
- );
172
- const input = screen.getByLabelText('DOB') as HTMLInputElement;
173
- fireEvent.change(input, { target: { value: '' } });
174
- expect(onValueChange).toHaveBeenCalledWith(null);
175
- });
51
+ it('picking a date updates the value and keeps the popover open', () => {
52
+ const onChange = vi.fn();
53
+ render(
54
+ <DateTimePicker name="d" defaultValue="2026-05-20T10:00" onChange={onChange} />,
55
+ );
56
+ fireEvent.click(screen.getByRole('button'));
57
+ fireEvent.click(screen.getByRole('button', { name: /May 15, 2026/ }));
58
+ expect(onChange).toHaveBeenCalledWith('2026-05-15T10:00');
59
+ expect(screen.queryByRole('dialog')).not.toBeNull();
176
60
  });
177
61
 
178
- describe('inside DashFormProvider', () => {
179
- it('mounts cleanly with the bridge attached', () => {
180
- render(
181
- <DashFormProvider defaultValues={{ dob: '2026-05-16' }}>
182
- <DateTimePicker name="dob" label="DOB" />
183
- </DashFormProvider>
184
- );
185
- // Mount assertion — defaultValues commit is timing-sensitive in
186
- // vitest (mirror of how Autocomplete / RadioGroup / NumberField
187
- // are tested), so we don't assert input.value here.
188
- expect(screen.getByLabelText('DOB')).toBeTruthy();
189
- });
62
+ it('picking a time completes the value and closes the popover', () => {
63
+ const onChange = vi.fn();
64
+ render(
65
+ <DateTimePicker name="d" defaultValue="2026-05-20T10:00" onChange={onChange} />,
66
+ );
67
+ fireEvent.click(screen.getByRole('button'));
68
+ fireEvent.click(screen.getByRole('option', { name: '14:30' }));
69
+ expect(onChange).toHaveBeenCalledWith('2026-05-20T14:30');
70
+ expect(screen.queryByRole('dialog')).toBeNull();
190
71
  });
191
72
 
192
- describe('visibility', () => {
193
- it('returns null when visibleWhen returns false', () => {
194
- const { container } = render(
195
- <DateTimePicker
196
- name="dob"
197
- label="DOB"
198
- visibleWhen={() => false}
199
- />
200
- );
201
- expect(container.querySelector('input')).toBeNull();
202
- });
73
+ it('does not open the popover when disabled', () => {
74
+ render(<DateTimePicker name="d" disabled />);
75
+ fireEvent.click(screen.getByRole('button'));
76
+ expect(screen.queryByRole('dialog')).toBeNull();
203
77
  });
204
78
  });