@datatechsolutions/tympan 0.3.1 → 0.4.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/dist/{Avatar-P14MVkK1.js → Avatar-CU7B1WsX.js} +61 -1
- package/dist/{Avatar-P14MVkK1.js.map → Avatar-CU7B1WsX.js.map} +1 -1
- package/dist/avatars.js +1 -1
- package/dist/flow.d.ts +1 -1
- package/dist/flow.js +2 -2
- package/dist/{index-6wR5ThnX.d.ts → index-J-7EBkCL.d.ts} +172 -6
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/{src-C9whlpC7.js → src-uCyIE-Te.js} +909 -379
- package/dist/src-uCyIE-Te.js.map +1 -0
- package/dist/styles.css +476 -0
- package/package.json +2 -2
- package/src/components/calendar/Calendar.css +260 -0
- package/src/components/calendar/Calendar.test.tsx +178 -0
- package/src/components/calendar/Calendar.tsx +341 -0
- package/src/components/date-range-field/DateRangeField.css +140 -0
- package/src/components/date-range-field/DateRangeField.test.tsx +187 -0
- package/src/components/date-range-field/DateRangeField.tsx +231 -0
- package/src/components/tooltip/Tooltip.css +70 -0
- package/src/components/tooltip/Tooltip.test.tsx +209 -0
- package/src/components/tooltip/Tooltip.tsx +102 -0
- package/src/index.ts +3 -0
- package/src/internal/messages/core-es.ts +17 -0
- package/src/internal/messages.ts +51 -0
- package/src/styles.css +3 -0
- package/dist/src-C9whlpC7.js.map +0 -1
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/* Calendar (spec: wave-5-general/calendar.md; design direction §2.3, §2.4, §2.6, §2.10). */
|
|
2
|
+
@layer tympan.components {
|
|
3
|
+
.ty-calendar {
|
|
4
|
+
--ty-calendar-cell: calc(var(--ty-control-height) + var(--ty-space-1));
|
|
5
|
+
display: flex;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
gap: var(--ty-space-2);
|
|
8
|
+
min-inline-size: 0;
|
|
9
|
+
}
|
|
10
|
+
.ty-calendar__rac {
|
|
11
|
+
display: block;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/* Header: previous / title (or pickers) / next. */
|
|
15
|
+
.ty-calendar__stepper {
|
|
16
|
+
display: flex;
|
|
17
|
+
align-items: center;
|
|
18
|
+
gap: var(--ty-space-1);
|
|
19
|
+
}
|
|
20
|
+
.ty-calendar__nav {
|
|
21
|
+
display: flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
justify-content: center;
|
|
24
|
+
inline-size: var(--ty-control-height);
|
|
25
|
+
block-size: var(--ty-control-height);
|
|
26
|
+
flex: none;
|
|
27
|
+
border: 0;
|
|
28
|
+
border-radius: var(--ty-radius-control);
|
|
29
|
+
background: transparent;
|
|
30
|
+
color: var(--ty-ink-2);
|
|
31
|
+
font: inherit;
|
|
32
|
+
cursor: pointer;
|
|
33
|
+
outline: none;
|
|
34
|
+
}
|
|
35
|
+
.ty-calendar__nav[data-hovered] {
|
|
36
|
+
background: var(--ty-surface-sunken);
|
|
37
|
+
}
|
|
38
|
+
.ty-calendar__nav[data-pressed] {
|
|
39
|
+
background: var(--ty-surface-sunken);
|
|
40
|
+
color: var(--ty-ink);
|
|
41
|
+
}
|
|
42
|
+
.ty-calendar__nav[data-focus-visible] {
|
|
43
|
+
outline: var(--ty-focus-width) solid var(--ty-focus-ring);
|
|
44
|
+
outline-offset: 2px;
|
|
45
|
+
}
|
|
46
|
+
.ty-calendar__nav[aria-disabled='true'],
|
|
47
|
+
.ty-calendar__nav[data-disabled] {
|
|
48
|
+
color: var(--ty-ink-3);
|
|
49
|
+
cursor: not-allowed;
|
|
50
|
+
opacity: 0.45;
|
|
51
|
+
}
|
|
52
|
+
.ty-calendar__heading {
|
|
53
|
+
flex: 1;
|
|
54
|
+
min-block-size: var(--ty-control-height);
|
|
55
|
+
display: flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
justify-content: center;
|
|
58
|
+
padding-inline: var(--ty-space-2);
|
|
59
|
+
color: var(--ty-ink);
|
|
60
|
+
font-size: var(--ty-font-size-label);
|
|
61
|
+
font-weight: var(--ty-font-weight-semibold);
|
|
62
|
+
text-align: center;
|
|
63
|
+
}
|
|
64
|
+
.ty-calendar__pickers {
|
|
65
|
+
flex: 1;
|
|
66
|
+
display: flex;
|
|
67
|
+
justify-content: center;
|
|
68
|
+
gap: var(--ty-space-2);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* Month grids, side by side, stacked below the small breakpoint. */
|
|
72
|
+
.ty-calendar__body {
|
|
73
|
+
display: flex;
|
|
74
|
+
gap: var(--ty-space-5);
|
|
75
|
+
}
|
|
76
|
+
.ty-calendar[data-months='2'] .ty-calendar__body,
|
|
77
|
+
.ty-calendar[data-months='3'] .ty-calendar__body {
|
|
78
|
+
justify-content: center;
|
|
79
|
+
}
|
|
80
|
+
.ty-calendar__month {
|
|
81
|
+
display: flex;
|
|
82
|
+
gap: var(--ty-space-1);
|
|
83
|
+
min-inline-size: 0;
|
|
84
|
+
}
|
|
85
|
+
.ty-calendar__weeks {
|
|
86
|
+
display: flex;
|
|
87
|
+
flex-direction: column;
|
|
88
|
+
padding-block: calc(var(--ty-font-size-meta) + var(--ty-space-2));
|
|
89
|
+
}
|
|
90
|
+
.ty-calendar__week {
|
|
91
|
+
display: flex;
|
|
92
|
+
align-items: center;
|
|
93
|
+
justify-content: flex-end;
|
|
94
|
+
min-block-size: var(--ty-calendar-cell);
|
|
95
|
+
padding: 1px;
|
|
96
|
+
color: var(--ty-ink-3);
|
|
97
|
+
font-size: var(--ty-font-size-meta);
|
|
98
|
+
font-variant-numeric: tabular-nums;
|
|
99
|
+
}
|
|
100
|
+
.ty-calendar__grid {
|
|
101
|
+
flex: 1;
|
|
102
|
+
border-collapse: collapse;
|
|
103
|
+
border-spacing: 0;
|
|
104
|
+
}
|
|
105
|
+
.ty-calendar__weekday {
|
|
106
|
+
padding-block-end: var(--ty-space-1);
|
|
107
|
+
color: var(--ty-ink-3);
|
|
108
|
+
font-size: var(--ty-font-size-meta);
|
|
109
|
+
font-weight: var(--ty-font-weight-regular);
|
|
110
|
+
text-align: center;
|
|
111
|
+
}
|
|
112
|
+
.ty-calendar__grid td {
|
|
113
|
+
padding: 1px;
|
|
114
|
+
text-align: center;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* Day cells: states carry shape and colour, never colour alone. */
|
|
118
|
+
.ty-calendar__day {
|
|
119
|
+
display: flex;
|
|
120
|
+
align-items: center;
|
|
121
|
+
justify-content: center;
|
|
122
|
+
min-inline-size: var(--ty-calendar-cell);
|
|
123
|
+
min-block-size: var(--ty-calendar-cell);
|
|
124
|
+
border-radius: var(--ty-radius-control);
|
|
125
|
+
color: var(--ty-ink-2);
|
|
126
|
+
font-size: var(--ty-font-size-body);
|
|
127
|
+
font-variant-numeric: tabular-nums;
|
|
128
|
+
cursor: pointer;
|
|
129
|
+
outline: none;
|
|
130
|
+
}
|
|
131
|
+
.ty-calendar__day-box {
|
|
132
|
+
display: flex;
|
|
133
|
+
flex-direction: column;
|
|
134
|
+
align-items: center;
|
|
135
|
+
justify-content: center;
|
|
136
|
+
min-inline-size: 100%;
|
|
137
|
+
min-block-size: 100%;
|
|
138
|
+
border-radius: inherit;
|
|
139
|
+
}
|
|
140
|
+
.ty-calendar__day[data-outside-month] {
|
|
141
|
+
color: var(--ty-ink-3);
|
|
142
|
+
}
|
|
143
|
+
.ty-calendar[data-months='2'] .ty-calendar__day[data-outside-month],
|
|
144
|
+
.ty-calendar[data-months='3'] .ty-calendar__day[data-outside-month] {
|
|
145
|
+
visibility: hidden;
|
|
146
|
+
}
|
|
147
|
+
.ty-calendar:not([data-outside-days]) .ty-calendar__day[data-outside-month] {
|
|
148
|
+
display: none;
|
|
149
|
+
}
|
|
150
|
+
.ty-calendar__day[data-hovered] {
|
|
151
|
+
background: var(--ty-surface-sunken);
|
|
152
|
+
}
|
|
153
|
+
.ty-calendar__day-box[data-today] {
|
|
154
|
+
color: var(--ty-ink);
|
|
155
|
+
font-weight: var(--ty-font-weight-semibold);
|
|
156
|
+
box-shadow: inset 0 0 0 1px var(--ty-line-strong);
|
|
157
|
+
}
|
|
158
|
+
.ty-calendar__day[data-selected] {
|
|
159
|
+
background: var(--ty-accent);
|
|
160
|
+
color: var(--ty-on-cta);
|
|
161
|
+
font-weight: var(--ty-font-weight-semibold);
|
|
162
|
+
}
|
|
163
|
+
.ty-calendar__day[data-selected] .ty-calendar__day-box {
|
|
164
|
+
color: inherit;
|
|
165
|
+
}
|
|
166
|
+
.ty-calendar__day[data-selection-start] {
|
|
167
|
+
border-start-end-radius: 0;
|
|
168
|
+
border-end-end-radius: 0;
|
|
169
|
+
}
|
|
170
|
+
.ty-calendar__day[data-selection-end] {
|
|
171
|
+
border-start-start-radius: 0;
|
|
172
|
+
border-end-start-radius: 0;
|
|
173
|
+
}
|
|
174
|
+
.ty-calendar__day[data-selected]:not([data-selection-start]):not([data-selection-end]) {
|
|
175
|
+
border-radius: 0;
|
|
176
|
+
}
|
|
177
|
+
.ty-calendar__day[data-focus-visible] {
|
|
178
|
+
outline: var(--ty-focus-width) solid var(--ty-focus-ring);
|
|
179
|
+
outline-offset: 1px;
|
|
180
|
+
}
|
|
181
|
+
.ty-calendar__day[data-unavailable] .ty-calendar__day-number {
|
|
182
|
+
color: var(--ty-ink-3);
|
|
183
|
+
text-decoration: line-through;
|
|
184
|
+
}
|
|
185
|
+
.ty-calendar__day[data-disabled],
|
|
186
|
+
.ty-calendar__day[data-unavailable] {
|
|
187
|
+
cursor: not-allowed;
|
|
188
|
+
}
|
|
189
|
+
.ty-calendar__day[data-disabled] {
|
|
190
|
+
opacity: 0.45;
|
|
191
|
+
}
|
|
192
|
+
.ty-calendar__error {
|
|
193
|
+
display: flex;
|
|
194
|
+
align-items: center;
|
|
195
|
+
gap: var(--ty-space-1);
|
|
196
|
+
color: var(--ty-danger);
|
|
197
|
+
font-size: var(--ty-font-size-meta);
|
|
198
|
+
line-height: var(--ty-font-line-height-meta);
|
|
199
|
+
}
|
|
200
|
+
.ty-calendar__footer {
|
|
201
|
+
display: flex;
|
|
202
|
+
flex-wrap: wrap;
|
|
203
|
+
gap: var(--ty-space-2);
|
|
204
|
+
padding-block-start: var(--ty-space-2);
|
|
205
|
+
border-block-start: 1px solid var(--ty-line-soft);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* Touch: every day cell reaches the target size. */
|
|
209
|
+
@media (pointer: coarse), (max-width: 1023.98px) {
|
|
210
|
+
.ty-calendar {
|
|
211
|
+
--ty-calendar-cell: var(--ty-control-target);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* Several months stack below the small breakpoint. */
|
|
216
|
+
@media (max-width: 639.98px) {
|
|
217
|
+
.ty-calendar[data-months='2'] .ty-calendar__body,
|
|
218
|
+
.ty-calendar[data-months='3'] .ty-calendar__body {
|
|
219
|
+
flex-direction: column;
|
|
220
|
+
align-items: center;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
@media (prefers-reduced-motion: reduce) {
|
|
225
|
+
.ty-calendar *,
|
|
226
|
+
.ty-calendar *::before,
|
|
227
|
+
.ty-calendar *::after {
|
|
228
|
+
animation-duration: 0s;
|
|
229
|
+
transition-duration: 0s;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
@media (forced-colors: active) {
|
|
234
|
+
.ty-calendar__day[data-selected] {
|
|
235
|
+
background: Highlight;
|
|
236
|
+
color: HighlightText;
|
|
237
|
+
forced-color-adjust: none;
|
|
238
|
+
}
|
|
239
|
+
.ty-calendar__day-box[data-today] {
|
|
240
|
+
box-shadow: none;
|
|
241
|
+
outline: 1px solid CanvasText;
|
|
242
|
+
}
|
|
243
|
+
.ty-calendar__day[data-unavailable] {
|
|
244
|
+
color: GrayText;
|
|
245
|
+
opacity: 1;
|
|
246
|
+
}
|
|
247
|
+
.ty-calendar__day[data-disabled] {
|
|
248
|
+
color: GrayText;
|
|
249
|
+
opacity: 1;
|
|
250
|
+
}
|
|
251
|
+
.ty-calendar__day[data-focus-visible] {
|
|
252
|
+
outline: 2px solid Highlight;
|
|
253
|
+
}
|
|
254
|
+
.ty-calendar__day,
|
|
255
|
+
.ty-calendar__weekday,
|
|
256
|
+
.ty-calendar__week {
|
|
257
|
+
forced-color-adjust: none;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { render, screen, waitFor, within } from '@testing-library/react'
|
|
2
|
+
import userEvent from '@testing-library/user-event'
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
4
|
+
import { expectNoAxeViolations } from '../../../test/axe'
|
|
5
|
+
import { cssOf, mediaBlock } from '../../../test/css'
|
|
6
|
+
import { renderRtl } from '../../../test/rtl'
|
|
7
|
+
import { ThemeScope } from '../../internal/ThemeScope'
|
|
8
|
+
import { Calendar } from './Calendar'
|
|
9
|
+
|
|
10
|
+
const day = (y: number, m: number, d: number) => new Date(y, m - 1, d)
|
|
11
|
+
const range = (sy: number, sm: number, sd: number, ey: number, em: number, ed: number) => ({
|
|
12
|
+
start: day(sy, sm, sd),
|
|
13
|
+
end: day(ey, em, ed),
|
|
14
|
+
})
|
|
15
|
+
const sameDay = (a: Date | null | undefined, y: number, m: number, d: number) =>
|
|
16
|
+
!!a && a.getFullYear() === y && a.getMonth() === m - 1 && a.getDate() === d
|
|
17
|
+
|
|
18
|
+
function grid() {
|
|
19
|
+
return screen.getByRole('grid')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('Calendar', () => {
|
|
23
|
+
it('moves focus by week with ArrowDown', async () => {
|
|
24
|
+
render(<Calendar label="Book" value={day(2026, 3, 10)} onChange={() => {}} locale="en-US" />)
|
|
25
|
+
await userEvent.click(within(grid()).getByRole('button', { name: /March 10/ }))
|
|
26
|
+
expect(document.activeElement).toHaveTextContent(/^10$/)
|
|
27
|
+
await userEvent.keyboard('{ArrowDown}')
|
|
28
|
+
expect(document.activeElement).toHaveTextContent(/^17$/)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('moves focus to the month end with PageDown and reads the heading', async () => {
|
|
32
|
+
render(<Calendar label="Later" value={day(2026, 1, 31)} onChange={() => {}} locale="en-US" />)
|
|
33
|
+
await userEvent.click(within(grid()).getByRole('button', { name: /January 31/ }))
|
|
34
|
+
await userEvent.keyboard('{PageDown}')
|
|
35
|
+
expect(document.activeElement).toHaveTextContent(/^28$/)
|
|
36
|
+
expect(screen.getByText('February 2026')).toBeVisible()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('swaps an earlier end into a 2-to-5 range', async () => {
|
|
40
|
+
const onChange = vi.fn()
|
|
41
|
+
render(
|
|
42
|
+
<Calendar label="Stay" mode="range" value={null} focusedValue={day(2026, 3, 1)} onChange={onChange} locale="en-US" />,
|
|
43
|
+
)
|
|
44
|
+
await userEvent.click(within(grid()).getByRole('button', { name: /March 5, 2026/ }))
|
|
45
|
+
await userEvent.click(within(grid()).getByRole('button', { name: /March 2, 2026/ }))
|
|
46
|
+
const reported = onChange.mock.calls.at(-1)![0] as { start: Date; end: Date }
|
|
47
|
+
expect(sameDay(reported.start, 2026, 3, 2)).toBe(true)
|
|
48
|
+
expect(sameDay(reported.end, 2026, 3, 5)).toBe(true)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('cancels a pending range start on Escape', async () => {
|
|
52
|
+
const onChange = vi.fn()
|
|
53
|
+
render(
|
|
54
|
+
<Calendar label="Stay" mode="range" value={null} focusedValue={day(2026, 3, 1)} onChange={onChange} locale="en-US" />,
|
|
55
|
+
)
|
|
56
|
+
await userEvent.click(within(grid()).getByRole('button', { name: /March 5, 2026/ }))
|
|
57
|
+
await userEvent.keyboard('{Escape}')
|
|
58
|
+
const selected = within(grid()).getAllByRole('gridcell').filter((c) => c.getAttribute('aria-selected') === 'true')
|
|
59
|
+
expect(selected).toHaveLength(0)
|
|
60
|
+
expect(onChange).not.toHaveBeenCalled()
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('keeps Sundays unselectable and announced as unavailable', async () => {
|
|
64
|
+
const onChange = vi.fn()
|
|
65
|
+
render(
|
|
66
|
+
<Calendar
|
|
67
|
+
label="Visit"
|
|
68
|
+
value={day(2026, 3, 10)}
|
|
69
|
+
onChange={onChange}
|
|
70
|
+
isDateUnavailable={(d) => d.getDay() === 0}
|
|
71
|
+
locale="en-US"
|
|
72
|
+
/>,
|
|
73
|
+
)
|
|
74
|
+
const sunday = within(grid()).getByRole('button', { name: /Sunday, March 8/ })
|
|
75
|
+
expect(sunday).toHaveAttribute('aria-disabled', 'true')
|
|
76
|
+
await userEvent.click(sunday)
|
|
77
|
+
expect(onChange).not.toHaveBeenCalled()
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('marks a range over an unavailable day invalid and shows the error', () => {
|
|
81
|
+
render(
|
|
82
|
+
<Calendar
|
|
83
|
+
label="Stay"
|
|
84
|
+
mode="range"
|
|
85
|
+
value={range(2026, 3, 6, 2026, 3, 12)}
|
|
86
|
+
onChange={() => {}}
|
|
87
|
+
isDateUnavailable={(d) => d.getDay() === 0}
|
|
88
|
+
errorMessage="Pick weekdays only."
|
|
89
|
+
locale="en-US"
|
|
90
|
+
/>,
|
|
91
|
+
)
|
|
92
|
+
expect(screen.getByRole('alert')).toHaveTextContent('Pick weekdays only.')
|
|
93
|
+
expect(within(grid()).getAllByRole('gridcell').filter((c) => c.getAttribute('aria-selected') === 'true').length).toBeGreaterThan(0)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('shows two consecutive months and pages by the set, or by one with pageBehavior single', async () => {
|
|
97
|
+
render(<Calendar label="Report" value={day(2026, 3, 10)} onChange={() => {}} visibleMonths={2} locale="en-US" />)
|
|
98
|
+
expect(screen.getAllByRole('grid')).toHaveLength(2)
|
|
99
|
+
expect(screen.getByText(/March 2026\s*–\s*April/)).toBeVisible()
|
|
100
|
+
await userEvent.click(screen.getByRole('button', { name: /Next month, May 2026/ }))
|
|
101
|
+
expect(screen.getByText(/May 2026\s*–\s*June/)).toBeVisible()
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('pages a two-month set by one month with pageBehavior single', async () => {
|
|
105
|
+
render(
|
|
106
|
+
<Calendar label="Report 2" value={day(2026, 3, 10)} onChange={() => {}} visibleMonths={2} pageBehavior="single" locale="en-US" />,
|
|
107
|
+
)
|
|
108
|
+
await userEvent.click(screen.getByRole('button', { name: /Next month, April 2026/ }))
|
|
109
|
+
expect(screen.getByText(/April 2026\s*–\s*May/)).toBeVisible()
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('disables Next while the maxValue month is visible', () => {
|
|
113
|
+
render(<Calendar label="Cutoff" value={day(2026, 4, 10)} onChange={() => {}} maxValue={day(2026, 4, 30)} locale="en-US" />)
|
|
114
|
+
expect(screen.getByRole('button', { name: /Next month/ })).toBeDisabled()
|
|
115
|
+
expect(screen.getByRole('button', { name: /Previous month/ })).not.toBeDisabled()
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it('jumps by picker and keeps focus in the year picker', async () => {
|
|
119
|
+
render(<Calendar label="Archive" value={day(2026, 3, 10)} onChange={() => {}} headerMode="pickers" locale="en-US" />)
|
|
120
|
+
// The trigger's name is the picked value ("2026"); the "Year" accessible
|
|
121
|
+
// label only shows as a suffix in jsdom's name computation.
|
|
122
|
+
await userEvent.click(screen.getByRole('button', { name: /Year/ }))
|
|
123
|
+
await userEvent.click(within(screen.getByRole('listbox')).getByText('2030'))
|
|
124
|
+
expect(within(grid()).getByRole('button', { name: /March 10, 2030/ })).toBeInTheDocument()
|
|
125
|
+
await waitFor(() => expect(screen.getByRole('button', { name: /Year/ })).toHaveFocus())
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('renders a non-interactive week-number column', () => {
|
|
129
|
+
render(<Calendar label="Plan" value={day(2026, 3, 10)} onChange={() => {}} showWeekNumbers locale="en-US" />)
|
|
130
|
+
const weeks = document.querySelectorAll('.ty-calendar__week')
|
|
131
|
+
expect(weeks.length).toBeGreaterThanOrEqual(5)
|
|
132
|
+
for (const w of weeks) {
|
|
133
|
+
expect(w.tagName).not.toBe('BUTTON')
|
|
134
|
+
expect(w.getAttribute('role')).not.toBe('button')
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('follows the Portuguese locale for weekday names and week start', () => {
|
|
139
|
+
const { container } = render(<Calendar label="Prazo" value={day(2026, 3, 10)} onChange={() => {}} locale="pt-BR" />)
|
|
140
|
+
// Weekday names are in the DOM; react-aria marks the header row
|
|
141
|
+
// aria-hidden (the names are already part of each day cell's label).
|
|
142
|
+
const weekdays = [...container.querySelectorAll('.ty-calendar__weekday')].map((el) => el.textContent)
|
|
143
|
+
expect(weekdays).toHaveLength(7)
|
|
144
|
+
expect(weekdays[0]).toMatch(/dom/i)
|
|
145
|
+
expect(within(grid()).getByRole('button', { name: /domingo, 1 de março/ })).toBeInTheDocument()
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
it('keeps day cells at the touch target, hides outside days on request and honours forced colours', () => {
|
|
149
|
+
const css = cssOf('components/calendar/Calendar.css')
|
|
150
|
+
expect(mediaBlock(css, /\(pointer:\s*coarse\)/)).toMatch(/var\(--ty-control-target\)/)
|
|
151
|
+
expect(css).toMatch(/:not\(\[data-outside-days\]\) \.ty-calendar__day\[data-outside-month\]/)
|
|
152
|
+
expect(mediaBlock(css, /\(forced-colors:\s*active\)/)).toMatch(/Highlight/)
|
|
153
|
+
expect(mediaBlock(css, /\(forced-colors:\s*active\)/)).toMatch(/GrayText/)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('has no axe violations, light and dark', async () => {
|
|
157
|
+
const { container } = render(
|
|
158
|
+
<>
|
|
159
|
+
{(['light', 'dark'] as const).map((scheme) => (
|
|
160
|
+
<ThemeScope key={scheme} scheme={scheme}>
|
|
161
|
+
<Calendar label={`Book ${scheme}`} value={day(2026, 3, 10)} onChange={() => {}} showWeekNumbers footer={<span>GMT+1</span>} />
|
|
162
|
+
</ThemeScope>
|
|
163
|
+
))}
|
|
164
|
+
</>,
|
|
165
|
+
)
|
|
166
|
+
await expectNoAxeViolations(container)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('renders mirrored navigation in right-to-left and passes axe', async () => {
|
|
170
|
+
const { container } = renderRtl(<Calendar label="الحجز" value={day(2026, 9, 20)} onChange={() => {}} />)
|
|
171
|
+
for (const svg of container.querySelectorAll('svg.ty-icon')) {
|
|
172
|
+
if (svg.classList.contains('lucide-chevron-left') || svg.classList.contains('lucide-chevron-right')) {
|
|
173
|
+
expect(svg).toHaveClass('ty-mirror-rtl')
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
await expectNoAxeViolations(container)
|
|
177
|
+
})
|
|
178
|
+
})
|