Skip to content

Commit

Permalink
Tabs (future): add carousel functionality when tabs overflow container
Browse files Browse the repository at this point in the history
  • Loading branch information
dougmacknz committed Dec 6, 2024
1 parent b4af064 commit 0f37a45
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 10 deletions.
5 changes: 5 additions & 0 deletions .changeset/thin-actors-knock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@kaizen/components': patch
---

Tabs (future): add carousel functionality when tabs overflow container width
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const Tab = (props: TabProps): JSX.Element => {
}

return (
<RACTab {...tabProps}>
<RACTab data-kz-tab {...tabProps}>
{({ isSelected, isFocusVisible, isHovered }) => (
<>
{children}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,47 @@
.container {
position: relative;
}

.tabList {
border-bottom: 1px solid rgba(var(--color-gray-600-rgb), 0.1);
padding: var(--spacing-xs) var(--spacing-md) 0;
padding: var(--spacing-xs) 0 0;
width: 100%;
height: 100%;
overflow-x: scroll;
white-space: nowrap;
scrollbar-width: none;
scroll-behavior: smooth;
}

.noPadding {
padding: 0;
}

.leftArrow,
.rightArrow {
--button-bg-color: var(--color-white);

position: absolute;
z-index: 10000;
inset-block: 0 1px;
border-radius: 0;
width: 48px;
}

.leftArrow {
inset-inline-start: 0;
}

.leftArrow,
.leftArrow:hover {
border-inline-end: 1px solid rgba(var(--color-gray-600-rgb), 0.1);
}

.rightArrow {
inset-inline-end: 0;
}

.rightArrow,
.rightArrow:hover {
border-inline-start: 1px solid rgba(var(--color-gray-600-rgb), 0.1);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import React, { ReactNode } from 'react'
import React, { ReactNode, useEffect, useRef, useState } from 'react'
import classnames from 'classnames'
import { TabList as RACTabList, TabListProps as RACTabListProps } from 'react-aria-components'

Check warning on line 3 in packages/components/src/__future__/Tabs/subcomponents/TabList/TabList.tsx

View workflow job for this annotation

GitHub Actions / eslint

'/home/runner/work/kaizen-design-system/kaizen-design-system/node_modules/.pnpm/[email protected][email protected][email protected][email protected]/node_modules/react-aria-components/dist/types.d.ts' imported multiple times
import { Button } from '~components/__actions__/v3'
import { Icon } from '~components/__future__/Icon'
import styles from './TabList.module.css'
import { Button as RACButton } from 'react-aria-components'

Check failure on line 7 in packages/components/src/__future__/Tabs/subcomponents/TabList/TabList.tsx

View workflow job for this annotation

GitHub Actions / eslint

`react-aria-components` import should occur before import of `~components/__actions__/v3`

Check failure on line 7 in packages/components/src/__future__/Tabs/subcomponents/TabList/TabList.tsx

View workflow job for this annotation

GitHub Actions / eslint

'RACButton' is defined but never used. Allowed unused vars must match /(^_|^React$)/u

Check warning on line 7 in packages/components/src/__future__/Tabs/subcomponents/TabList/TabList.tsx

View workflow job for this annotation

GitHub Actions / eslint

'/home/runner/work/kaizen-design-system/kaizen-design-system/node_modules/.pnpm/[email protected][email protected][email protected][email protected]/node_modules/react-aria-components/dist/types.d.ts' imported multiple times

export type TabListProps = {
/**
Expand All @@ -20,13 +23,93 @@ export type TabListProps = {
*/
export const TabList = (props: TabListProps): JSX.Element => {
const { 'aria-label': ariaLabel, noPadding = false, children, className, ...restProps } = props
const [isDocumentReady, setIsDocumentReady] = useState<boolean>(false)
const [leftArrowEnabled, setLeftArrowEnabled] = useState<boolean>(false)
const [rightArrowEnabled, setRightArrowEnabled] = useState<boolean>(false)

useEffect(() => {
if (!isDocumentReady) {
setIsDocumentReady(true)
return
}

const tabs = document.querySelectorAll('[data-kz-tab]')

const firstTabObserver = new IntersectionObserver(
(entries) => {
if (!entries[0].isIntersecting) {
setLeftArrowEnabled(true)
return
}
setLeftArrowEnabled(false)
},
{
threshold: 0.75,
},
)
firstTabObserver.observe(tabs[0])

const lastTabObserver = new IntersectionObserver(
(entries) => {
if (!entries[0].isIntersecting) {
setRightArrowEnabled(true)
return
}
setRightArrowEnabled(false)
},
{
threshold: 0.75,
},
)
lastTabObserver.observe(tabs[tabs.length - 1])
}, [isDocumentReady])

const tabListRef = useRef<HTMLDivElement | null>(null)

const handleArrowPress = (direction: 'left' | 'right'): void => {
if (tabListRef.current) {
const scrollAmount = 120
const tabListScrollPos = tabListRef.current.scrollLeft
const newSpot =
direction === 'left' ? tabListScrollPos - scrollAmount : tabListScrollPos + scrollAmount
tabListRef.current.scrollLeft = newSpot
}
}

return (
<RACTabList
aria-label={ariaLabel}
className={classnames(styles.tabList, className, noPadding && styles.noPadding)}
{...restProps}
>
{children}
</RACTabList>
<div className={styles.container}>
{leftArrowEnabled && (
<Button
hasHiddenLabel
icon={<Icon name="chevron_left" isPresentational />}
onPress={() => handleArrowPress('left')}
variant="tertiary"
className={styles.leftArrow}
excludeFromTabOrder
>
Slide tab list left
</Button>
)}
<RACTabList
aria-label={ariaLabel}
ref={tabListRef}
className={classnames(styles.tabList, className, noPadding && styles.noPadding)}
{...restProps}
>
{children}
</RACTabList>
{rightArrowEnabled && (
<Button
hasHiddenLabel
icon={<Icon name="chevron_right" isPresentational />}
onPress={() => handleArrowPress('right')}
variant="tertiary"
className={styles.rightArrow}
excludeFromTabOrder
>
Slide tab list right
</Button>
)}
</div>
)
}

0 comments on commit 0f37a45

Please sign in to comment.