Skip to content

Commit

Permalink
add: keyboard navigation of tabs
Browse files Browse the repository at this point in the history
  • Loading branch information
kotAPI committed Jun 19, 2024
1 parent 5f36c8c commit f6625cb
Show file tree
Hide file tree
Showing 10 changed files with 137 additions and 85 deletions.
17 changes: 9 additions & 8 deletions src/components/ui/Tabs/Tabs.tsx
Original file line number Diff line number Diff line change
@@ -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<string, any>[]
}

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 (
<TabRoot {...props} >
<TabList activeTab={activeTab} setActiveTab={setActiveTab} tabs={tabs} />
<TabContent activeTab={activeTab} tabs={tabs} />
<TabRoot tabs={tabs} defaultTab={defaultActiveTab} >
<TabList />
<TabContent />
</TabRoot>
);
};
Expand Down
5 changes: 0 additions & 5 deletions src/components/ui/Tabs/context/TabsContext.tsx

This file was deleted.

6 changes: 6 additions & 0 deletions src/components/ui/Tabs/context/TabsRootContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

import {createContext} from 'react';

const TabsRootContext = createContext(null);

export default TabsRootContext;
26 changes: 0 additions & 26 deletions src/components/ui/Tabs/segments/TabRoot.tsx

This file was deleted.

41 changes: 0 additions & 41 deletions src/components/ui/Tabs/segments/TabTrigger.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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 ={
Expand All @@ -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 <div className={`${rootClass} ${className}`}>
{tabs.map((tab, index) => {
if (tab.value === activeTab) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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) => {
Expand Down
52 changes: 52 additions & 0 deletions src/components/ui/Tabs/shards/TabRoot.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<TabsRootContext.Provider value={{
activeTab,
setActiveTab,
nextTab,
previousTab,
tabs
}}>
<div className={`${rootClass} ${className}`} data-accent-color={color} {...props} >
{children}
</div>
</TabsRootContext.Provider>
);
};

TabRoot.displayName = COMPONENT_NAME;

export default TabRoot;
59 changes: 59 additions & 0 deletions src/components/ui/Tabs/shards/TabTrigger.tsx
Original file line number Diff line number Diff line change
@@ -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<Tab>;
activeTab: TabProps;
className?: string;
customRootClass?: string;
index: number;
props?: Record<string, any>[]
}

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 (
<button
role="tab" key={index} className={`${rootClass} ${isActive?'active':''} ${className}`} {...props} onKeyDown={handleKeyDownEvent}
onClick={() => handleClick(tab)}>
<span className={`${rootClass}-inner`}>
{tab.label}
</span>
</button>
);
};

TabTrigger.displayName = 'TabTrigger';

export default TabTrigger;
2 changes: 1 addition & 1 deletion src/components/ui/Tabs/types/TabRootProps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ type TabRootProps = {
color?: string;
props?: Record<string, any>[];
tabs: [];
activeTab: any
defaultTab: string;
}

export default TabRootProps;

0 comments on commit f6625cb

Please sign in to comment.