@instructure/ui-tabs 8.46.2-snapshot-6 → 8.46.2-snapshot-8

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.
@@ -0,0 +1,128 @@
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 React from 'react'
26
+
27
+ import { Tabs } from '../index'
28
+ import { fireEvent, render, screen } from '@testing-library/react'
29
+ import '@testing-library/jest-dom'
30
+
31
+ const TabExample = (props: { onIndexChange: (arg: number) => void }) => {
32
+ const [selectedIndex, setSelectedIndex] = React.useState(0)
33
+ return (
34
+ <Tabs
35
+ onRequestTabChange={(_event, { index }) => {
36
+ setSelectedIndex(index)
37
+ props.onIndexChange(index)
38
+ }}
39
+ variant="default"
40
+ margin="medium"
41
+ >
42
+ <Tabs.Panel renderTitle="First Tab" id="first" isSelected={selectedIndex === 0} active>
43
+ <p>CONTENT</p>
44
+ </Tabs.Panel>
45
+ <Tabs.Panel renderTitle="Second Tab" id="second" isSelected={selectedIndex === 1} />
46
+ <Tabs.Panel renderTitle="Third Tab" id="third" isSelected={selectedIndex === 2} />
47
+ </Tabs>
48
+ )
49
+ }
50
+
51
+ describe('<Tabs />', () => {
52
+ it('should render the correct number of panels', () => {
53
+ const { container } = render(
54
+ <Tabs>
55
+ <Tabs.Panel renderTitle="First Tab">Tab 1 content</Tabs.Panel>
56
+ <Tabs.Panel renderTitle="Second Tab">Tab 2 content</Tabs.Panel>
57
+ <Tabs.Panel renderTitle="Third Tab" isDisabled>
58
+ Tab 3 content
59
+ </Tabs.Panel>
60
+ </Tabs>
61
+ )
62
+
63
+ expect(container.firstChild).toBeInTheDocument()
64
+ })
65
+
66
+ it('should render same content for other tabs as for the active one', () => {
67
+ const { container } = render(
68
+ <Tabs>
69
+ <Tabs.Panel renderTitle="First Tab" active>
70
+ CONTENT
71
+ </Tabs.Panel>
72
+ <Tabs.Panel id="secondTab" renderTitle="Second Tab" isSelected>
73
+ Child
74
+ </Tabs.Panel>
75
+ <Tabs.Panel renderTitle="Third Tab">Child</Tabs.Panel>
76
+ </Tabs>
77
+ )
78
+
79
+ const tabContent = screen.getByText('CONTENT')
80
+
81
+ expect(container).toBeInTheDocument()
82
+ expect(tabContent).toBeInTheDocument()
83
+
84
+ const childContent = screen.queryByText('Child')
85
+
86
+ expect(childContent).toBeNull()
87
+ })
88
+
89
+ it('should render the same content in second tab when selected', () => {
90
+ const onIndexChange = jest.fn()
91
+
92
+ const { container } = render(<TabExample onIndexChange={onIndexChange} />)
93
+ expect(container).toBeInTheDocument()
94
+
95
+ const secondTab = screen.getAllByRole('tab')[1]
96
+
97
+ fireEvent.click(secondTab)
98
+
99
+ expect(onIndexChange).toHaveBeenCalledWith(1)
100
+
101
+ const panelContent = screen.queryByText('CONTENT')
102
+
103
+ expect(panelContent).toBeInTheDocument()
104
+ })
105
+
106
+ it('should warn if multiple active tabs exist', () => {
107
+ const consoleMock = jest.spyOn(console, 'error').mockImplementation()
108
+ const { container } = render(
109
+ <Tabs>
110
+ <Tabs.Panel renderTitle="First Tab" active>
111
+ Tab 1 content
112
+ </Tabs.Panel>
113
+ <Tabs.Panel renderTitle="Second Tab" active>
114
+ Tab 2 content
115
+ </Tabs.Panel>
116
+ <Tabs.Panel renderTitle="Third Tab" isDisabled>
117
+ Tab 3 content
118
+ </Tabs.Panel>
119
+ </Tabs>
120
+ )
121
+
122
+ expect(container.firstChild).toBeInTheDocument()
123
+
124
+ expect(consoleMock.mock.calls[0][0]).toEqual(
125
+ 'Warning: [Tabs] Only one Panel can be marked as active.'
126
+ )
127
+ })
128
+ })
@@ -27,7 +27,8 @@ import React, {
27
27
  Component,
28
28
  ComponentClass,
29
29
  ComponentElement,
30
- createElement
30
+ createElement,
31
+ ReactElement
31
32
  } from 'react'
