Skip to content

Commit

Permalink
First working component
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Marszalek committed Oct 21, 2020
1 parent a7d1fe1 commit aff720c
Show file tree
Hide file tree
Showing 10 changed files with 84 additions and 113 deletions.
2 changes: 1 addition & 1 deletion libraries/core-react/src/Tabs/Tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ const StyledTab = styled.button.attrs<Props>(
}
`

type Props = {
export type Props = {
/** If `true`, the tab will be active. */
active?: boolean
/** If `true`, the tab will be disabled. */
Expand Down
47 changes: 17 additions & 30 deletions libraries/core-react/src/Tabs/TabList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ import React, {
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'

type Variants = 'fullWidth' | 'minWidth'
import { Variants } from './Tabs.types'

type VariantsRecord = {
fullWidth: string
Expand All @@ -22,21 +21,25 @@ const variants: VariantsRecord = {
minWidth: 'max-content',
}

type StyledProps = Pick<Props, 'variant'>
type StyledProps = Props

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

type Props = {
/** Sets the width of the tabs */
variant: Variants
variant?: Variants
} & React.HTMLAttributes<HTMLDivElement>

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

const TabList = forwardRef<HTMLDivElement, Props>(function TabsList(
{ children, ...props },
ref,
Expand Down Expand Up @@ -64,7 +67,7 @@ const TabList = forwardRef<HTMLDivElement, Props>(function TabsList(
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 @@ -80,9 +83,10 @@ const TabList = forwardRef<HTMLDivElement, Props>(function TabsList(
})
})

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 @@ -94,7 +98,7 @@ const TabList = forwardRef<HTMLDivElement, Props>(function TabsList(
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 @@ -116,21 +120,4 @@ const TabList = forwardRef<HTMLDivElement, Props>(function TabsList(
)
})

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 }
36 changes: 15 additions & 21 deletions libraries/core-react/src/Tabs/TabPanel.tsx
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 }
27 changes: 8 additions & 19 deletions libraries/core-react/src/Tabs/TabPanels.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
// @ts-nocheck
import React, { forwardRef, useContext } from 'react'
import PropTypes from 'prop-types'
import React, { forwardRef, ReactElement, useContext } from 'react'
import { TabsContext } from './Tabs.context'

const TabPanels = forwardRef(function TabPanels({ children, ...props }, ref) {
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, index) =>
const Panels = React.Children.map(children, (child: ReactElement, index) =>
React.cloneElement(child, {
id: `${tabsId}-panel-${index + 1}`,
'aria-labelledby': `${tabsId}-tab-${index + 1}`,
Expand All @@ -20,18 +23,4 @@ const TabPanels = forwardRef(function TabPanels({ children, ...props }, ref) {
)
})

TabPanels.propTypes = {
/** @ignore */
className: PropTypes.string,
/** @ignore */
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.element),
PropTypes.element,
]).isRequired,
}

TabPanels.defaultProps = {
className: null,
}

export { TabPanels }
18 changes: 13 additions & 5 deletions libraries/core-react/src/Tabs/Tabs.context.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
// @ts-nocheck
import React, { createContext } from 'react'
import { createContext } from 'react'
import { Variants } from './Tabs.types'

const TabsContext = createContext({
variant: '',
handleChange: () => {},
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,
Expand Down
8 changes: 4 additions & 4 deletions libraries/core-react/src/Tabs/Tabs.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import { Tabs } from '.'

const { TabList, Tab, TabPanels, TabPanel } = Tabs

const noop = () => {}
const noop = () => null

afterEach(cleanup)

const TabsWithRefs = () => {
const activeRef = useRef(null)
const inactiveRef = useRef(null)
const activeRef = useRef<HTMLButtonElement>(null)
const inactiveRef = useRef<HTMLButtonElement>(null)

useEffect(() => {
activeRef.current.textContent = 'Active tab'
Expand All @@ -32,7 +32,7 @@ const TabsWithRefs = () => {
)
}

const TabsWithPanels = ({ selectedTabIndex }) => {
const TabsWithPanels = ({ selectedTabIndex = 0 }) => {
const [activeTab, setActiveTab] = useState(selectedTabIndex)

const handleChange = (index) => {
Expand Down
1 change: 0 additions & 1 deletion libraries/core-react/src/Tabs/Tabs.tokens.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// @ts-nocheck
import { tokens } from '@equinor/eds-tokens'

const {
Expand Down
43 changes: 14 additions & 29 deletions libraries/core-react/src/Tabs/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
// @ts-nocheck
import React, { forwardRef, useMemo, useState } from 'react'
import PropTypes from 'prop-types'
import createId from 'lodash/uniqueId'
import { TabsProvider } from './Tabs.context'
import { Variants } from './Tabs.types'

const Tabs = forwardRef(function Tabs(
{ activeTab, onChange, onBlur, onFocus, variant, ...props },
type Props = {
/** The index of the active tab */
activeTab?: number
/** The callback function for selecting a tab */
onChange?: (index: number) => void
/** Sets the width of the tabs */
variant?: Variants
} & React.HTMLAttributes<HTMLDivElement>

const Tabs = forwardRef<HTMLDivElement, Props>(function Tabs(
{ activeTab, onChange, onBlur, onFocus, variant = 'minWidth', ...props },
ref,
) {
const tabsId = useMemo(() => createId('tabs-'), [])
Expand All @@ -14,7 +22,7 @@ const Tabs = forwardRef(function Tabs(

let blurTimer

const handleBlur = (e) => {
const handleBlur = (e: React.FocusEvent<HTMLDivElement>) => {
blurTimer = setTimeout(() => {
if (tabsFocused) {
setTabsFocused(false)
Expand All @@ -23,7 +31,7 @@ const Tabs = forwardRef(function Tabs(
onBlur(e)
}

const handleFocus = (e) => {
const handleFocus = (e: React.FocusEvent<HTMLDivElement>) => {
if (e.target.getAttribute('role') !== 'tab') {
return
}
Expand All @@ -49,27 +57,4 @@ const Tabs = forwardRef(function Tabs(
)
})

Tabs.propTypes = {
/** The index of the active tab */
activeTab: PropTypes.number,
/** The callback function for selecting a tab */
onChange: PropTypes.func,
/** The callback function for removing focus from a tab */
onBlur: PropTypes.func,
/** The callback function for focusing on a tab */
onFocus: PropTypes.func,
/** Sets the width of the tabs */
variant: PropTypes.oneOf(['fullWidth', 'minWidth']),
/** @ignore */
children: PropTypes.node.isRequired,
}

Tabs.defaultProps = {
activeTab: 0,
onChange: () => {},
onBlur: () => {},
onFocus: () => {},
variant: 'minWidth',
}

export { Tabs }
1 change: 1 addition & 0 deletions libraries/core-react/src/Tabs/Tabs.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type Variants = 'fullWidth' | 'minWidth' | ''
14 changes: 11 additions & 3 deletions libraries/core-react/src/Tabs/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
// @ts-nocheck
import { Tabs } from './Tabs'
import { Tabs as BaseComponent } from './Tabs'
import { TabList } from './TabList'
import { Tab } from './Tab'
import { TabPanels } from './TabPanels'
import { TabPanel } from './TabPanel'

type TabsType = typeof BaseComponent & {
Tab: typeof Tab
TabList: typeof TabList
TabPanels: typeof TabPanels
TabPanel: typeof TabPanel
}

const Tabs = BaseComponent as TabsType

Tabs.Tab = Tab
Tabs.TabList = TabList
Tabs.TabPanels = TabPanels
Tabs.TabPanel = TabPanel

export { Tabs } from './Tabs'
export { Tabs }

0 comments on commit aff720c

Please sign in to comment.