@instructure/ui-tabs 11.7.10 → 11.7.11-snapshot-4
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 +7 -0
- package/es/Tabs/v2/Tab/index.js +21 -2
- package/es/Tabs/v2/Tab/props.js +1 -1
- package/es/Tabs/v2/index.js +195 -85
- package/es/Tabs/v2/props.js +1 -1
- package/lib/Tabs/v2/Tab/index.js +21 -2
- package/lib/Tabs/v2/Tab/props.js +1 -1
- package/lib/Tabs/v2/index.js +194 -84
- package/lib/Tabs/v2/props.js +1 -1
- package/package.json +16 -16
- package/src/Tabs/v2/README.md +57 -1
- package/src/Tabs/v2/Tab/index.tsx +16 -2
- package/src/Tabs/v2/Tab/props.ts +7 -0
- package/src/Tabs/v2/index.tsx +227 -124
- package/src/Tabs/v2/props.ts +10 -1
- package/tsconfig.build.tsbuildinfo +1 -1
- package/types/Tabs/v2/Tab/index.d.ts +6 -0
- package/types/Tabs/v2/Tab/index.d.ts.map +1 -1
- package/types/Tabs/v2/Tab/props.d.ts +5 -0
- package/types/Tabs/v2/Tab/props.d.ts.map +1 -1
- package/types/Tabs/v2/index.d.ts +25 -13
- package/types/Tabs/v2/index.d.ts.map +1 -1
- package/types/Tabs/v2/props.d.ts +8 -0
- package/types/Tabs/v2/props.d.ts.map +1 -1
package/lib/Tabs/v2/index.js
CHANGED
|
@@ -16,7 +16,7 @@ var _keycode = _interopRequireDefault(require("keycode"));
|
|
|
16
16
|
var _latest = require("@instructure/ui-view/latest");
|
|
17
17
|
var _uiReactUtils = require("@instructure/ui-react-utils");
|
|
18
18
|
var _console = require("@instructure/console");
|
|
19
|
-
var
|
|
19
|
+
var _uiI18n = require("@instructure/ui-i18n");
|
|
20
20
|
var _uiDomUtils = require("@instructure/ui-dom-utils");
|
|
21
21
|
var _debounce = require("@instructure/debounce");
|
|
22
22
|
var _uiUtils = require("@instructure/ui-utils");
|
|
@@ -26,7 +26,7 @@ var _index = require("./Tab/index.js");
|
|
|
26
26
|
var _index2 = require("./Panel/index.js");
|
|
27
27
|
var _props = require("./props.js");
|
|
28
28
|
var _jsxRuntime = require("@emotion/react/jsx-runtime");
|
|
29
|
-
var _dec, _dec2, _class;
|
|
29
|
+
var _dec, _dec2, _dec3, _class;
|
|
30
30
|
/*
|
|
31
31
|
* The MIT License (MIT)
|
|
32
32
|
*
|
|
@@ -55,19 +55,19 @@ var _dec, _dec2, _class;
|
|
|
55
55
|
category: components
|
|
56
56
|
---
|
|
57
57
|
**/
|
|
58
|
-
let Tabs = exports.Tabs = (_dec = (0, _uiReactUtils.withDeterministicId)(), _dec2 = (0, _emotion.withStyleNew)(_styles.default), _dec(_class = _dec2(_class = class Tabs extends _react.Component {
|
|
58
|
+
let Tabs = exports.Tabs = (_dec = (0, _uiReactUtils.withDeterministicId)(), _dec2 = (0, _emotion.withStyleNew)(_styles.default), _dec3 = (0, _uiI18n.textDirectionContextConsumer)(), _dec(_class = _dec2(_class = _dec3(_class = class Tabs extends _react.Component {
|
|
59
59
|
static displayName = 'Tabs';
|
|
60
60
|
static componentId = 'Tabs';
|
|
61
61
|
static allowedProps = _props.allowedProps;
|
|
62
62
|
static defaultProps = {
|
|
63
63
|
variant: 'default',
|
|
64
64
|
shouldFocusOnRender: false,
|
|
65
|
-
tabOverflow: 'stack'
|
|
65
|
+
tabOverflow: 'stack',
|
|
66
|
+
activationMode: 'auto'
|
|
66
67
|
};
|
|
67
68
|
static Panel = _index2.Panel;
|
|
68
69
|
static Tab = _index.Tab;
|
|
69
70
|
_tabList = null;
|
|
70
|
-
_focusable = null;
|
|
71
71
|
_tabListPosition;
|
|
72
72
|
_debounced;
|
|
73
73
|
_resizeListener;
|
|
@@ -197,26 +197,74 @@ let Tabs = exports.Tabs = (_dec = (0, _uiReactUtils.withDeterministicId)(), _dec
|
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
|
+
get isRtl() {
|
|
201
|
+
return this.props.dir === _uiI18n.textDirectionContextConsumer.DIRECTION.rtl;
|
|
202
|
+
}
|
|
200
203
|
handleTabClick = (event, {
|
|
201
204
|
index
|
|
202
205
|
}) => {
|
|
203
|
-
const
|
|
204
|
-
|
|
206
|
+
const target = this.getNextTab(this.getTabs(), index, 0);
|
|
207
|
+
if (target) {
|
|
208
|
+
this.fireOnChange(event, target);
|
|
209
|
+
}
|
|
205
210
|
};
|
|
206
211
|
handleTabKeyDown = (event, {
|
|
207
212
|
index
|
|
208
213
|
}) => {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
214
|
+
const tabs = this.getTabs();
|
|
215
|
+
let target;
|
|
216
|
+
let isActivation = false;
|
|
217
|
+
switch (event.keyCode) {
|
|
218
|
+
case _keycode.default.codes.left:
|
|
219
|
+
target = this.getNextTab(tabs, index, this.isRtl ? 1 : -1);
|
|
220
|
+
break;
|
|
221
|
+
case _keycode.default.codes.right:
|
|
222
|
+
target = this.getNextTab(tabs, index, this.isRtl ? -1 : 1);
|
|
223
|
+
break;
|
|
224
|
+
case _keycode.default.codes.up:
|
|
225
|
+
target = this.getNextTab(tabs, index, -1);
|
|
226
|
+
break;
|
|
227
|
+
case _keycode.default.codes.down:
|
|
228
|
+
target = this.getNextTab(tabs, index, 1);
|
|
229
|
+
break;
|
|
230
|
+
case _keycode.default.codes.home:
|
|
231
|
+
target = this.getEdgeTab(tabs, 'first');
|
|
232
|
+
break;
|
|
233
|
+
case _keycode.default.codes.end:
|
|
234
|
+
target = this.getEdgeTab(tabs, 'last');
|
|
235
|
+
break;
|
|
236
|
+
case _keycode.default.codes.enter:
|
|
237
|
+
case _keycode.default.codes.space:
|
|
238
|
+
target = this.getNextTab(tabs, index, 0);
|
|
239
|
+
isActivation = true;
|
|
240
|
+
break;
|
|
241
|
+
default:
|
|
242
|
+
return;
|
|
216
243
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
244
|
+
event.stopPropagation();
|
|
245
|
+
event.preventDefault();
|
|
246
|
+
if (!target) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (isActivation || this.props.activationMode !== 'manual') {
|
|
250
|
+
this.fireOnChange(event, target);
|
|
251
|
+
}
|
|
252
|
+
this.focusTab(target.tabDomId);
|
|
253
|
+
};
|
|
254
|
+
handleTabFocus = (_event, {
|
|
255
|
+
id
|
|
256
|
+
}) => {
|
|
257
|
+
if (this.state.focusedTabId !== id) {
|
|
258
|
+
this.setState({
|
|
259
|
+
focusedTabId: id
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
handleTabListBlur = event => {
|
|
264
|
+
if (!(0, _uiDomUtils.contains)(event.currentTarget, event.relatedTarget)) {
|
|
265
|
+
this.setState({
|
|
266
|
+
focusedTabId: undefined
|
|
267
|
+
});
|
|
220
268
|
}
|
|
221
269
|
};
|
|
222
270
|
handleResize = () => {
|
|
@@ -225,67 +273,129 @@ let Tabs = exports.Tabs = (_dec = (0, _uiReactUtils.withDeterministicId)(), _dec
|
|
|
225
273
|
});
|
|
226
274
|
this._tabListPosition = (0, _uiDomUtils.getBoundingClientRect)(this._tabList);
|
|
227
275
|
};
|
|
228
|
-
|
|
229
|
-
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* The single source of truth for tab order, ids and state.
|
|
279
|
+
*/
|
|
280
|
+
getTabs() {
|
|
281
|
+
const panels = [];
|
|
282
|
+
_react.Children.toArray(this.props.children).forEach((child, index) => {
|
|
283
|
+
if ((0, _uiReactUtils.matchComponentTypes)(child, [_index2.Panel])) {
|
|
284
|
+
panels.push({
|
|
285
|
+
index,
|
|
286
|
+
panel: child
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
const explicitlySelected = panels.find(({
|
|
291
|
+
panel
|
|
292
|
+
}) => panel.props.isSelected && !panel.props.isDisabled);
|
|
293
|
+
const fallbackIndex = explicitlySelected ? explicitlySelected.index : 0;
|
|
294
|
+
return panels.map(({
|
|
295
|
+
index,
|
|
296
|
+
panel
|
|
297
|
+
}) => {
|
|
298
|
+
const baseId = panel.props.id || this.props.deterministicId(`Tabs_${index}`);
|
|
299
|
+
const isDisabled = !!panel.props.isDisabled;
|
|
300
|
+
return {
|
|
301
|
+
index,
|
|
302
|
+
tabDomId: `tab-${baseId}`,
|
|
303
|
+
panelDomId: panel.props.id || `panel-${baseId}`,
|
|
304
|
+
isDisabled,
|
|
305
|
+
isSelected: !isDisabled && (!!panel.props.isSelected || index === fallbackIndex),
|
|
306
|
+
panel
|
|
307
|
+
};
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
getNextTab(tabs, startIndex, step) {
|
|
230
311
|
const count = tabs.length;
|
|
231
|
-
const
|
|
232
|
-
(0, _console.logError)(
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
312
|
+
const from = tabs.findIndex(tab => tab.index === startIndex);
|
|
313
|
+
(0, _console.logError)(from >= 0, `[Tabs] Invalid tab index: '${startIndex}'.`);
|
|
314
|
+
if (from < 0) {
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
317
|
+
if (step === 0) {
|
|
318
|
+
return tabs[from];
|
|
319
|
+
}
|
|
320
|
+
for (let offset = 1; offset <= count; offset++) {
|
|
321
|
+
const candidate = tabs[((from + step * offset) % count + count) % count];
|
|
322
|
+
if (!candidate.isDisabled) {
|
|
323
|
+
return candidate;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return undefined;
|
|
327
|
+
}
|
|
328
|
+
getEdgeTab(tabs, edge) {
|
|
329
|
+
const enabled = tabs.filter(tab => !tab.isDisabled);
|
|
330
|
+
return edge === 'first' ? enabled[0] : enabled[enabled.length - 1];
|
|
331
|
+
}
|
|
332
|
+
getRovingTabId(tabs) {
|
|
333
|
+
const {
|
|
334
|
+
focusedTabId
|
|
335
|
+
} = this.state;
|
|
336
|
+
const focused = tabs.find(tab => tab.tabDomId === focusedTabId && !tab.isDisabled);
|
|
337
|
+
return focused?.tabDomId ?? tabs.find(tab => tab.isSelected)?.tabDomId ?? tabs.find(tab => !tab.isDisabled)?.tabDomId;
|
|
244
338
|
}
|
|
245
339
|
fireOnChange(event, {
|
|
246
340
|
index,
|
|
247
|
-
|
|
341
|
+
panel,
|
|
342
|
+
tabDomId
|
|
248
343
|
}) {
|
|
249
344
|
if (typeof this.props.onRequestTabChange === 'function') {
|
|
250
345
|
this.props.onRequestTabChange(event, {
|
|
251
346
|
index,
|
|
252
|
-
id
|
|
347
|
+
id: panel.props.id
|
|
253
348
|
});
|
|
254
349
|
}
|
|
255
|
-
|
|
256
|
-
|
|
350
|
+
this.scrollTabIntoView(tabDomId);
|
|
351
|
+
}
|
|
352
|
+
scrollTabIntoView(tabDomId) {
|
|
257
353
|
// one "tick" later than the keypress
|
|
258
354
|
setTimeout(() => {
|
|
259
355
|
if (this.state.withTabListOverflow) {
|
|
260
|
-
|
|
261
|
-
this.showActiveTabIfOverlayed(tab);
|
|
356
|
+
this.showActiveTabIfOverlayed(this._tabList.querySelector(`#${CSS.escape(tabDomId)}`));
|
|
262
357
|
}
|
|
263
358
|
}, 0);
|
|
264
359
|
}
|
|
265
|
-
createTab(
|
|
266
|
-
const
|
|
360
|
+
createTab(tab, isTabbable) {
|
|
361
|
+
const {
|
|
362
|
+
index,
|
|
363
|
+
tabDomId,
|
|
364
|
+
panelDomId,
|
|
365
|
+
isSelected,
|
|
366
|
+
isDisabled,
|
|
367
|
+
panel
|
|
368
|
+
} = tab;
|
|
267
369
|
return (0, _jsxRuntime.jsx)(_index.Tab, {
|
|
268
370
|
variant: this.props.variant,
|
|
269
|
-
id:
|
|
270
|
-
controls:
|
|
371
|
+
id: tabDomId,
|
|
372
|
+
controls: panelDomId,
|
|
271
373
|
index: index,
|
|
272
|
-
isSelected:
|
|
273
|
-
isDisabled:
|
|
374
|
+
isSelected: isSelected,
|
|
375
|
+
isDisabled: isDisabled,
|
|
376
|
+
isTabbable: isTabbable,
|
|
274
377
|
onClick: this.handleTabClick,
|
|
275
378
|
onKeyDown: this.handleTabKeyDown,
|
|
379
|
+
onFocus: this.handleTabFocus,
|
|
276
380
|
isOverflowScroll: this.props.tabOverflow === 'scroll',
|
|
277
381
|
children: panel.props.renderTitle
|
|
278
382
|
}, `tab-${index}`);
|
|
279
383
|
}
|
|
280
|
-
clonePanel(
|
|
281
|
-
const
|
|
384
|
+
clonePanel(tab, activePanel) {
|
|
385
|
+
const {
|
|
386
|
+
index,
|
|
387
|
+
tabDomId,
|
|
388
|
+
panelDomId,
|
|
389
|
+
isSelected,
|
|
390
|
+
panel
|
|
391
|
+
} = tab;
|
|
282
392
|
|
|
283
393
|
// fixHeight can be 0, so simply `fixheight` could return falsy value
|
|
284
394
|
const hasFixedHeight = typeof this.props.fixHeight !== 'undefined';
|
|
285
395
|
const commonProps = {
|
|
286
|
-
id:
|
|
287
|
-
labelledBy:
|
|
288
|
-
isSelected
|
|
396
|
+
id: panelDomId,
|
|
397
|
+
labelledBy: tabDomId,
|
|
398
|
+
isSelected,
|
|
289
399
|
variant: this.props.variant,
|
|
290
400
|
maxHeight: !hasFixedHeight ? this.props.maxHeight : undefined,
|
|
291
401
|
minHeight: !hasFixedHeight ? this.props.minHeight : '100%'
|
|
@@ -311,14 +421,19 @@ let Tabs = exports.Tabs = (_dec = (0, _uiReactUtils.withDeterministicId)(), _dec
|
|
|
311
421
|
});
|
|
312
422
|
}
|
|
313
423
|
}
|
|
314
|
-
handleFocusableRef = el => {
|
|
315
|
-
this._focusable = el;
|
|
316
|
-
};
|
|
317
424
|
handleTabListRef = el => {
|
|
318
425
|
this._tabList = el;
|
|
319
426
|
};
|
|
320
427
|
focus() {
|
|
321
|
-
this.
|
|
428
|
+
this.focusTab(this.getRovingTabId(this.getTabs()));
|
|
429
|
+
}
|
|
430
|
+
focusTab(tabDomId) {
|
|
431
|
+
if (!tabDomId) {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
const tab = this._tabList?.querySelector(`#${CSS.escape(tabDomId)}`);
|
|
435
|
+
tab?.focus();
|
|
436
|
+
this.scrollTabIntoView(tabDomId);
|
|
322
437
|
}
|
|
323
438
|
handleScroll = event => {
|
|
324
439
|
if (this.props.tabOverflow !== 'scroll' || !this.state.withTabListOverflow) {
|
|
@@ -346,27 +461,24 @@ let Tabs = exports.Tabs = (_dec = (0, _uiReactUtils.withDeterministicId)(), _dec
|
|
|
346
461
|
onRequestTabChange,
|
|
347
462
|
tabOverflow,
|
|
348
463
|
styles,
|
|
464
|
+
dir,
|
|
349
465
|
...props
|
|
350
466
|
} = this.props;
|
|
351
|
-
const
|
|
467
|
+
const tabDescriptors = this.getTabs();
|
|
468
|
+
const rovingTabId = this.getRovingTabId(tabDescriptors);
|
|
469
|
+
const tabsByIndex = new Map(tabDescriptors.map(tab => [tab.index, tab]));
|
|
470
|
+
const activePanels = tabDescriptors.map(tab => tab.panel).filter(panel => panel.props.active);
|
|
352
471
|
if (activePanels.length > 1) {
|
|
353
472
|
(0, _console.logError)(false, `[Tabs] Only one Panel can be marked as active.`);
|
|
354
473
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
if ((0, _uiReactUtils.matchComponentTypes)(child, [_index2.Panel])) {
|
|
359
|
-
const selected = !child.props.isDisabled && (child.props.isSelected || selectedIndex === index);
|
|
360
|
-
const id = this.props.deterministicId(`Tabs_${index}`);
|
|
361
|
-
tabs.push(this.createTab(index, id, selected, child));
|
|
362
|
-
if (activePanels.length === 1) {
|
|
363
|
-
panels.push(this.clonePanel(index, id, selected, child, activePanels[0]));
|
|
364
|
-
} else {
|
|
365
|
-
panels.push(this.clonePanel(index, id, selected, child));
|
|
366
|
-
}
|
|
367
|
-
} else {
|
|
474
|
+
_react.Children.toArray(children).forEach((child, index) => {
|
|
475
|
+
const tab = tabsByIndex.get(index);
|
|
476
|
+
if (!tab) {
|
|
368
477
|
panels.push(child);
|
|
478
|
+
return;
|
|
369
479
|
}
|
|
480
|
+
tabs.push(this.createTab(tab, tab.tabDomId === rovingTabId));
|
|
481
|
+
panels.push(this.clonePanel(tab, activePanels.length === 1 ? activePanels[0] : undefined));
|
|
370
482
|
});
|
|
371
483
|
const withScrollFade = tabOverflow === 'scroll' && this.state.withTabListOverflow;
|
|
372
484
|
|
|
@@ -385,23 +497,21 @@ let Tabs = exports.Tabs = (_dec = (0, _uiReactUtils.withDeterministicId)(), _dec
|
|
|
385
497
|
as: "div",
|
|
386
498
|
css: styles?.container,
|
|
387
499
|
"data-cid": "Tabs",
|
|
388
|
-
children: [(0, _jsxRuntime.jsx)(
|
|
389
|
-
|
|
390
|
-
|
|
500
|
+
children: [(0, _jsxRuntime.jsx)(_latest.View, {
|
|
501
|
+
as: "div",
|
|
502
|
+
position: "relative",
|
|
503
|
+
borderRadius: "medium",
|
|
504
|
+
shouldAnimateFocus: false,
|
|
505
|
+
css: styles?.tabs,
|
|
506
|
+
children: (0, _jsxRuntime.jsxs)(_latest.View, {
|
|
391
507
|
as: "div",
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
css: styles?.tabList,
|
|
400
|
-
"aria-label": screenReaderLabel,
|
|
401
|
-
elementRef: this.handleTabListRef,
|
|
402
|
-
onScroll: this.handleScroll,
|
|
403
|
-
children: [tabs, withScrollFade && startScrollOverlay, withScrollFade && endScrollOverlay]
|
|
404
|
-
})
|
|
508
|
+
role: "tablist",
|
|
509
|
+
css: styles?.tabList,
|
|
510
|
+
"aria-label": screenReaderLabel,
|
|
511
|
+
elementRef: this.handleTabListRef,
|
|
512
|
+
onScroll: this.handleScroll,
|
|
513
|
+
onBlur: this.handleTabListBlur,
|
|
514
|
+
children: [tabs, withScrollFade && startScrollOverlay, withScrollFade && endScrollOverlay]
|
|
405
515
|
})
|
|
406
516
|
}), (0, _jsxRuntime.jsx)("div", {
|
|
407
517
|
css: styles?.panelsContainer,
|
|
@@ -409,5 +519,5 @@ let Tabs = exports.Tabs = (_dec = (0, _uiReactUtils.withDeterministicId)(), _dec
|
|
|
409
519
|
})]
|
|
410
520
|
});
|
|
411
521
|
}
|
|
412
|
-
}) || _class) || _class);
|
|
522
|
+
}) || _class) || _class) || _class);
|
|
413
523
|
var _default = exports.default = Tabs;
|
package/lib/Tabs/v2/props.js
CHANGED
|
@@ -28,4 +28,4 @@ exports.allowedProps = void 0;
|
|
|
28
28
|
* SOFTWARE.
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
-
const allowedProps = exports.allowedProps = ['children', 'variant', 'screenReaderLabel', 'onRequestTabChange', 'maxWidth', 'maxHeight', 'minHeight', 'fixHeight', 'margin', 'padding', 'textAlign', 'elementRef', 'tabOverflow', 'shouldFocusOnRender'];
|
|
31
|
+
const allowedProps = exports.allowedProps = ['children', 'variant', 'screenReaderLabel', 'onRequestTabChange', 'maxWidth', 'maxHeight', 'minHeight', 'fixHeight', 'margin', 'padding', 'textAlign', 'elementRef', 'tabOverflow', 'shouldFocusOnRender', 'activationMode'];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@instructure/ui-tabs",
|
|
3
|
-
"version": "11.7.
|
|
3
|
+
"version": "11.7.11-snapshot-4",
|
|
4
4
|
"description": "A UI component library made by Instructure Inc.",
|
|
5
5
|
"author": "Instructure, Inc. Engineering and Product Design",
|
|
6
6
|
"module": "./es/index.js",
|
|
@@ -15,24 +15,24 @@
|
|
|
15
15
|
"license": "MIT",
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@babel/runtime": "^7.29.7",
|
|
18
|
-
"@instructure/console": "11.7.
|
|
19
|
-
"@instructure/debounce": "11.7.
|
|
20
|
-
"@instructure/emotion": "11.7.
|
|
21
|
-
"@instructure/shared-types": "11.7.
|
|
22
|
-
"@instructure/ui-dom-utils": "11.7.
|
|
23
|
-
"@instructure/ui-focusable": "11.7.
|
|
24
|
-
"@instructure/ui-i18n": "11.7.
|
|
25
|
-
"@instructure/ui-motion": "11.7.
|
|
26
|
-
"@instructure/ui-react-utils": "11.7.
|
|
27
|
-
"@instructure/ui-themes": "11.7.
|
|
28
|
-
"@instructure/ui-utils": "11.7.
|
|
29
|
-
"@instructure/ui-view": "11.7.
|
|
18
|
+
"@instructure/console": "11.7.11-snapshot-4",
|
|
19
|
+
"@instructure/debounce": "11.7.11-snapshot-4",
|
|
20
|
+
"@instructure/emotion": "11.7.11-snapshot-4",
|
|
21
|
+
"@instructure/shared-types": "11.7.11-snapshot-4",
|
|
22
|
+
"@instructure/ui-dom-utils": "11.7.11-snapshot-4",
|
|
23
|
+
"@instructure/ui-focusable": "11.7.11-snapshot-4",
|
|
24
|
+
"@instructure/ui-i18n": "11.7.11-snapshot-4",
|
|
25
|
+
"@instructure/ui-motion": "11.7.11-snapshot-4",
|
|
26
|
+
"@instructure/ui-react-utils": "11.7.11-snapshot-4",
|
|
27
|
+
"@instructure/ui-themes": "11.7.11-snapshot-4",
|
|
28
|
+
"@instructure/ui-utils": "11.7.11-snapshot-4",
|
|
29
|
+
"@instructure/ui-view": "11.7.11-snapshot-4",
|
|
30
30
|
"keycode": "^2"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@instructure/ui-axe-check": "11.7.
|
|
34
|
-
"@instructure/ui-babel-preset": "11.7.
|
|
35
|
-
"@instructure/ui-color-utils": "11.7.
|
|
33
|
+
"@instructure/ui-axe-check": "11.7.11-snapshot-4",
|
|
34
|
+
"@instructure/ui-babel-preset": "11.7.11-snapshot-4",
|
|
35
|
+
"@instructure/ui-color-utils": "11.7.11-snapshot-4",
|
|
36
36
|
"vitest": "^5.0.0"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
package/src/Tabs/v2/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
describes: Tabs
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
`<Tabs />` is an accessible tabbed navigation component. Use the TAB key to focus the component and arrow keys to navigate between panels of content. To set a default panel that should be selected on initial render, set the `selected` prop on that `<Tabs.Panel>`.
|
|
5
|
+
`<Tabs />` is an accessible tabbed navigation component. Use the TAB key to focus the component and arrow keys to navigate between panels of content. Home and End jump to the first and last tab, and the arrow keys are mirrored in right-to-left text direction. By default a tab is selected as soon as an arrow key reaches it; see the Manual activation section below to require a key press instead. To set a default panel that should be selected on initial render, set the `selected` prop on that `<Tabs.Panel>`.
|
|
6
6
|
|
|
7
7
|
```js
|
|
8
8
|
---
|
|
@@ -533,6 +533,62 @@ const Example = () => {
|
|
|
533
533
|
render(<Example />)
|
|
534
534
|
```
|
|
535
535
|
|
|
536
|
+
### Manual activation
|
|
537
|
+
|
|
538
|
+
Set `activationMode="manual"` to separate the two: the arrow keys, Home
|
|
539
|
+
and End only move focus, and Enter or Space selects the focused tab.
|
|
540
|
+
|
|
541
|
+
Use manual activation for expensive panels. This is also important for VoiceOver, which reserves arrow keys for its cursor, while Enter and Space work across screen readers.
|
|
542
|
+
|
|
543
|
+
```js
|
|
544
|
+
---
|
|
545
|
+
type: example
|
|
546
|
+
---
|
|
547
|
+
const Example = () => {
|
|
548
|
+
const [selectedIndex, setSelectedIndex] = useState(0)
|
|
549
|
+
|
|
550
|
+
const handleTabChange = (event, { index }) => {
|
|
551
|
+
setSelectedIndex(index)
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
return (
|
|
555
|
+
<Tabs
|
|
556
|
+
activationMode="manual"
|
|
557
|
+
margin="general.space2xl auto"
|
|
558
|
+
padding="general.spaceXl"
|
|
559
|
+
onRequestTabChange={handleTabChange}
|
|
560
|
+
>
|
|
561
|
+
<Tabs.Panel
|
|
562
|
+
id="manualA"
|
|
563
|
+
renderTitle="First Tab"
|
|
564
|
+
isSelected={selectedIndex === 0}
|
|
565
|
+
tabIndex={0}
|
|
566
|
+
>
|
|
567
|
+
Arrow to another tab, then press Enter or Space to open it.
|
|
568
|
+
</Tabs.Panel>
|
|
569
|
+
<Tabs.Panel
|
|
570
|
+
id="manualB"
|
|
571
|
+
renderTitle="Second Tab"
|
|
572
|
+
isSelected={selectedIndex === 1}
|
|
573
|
+
tabIndex={0}
|
|
574
|
+
>
|
|
575
|
+
Second panel
|
|
576
|
+
</Tabs.Panel>
|
|
577
|
+
<Tabs.Panel
|
|
578
|
+
id="manualC"
|
|
579
|
+
renderTitle="Third Tab"
|
|
580
|
+
isSelected={selectedIndex === 2}
|
|
581
|
+
tabIndex={0}
|
|
582
|
+
>
|
|
583
|
+
Third panel
|
|
584
|
+
</Tabs.Panel>
|
|
585
|
+
</Tabs>
|
|
586
|
+
)
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
render(<Example />)
|
|
590
|
+
```
|
|
591
|
+
|
|
536
592
|
### Guidelines
|
|
537
593
|
|
|
538
594
|
```js
|
|
@@ -86,12 +86,25 @@ class Tab extends Component<TabsTabProps> {
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
handleFocus = (event: React.FocusEvent<ViewOwnProps>) => {
|
|
90
|
+
const { onFocus, index, id, isDisabled } = this.props
|
|
91
|
+
|
|
92
|
+
if (isDisabled) {
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (typeof onFocus === 'function') {
|
|
97
|
+
onFocus(event, { index, id })
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
89
101
|
render() {
|
|
90
102
|
const {
|
|
91
103
|
id,
|
|
92
104
|
variant,
|
|
93
105
|
isSelected,
|
|
94
106
|
isDisabled,
|
|
107
|
+
isTabbable = isSelected,
|
|
95
108
|
controls,
|
|
96
109
|
children,
|
|
97
110
|
styles,
|
|
@@ -106,11 +119,12 @@ class Tab extends Component<TabsTabProps> {
|
|
|
106
119
|
id={id}
|
|
107
120
|
onClick={this.handleClick}
|
|
108
121
|
onKeyDown={this.handleKeyDown}
|
|
122
|
+
onFocus={this.handleFocus}
|
|
109
123
|
css={styles?.tab}
|
|
110
|
-
aria-selected={isSelected ? 'true' :
|
|
124
|
+
aria-selected={isSelected ? 'true' : 'false'}
|
|
111
125
|
aria-disabled={isDisabled ? 'true' : undefined}
|
|
112
126
|
aria-controls={controls}
|
|
113
|
-
tabIndex={
|
|
127
|
+
tabIndex={isDisabled ? undefined : isTabbable ? 0 : -1}
|
|
114
128
|
position="relative"
|
|
115
129
|
focusPosition="offset"
|
|
116
130
|
>
|
package/src/Tabs/v2/Tab/props.ts
CHANGED
|
@@ -35,6 +35,7 @@ type TabsTabOwnProps = {
|
|
|
35
35
|
controls: string
|
|
36
36
|
isDisabled?: boolean
|
|
37
37
|
isSelected?: boolean
|
|
38
|
+
isTabbable?: boolean
|
|
38
39
|
onClick?: (
|
|
39
40
|
event: React.MouseEvent<ViewOwnProps>,
|
|
40
41
|
tabData: { index: number; id: string }
|
|
@@ -43,6 +44,10 @@ type TabsTabOwnProps = {
|
|
|
43
44
|
event: React.KeyboardEvent<ViewOwnProps>,
|
|
44
45
|
tabData: { index: number; id: string }
|
|
45
46
|
) => void
|
|
47
|
+
onFocus?: (
|
|
48
|
+
event: React.FocusEvent<ViewOwnProps>,
|
|
49
|
+
tabData: { index: number; id: string }
|
|
50
|
+
) => void
|
|
46
51
|
children?: Renderable
|
|
47
52
|
/**
|
|
48
53
|
* Whether tabOverflow prop in Tabs is set to 'scroll'.
|
|
@@ -67,8 +72,10 @@ const allowedProps: AllowedPropKeys = [
|
|
|
67
72
|
'controls',
|
|
68
73
|
'isDisabled',
|
|
69
74
|
'isSelected',
|
|
75
|
+
'isTabbable',
|
|
70
76
|
'onClick',
|
|
71
77
|
'onKeyDown',
|
|
78
|
+
'onFocus',
|
|
72
79
|
'children',
|
|
73
80
|
'isOverflowScroll'
|
|
74
81
|
]
|