@instructure/ui-tabs 11.6.0 → 11.6.1-snapshot-129

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 (134) hide show
  1. package/CHANGELOG.md +48 -308
  2. package/es/Tabs/{Panel → v1/Panel}/index.js +2 -2
  3. package/es/Tabs/{Tab → v1/Tab}/index.js +2 -2
  4. package/es/Tabs/{index.js → v1/index.js} +2 -2
  5. package/es/Tabs/v2/Panel/index.js +126 -0
  6. package/es/Tabs/v2/Panel/props.js +26 -0
  7. package/es/Tabs/v2/Panel/styles.js +81 -0
  8. package/es/Tabs/v2/Tab/index.js +118 -0
  9. package/es/Tabs/v2/Tab/props.js +26 -0
  10. package/es/Tabs/v2/Tab/styles.js +145 -0
  11. package/es/Tabs/v2/index.js +396 -0
  12. package/es/Tabs/v2/props.js +26 -0
  13. package/es/Tabs/v2/styles.js +145 -0
  14. package/es/{index.js → exports/a.js} +3 -3
  15. package/es/exports/b.js +26 -0
  16. package/lib/Tabs/v1/Panel/index.js +132 -0
  17. package/lib/Tabs/v1/Tab/index.js +125 -0
  18. package/lib/Tabs/v1/index.js +410 -0
  19. package/lib/Tabs/{Panel → v2/Panel}/index.js +3 -4
  20. package/lib/Tabs/v2/Panel/props.js +31 -0
  21. package/lib/Tabs/v2/Panel/styles.js +87 -0
  22. package/lib/Tabs/{Tab → v2/Tab}/index.js +3 -4
  23. package/lib/Tabs/v2/Tab/props.js +31 -0
  24. package/lib/Tabs/v2/Tab/styles.js +151 -0
  25. package/lib/Tabs/{index.js → v2/index.js} +5 -6
  26. package/lib/Tabs/v2/props.js +31 -0
  27. package/lib/Tabs/v2/styles.js +151 -0
  28. package/lib/{index.js → exports/a.js} +4 -4
  29. package/lib/exports/b.js +26 -0
  30. package/package.json +46 -24
  31. package/src/Tabs/{Panel → v1/Panel}/index.tsx +2 -2
  32. package/src/Tabs/{Tab → v1/Tab}/index.tsx +3 -3
  33. package/src/Tabs/{Tab → v1/Tab}/props.ts +1 -1
  34. package/src/Tabs/{index.tsx → v1/index.tsx} +3 -3
  35. package/src/Tabs/{props.ts → v1/props.ts} +1 -1
  36. package/src/Tabs/v2/Panel/index.tsx +138 -0
  37. package/src/Tabs/v2/Panel/props.ts +100 -0
  38. package/src/Tabs/v2/Panel/styles.ts +92 -0
  39. package/src/Tabs/v2/README.md +559 -0
  40. package/src/Tabs/v2/Tab/index.tsx +123 -0
  41. package/src/Tabs/v2/Tab/props.ts +80 -0
  42. package/src/Tabs/v2/Tab/styles.ts +161 -0
  43. package/src/Tabs/v2/index.tsx +547 -0
  44. package/src/Tabs/v2/props.ts +126 -0
  45. package/src/Tabs/v2/styles.ts +156 -0
  46. package/src/{index.ts → exports/a.ts} +6 -6
  47. package/src/exports/b.ts +31 -0
  48. package/tsconfig.build.tsbuildinfo +1 -1
  49. package/types/Tabs/v1/Panel/index.d.ts.map +1 -0
  50. package/types/Tabs/v1/Panel/props.d.ts.map +1 -0
  51. package/types/Tabs/v1/Panel/styles.d.ts.map +1 -0
  52. package/types/Tabs/v1/Panel/theme.d.ts.map +1 -0
  53. package/types/Tabs/{Tab → v1/Tab}/index.d.ts +1 -1
  54. package/types/Tabs/v1/Tab/index.d.ts.map +1 -0
  55. package/types/Tabs/{Tab → v1/Tab}/props.d.ts +1 -1
  56. package/types/Tabs/v1/Tab/props.d.ts.map +1 -0
  57. package/types/Tabs/v1/Tab/styles.d.ts.map +1 -0
  58. package/types/Tabs/v1/Tab/theme.d.ts.map +1 -0
  59. package/types/Tabs/{index.d.ts → v1/index.d.ts} +1 -1
  60. package/types/Tabs/v1/index.d.ts.map +1 -0
  61. package/types/Tabs/{props.d.ts → v1/props.d.ts} +1 -1
  62. package/types/Tabs/v1/props.d.ts.map +1 -0
  63. package/types/Tabs/v1/styles.d.ts.map +1 -0
  64. package/types/Tabs/v1/theme.d.ts.map +1 -0
  65. package/types/Tabs/v2/Panel/index.d.ts +46 -0
  66. package/types/Tabs/v2/Panel/index.d.ts.map +1 -0
  67. package/types/Tabs/v2/Panel/props.d.ts +46 -0
  68. package/types/Tabs/v2/Panel/props.d.ts.map +1 -0
  69. package/types/Tabs/v2/Panel/styles.d.ts +19 -0
  70. package/types/Tabs/v2/Panel/styles.d.ts.map +1 -0
  71. package/types/Tabs/v2/Tab/index.d.ts +43 -0
  72. package/types/Tabs/v2/Tab/index.d.ts.map +1 -0
  73. package/types/Tabs/v2/Tab/props.d.ts +33 -0
  74. package/types/Tabs/v2/Tab/props.d.ts.map +1 -0
  75. package/types/Tabs/v2/Tab/styles.d.ts +20 -0
  76. package/types/Tabs/v2/Tab/styles.d.ts.map +1 -0
  77. package/types/Tabs/v2/index.d.ts +80 -0
  78. package/types/Tabs/v2/index.d.ts.map +1 -0
  79. package/types/Tabs/v2/props.d.ts +68 -0
  80. package/types/Tabs/v2/props.d.ts.map +1 -0
  81. package/types/Tabs/v2/styles.d.ts +19 -0
  82. package/types/Tabs/v2/styles.d.ts.map +1 -0
  83. package/types/exports/a.d.ts +7 -0
  84. package/types/exports/a.d.ts.map +1 -0
  85. package/types/exports/b.d.ts +7 -0
  86. package/types/exports/b.d.ts.map +1 -0
  87. package/types/Tabs/Panel/index.d.ts.map +0 -1
  88. package/types/Tabs/Panel/props.d.ts.map +0 -1
  89. package/types/Tabs/Panel/styles.d.ts.map +0 -1
  90. package/types/Tabs/Panel/theme.d.ts.map +0 -1
  91. package/types/Tabs/Tab/index.d.ts.map +0 -1
  92. package/types/Tabs/Tab/props.d.ts.map +0 -1
  93. package/types/Tabs/Tab/styles.d.ts.map +0 -1
  94. package/types/Tabs/Tab/theme.d.ts.map +0 -1
  95. package/types/Tabs/index.d.ts.map +0 -1
  96. package/types/Tabs/props.d.ts.map +0 -1
  97. package/types/Tabs/styles.d.ts.map +0 -1
  98. package/types/Tabs/theme.d.ts.map +0 -1
  99. package/types/index.d.ts +0 -7
  100. package/types/index.d.ts.map +0 -1
  101. /package/es/Tabs/{Panel → v1/Panel}/props.js +0 -0
  102. /package/es/Tabs/{Panel → v1/Panel}/styles.js +0 -0
  103. /package/es/Tabs/{Panel → v1/Panel}/theme.js +0 -0
  104. /package/es/Tabs/{Tab → v1/Tab}/props.js +0 -0
  105. /package/es/Tabs/{Tab → v1/Tab}/styles.js +0 -0
  106. /package/es/Tabs/{Tab → v1/Tab}/theme.js +0 -0
  107. /package/es/Tabs/{props.js → v1/props.js} +0 -0
  108. /package/es/Tabs/{styles.js → v1/styles.js} +0 -0
  109. /package/es/Tabs/{theme.js → v1/theme.js} +0 -0
  110. /package/lib/Tabs/{Panel → v1/Panel}/props.js +0 -0
  111. /package/lib/Tabs/{Panel → v1/Panel}/styles.js +0 -0
  112. /package/lib/Tabs/{Panel → v1/Panel}/theme.js +0 -0
  113. /package/lib/Tabs/{Tab → v1/Tab}/props.js +0 -0
  114. /package/lib/Tabs/{Tab → v1/Tab}/styles.js +0 -0
  115. /package/lib/Tabs/{Tab → v1/Tab}/theme.js +0 -0
  116. /package/lib/Tabs/{props.js → v1/props.js} +0 -0
  117. /package/lib/Tabs/{styles.js → v1/styles.js} +0 -0
  118. /package/lib/Tabs/{theme.js → v1/theme.js} +0 -0
  119. /package/src/Tabs/{Panel → v1/Panel}/props.ts +0 -0
  120. /package/src/Tabs/{Panel → v1/Panel}/styles.ts +0 -0
  121. /package/src/Tabs/{Panel → v1/Panel}/theme.ts +0 -0
  122. /package/src/Tabs/{README.md → v1/README.md} +0 -0
  123. /package/src/Tabs/{Tab → v1/Tab}/styles.ts +0 -0
  124. /package/src/Tabs/{Tab → v1/Tab}/theme.ts +0 -0
  125. /package/src/Tabs/{styles.ts → v1/styles.ts} +0 -0
  126. /package/src/Tabs/{theme.ts → v1/theme.ts} +0 -0
  127. /package/types/Tabs/{Panel → v1/Panel}/index.d.ts +0 -0
  128. /package/types/Tabs/{Panel → v1/Panel}/props.d.ts +0 -0
  129. /package/types/Tabs/{Panel → v1/Panel}/styles.d.ts +0 -0
  130. /package/types/Tabs/{Panel → v1/Panel}/theme.d.ts +0 -0
  131. /package/types/Tabs/{Tab → v1/Tab}/styles.d.ts +0 -0
  132. /package/types/Tabs/{Tab → v1/Tab}/theme.d.ts +0 -0
  133. /package/types/Tabs/{styles.d.ts → v1/styles.d.ts} +0 -0
  134. /package/types/Tabs/{theme.d.ts → v1/theme.d.ts} +0 -0
