From f6625cbcd5bc4c9e92cf2794f4f3a1521ce37c5c Mon Sep 17 00:00:00 2001 From: Pranay Kothapalli Date: Wed, 19 Jun 2024 12:10:27 +0530 Subject: [PATCH] add: keyboard navigation of tabs --- src/components/ui/Tabs/Tabs.tsx | 17 +++--- .../ui/Tabs/context/TabsContext.tsx | 5 -- .../ui/Tabs/context/TabsRootContext.tsx | 6 ++ src/components/ui/Tabs/segments/TabRoot.tsx | 26 -------- .../ui/Tabs/segments/TabTrigger.tsx | 41 ------------- .../Tabs/{segments => shards}/TabContent.tsx | 8 ++- .../ui/Tabs/{segments => shards}/TabList.tsx | 6 +- src/components/ui/Tabs/shards/TabRoot.tsx | 52 ++++++++++++++++ src/components/ui/Tabs/shards/TabTrigger.tsx | 59 +++++++++++++++++++ src/components/ui/Tabs/types/TabRootProps.tsx | 2 +- 10 files changed, 137 insertions(+), 85 deletions(-) delete mode 100644 src/components/ui/Tabs/context/TabsContext.tsx create mode 100644 src/components/ui/Tabs/context/TabsRootContext.tsx delete mode 100644 src/components/ui/Tabs/segments/TabRoot.tsx delete mode 100644 src/components/ui/Tabs/segments/TabTrigger.tsx rename src/components/ui/Tabs/{segments => shards}/TabContent.tsx (73%) rename src/components/ui/Tabs/{segments => shards}/TabList.tsx (82%) create mode 100644 src/components/ui/Tabs/shards/TabRoot.tsx create mode 100644 src/components/ui/Tabs/shards/TabTrigger.tsx diff --git a/src/components/ui/Tabs/Tabs.tsx b/src/components/ui/Tabs/Tabs.tsx index 16dc7038..4e22a23a 100644 --- a/src/components/ui/Tabs/Tabs.tsx +++ b/src/components/ui/Tabs/Tabs.tsx @@ -1,25 +1,26 @@ 'use client'; import React, {useState} from 'react'; -import TabList from './segments/TabList'; -import TabContent from './segments/TabContent'; -import TabRoot from './segments/TabRoot'; +import TabList from './shards/TabList'; +import TabContent from './shards/TabContent'; +import TabRoot from './shards/TabRoot'; -import {Tab} from './types'; +import {TabProps} from './types'; export type TabsProps = { - tabs?: Tab[] + tabs?: TabProps[] props?: Record[] } const Tabs = ({tabs = [], ...props}: TabsProps) => { // This should be a value <`tabs.value`> that is passed in from the parent component const [activeTab, setActiveTab] = useState(tabs[0].value || ''); + const defaultActiveTab = tabs[0].value || ''; return ( - - - + + + ); }; diff --git a/src/components/ui/Tabs/context/TabsContext.tsx b/src/components/ui/Tabs/context/TabsContext.tsx deleted file mode 100644 index cb2a8e13..00000000 --- a/src/components/ui/Tabs/context/TabsContext.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import {createContext} from 'react'; - -const TabRootContext = createContext(null); - -export default TabRootContext; diff --git a/src/components/ui/Tabs/context/TabsRootContext.tsx b/src/components/ui/Tabs/context/TabsRootContext.tsx new file mode 100644 index 00000000..c3a26a81 --- /dev/null +++ b/src/components/ui/Tabs/context/TabsRootContext.tsx @@ -0,0 +1,6 @@ + +import {createContext} from 'react'; + +const TabsRootContext = createContext(null); + +export default TabsRootContext; diff --git a/src/components/ui/Tabs/segments/TabRoot.tsx b/src/components/ui/Tabs/segments/TabRoot.tsx deleted file mode 100644 index f5f5214d..00000000 --- a/src/components/ui/Tabs/segments/TabRoot.tsx +++ /dev/null @@ -1,26 +0,0 @@ - -'use client'; -import React from 'react'; -import {customClassSwitcher} from '~/core'; - -import TabsContext from '../context/TabsContext'; - -import {TabRootProps} from '../types'; - -const COMPONENT_NAME = 'Tabs'; - - -const TabRoot = ({children, customRootClass, className, color, ...props}: TabRootProps) => { - const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME); - return ( - -
- {children} -
-
- ); -}; - -TabRoot.displayName = COMPONENT_NAME; - -export default TabRoot; diff --git a/src/components/ui/Tabs/segments/TabTrigger.tsx b/src/components/ui/Tabs/segments/TabTrigger.tsx deleted file mode 100644 index 07bceceb..00000000 --- a/src/components/ui/Tabs/segments/TabTrigger.tsx +++ /dev/null @@ -1,41 +0,0 @@ -'use client'; -import React from 'react'; -import {customClassSwitcher} from '~/core'; -import {TabProps} from '../types'; - -const COMPONENT_NAME = 'TabTrigger'; - -export type TabTriggerProps = { - tab: TabProps; - setActiveTab: React.Dispatch; - activeTab: TabProps; - className?: string; - customRootClass?: string; - index: number; - props?: Record[] -} - -const TabTrigger = ({tab, setActiveTab, activeTab, className, customRootClass, index, ...props}: TabTriggerProps) => { - const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME); - - const isActive = activeTab === tab.value; - - const handleClick = (tab: Tab) => { - setActiveTab(tab.value); - }; - - return ( - - ); -}; - -TabTrigger.displayName = 'TabTrigger'; - -export default TabTrigger; diff --git a/src/components/ui/Tabs/segments/TabContent.tsx b/src/components/ui/Tabs/shards/TabContent.tsx similarity index 73% rename from src/components/ui/Tabs/segments/TabContent.tsx rename to src/components/ui/Tabs/shards/TabContent.tsx index 0cfab3f9..14ae04eb 100644 --- a/src/components/ui/Tabs/segments/TabContent.tsx +++ b/src/components/ui/Tabs/shards/TabContent.tsx @@ -1,8 +1,9 @@ 'use client'; -import React from 'react'; +import React, {useContext} from 'react'; import {customClassSwitcher} from '~/core'; import {TabProps} from '../types'; const COMPONENT_NAME = 'TabContent'; +import TabsRootContext from '../context/TabsRootContext'; export type TabContentProps ={ @@ -12,9 +13,12 @@ export type TabContentProps ={ customRootClass?: string; } -const TabContent = ({tabs = [], activeTab, className, customRootClass}: TabContentProps) => { +const TabContent = ({ className, customRootClass}: TabContentProps) => { const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME); + const {tabs, activeTab, setActiveTab} = useContext(TabsRootContext); + + return
{tabs.map((tab, index) => { if (tab.value === activeTab) { diff --git a/src/components/ui/Tabs/segments/TabList.tsx b/src/components/ui/Tabs/shards/TabList.tsx similarity index 82% rename from src/components/ui/Tabs/segments/TabList.tsx rename to src/components/ui/Tabs/shards/TabList.tsx index e2b1e669..525546f7 100644 --- a/src/components/ui/Tabs/segments/TabList.tsx +++ b/src/components/ui/Tabs/shards/TabList.tsx @@ -1,9 +1,10 @@ 'use client'; -import React from 'react'; +import React, {useContext} from 'react'; import {customClassSwitcher} from '~/core'; import TabTrigger from './TabTrigger'; import {TabProps} from '../types'; +import TabsRootContext from '../context/TabsRootContext'; const COMPONENT_NAME = 'TabList'; @@ -15,8 +16,9 @@ export type TabListProps = { activeTab: TabProps; } -const TabList = ({tabs = [], className='', customRootClass='', setActiveTab, activeTab}: TabListProps) => { +const TabList = ({className='', customRootClass=''}: TabListProps) => { const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME); + const {tabs, activeTab, setActiveTab} = useContext(TabsRootContext); // TODO: in the previous return value of // {tabs.map((tab, index) => { diff --git a/src/components/ui/Tabs/shards/TabRoot.tsx b/src/components/ui/Tabs/shards/TabRoot.tsx new file mode 100644 index 00000000..22c72b1a --- /dev/null +++ b/src/components/ui/Tabs/shards/TabRoot.tsx @@ -0,0 +1,52 @@ + +'use client'; +import React, {useState} from 'react'; +import {customClassSwitcher} from '~/core'; + +import TabsRootContext from '../context/TabsRootContext'; + +import {TabRootProps} from '../types'; + +const COMPONENT_NAME = 'Tabs'; + + +const TabRoot = ({children, defaultTab='', customRootClass, tabs=[], className, color, ...props}: TabRootProps) => { + const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME); + + const [activeTab, setActiveTab] = useState(defaultTab || tabs[0].value || ''); + + const nextTab = ()=>{ + const currentIndex = tabs.findIndex(tab => tab.value === activeTab); + const nextIndex = currentIndex + 1; + if (nextIndex < tabs.length) { + setActiveTab(tabs[nextIndex].value); + } + } + + const previousTab = ()=>{ + const currentIndex = tabs.findIndex(tab => tab.value === activeTab); + const previousIndex = currentIndex - 1; + if (previousIndex >= 0) { + setActiveTab(tabs[previousIndex].value); + } + } + + + return ( + +
+ {children} +
+
+ ); +}; + +TabRoot.displayName = COMPONENT_NAME; + +export default TabRoot; diff --git a/src/components/ui/Tabs/shards/TabTrigger.tsx b/src/components/ui/Tabs/shards/TabTrigger.tsx new file mode 100644 index 00000000..a65756a9 --- /dev/null +++ b/src/components/ui/Tabs/shards/TabTrigger.tsx @@ -0,0 +1,59 @@ +'use client'; +import React, {useContext} from 'react'; +import {customClassSwitcher} from '~/core'; +import {TabProps} from '../types'; + +import TabsRootContext from '../context/TabsRootContext'; + + +const COMPONENT_NAME = 'TabTrigger'; + +export type TabTriggerProps = { + tab: TabProps; + setActiveTab: React.Dispatch; + activeTab: TabProps; + className?: string; + customRootClass?: string; + index: number; + props?: Record[] +} + +const TabTrigger = ({tab, setActiveTab, activeTab, className, customRootClass, index, ...props}: TabTriggerProps) => { + + // use context + const {tabs, previousTab, nextTab} = useContext(TabsRootContext); + console.log(tabs); + + + const rootClass = customClassSwitcher(customRootClass, COMPONENT_NAME); + + const isActive = activeTab === tab.value; + + const handleClick = (tab: Tab) => { + setActiveTab(tab.value); + }; + + const handleKeyDownEvent = (e: React.KeyboardEvent) => { + console.log(e.key); + if(e.key=="ArrowLeft"){ + previousTab(); + } + if(e.key=="ArrowRight"){ + nextTab(); + } + } + + return ( + + ); +}; + +TabTrigger.displayName = 'TabTrigger'; + +export default TabTrigger; diff --git a/src/components/ui/Tabs/types/TabRootProps.tsx b/src/components/ui/Tabs/types/TabRootProps.tsx index f037f4fd..9903e965 100644 --- a/src/components/ui/Tabs/types/TabRootProps.tsx +++ b/src/components/ui/Tabs/types/TabRootProps.tsx @@ -5,7 +5,7 @@ type TabRootProps = { color?: string; props?: Record[]; tabs: []; - activeTab: any + defaultTab: string; } export default TabRootProps;