Skip to content

Commit

Permalink
Tabs typescript (#715)
Browse files Browse the repository at this point in the history
* Renamed to typescript files

* WIP Tabslist

* First working component

* Added fallback and tests for if missing children
  • Loading branch information
Michael Marszalek authored and vnys committed Nov 13, 2020
1 parent a228afe commit f74dd50
Show file tree
Hide file tree
Showing 13 changed files with 207 additions and 178 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
// @ts-nocheck
import React, { forwardRef } from 'react'
import PropTypes from 'prop-types'
import styled, { css } from 'styled-components'
import { tab as tokens } from './Tabs.tokens'

Expand Down Expand Up @@ -29,13 +27,15 @@ const focusedStyles = css`
outline-offset: ${outlineOffset};
`

const StyledTab = styled.button.attrs(({ active, disabled }) => ({
type: 'button',
role: 'tab',
'aria-selected': active,
'aria-disabled': disabled,
tabIndex: active ? '0' : '-1',
}))`
const StyledTab = styled.button.attrs<Props>(
({ active = false, disabled = false }) => ({
type: 'button',
role: 'tab',
'aria-selected': active,
'aria-disabled': disabled,
tabIndex: active ? '0' : '-1',
}),
)<Props>`
appearance: none;
box-sizing: border-box;
font-family: inherit;
Expand Down Expand Up @@ -82,23 +82,16 @@ const StyledTab = styled.button.attrs(({ active, disabled }) => ({
}
`

export const Tab = forwardRef(function Tab(props, ref) {
return <StyledTab ref={ref} {...props} />
})

Tab.propTypes = {
export type Props = {
/** If `true`, the tab will be active. */
active: PropTypes.bool,
active?: boolean
/** If `true`, the tab will be disabled. */
disabled: PropTypes.bool,
/** @ignore */
className: PropTypes.string,
/** @ignore */
children: PropTypes.node.isRequired,
disabled?: boolean
}

Tab.defaultProps = {
active: false,
disabled: false,
className: null,
}
export const Tab = forwardRef<
HTMLButtonElement,
Props & React.HTMLAttributes<HTMLButtonElement>
>(function Tab(props, ref) {
return <StyledTab ref={ref} {...props} />
})
Original file line number Diff line number Diff line change
@@ -1,38 +1,61 @@
// @ts-nocheck
import React, {
forwardRef,
useContext,
useRef,
useCallback,
useEffect,
ReactElement,
} from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import { useCombinedRefs } from '../_common/useCombinedRefs'
import { TabsContext } from './Tabs.context'
import { Variants } from './Tabs.types'

const variants = {
type VariantsRecord = {
fullWidth: string
minWidth: string
}

const variants: VariantsRecord = {
fullWidth: 'minmax(1%, 360px)',
minWidth: 'max-content',
}

const StyledTabList = styled.div.attrs(() => ({
role: 'tablist',
}))`
type StyledProps = Props

const StyledTabList = styled.div.attrs(
(): React.HTMLAttributes<HTMLDivElement> => ({
role: 'tablist',
}),
)<StyledProps>`
display: grid;
grid-auto-flow: column;
grid-auto-columns: ${({ variant }) => variants[variant]};
`

const TabList = forwardRef(function TabsList({ children, ...props }, ref) {
const { activeTab, handleChange, tabsId, variant, tabsFocused } = useContext(
TabsContext,
)
type Props = {
/** Sets the width of the tabs */
variant?: Variants
} & React.HTMLAttributes<HTMLDivElement>

type TabChild = JSX.IntrinsicElements['button'] & ReactElement

const TabList = forwardRef<HTMLDivElement, Props>(function TabsList(
{ children = [], ...props },
ref,
) {
const {
activeTab,
handleChange,
tabsId,
variant = 'minWidth',
tabsFocused,
} = useContext(TabsContext)

const currentTab = useRef(activeTab)

const selectedTabRef = useCallback(
(node) => {
(node: HTMLElement) => {
if (node !== null && tabsFocused) {
node.focus()
}
Expand All @@ -44,7 +67,7 @@ const TabList = forwardRef(function TabsList({ children, ...props }, ref) {
currentTab.current = activeTab
}, [activeTab])

const Tabs = React.Children.map(children, (child, index) => {
const Tabs = React.Children.map(children, (child: TabChild, index) => {
const tabRef =
index === activeTab
? useCombinedRefs(child.ref, selectedTabRef)
Expand All @@ -60,9 +83,10 @@ const TabList = forwardRef(function TabsList({ children, ...props }, ref) {
})
})

const focusableChildren = Tabs.filter((child) => !child.props.disabled).map(
(child) => child.props.index,
)
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const focusableChildren: number[] = Tabs.filter(
(child) => !child.props.disabled,
).map((child) => child.props.index)

const firstFocusableChild = focusableChildren[0]
const lastFocusableChild = focusableChildren[focusableChildren.length - 1]
Expand All @@ -74,7 +98,7 @@ const TabList = forwardRef(function TabsList({ children, ...props }, ref) {
handleChange(nextTab === undefined ? fallbackTab : nextTab)
}

const handleKeyPress = (event) => {
const handleKeyPress = (event: React.KeyboardEvent<HTMLDivElement>) => {
const { key } = event
if (key === 'ArrowLeft') {
handleTabsChange('left', lastFocusableChild)
Expand All @@ -96,21 +120,4 @@ const TabList = forwardRef(function TabsList({ children, ...props }, ref) {
)
})

TabList.propTypes = {
/** @ignore */
className: PropTypes.string,
/** Sets the width of the tabs */
variant: PropTypes.oneOf(['fullWidth', 'minWidth']),
/** @ignore */
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.element),
PropTypes.element,
]).isRequired,
}

TabList.defaultProps = {
className: null,
variant: 'minWidth',
}

export { TabList }
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
// @ts-nocheck
import React, { forwardRef } from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import { tabPanel as tokens } from './Tabs.tokens'

Expand All @@ -12,10 +10,12 @@ const {
},
} = tokens

const StyledTabPanel = styled.div.attrs(() => ({
tabIndex: 0,
role: 'tabpanel',
}))({
const StyledTabPanel = styled.div.attrs(
(): React.HTMLAttributes<HTMLDivElement> => ({
tabIndex: 0,
role: 'tabpanel',
}),
)({
paddingTop,
paddingBottom,
outline: 'none',
Expand All @@ -25,26 +25,20 @@ const StyledTabPanel = styled.div.attrs(() => ({
},
})

const TabPanel = forwardRef(function TabPanel({ ...props }, ref) {
type Props = {
/** If `true`, the panel will be hidden. */
hidden?: boolean
} & React.HTMLAttributes<HTMLDivElement>

const TabPanel = forwardRef<HTMLDivElement, Props>(function TabPanel(
{ ...props },
ref,
) {
return (
<StyledTabPanel ref={ref} {...props}>
{props.children}
</StyledTabPanel>
)
})

TabPanel.propTypes = {
/** @ignore */
children: PropTypes.node.isRequired,
/** @ignore */
className: PropTypes.string,
/** If `true`, the panel will be hidden. */
hidden: PropTypes.bool,
}

TabPanel.defaultProps = {
className: null,
hidden: null,
}

export { TabPanel }
37 changes: 0 additions & 37 deletions libraries/core-react/src/Tabs/TabPanels.jsx

This file was deleted.

26 changes: 26 additions & 0 deletions libraries/core-react/src/Tabs/TabPanels.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React, { forwardRef, ReactElement, useContext } from 'react'
import { TabsContext } from './Tabs.context'

type Props = React.HTMLAttributes<HTMLDivElement>

const TabPanels = forwardRef<HTMLDivElement, Props>(function TabPanels(
{ children, ...props },
ref,
) {
const { activeTab, tabsId } = useContext(TabsContext)

const Panels = React.Children.map(children, (child: ReactElement, index) =>
React.cloneElement(child, {
id: `${tabsId}-panel-${index + 1}`,
'aria-labelledby': `${tabsId}-tab-${index + 1}`,
hidden: activeTab !== index,
}),
)
return (
<div ref={ref} {...props}>
{Panels}
</div>
)
})

export { TabPanels }
15 changes: 0 additions & 15 deletions libraries/core-react/src/Tabs/Tabs.context.js

This file was deleted.

23 changes: 23 additions & 0 deletions libraries/core-react/src/Tabs/Tabs.context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { createContext } from 'react'
import { Variants } from './Tabs.types'

type State = {
variant: Variants
handleChange: (index: number) => void
activeTab: number
tabsId: string
tabsFocused: boolean
}

const TabsContext = createContext<State>({
variant: 'minWidth',
handleChange: () => null,
activeTab: 0,
tabsId: '',
tabsFocused: false,
})

const TabsProvider = TabsContext.Provider
const TabsConsumer = TabsContext.Consumer

export { TabsContext, TabsProvider, TabsConsumer }
Loading

0 comments on commit f74dd50

Please sign in to comment.