From 0f37a4591b604fa51f5642ee69b59cc96522a478 Mon Sep 17 00:00:00 2001 From: Doug MacKenzie Date: Wed, 4 Dec 2024 14:02:42 +1100 Subject: [PATCH] Tabs (future): add carousel functionality when tabs overflow container --- .changeset/thin-actors-knock.md | 5 + .../__future__/Tabs/subcomponents/Tab/Tab.tsx | 2 +- .../subcomponents/TabList/TabList.module.css | 41 +++++++- .../Tabs/subcomponents/TabList/TabList.tsx | 99 +++++++++++++++++-- 4 files changed, 137 insertions(+), 10 deletions(-) create mode 100644 .changeset/thin-actors-knock.md diff --git a/.changeset/thin-actors-knock.md b/.changeset/thin-actors-knock.md new file mode 100644 index 00000000000..37f56f49323 --- /dev/null +++ b/.changeset/thin-actors-knock.md @@ -0,0 +1,5 @@ +--- +'@kaizen/components': patch +--- + +Tabs (future): add carousel functionality when tabs overflow container width diff --git a/packages/components/src/__future__/Tabs/subcomponents/Tab/Tab.tsx b/packages/components/src/__future__/Tabs/subcomponents/Tab/Tab.tsx index e522ccf2cd9..4a16e6b7b3d 100644 --- a/packages/components/src/__future__/Tabs/subcomponents/Tab/Tab.tsx +++ b/packages/components/src/__future__/Tabs/subcomponents/Tab/Tab.tsx @@ -28,7 +28,7 @@ export const Tab = (props: TabProps): JSX.Element => { } return ( - + {({ isSelected, isFocusVisible, isHovered }) => ( <> {children} diff --git a/packages/components/src/__future__/Tabs/subcomponents/TabList/TabList.module.css b/packages/components/src/__future__/Tabs/subcomponents/TabList/TabList.module.css index dc9cad7ca7e..9d918d3837a 100644 --- a/packages/components/src/__future__/Tabs/subcomponents/TabList/TabList.module.css +++ b/packages/components/src/__future__/Tabs/subcomponents/TabList/TabList.module.css @@ -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); +} diff --git a/packages/components/src/__future__/Tabs/subcomponents/TabList/TabList.tsx b/packages/components/src/__future__/Tabs/subcomponents/TabList/TabList.tsx index 274a08b752b..12af22b35bc 100644 --- a/packages/components/src/__future__/Tabs/subcomponents/TabList/TabList.tsx +++ b/packages/components/src/__future__/Tabs/subcomponents/TabList/TabList.tsx @@ -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' +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' export type TabListProps = { /** @@ -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(false) + const [leftArrowEnabled, setLeftArrowEnabled] = useState(false) + const [rightArrowEnabled, setRightArrowEnabled] = useState(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(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 ( - - {children} - +
+ {leftArrowEnabled && ( + + )} + + {children} + + {rightArrowEnabled && ( + + )} +
) }