@instructure/ui-calendar 11.7.4-snapshot-0 → 11.7.4-snapshot-3

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/CHANGELOG.md CHANGED
@@ -3,9 +3,12 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
- ## [11.7.4-snapshot-0](https://github.com/instructure/instructure-ui/compare/v11.7.3...v11.7.4-snapshot-0) (2026-05-08)
6
+ ## [11.7.4-snapshot-3](https://github.com/instructure/instructure-ui/compare/v11.7.3...v11.7.4-snapshot-3) (2026-05-12)
7
7
 
8
- **Note:** Version bump only for package @instructure/ui-calendar
8
+
9
+ ### Bug Fixes
10
+
11
+ * **ui-dialog,ui-date-input,ui-calendar:** add aria-live to calendar header, descriptive nav button labels with target month, aria-modal on focused dialogs ([8de1026](https://github.com/instructure/instructure-ui/commit/8de102636517415d2905bb40b914147f330aaed1))
9
12
 
10
13
 
11
14
 
@@ -115,24 +115,37 @@ let Calendar = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle, g
115
115
  renderNextMonthButton,
116
116
  renderPrevMonthButton
117
117
  } = this.props;
118
+ const {
119
+ visibleMonth
120
+ } = this.state;
121
+ const prevMonthName = visibleMonth.clone().subtract({
122
+ months: 1
123
+ }).format('MMMM YYYY');
124
+ const nextMonthName = visibleMonth.clone().add({
125
+ months: 1
126
+ }).format('MMMM YYYY');
118
127
  return {
119
- prevButton: renderPrevMonthButton ? callRenderProp(renderPrevMonthButton) : _jsx(IconButton, {
128
+ prevButton: renderPrevMonthButton ? callRenderProp(renderPrevMonthButton, {
129
+ targetMonthSrLabel: prevMonthName
130
+ }) : _jsx(IconButton, {
120
131
  size: "small",
121
132
  withBackground: false,
122
133
  withBorder: false,
123
134
  renderIcon: _jsx(IconArrowOpenStartSolid, {
124
135
  color: "primary"
125
136
  }),
126
- screenReaderLabel: "Previous month"
137
+ screenReaderLabel: `Previous month, ${prevMonthName}`
127
138
  }),
128
- nextButton: renderNextMonthButton ? callRenderProp(renderNextMonthButton) : _jsx(IconButton, {
139
+ nextButton: renderNextMonthButton ? callRenderProp(renderNextMonthButton, {
140
+ targetMonthSrLabel: nextMonthName
141
+ }) : _jsx(IconButton, {
129
142
  size: "small",
130
143
  withBackground: false,
131
144
  withBorder: false,
132
145
  renderIcon: _jsx(IconArrowOpenEndSolid, {
133
146
  color: "primary"
134
147
  }),
135
- screenReaderLabel: "Next month"
148
+ screenReaderLabel: `Next month, ${nextMonthName}`
136
149
  })
137
150
  };
138
151
  };
@@ -225,7 +238,9 @@ let Calendar = (_dec = withDeterministicId(), _dec2 = withStyle(generateStyle, g
225
238
  return _jsxs("div", {
226
239
  children: [_jsxs("div", {
227
240
  css: style,
228
- children: [prevButton && cloneButton(prevButton, this.handleMonthChange('prev')), renderNavigationLabel ? callRenderProp(renderNavigationLabel) : _jsxs("span", {
241
+ children: [prevButton && cloneButton(prevButton, this.handleMonthChange('prev')), renderNavigationLabel ? callRenderProp(renderNavigationLabel) : _jsxs("div", {
242
+ "aria-live": "polite",
243
+ "aria-atomic": "true",
229
244
  children: [_jsx("div", {
230
245
  children: visibleMonth.format('MMMM')
231
246
  }), !withYearPicker ? _jsx("div", {
@@ -114,24 +114,37 @@ let Calendar = (_dec = withDeterministicId(), _dec2 = withStyleNew(generateStyle
114
114
  renderNextMonthButton,
115
115
  renderPrevMonthButton
116
116
  } = this.props;
117
+ const {
118
+ visibleMonth
119
+ } = this.state;
120
+ const prevMonthName = visibleMonth.clone().subtract({
121
+ months: 1
122
+ }).format('MMMM YYYY');
123
+ const nextMonthName = visibleMonth.clone().add({
124
+ months: 1
125
+ }).format('MMMM YYYY');
117
126
  return {
118
- prevButton: renderPrevMonthButton ? callRenderProp(renderPrevMonthButton) : _jsx(IconButton, {
127
+ prevButton: renderPrevMonthButton ? callRenderProp(renderPrevMonthButton, {
128
+ targetMonthSrLabel: prevMonthName
129
+ }) : _jsx(IconButton, {
119
130
  size: "small",
120
131
  withBackground: false,
121
132
  withBorder: false,
122
133
  renderIcon: _jsx(ChevronLeftInstUIIcon, {
123
134
  color: "baseColor"
124
135
  }),
125
- screenReaderLabel: "Previous month"
136
+ screenReaderLabel: `Previous month, ${prevMonthName}`
126
137
  }),
127
- nextButton: renderNextMonthButton ? callRenderProp(renderNextMonthButton) : _jsx(IconButton, {
138
+ nextButton: renderNextMonthButton ? callRenderProp(renderNextMonthButton, {
139
+ targetMonthSrLabel: nextMonthName
140
+ }) : _jsx(IconButton, {
128
141
  size: "small",
129
142
  withBackground: false,
130
143
  withBorder: false,
131
144
  renderIcon: _jsx(ChevronRightInstUIIcon, {
132
145
  color: "baseColor"
133
146
  }),
134
- screenReaderLabel: "Next month"
147
+ screenReaderLabel: `Next month, ${nextMonthName}`
135
148
  })
136
149
  };
137
150
  };
@@ -224,17 +237,20 @@ let Calendar = (_dec = withDeterministicId(), _dec2 = withStyleNew(generateStyle
224
237
  return _jsxs("div", {
225
238
  children: [_jsxs("div", {
226
239
  css: style,
227
- children: [prevButton && cloneButton(prevButton, this.handleMonthChange('prev')), renderNavigationLabel ? callRenderProp(renderNavigationLabel) : _jsxs("span", {
228
- children: [_jsx("div", {
240
+ children: [prevButton && cloneButton(prevButton, this.handleMonthChange('prev')), renderNavigationLabel ? callRenderProp(renderNavigationLabel) : _jsxs("h2", {
241
+ "aria-live": "polite",
242
+ "aria-atomic": "true",
243
+ css: styles?.navigationLabel,
244
+ children: [_jsx("span", {
229
245
  children: visibleMonth.format('MMMM')
230
- }), !withYearPicker ? _jsx("div", {
246
+ }), !withYearPicker ? _jsx("span", {
231
247
  children: visibleMonth.format('YYYY')
232
248
  }) : null]
233
249
  }), nextButton && cloneButton(nextButton, this.handleMonthChange('next'))]
234
250
  }), withYearPicker ? _jsx("div", {
235
251
  css: styles?.yearPicker,
236
252
  children: _jsx(SimpleSelect, {
237
- width: "90px",
253
+ width: "95px",
238
254
  renderLabel: "",
239
255
  placeholder: "--",
240
256
  assistiveText: withYearPicker.screenReaderLabel,
@@ -57,6 +57,15 @@ const generateStyle = (componentTheme, props) => {
57
57
  maxWidth: componentTheme.maxHeaderWidth,
58
58
  lineHeight: componentTheme.lineHeight
59
59
  },
60
+ navigationLabel: {
61
+ label: 'calendar__navigation-label',
62
+ margin: 0,
63
+ fontSize: 'inherit',
64
+ fontWeight: 'inherit',
65
+ '& span': {
66
+ display: 'block'
67
+ }
68
+ },
60
69
  yearPicker: {
61
70
  display: 'flex',
62
71
  justifyContent: 'center',
@@ -126,24 +126,37 @@ let Calendar = exports.Calendar = (_dec = (0, _withDeterministicId.withDetermini
126
126
  renderNextMonthButton,
127
127
  renderPrevMonthButton
128
128
  } = this.props;
129
+ const {
130
+ visibleMonth
131
+ } = this.state;
132
+ const prevMonthName = visibleMonth.clone().subtract({
133
+ months: 1
134
+ }).format('MMMM YYYY');
135
+ const nextMonthName = visibleMonth.clone().add({
136
+ months: 1
137
+ }).format('MMMM YYYY');
129
138
  return {
130
- prevButton: renderPrevMonthButton ? (0, _callRenderProp.callRenderProp)(renderPrevMonthButton) : (0, _jsxRuntime.jsx)(_v11_2.IconButton, {
139
+ prevButton: renderPrevMonthButton ? (0, _callRenderProp.callRenderProp)(renderPrevMonthButton, {
140
+ targetMonthSrLabel: prevMonthName
141
+ }) : (0, _jsxRuntime.jsx)(_v11_2.IconButton, {
131
142
  size: "small",
132
143
  withBackground: false,
133
144
  withBorder: false,
134
145
  renderIcon: (0, _jsxRuntime.jsx)(_IconArrowOpenStartSolid.IconArrowOpenStartSolid, {
135
146
  color: "primary"
136
147
  }),
137
- screenReaderLabel: "Previous month"
148
+ screenReaderLabel: `Previous month, ${prevMonthName}`
138
149
  }),
139
- nextButton: renderNextMonthButton ? (0, _callRenderProp.callRenderProp)(renderNextMonthButton) : (0, _jsxRuntime.jsx)(_v11_2.IconButton, {
150
+ nextButton: renderNextMonthButton ? (0, _callRenderProp.callRenderProp)(renderNextMonthButton, {
151
+ targetMonthSrLabel: nextMonthName
152
+ }) : (0, _jsxRuntime.jsx)(_v11_2.IconButton, {
140
153
  size: "small",
141
154
  withBackground: false,
142
155
  withBorder: false,
143
156
  renderIcon: (0, _jsxRuntime.jsx)(_IconArrowOpenEndSolid.IconArrowOpenEndSolid, {
144
157
  color: "primary"
145
158
  }),
146
- screenReaderLabel: "Next month"
159
+ screenReaderLabel: `Next month, ${nextMonthName}`
147
160
  })
148
161
  };
149
162
  };
@@ -236,7 +249,9 @@ let Calendar = exports.Calendar = (_dec = (0, _withDeterministicId.withDetermini
236
249
  return (0, _jsxRuntime.jsxs)("div", {
237
250
  children: [(0, _jsxRuntime.jsxs)("div", {
238
251
  css: style,
239
- children: [prevButton && cloneButton(prevButton, this.handleMonthChange('prev')), renderNavigationLabel ? (0, _callRenderProp.callRenderProp)(renderNavigationLabel) : (0, _jsxRuntime.jsxs)("span", {
252
+ children: [prevButton && cloneButton(prevButton, this.handleMonthChange('prev')), renderNavigationLabel ? (0, _callRenderProp.callRenderProp)(renderNavigationLabel) : (0, _jsxRuntime.jsxs)("div", {
253
+ "aria-live": "polite",
254
+ "aria-atomic": "true",
240
255
  children: [(0, _jsxRuntime.jsx)("div", {
241
256
  children: visibleMonth.format('MMMM')
242
257
  }), !withYearPicker ? (0, _jsxRuntime.jsx)("div", {
@@ -124,24 +124,37 @@ let Calendar = exports.Calendar = (_dec = (0, _withDeterministicId.withDetermini
124
124
  renderNextMonthButton,
125
125
  renderPrevMonthButton
126
126
  } = this.props;
127
+ const {
128
+ visibleMonth
129
+ } = this.state;
130
+ const prevMonthName = visibleMonth.clone().subtract({
131
+ months: 1
132
+ }).format('MMMM YYYY');
133
+ const nextMonthName = visibleMonth.clone().add({
134
+ months: 1
135
+ }).format('MMMM YYYY');
127
136
  return {
128
- prevButton: renderPrevMonthButton ? (0, _callRenderProp.callRenderProp)(renderPrevMonthButton) : (0, _jsxRuntime.jsx)(_latest2.IconButton, {
137
+ prevButton: renderPrevMonthButton ? (0, _callRenderProp.callRenderProp)(renderPrevMonthButton, {
138
+ targetMonthSrLabel: prevMonthName
139
+ }) : (0, _jsxRuntime.jsx)(_latest2.IconButton, {
129
140
  size: "small",
130
141
  withBackground: false,
131
142
  withBorder: false,
132
143
  renderIcon: (0, _jsxRuntime.jsx)(_uiIcons.ChevronLeftInstUIIcon, {
133
144
  color: "baseColor"
134
145
  }),
135
- screenReaderLabel: "Previous month"
146
+ screenReaderLabel: `Previous month, ${prevMonthName}`
136
147
  }),
137
- nextButton: renderNextMonthButton ? (0, _callRenderProp.callRenderProp)(renderNextMonthButton) : (0, _jsxRuntime.jsx)(_latest2.IconButton, {
148
+ nextButton: renderNextMonthButton ? (0, _callRenderProp.callRenderProp)(renderNextMonthButton, {
149
+ targetMonthSrLabel: nextMonthName
150
+ }) : (0, _jsxRuntime.jsx)(_latest2.IconButton, {
138
151
  size: "small",
139
152
  withBackground: false,
140
153
  withBorder: false,
141
154
  renderIcon: (0, _jsxRuntime.jsx)(_uiIcons.ChevronRightInstUIIcon, {
142
155
  color: "baseColor"
143
156
  }),
144
- screenReaderLabel: "Next month"
157
+ screenReaderLabel: `Next month, ${nextMonthName}`
145
158
  })
146
159
  };
147
160
  };
@@ -234,17 +247,20 @@ let Calendar = exports.Calendar = (_dec = (0, _withDeterministicId.withDetermini
234
247
  return (0, _jsxRuntime.jsxs)("div", {
235
248
  children: [(0, _jsxRuntime.jsxs)("div", {
236
249
  css: style,
237
- children: [prevButton && cloneButton(prevButton, this.handleMonthChange('prev')), renderNavigationLabel ? (0, _callRenderProp.callRenderProp)(renderNavigationLabel) : (0, _jsxRuntime.jsxs)("span", {
238
- children: [(0, _jsxRuntime.jsx)("div", {
250
+ children: [prevButton && cloneButton(prevButton, this.handleMonthChange('prev')), renderNavigationLabel ? (0, _callRenderProp.callRenderProp)(renderNavigationLabel) : (0, _jsxRuntime.jsxs)("h2", {
251
+ "aria-live": "polite",
252
+ "aria-atomic": "true",
253
+ css: styles?.navigationLabel,
254
+ children: [(0, _jsxRuntime.jsx)("span", {
239
255
  children: visibleMonth.format('MMMM')
240
- }), !withYearPicker ? (0, _jsxRuntime.jsx)("div", {
256
+ }), !withYearPicker ? (0, _jsxRuntime.jsx)("span", {
241
257
  children: visibleMonth.format('YYYY')
242
258
  }) : null]
243
259
  }), nextButton && cloneButton(nextButton, this.handleMonthChange('next'))]
244
260
  }), withYearPicker ? (0, _jsxRuntime.jsx)("div", {
245
261
  css: styles?.yearPicker,
246
262
  children: (0, _jsxRuntime.jsx)(_latest3.SimpleSelect, {
247
- width: "90px",
263
+ width: "95px",
248
264
  renderLabel: "",
249
265
  placeholder: "--",
250
266
  assistiveText: withYearPicker.screenReaderLabel,
@@ -63,6 +63,15 @@ const generateStyle = (componentTheme, props) => {
63
63
  maxWidth: componentTheme.maxHeaderWidth,
64
64
  lineHeight: componentTheme.lineHeight
65
65
  },
66
+ navigationLabel: {
67
+ label: 'calendar__navigation-label',
68
+ margin: 0,
69
+ fontSize: 'inherit',
70
+ fontWeight: 'inherit',
71
+ '& span': {
72
+ display: 'block'
73
+ }
74
+ },
66
75
  yearPicker: {
67
76
  display: 'flex',
68
77
  justifyContent: 'center',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instructure/ui-calendar",
3
- "version": "11.7.4-snapshot-0",
3
+ "version": "11.7.4-snapshot-3",
4
4
  "description": "A calendar component.",
5
5
  "author": "Instructure, Inc. Engineering and Product Design",
6
6
  "module": "./es/index.js",
@@ -15,27 +15,27 @@
15
15
  "license": "MIT",
16
16
  "dependencies": {
17
17
  "@babel/runtime": "^7.29.2",
18
- "@instructure/console": "11.7.4-snapshot-0",
19
- "@instructure/ui-a11y-content": "11.7.4-snapshot-0",
20
- "@instructure/shared-types": "11.7.4-snapshot-0",
21
- "@instructure/ui-buttons": "11.7.4-snapshot-0",
22
- "@instructure/emotion": "11.7.4-snapshot-0",
23
- "@instructure/ui-i18n": "11.7.4-snapshot-0",
24
- "@instructure/ui-icons": "11.7.4-snapshot-0",
25
- "@instructure/ui-react-utils": "11.7.4-snapshot-0",
26
- "@instructure/ui-simple-select": "11.7.4-snapshot-0",
27
- "@instructure/ui-themes": "11.7.4-snapshot-0",
28
- "@instructure/ui-utils": "11.7.4-snapshot-0",
29
- "@instructure/ui-view": "11.7.4-snapshot-0",
30
- "@instructure/uid": "11.7.4-snapshot-0"
18
+ "@instructure/console": "11.7.4-snapshot-3",
19
+ "@instructure/shared-types": "11.7.4-snapshot-3",
20
+ "@instructure/ui-a11y-content": "11.7.4-snapshot-3",
21
+ "@instructure/ui-buttons": "11.7.4-snapshot-3",
22
+ "@instructure/ui-i18n": "11.7.4-snapshot-3",
23
+ "@instructure/emotion": "11.7.4-snapshot-3",
24
+ "@instructure/ui-icons": "11.7.4-snapshot-3",
25
+ "@instructure/ui-react-utils": "11.7.4-snapshot-3",
26
+ "@instructure/ui-simple-select": "11.7.4-snapshot-3",
27
+ "@instructure/ui-themes": "11.7.4-snapshot-3",
28
+ "@instructure/ui-view": "11.7.4-snapshot-3",
29
+ "@instructure/ui-utils": "11.7.4-snapshot-3",
30
+ "@instructure/uid": "11.7.4-snapshot-3"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@testing-library/jest-dom": "^6.6.3",
34
34
  "@testing-library/react": "15.0.7",
35
35
  "@testing-library/user-event": "^14.6.1",
36
36
  "vitest": "^3.2.2",
37
- "@instructure/ui-babel-preset": "11.7.4-snapshot-0",
38
- "@instructure/ui-color-utils": "11.7.4-snapshot-0"
37
+ "@instructure/ui-color-utils": "11.7.4-snapshot-3",
38
+ "@instructure/ui-babel-preset": "11.7.4-snapshot-3"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "react": ">=18 <=19"
@@ -175,28 +175,41 @@ class Calendar extends Component<CalendarProps, CalendarState> {
175
175
 
176
176
  renderMonthNavigationButtons = () => {
177
177
  const { renderNextMonthButton, renderPrevMonthButton } = this.props
178
+ const { visibleMonth } = this.state
179
+ const prevMonthName = visibleMonth
180
+ .clone()
181
+ .subtract({ months: 1 })
182
+ .format('MMMM YYYY')
183
+ const nextMonthName = visibleMonth
184
+ .clone()
185
+ .add({ months: 1 })
186
+ .format('MMMM YYYY')
178
187
 
179
188
  return {
180
189
  prevButton: renderPrevMonthButton ? (
181
- callRenderProp(renderPrevMonthButton)
190
+ callRenderProp(renderPrevMonthButton, {
191
+ targetMonthSrLabel: prevMonthName
192
+ })
182
193
  ) : (
183
194
  <IconButton
184
195
  size="small"
185
196
  withBackground={false}
186
197
  withBorder={false}
187
198
  renderIcon={<IconArrowOpenStartSolid color="primary" />}
188
- screenReaderLabel="Previous month"
199
+ screenReaderLabel={`Previous month, ${prevMonthName}`}
189
200
  />
190
201
  ),
191
202
  nextButton: renderNextMonthButton ? (
192
- callRenderProp(renderNextMonthButton)
203
+ callRenderProp(renderNextMonthButton, {
204
+ targetMonthSrLabel: nextMonthName
205
+ })
193
206
  ) : (
194
207
  <IconButton
195
208
  size="small"
196
209
  withBackground={false}
197
210
  withBorder={false}
198
211
  renderIcon={<IconArrowOpenEndSolid color="primary" />}
199
- screenReaderLabel="Next month"
212
+ screenReaderLabel={`Next month, ${nextMonthName}`}
200
213
  />
201
214
  )
202
215
  }
@@ -299,12 +312,12 @@ class Calendar extends Component<CalendarProps, CalendarState> {
299
312
  {renderNavigationLabel ? (
300
313
  callRenderProp(renderNavigationLabel)
301
314
  ) : (
302
- <span>
315
+ <div aria-live="polite" aria-atomic="true">
303
316
  <div>{visibleMonth.format('MMMM')}</div>
304
317
  {!withYearPicker ? (
305
318
  <div>{visibleMonth.format('YYYY')}</div>
306
319
  ) : null}
307
- </span>
320
+ </div>
308
321
  )}
309
322
  {nextButton &&
310
323
  cloneButton(nextButton, this.handleMonthChange('next'))}
@@ -98,19 +98,23 @@ type CalendarOwnProps = {
98
98
  */
99
99
  renderNavigationLabel?: Renderable
100
100
  /**
101
- * A button to render in the navigation header. The recommendation is to
102
- * compose it with the [IconButton](IconButton) component by setting the `size`
103
- * prop to `small`, `withBorder` and `withBackground` to `false`, and setting
104
- * `renderIcon` to [IconArrowOpenEnd](icons).
105
- */
106
- renderNextMonthButton?: Renderable
107
- /**
108
- * A button to render in the navigation header. The recommendation is to
109
- * compose it with the [IconButton](Button) component by setting the `size`
110
- * prop to `small`, `withBorder` and `withBackground` to `false`, and setting
111
- * `renderIcon` to [IconArrowOpenStart](icons).
112
- */
113
- renderPrevMonthButton?: Renderable
101
+ * A button to render in the navigation header.
102
+ * When passed as a function, receives `{ targetMonthSrLabel }` —
103
+ * a pre-formatted screen reader label for the target month (e.g. "November 2023").
104
+ * The recommendation is to compose it with the [IconButton](Button) component
105
+ * by setting the `size` prop to `small`, `withBorder` and `withBackground` to `false`,
106
+ * and setting `renderIcon` to [IconArrowOpenStart](icons).
107
+ */
108
+ renderNextMonthButton?: Renderable<{ targetMonthSrLabel: string }>
109
+ /**
110
+ * A button to render in the navigation header.
111
+ * When passed as a function, receives `{ targetMonthSrLabel }` —
112
+ * a pre-formatted screen reader label for the target month (e.g. "November 2023").
113
+ * The recommendation is to compose it with the [IconButton](Button) component
114
+ * by setting the `size` prop to `small`, `withBorder` and `withBackground` to `false`,
115
+ * and setting `renderIcon` to [IconArrowOpenStart](icons).
116
+ */
117
+ renderPrevMonthButton?: Renderable<{ targetMonthSrLabel: string }>
114
118
  /**
115
119
  * An array of labels containing the name of each day of the week. The visible
116
120
  * portion of the label should be abbreviated (no longer than three characters).
@@ -174,23 +174,26 @@ type: example
174
174
 
175
175
  const date = parseDate(renderedDate)
176
176
 
177
- const buttonProps = (type = 'prev') => ({
178
- size: 'small',
179
- withBackground: false,
180
- withBorder: false,
181
- renderIcon:
182
- type === 'prev' ? (
183
- <ChevronLeftInstUIIcon color="baseColor" />
184
- ) : (
185
- <ChevronRightInstUIIcon color="baseColor" />
186
- ),
187
- screenReaderLabel: type === 'prev' ? 'Previous month' : 'Next month'
188
- })
177
+ const renderMonthButton = (type = 'prev') => ({ targetMonthSrLabel }) => (
178
+ <IconButton
179
+ size="small"
180
+ withBackground={false}
181
+ withBorder={false}
182
+ renderIcon={
183
+ type === 'prev' ? (
184
+ <ChevronLeftInstUIIcon color="baseColor" />
185
+ ) : (
186
+ <ChevronRightInstUIIcon color="baseColor" />
187
+ )
188
+ }
189
+ screenReaderLabel={`${type === 'prev' ? 'Previous month' : 'Next month'}, ${targetMonthSrLabel}`}
190
+ />
191
+ )
189
192
 
190
193
  return (
191
194
  <Calendar
192
- renderPrevMonthButton={<IconButton {...buttonProps('prev')} />}
193
- renderNextMonthButton={<IconButton {...buttonProps('next')} />}
195
+ renderPrevMonthButton={renderMonthButton('prev')}
196
+ renderNextMonthButton={renderMonthButton('next')}
194
197
  renderNavigationLabel={
195
198
  <span>
196
199
  <div>{date.format('MMMM')}</div>
@@ -251,7 +254,4 @@ the abbreviation. ex. `[<AccessibleContent alt="Sunday">Sun</AccessibleContent>,
251
254
 
252
255
  #### Rendering next and previous month buttons
253
256
 
254
- The `renderNextMonthButton` and `renderPrevMonthButton` can be supplied using the
255
- [IconButton](IconButton) component with the `size` prop set to
256
- `small`, the `withBackground` and `withBorder` props both set to `false`, and the `renderIcon` prop set to `ChevronLeftInstUIIcon` or
257
- `ChevronRightInstUIIcon`.
257
+ The `renderNextMonthButton` and `renderPrevMonthButton` can be supplied as a function that receives `{ targetMonthSrLabel }` — a pre-formatted screen reader label for the target month (e.g. "November 2023"). Use this to compose an [IconButton](IconButton) component with the `size` prop set to `small`, the `withBackground` and `withBorder` props both set to `false`, and the `renderIcon` prop set to `ChevronLeftInstUIIcon` or `ChevronRightInstUIIcon`. The `screenReaderLabel` should include the target month for better accessibility: e.g. "Previous month, November 2023".
@@ -174,28 +174,41 @@ class Calendar extends Component<CalendarProps, CalendarState> {
174
174
 
175
175
  renderMonthNavigationButtons = () => {
176
176
  const { renderNextMonthButton, renderPrevMonthButton } = this.props
177
+ const { visibleMonth } = this.state
178
+ const prevMonthName = visibleMonth
179
+ .clone()
180
+ .subtract({ months: 1 })
181
+ .format('MMMM YYYY')
182
+ const nextMonthName = visibleMonth
183
+ .clone()
184
+ .add({ months: 1 })
185
+ .format('MMMM YYYY')
177
186
 
178
187
  return {
179
188
  prevButton: renderPrevMonthButton ? (
180
- callRenderProp(renderPrevMonthButton)
189
+ callRenderProp(renderPrevMonthButton, {
190
+ targetMonthSrLabel: prevMonthName
191
+ })
181
192
  ) : (
182
193
  <IconButton
183
194
  size="small"
184
195
  withBackground={false}
185
196
  withBorder={false}
186
197
  renderIcon={<ChevronLeftInstUIIcon color="baseColor" />}
187
- screenReaderLabel="Previous month"
198
+ screenReaderLabel={`Previous month, ${prevMonthName}`}
188
199
  />
189
200
  ),
190
201
  nextButton: renderNextMonthButton ? (
191
- callRenderProp(renderNextMonthButton)
202
+ callRenderProp(renderNextMonthButton, {
203
+ targetMonthSrLabel: nextMonthName
204
+ })
192
205
  ) : (
193
206
  <IconButton
194
207
  size="small"
195
208
  withBackground={false}
196
209
  withBorder={false}
197
210
  renderIcon={<ChevronRightInstUIIcon color="baseColor" />}
198
- screenReaderLabel="Next month"
211
+ screenReaderLabel={`Next month, ${nextMonthName}`}
199
212
  />
200
213
  )
201
214
  }
@@ -298,12 +311,16 @@ class Calendar extends Component<CalendarProps, CalendarState> {
298
311
  {renderNavigationLabel ? (
299
312
  callRenderProp(renderNavigationLabel)
300
313
  ) : (
301
- <span>
302
- <div>{visibleMonth.format('MMMM')}</div>
314
+ <h2
315
+ aria-live="polite"
316
+ aria-atomic="true"
317
+ css={styles?.navigationLabel}
318
+ >
319
+ <span>{visibleMonth.format('MMMM')}</span>
303
320
  {!withYearPicker ? (
304
- <div>{visibleMonth.format('YYYY')}</div>
321
+ <span>{visibleMonth.format('YYYY')}</span>
305
322
  ) : null}
306
- </span>
323
+ </h2>
307
324
  )}
308
325
  {nextButton &&
309
326
  cloneButton(nextButton, this.handleMonthChange('next'))}
@@ -312,7 +329,7 @@ class Calendar extends Component<CalendarProps, CalendarState> {
312
329
  {withYearPicker ? (
313
330
  <div css={styles?.yearPicker}>
314
331
  <SimpleSelect
315
- width="90px"
332
+ width="95px"
316
333
  renderLabel=""
317
334
  placeholder="--"
318
335
  assistiveText={withYearPicker.screenReaderLabel}
@@ -98,19 +98,23 @@ type CalendarOwnProps = {
98
98
  */
99
99
  renderNavigationLabel?: Renderable
100
100
  /**
101
- * A button to render in the navigation header. The recommendation is to
102
- * compose it with the [IconButton](IconButton) component by setting the `size`
103
- * prop to `small`, `withBorder` and `withBackground` to `false`, and setting
104
- * `renderIcon` to [IconArrowOpenEnd](icons).
101
+ * A button to render in the navigation header.
102
+ * When passed as a function, receives `{ targetMonthSrLabel }` —
103
+ * a pre-formatted screen reader label for the target month (e.g. "January 2024").
104
+ * The recommendation is to compose it with the [IconButton](Button) component
105
+ * by setting the `size` prop to `small`, `withBorder` and `withBackground` to `false`,
106
+ * and setting `renderIcon` to [IconArrowOpenEnd](icons).
105
107
  */
106
- renderNextMonthButton?: Renderable
108
+ renderNextMonthButton?: Renderable<{ targetMonthSrLabel: string }>
107
109
  /**
108
- * A button to render in the navigation header. The recommendation is to
109
- * compose it with the [IconButton](Button) component by setting the `size`
110
- * prop to `small`, `withBorder` and `withBackground` to `false`, and setting
111
- * `renderIcon` to [IconArrowOpenStart](icons).
110
+ * A button to render in the navigation header.
111
+ * When passed as a function, receives `{ targetMonthSrLabel }` —
112
+ * a pre-formatted screen reader label for the target month (e.g. "November 2023").
113
+ * The recommendation is to compose it with the [IconButton](Button) component
114
+ * by setting the `size` prop to `small`, `withBorder` and `withBackground` to `false`,
115
+ * and setting `renderIcon` to [IconArrowOpenStart](icons).
112
116
  */
113
- renderPrevMonthButton?: Renderable
117
+ renderPrevMonthButton?: Renderable<{ targetMonthSrLabel: string }>
114
118
  /**
115
119
  * An array of labels containing the name of each day of the week. The visible
116
120
  * portion of the label should be abbreviated (no longer than three characters).
@@ -182,7 +186,11 @@ type CalendarProps = CalendarOwnProps &
182
186
  WithDeterministicIdProps
183
187
 
184
188
  type CalendarStyle = ComponentStyle<
185
- 'navigation' | 'navigationWithButtons' | 'weekdayHeader' | 'yearPicker'
189
+ | 'navigation'
190
+ | 'navigationWithButtons'
191
+ | 'navigationLabel'
192
+ | 'weekdayHeader'
193
+ | 'yearPicker'
186
194
  >
187
195
  const allowedProps: AllowedPropKeys = [
188
196
  'as',
@@ -63,6 +63,15 @@ const generateStyle = (
63
63
  maxWidth: componentTheme.maxHeaderWidth,
64
64
  lineHeight: componentTheme.lineHeight
65
65
  },
66
+ navigationLabel: {
67
+ label: 'calendar__navigation-label',
68
+ margin: 0,
69
+ fontSize: 'inherit',
70
+ fontWeight: 'inherit',
71
+ '& span': {
72
+ display: 'block'
73
+ }
74
+ },
66
75
  yearPicker: {
67
76
  display: 'flex',
68
77
  justifyContent: 'center',