32
33
 
33
34
  import keycode from 'keycode'
@@ -359,24 +360,44 @@ class Tabs extends Component<TabsProps, TabsState> {
359
360
  _index: number,
360
361
  generatedId: string,
361
362
  selected: boolean,
362
- panel: PanelChild
363
+ panel: PanelChild,
364
+ activePanel?: PanelChild
363
365
  ) {
364
366
  const id = panel.props.id || generatedId
365
367
 
366
368
  // fixHeight can be 0, so simply `fixheight` could return falsy value
367
369
  const hasFixedHeight = typeof this.props.fixHeight !== 'undefined'
368
370
 
369
- return safeCloneElement(panel, {
371
+ const commonProps = {
370
372
  id: panel.props.id || `panel-${id}`,
371
373
  labelledBy: `tab-${id}`,
372
374
  isSelected: selected,
373
- key: panel.props.id || `panel-${id}`,
374
375
  variant: this.props.variant,
375
- padding: panel.props.padding || this.props.padding,
376
- textAlign: panel.props.textAlign || this.props.textAlign,
377
376
  maxHeight: !hasFixedHeight ? this.props.maxHeight : undefined,
378
377
  minHeight: !hasFixedHeight ? this.props.minHeight : '100%'
379
- } as TabsPanelProps & { key: string }) as PanelChild
378
+ }
379
+
380
+ let activePanelClone = null
381
+ if (activePanel !== undefined) {
382
+ // cloning active panel with a proper custom key as a workaround because
383
+ // safeCloneElement overwrites it with the key from the original element
384
+ activePanelClone = React.cloneElement(activePanel as ReactElement, {
385
+ key: panel.props.id || `panel-${id}`
386
+ })
387
+
388
+ return safeCloneElement(activePanelClone, {
389
+ padding: activePanelClone.props.padding || this.props.padding,
390
+ textAlign: activePanelClone.props.textAlign || this.props.textAlign,
391
+ ...commonProps
392
+ } as TabsPanelProps & { key: string }) as PanelChild
393
+ } else {
394
+ return safeCloneElement(panel, {
395
+ key: panel.props.id || `panel-${id}`,
396
+ padding: panel.props.padding || this.props.padding,
397
+ textAlign: panel.props.textAlign || this.props.textAlign,
398
+ ...commonProps
399
+ } as TabsPanelProps & { key: string }) as PanelChild
400
+ }
380
401
  }
381
402
 
382
403
  handleFocusableRef = (el: Focusable | null) => {
@@ -430,6 +451,14 @@ class Tabs extends Component<TabsProps, TabsState> {
430
451
  ...props
431
452
  } = this.props
432
453
 
454
+ const activePanels = (React.Children.toArray(children) as PanelChild[])
455
+ .filter((child) => matchComponentTypes<PanelChild>(child, [Panel]))
456
+ .filter((child) => child.props.active)
457
+
458
+ if (activePanels.length > 1) {
459
+ error(false, `[Tabs] Only one Panel can be marked as active.`)
460
+ }
461
+
433
462
  const selectedChildIndex = (
434
463
  React.Children.toArray(children) as PanelChild[]
435
464
  )
@@ -447,7 +476,13 @@ class Tabs extends Component<TabsProps, TabsState> {
447
476
  const id = uid()
448
477
 
449
478
  tabs.push(this.createTab(index, id, selected, child))
450
- panels.push(this.clonePanel(index, id, selected, child))
479
+ if (activePanels.length === 1) {
480
+ panels.push(
481
+ this.clonePanel(index, id, selected, child, activePanels[0])
482
+ )
483
+ } else {
484
+ panels.push(this.clonePanel(index, id, selected, child))
485
+ }
451
486
 
452
487
  index++
453
488
  } else {