@@ -0,0 +1,559 @@
1
+ ---
2
+ describes: Tabs
3
+ ---
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>`.
6
+
7
+ ```js
8
+ ---
9
+ type: example
10
+ ---
11
+ const Example = () => {
12
+ const [selectedIndex, setSelectedIndex] = useState(0)
13
+
14
+ const handleTabChange = (event, { index }) => {
15
+ setSelectedIndex(index)
16
+ }
17
+
18
+ return (
19
+ <Tabs
20
+ margin="large auto"
21
+ padding="medium"
22
+ onRequestTabChange={handleTabChange}
23
+ >
24
+ <Tabs.Panel
25
+ id="tabA"
26
+ tabIndex={-1}
27
+ renderTitle="Tab A"
28
+ textAlign="center"
29
+ padding="large"
30
+ isSelected={selectedIndex === 0}
31
+ >
32
+ <Button>Focus Me</Button>
33
+ </Tabs.Panel>
34
+ <Tabs.Panel id="tabB" renderTitle="Disabled Tab" isDisabled>
35
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod
36
+ tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
37
+ veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
38
+ commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
39
+ velit esse cillum dolore eu fugiat nulla pariatur.
40
+ </Tabs.Panel>
41
+ <Tabs.Panel
42
+ id="tabC"
43
+ renderTitle="Tab C"
44
+ isSelected={selectedIndex === 2}
45
+ tabIndex={0}
46
+ >
47
+ Sed ut perspiciatis unde omnis iste natus error sit voluptatem
48
+ accusantium doloremque laudantium. Totam rem aperiam, eaque ipsa quae ab
49
+ illo inventore veritatis et quasi architecto beatae vitae dicta sunt
50
+ explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut
51
+ odit aut fugit, sed quia consequuntur magni dolores.
52
+ </Tabs.Panel>
53
+ <Tabs.Panel
54
+ id="tabD"
55
+ renderTitle="Tab D"
56
+ isSelected={selectedIndex === 3}
57
+ tabIndex={0}
58
+ >
59
+ At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis
60
+ praesentium voluptatum deleniti atque corrupti. Quos dolores et quas
61
+ molestias excepturi sint occaecati cupiditate non provident, similique
62
+ sunt in culpa. Qui officia deserunt mollitia animi, id est laborum et
63
+ dolorum fuga.
64
+ </Tabs.Panel>
65
+ </Tabs>
66
+ )
67
+ }
68
+
69
+ render(<Example />)
70
+ ```
71
+
72
+ ### Secondary Tabs
73
+
74
+ ```js
75
+ ---
76
+ type: example
77
+ ---
78
+ const Example = () => {
79
+ const [selectedIndex, setSelectedIndex] = useState(2)
80
+
81
+ const handleTabChange = (event, { index }) => {
82
+ setSelectedIndex(index)
83
+ }
84
+
85
+ return (
86
+ <Tabs
87
+ variant="secondary"
88
+ onRequestTabChange={handleTabChange}
89
+ minHeight="10rem"
90
+ maxHeight="10rem"
91
+ >
92
+ <Tabs.Panel renderTitle="First Tab" isSelected={selectedIndex === 0} tabIndex={0}>
93
+ Hello World
94
+ </Tabs.Panel>
95
+ <Tabs.Panel renderTitle="Disabled Tab" isDisabled>
96
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod
97
+ tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
98
+ veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
99
+ commodo consequat. Duis aute irure dolor in reprehenderit in voluptate
100
+ velit esse cillum dolore eu fugiat nulla pariatur.
101
+ </Tabs.Panel>
102
+ <Tabs.Panel renderTitle="Third Tab" isSelected={selectedIndex === 2} tabIndex={0}>
103
+ Sed ut perspiciatis unde omnis iste natus error sit voluptatem
104
+ accusantium doloremque laudantium. Totam rem aperiam, eaque ipsa quae ab
105
+ illo inventore veritatis et quasi architecto beatae vitae dicta sunt
106
+ explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut
107
+ odit aut fugit, sed quia consequuntur magni dolores.
108
+ </Tabs.Panel>
109
+ <Tabs.Panel renderTitle="Fourth Tab" isSelected={selectedIndex === 3} tabIndex={0}>
110
+ At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis
111
+ praesentium voluptatum deleniti atque corrupti. Quos dolores et quas
112
+ molestias excepturi sint occaecati cupiditate non provident, similique
113
+ sunt in culpa. Qui officia deserunt mollitia animi, id est laborum et
114
+ dolorum fuga.
115
+ </Tabs.Panel>
116
+ </Tabs>
117
+ )
118
+ }
119
+
120
+ render(<Example />)
121
+ ```
122
+
123
+ ### Handling Tab overflow
124
+
125
+ By default, `<Tabs />` will stack each individual `<Tab />` if there isn't enough horizontal
126
+ space to display them all inline. For a more compact tab navigation, set `tabOverflow` to
127
+ `scroll`, which allows the Tabs to scroll horizontally.
128
+
129
+ ```js
130
+ ---
131
+ type: example
132
+ ---
133
+ const Example = () => {
134
+ const [selectedIndex, setSelectedIndex] = useState(4)
135
+
136
+ const handleTabChange = (event, { index }) => {
137
+ setSelectedIndex(index)
138
+ }
139
+
140
+ return (
141
+ <Tabs
142
+ margin="large auto"
143
+ padding="medium"
144
+ onRequestTabChange={handleTabChange}
145
+ tabOverflow="scroll"
146
+ maxWidth="20rem"
147
+ >
148
+ <Tabs.Panel
149
+ id="tabA"
150
+ renderTitle="Tab A"
151
+ isSelected={selectedIndex === 0}
152
+ tabIndex={0}
153
+ >
154
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
155
+ </Tabs.Panel>
156
+ <Tabs.Panel
157
+ id="tabB"
158
+ renderTitle="Tab B"
159
+ isSelected={selectedIndex === 1}
160
+ tabIndex={0}
161
+ >
162
+ Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
163
+ </Tabs.Panel>
164
+ <Tabs.Panel
165
+ id="tabC"
166
+ renderTitle="Tab C"
167
+ isSelected={selectedIndex === 2}
168
+ tabIndex={0}
169
+ >
170
+ Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
171
+ </Tabs.Panel>
172
+ <Tabs.Panel
173
+ id="tabD"
174
+ renderTitle="Tab D"
175
+ isSelected={selectedIndex === 3}
176
+ tabIndex={0}
177
+ >
178
+ Duis aute irure dolor in reprehenderit in voluptate velit esse.
179
+ </Tabs.Panel>
180
+ <Tabs.Panel
181
+ id="tabE"
182
+ renderTitle="Tab E"
183
+ isSelected={selectedIndex === 4}
184
+ tabIndex={0}
185
+ >
186
+ Excepteur sint occaecat cupidatat non proident, sunt in culpa.
187
+ </Tabs.Panel>
188
+ <Tabs.Panel
189
+ id="tabF"
190
+ renderTitle="Tab F"
191
+ isSelected={selectedIndex === 5}
192
+ tabIndex={0}
193
+ >
194
+ Sed ut perspiciatis unde omnis iste natus error sit voluptatem.
195
+ </Tabs.Panel>
196
+ <Tabs.Panel
197
+ id="tabG"
198
+ renderTitle="Tab G"
199
+ isSelected={selectedIndex === 6}
200
+ tabIndex={0}
201
+ >
202
+ Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit.
203
+ </Tabs.Panel>
204
+ </Tabs>
205
+ )
206
+ }
207
+
208
+ render(<Example />)
209
+ ```
210
+
211
+ ### Controlling the size and the spacing
212
+
213
+ To restrict the width of `<Tabs />`, use the `maxWidth` prop. Add space around the entire component using the `margin` prop. Adjust the padding around the panel content via `padding` (default is `small`) on each `<Tabs.Panel>`.
214
+
215
+ Set the height of the Tabs component with the `fixHeight` property (set to '100%' to fill out it's parent element). You can also restrict the height of the **panels** using the `minHeight` and `maxHeight` properties (they don't work if you set `fixHeight` on the whole Tabs component).
216
+
217
+ Finally, switch the text alignment of the panel content with `textAlign`.
218
+
219
+ ```js
220
+ ---
221
+ type: example
222
+ ---
223
+ const Example = () => {
224
+ const [selectedIndex, setSelectedIndex] = useState(0)
225
+ const [heightOption, setHeightOption] = useState('fixHeight: 100%')
226
+
227
+ const heightOptions = {
228
+ ['fixHeight: 100%']: { fixHeight: '100%' },
229
+ ['fixHeight: 15rem']: { fixHeight: '15rem' },
230
+ ['minHeight: 17rem']: { minHeight: '17rem' },
231
+ ['maxHeight: 10rem']: { maxHeight: '10rem' }
232
+ }
233
+
234
+ const handleTabChange = (event, { index }) => {
235
+ setSelectedIndex(index)
236
+ }
237
+
238
+ const handleHeightOptionSelect = (e, heightOption) => {
239
+ setHeightOption(heightOption)
240
+ }
241
+
242
+ const containerProps = {
243
+ as: 'div',
244
+ ...(heightOption.includes('fixHeight') && {
245
+ height: '22rem',
246
+ withVisualDebug: true
247
+ })
248
+ }
249
+
250
+ return (
251
+ <>
252
+ <View display="block" margin="none none medium">
253
+ <RadioInputGroup
254
+ name="tabsHeightOptions"
255
+ defaultValue="fixHeight: 100%"
256
+ description={
257
+ <ScreenReaderContent>Tabs height selector</ScreenReaderContent>
258
+ }
259
+ variant="toggle"
260
+ onChange={handleHeightOptionSelect}
261
+ >
262
+ {Object.keys(heightOptions).map((heightOption) => (
263
+ <RadioInput
264
+ key={heightOption}
265
+ label={heightOption}
266
+ value={heightOption}
267
+ />
268
+ ))}
269
+ </RadioInputGroup>
270
+ </View>
271
+
272
+ <View {...containerProps}>
273
+ <Tabs
274
+ margin="large auto"
275
+ padding="medium"
276
+ onRequestTabChange={handleTabChange}
277
+ {...heightOptions[heightOption]}
278
+ >
279
+ <Tabs.Panel
280
+ id="tabA"
281
+ tabIndex={-1}
282
+ renderTitle="Tab A"
283
+ textAlign="center"
284
+ padding="large"
285
+ isSelected={selectedIndex === 0}
286
+ >
287
+ <Button>Focus Me</Button>
288
+ </Tabs.Panel>
289
+ <Tabs.Panel id="tabB" renderTitle="Disabled Tab" isDisabled>
290
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
291
+ eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
292
+ ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
293
+ aliquip ex ea commodo consequat. Duis aute irure dolor in
294
+ reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
295
+ pariatur.
296
+ </Tabs.Panel>
297
+ <Tabs.Panel
298
+ id="tabC"
299
+ renderTitle="Tab C"
300
+ isSelected={selectedIndex === 2}
301
+ tabIndex={0}
302
+ >
303
+ Sed ut perspiciatis unde omnis iste natus error sit voluptatem
304
+ accusantium doloremque laudantium. Totam rem aperiam, eaque ipsa quae
305
+ ab illo inventore veritatis et quasi architecto beatae vitae dicta
306
+ sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit
307
+ aspernatur aut odit aut fugit, sed quia consequuntur magni dolores.
308
+ </Tabs.Panel>
309
+ <Tabs.Panel
310
+ id="tabD"
311
+ renderTitle="Tab D"
312
+ isSelected={selectedIndex === 3}
313
+ tabIndex={0}
314
+ >
315
+ At vero eos et accusamus et iusto odio dignissimos ducimus qui
316
+ blanditiis praesentium voluptatum deleniti atque corrupti. Quos
317
+ dolores et quas molestias excepturi sint occaecati cupiditate non
318
+ provident, similique sunt in culpa. Qui officia deserunt mollitia
319
+ animi, id est laborum et dolorum fuga.
320
+ </Tabs.Panel>
321
+ </Tabs>
322
+ </View>
323
+ </>
324
+ )
325
+ }
326
+
327
+ render(<Example />)
328
+ ```
329
+
330
+ ### Support for dynamic content with active panel
331
+
332
+ Marking one of the `<Tabs.Panel>` as `active` will render that panel's content in all the panels. This is useful for dynamic content rendering: the panel area can be used as a container, what routing libraries, such as React Router, can use to render their children elements into.
333
+
334
+ ```js
335
+ ---
336
+ type: example
337
+ ---
338
+ const Outlet = () => {
339
+ const [show, setShow] = useState(false)
340
+
341
+ useEffect(() => {
342
+ const timer = setTimeout(() => setShow(true), 2000)
343
+ return () => clearTimeout(timer)
344
+ }, [])
345
+
346
+ return (
347
+ <div>
348
+ <Heading level="h1" as="h1" margin="0 0 x-small">
349
+ {show ? 'Hello Developer' : 'Simulating network call...'}
350
+ </Heading>
351
+ {show ? (
352
+ <div>
353
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
354
+ eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad
355
+ minim veniam, quis nostrud exercitation ullamco laboris nisi ut
356
+ aliquip ex ea commodo consequat. Duis aute irure dolor in
357
+ reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
358
+ pariatur.
359
+ </div>
360
+ ) : (
361
+ <Spinner renderTitle="Loading" size="medium" />
362
+ )}
363
+ </div>
364
+ )
365
+ }
366
+
367
+ const Example = () => {
368
+ const [selectedIndex, setSelectedIndex] = useState(0)
369
+
370
+ const handleTabChange = (event, { index }) => {
371
+ setSelectedIndex(index)
372
+ }
373
+
374
+ return (
375
+ <Tabs
376
+ margin="large auto"
377
+ padding="medium"
378
+ onRequestTabChange={handleTabChange}
379
+ >
380
+ <Tabs.Panel
381
+ id="tabA"
382
+ renderTitle="Tab A"
383
+ textAlign="center"
384
+ padding="large"
385
+ isSelected={selectedIndex === 0}
386
+ active
387
+ tabIndex={0}
388
+ >
389
+ <Outlet />
390
+ </Tabs.Panel>
391
+ <Tabs.Panel id="tabB" renderTitle="Disabled Tab" isDisabled />
392
+ <Tabs.Panel
393
+ id="tabC"
394
+ renderTitle="Tab C"
395
+ isSelected={selectedIndex === 2}
396
+ />
397
+ <Tabs.Panel
398
+ id="tabD"
399
+ renderTitle="Tab D"
400
+ isSelected={selectedIndex === 3}
401
+ />
402
+ </Tabs>
403
+ )
404
+ }
405
+
406
+ render(<Example />)
407
+ ```
408
+
409
+ ### Persisting the selected tab
410
+
411
+ If you need to persist the rendered content of the tabpanels between tabbing, you can set the `unmountOnExit` prop to `false` on the `<Tabs.Panel>` component. It works case by case, so you can set it to `false` only on the tabpanels you want to persist.
412
+
413
+ ```js
414
+ ---
415
+ type: example
416
+ ---
417
+ const Counter = () => {
418
+ const [counter, setCounter] = useState(0)
419
+
420
+ const handleIncrement = () => {
421
+ setCounter(counter + 1)
422
+ }
423
+
424
+ return (
425
+ <div>
426
+ <Button onClick={handleIncrement}>Increment</Button>
427
+ <hr />
428
+ <Text>{counter}</Text>
429
+ </div>
430
+ )
431
+ }
432
+
433
+ const Example = () => {
434
+ const [selectedIndex, setSelectedIndex] = useState(0)
435
+
436
+ const handleTabChange = (event, { index }) => {
437
+ setSelectedIndex(index)
438
+ }
439
+
440
+ return (
441
+ <Tabs
442
+ margin="large auto"
443
+ padding="medium"
444
+ onRequestTabChange={handleTabChange}
445
+ >
446
+ <Tabs.Panel
447
+ id="tabA"
448
+ tabIndex={-1}
449
+ renderTitle="I will persist"
450
+ textAlign="center"
451
+ padding="large"
452
+ isSelected={selectedIndex === 0}
453
+ unmountOnExit={false}
454
+ >
455
+ <Counter />
456
+ </Tabs.Panel>
457
+ <Tabs.Panel
458
+ id="tabB"
459
+ tabIndex={-1}
460
+ renderTitle="I will unmount"
461
+ isSelected={selectedIndex === 1}
462
+ textAlign="center"
463
+ padding="large"
464
+ >
465
+ <Counter />
466
+ </Tabs.Panel>
467
+ <Tabs.Panel
468
+ id="tabC"
469
+ renderTitle="Tab C"
470
+ isSelected={selectedIndex === 2}
471
+ tabIndex={0}
472
+ >
473
+ Tab C
474
+ </Tabs.Panel>
475
+ <Tabs.Panel
476
+ id="tabD"
477
+ renderTitle="Tab D"
478
+ isSelected={selectedIndex === 3}
479
+ tabIndex={0}
480
+ >
481
+ Tab D
482
+ </Tabs.Panel>
483
+ </Tabs>
484
+ )
485
+ }
486
+
487
+ render(<Example />)
488
+ ```
489
+
490
+ ### Managing focus with tabIndex
491
+
492
+ **Best practice:** For text-only panels, set `tabIndex={0}` to include the panel in the keyboard tab sequence—this ensures screen reader users can navigate to and read the content. For panels containing interactive elements (buttons, inputs, links), leave `tabIndex` unset so keyboard users tab directly to the controls without stopping on the panel container first.
493
+
494
+ ```js
495
+ ---
496
+ type: example
497
+ ---
498
+ const Example = () => {
499
+ const [selectedIndex, setSelectedIndex] = useState(0)
500
+
501
+ const handleTabChange = (event, { index }) => {
502
+ setSelectedIndex(index)
503
+ }
504
+
505
+ return (
506
+ <Tabs
507
+ margin="large auto"
508
+ padding="medium"
509
+ onRequestTabChange={handleTabChange}
510
+ >
511
+ <Tabs.Panel
512
+ id="tabA"
513
+ renderTitle="Panel with button"
514
+ textAlign="center"
515
+ padding="large"
516
+ isSelected={selectedIndex === 0}
517
+ tabIndex={-1}
518
+ >
519
+ <Button>Focus Me First</Button>
520
+ </Tabs.Panel>
521
+ <Tabs.Panel
522
+ id="tabB"
523
+ renderTitle="Panel with text only"
524
+ isSelected={selectedIndex === 1}
525
+ tabIndex={0}
526
+ >
527
+ This panel only contains text, so tabIndex is set to 0 to include it in the tab sequence.
528
+ </Tabs.Panel>
529
+ </Tabs>
530
+ )
531
+ }
532
+
533
+ render(<Example />)
534
+ ```
535
+
536
+ ### Guidelines
537
+
538
+ ```js
539
+ ---
540
+ type: embed
541
+ ---
542
+ <Guidelines>
543
+ <Figure recommendation="yes" title="Do">
544
+ <Figure.Item>Title should be a single row above content</Figure.Item>
545
+ <Figure.Item>TabButton content should be succinct, preferably one word</Figure.Item>
546
+ <Figure.Item>Use title case</Figure.Item>
547
+ <Figure.Item>Use default variant when a single Tabs component exists on the page</Figure.Item>
548
+ <Figure.Item>Ensure each Tablist.Panel content is mutually exclusive of the others’</Figure.Item>
549
+ </Figure>
550
+ <Figure recommendation="no" title="Don't">
551
+ <Figure.Item>Exceed 20 characters including spaces for the title</Figure.Item>
552
+ <Figure.Item>Exceed 5 tabs</Figure.Item>
553
+ <Figure.Item>Use avatars, pills, icons, etc in the TabButton</Figure.Item>
554
+ <Figure.Item>Use `tabOverflow="scroll"` with `secondary` Tabs</Figure.Item>
555
+ <Figure.Item>Nest tabbed content within a tab</Figure.Item>
556
+ <Figure.Item>Align tabs to the center of the page</Figure.Item>
557
+ </Figure>
558
+ </Guidelines>
559
+ ```
@@ -0,0 +1,123 @@
1
+ /*
2
+ * The MIT License (MIT)
3
+ *
4
+ * Copyright (c) 2015 - present Instructure, Inc.
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ import { Component } from 'react'
26
+
27
+ import { passthroughProps, callRenderProp } from '@instructure/ui-react-utils'
28
+ import { View } from '@instructure/ui-view/latest'
29
+ import type { ViewOwnProps } from '@instructure/ui-view/latest'
30
+
31
+ import { withStyle } from '@instructure/emotion'
32
+
33
+ import generateStyle from './styles'
34
+
35
+ import type { TabsTabProps } from './props'
36
+ import { allowedProps } from './props'
37
+
38
+ /**
39
+ ---
40
+ parent: Tabs
41
+ id: Tabs.Tab
42
+ ---
43
+ **/
44
+ @withStyle(generateStyle)
45
+ class Tab extends Component<TabsTabProps> {
46
+ static readonly componentId = 'Tabs.Tab'
47
+
48
+ static allowedProps = allowedProps
49
+
50
+ static defaultProps = {
51
+ variant: 'default',
52
+ isDisabled: false,
53
+ isSelected: false
54
+ }
55
+
56
+ componentDidMount() {
57
+ this.props.makeStyles?.()
58
+ }
59
+
60
+ componentDidUpdate() {
61
+ this.props.makeStyles?.()
62
+ }
63
+
64
+ handleClick = (event: React.MouseEvent<ViewOwnProps>) => {
65
+ const { onClick, index, id, isDisabled } = this.props
66
+
67
+ if (isDisabled) {
68
+ return
69
+ }
70
+
71
+ if (typeof onClick === 'function') {
72
+ onClick(event, { index, id })
73
+ }
74
+ }
75
+
76
+ handleKeyDown = (event: React.KeyboardEvent<ViewOwnProps>) => {
77
+ const { onKeyDown, index, id, isDisabled } = this.props
78
+
79
+ if (isDisabled) {
80
+ return
81
+ }
82
+
83
+ if (typeof onKeyDown === 'function') {
84
+ onKeyDown(event, { index, id })
85
+ }
86
+ }
87
+
88
+ render() {
89
+ const {
90
+ id,
91
+ variant,
92
+ isSelected,
93
+ isDisabled,
94
+ controls,
95
+ children,
96
+ styles,
97
+ ...props
98
+ } = this.props
99
+
100
+ return (
101
+ <View
102
+ {...passthroughProps(props)}
103
+ as="div"
104
+ role="tab"
105
+ id={id}
106
+ onClick={this.handleClick}
107
+ onKeyDown={this.handleKeyDown}
108
+ css={styles?.tab}
109
+ aria-selected={isSelected ? 'true' : undefined}
110
+ aria-disabled={isDisabled ? 'true' : undefined}
111
+ aria-controls={controls}
112
+ tabIndex={isSelected && !isDisabled ? 0 : undefined}
113
+ position="relative"
114
+ focusPosition="inset"
115
+ >
116
+ {callRenderProp(children)}
117
+ </View>
118
+ )
119
+ }
120
+ }
121
+
122
+ export default Tab
123
+ export { Tab }