diff --git a/lib/components/VerticalTab/CollapsibleVerticalTabItem.tsx b/lib/components/VerticalTab/CollapsibleVerticalTabItem.tsx new file mode 100644 index 00000000..8cd8f0fd --- /dev/null +++ b/lib/components/VerticalTab/CollapsibleVerticalTabItem.tsx @@ -0,0 +1,59 @@ + +import React, { useState } from 'react'; +import { Text } from '../Text'; +import clsx from 'clsx'; +import { VerticalTabItem, TabItem } from './VerticalTabItem'; + +type CollapsibleVerticalTabItemProps = { + item: TabItem; + onSelectItemHandler: (title: string) => void; + selectedTab: string; + className?: string; + iconClassName?: string; +} + + +const ArrowIcon = ({ is_open }: { is_open: boolean }) => + +; + +export const CollapsibleVerticalTabItem = ({ + item, + onSelectItemHandler, + selectedTab, + className, + iconClassName, +}: CollapsibleVerticalTabItemProps) => { + const selectedSubItemSelected = item?.subItems?.find((subItem) => subItem.title === selectedTab) + const [open, setOpen] = useState(selectedSubItemSelected ? true : false); + + const onClickHandler = () => { + const shouldCollapse = !selectedSubItemSelected; + if (shouldCollapse) + setOpen(!open); + } + return ( +
+
onClickHandler()} + > + {item?.icon} + {item.title} + +
+ {open &&
+ {item?.subItems?.map((subItem) => { + return ( + onSelectItemHandler(subItem.title)} /> + ) + })} +
} +
+ ) +} diff --git a/lib/components/VerticalTab/VerticalTab.scss b/lib/components/VerticalTab/VerticalTab.scss new file mode 100644 index 00000000..a4e02012 --- /dev/null +++ b/lib/components/VerticalTab/VerticalTab.scss @@ -0,0 +1,94 @@ +.vertical-tab__wrapper { + padding: 8px; + display: flex; + justify-content: center; + gap:25px; + @include mobile { + flex-direction: column;; + } +} + +.vertical-tab { + &__items-container{ + background: #f2f3f4; + flex: 1; + border-radius: 4px; + padding: 8px; + } + + &__item { + display: flex; + padding: 10px 16px; + align-items: center; + cursor: pointer; + border-radius: 4px; + + &:hover{ + background-color: #e6e9e9; + } + + &--active { + background-color: #fff; + border-left: 4px solid #ff444f; + padding-left: 12px; + + &:hover{ + background-color: #fff; + } + } + + &--disabled { + & > span{ + color: #999999; + } + + &:hover{ + cursor: unset; + background-color: inherit; + } + } + } + + &__icon { + width: 16px; + height: 16px; + margin-right: 16px; + } + + &__label { + flex:1; + } + + &__panel { + flex: 2; + } + + &__arrow--open{ + transform: rotate(180deg); + } +} + +.collapsible-vertical-tab { + &__header { + display: flex; + padding: 10px 16px; + align-items: center; + cursor: pointer; + border-radius: 4px; + + &--open > :nth-child(2) { + font-weight: bold; + } + } + + &__icon { + width: 16px; + height: 16px; + margin-right: 16px; + } + + &__label { + flex:1; + } +} + diff --git a/lib/components/VerticalTab/VerticalTab.tsx b/lib/components/VerticalTab/VerticalTab.tsx new file mode 100644 index 00000000..0ed2c73b --- /dev/null +++ b/lib/components/VerticalTab/VerticalTab.tsx @@ -0,0 +1,18 @@ +import React, { memo } from 'react'; +import clsx from 'clsx'; +import './VerticalTab.scss'; + +type VerticalTabProps = { + className?: string; +} + +export const VerticalTab = memo(({ + className, + children +}: React.PropsWithChildren) => { + return ( +
+ {children} +
+ ); +}) diff --git a/lib/components/VerticalTab/VerticalTabItem.tsx b/lib/components/VerticalTab/VerticalTabItem.tsx new file mode 100644 index 00000000..96e3a74b --- /dev/null +++ b/lib/components/VerticalTab/VerticalTabItem.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import clsx from 'clsx'; + +import { Text } from '../Text'; + +export type TabItem = { + icon?: React.ReactNode; + is_disabled?: boolean; + panel?: React.ReactNode; + subItems?: { + is_disabled?: boolean; + panel: React.ReactNode; + title: string; + }[]; + title: string; +} + +type VerticalTabItemProps = { + tab: TabItem; + onClick: (title: string) => void; + className?: string; + selectedTab: string; +} + +export const VerticalTabItem = ({ tab, onClick, className, selectedTab }: VerticalTabItemProps) => { + return ( +
!tab.is_disabled && onClick(tab.title)} + > + {tab?.icon && ( + {tab?.icon} + )} + {tab.title} +
+ ) +} diff --git a/lib/components/VerticalTab/VerticalTabItems.tsx b/lib/components/VerticalTab/VerticalTabItems.tsx new file mode 100644 index 00000000..b2890532 --- /dev/null +++ b/lib/components/VerticalTab/VerticalTabItems.tsx @@ -0,0 +1,131 @@ +import React, { memo, useEffect, useState } from 'react'; +import { CollapsibleVerticalTabItem } from './CollapsibleVerticalTabItem'; +import { VerticalTabItem, type TabItem } from './VerticalTabItem' +import clsx from 'clsx'; + +type VerticalTabItemsProps = { + activeTab: string; + onSelectItem?: (title: string) => void; + wrapperClassName?: string; + panelClassName?: string; + itemClassName?: string; + items: TabItem[]; + should_have_panel?: boolean; +} + +/** + * Component to display the vertical tab items. iit should be wrapperd inside the VerticalTab component + * @param {TabItem} items - tab items + * @param {string} panelClassName -it applies the classname to the right panel + * @param {string} wrapperClassName - it applies the classname to the left side menu container + * @param {string} itemClassName - it applies the classname to the each items whether it's sub-item or single item + * @param {string} activeTab - indicates the active tab. you can pass the title of the tab + * @param {Function} onSelectItem - callback to handle selecting each tab item + * @param {boolean} should_have_panel - indicates whether the panel should be displayed or not + * @returns {React.JSX.Element} - returns the vertical tab component + * + * @example + * const items = [ + * { + * title: 'Item 1', + * icon: Icon, + * panel:
Item 1 pane
+ * }, + * { + * title: 'Item 2', + * icon: Icon, + * panel:
Item 2 pane
, + * subItems: [ + * { + * title: 'Item 2.1', + * panel:
Item 2.1 pane
+ * }, +* { + * title: 'Item 2.2', + * is_disabled: true, + * panel:
Item 2.2 pane
+ * }, + * ] + * }, + * { + * title: 'Item 3', + * icon: Icon, + * panel:
Item 3 pane
+ * }, + * ] + * + * + * console.log('clicked on:', title) + * } + * /> + * + */ + +export const VerticalTabItems = memo(({ + items, + panelClassName, + wrapperClassName, + itemClassName, + activeTab, + should_have_panel = true, + onSelectItem }: VerticalTabItemsProps) => { + const [selectedTab, setSelectedTab] = useState(activeTab); + useEffect(() => { + if (activeTab) { + setSelectedTab(activeTab); + } + }, [activeTab]); + + const findActiveTab = (title: string) => { + for (const item of items) { + if (item?.subItems) { + const foundItem = item?.subItems.find((subItem) => subItem.title === title); + if (foundItem) { + return foundItem; + } + } else { + if (item.title === title) { + return item; + } + } + } + } + + const onSelectItemHandler = (title: string) => { + const new_active_tab = findActiveTab(title)?.title; + setSelectedTab(() => new_active_tab ?? activeTab); + onSelectItem?.(title); + } + + + return ( + <> +
+ {items.map((item) => { + if (!item?.subItems) { + return ( + onSelectItemHandler(item.title)} /> + ) + } else { + return ( + + ) + } + })} +
+ {should_have_panel &&
+ {findActiveTab(selectedTab)?.panel} +
} + + ); +}) + diff --git a/lib/components/VerticalTab/index.ts b/lib/components/VerticalTab/index.ts new file mode 100644 index 00000000..37fa7427 --- /dev/null +++ b/lib/components/VerticalTab/index.ts @@ -0,0 +1,2 @@ +export {VerticalTab} from './VerticalTab' +export {VerticalTabItems} from './VerticalTabItems' \ No newline at end of file diff --git a/lib/main.ts b/lib/main.ts index e8990c2e..0f5c3f9f 100644 --- a/lib/main.ts +++ b/lib/main.ts @@ -13,3 +13,4 @@ export { ToggleSwitch } from "./components/ToggleSwitch"; export { Tooltip } from "./components/Tooltip"; export { useDevice } from "./hooks/useDevice"; export { useOnClickOutside } from "./hooks/useOnClickOutside"; +export { VerticalTab, VerticalTabItems } from "./components/VerticalTab"; \ No newline at end of file diff --git a/src/main.tsx b/src/main.tsx index 6661c4e6..21ea6698 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,31 +1,58 @@ import React from 'react' import ReactDOM from 'react-dom/client' -import { Loader } from '../dist/components/Loader'; -import { Button } from '../dist/components/Button'; -import { Text } from '../dist/components/Text'; -import { Tab, Tabs } from '../dist/components/Tabs'; -import {Dropdown} from '../dist/components/Dropdown'; +import {VerticalTab,VerticalTabItems} from '../dist/components/VerticalTab' + import './style.scss' -ReactDOM.createRoot(document.getElementById('root')!).render( - - <> - console.log(value)} - name='test name' - /> - +const Icon = + + + + + + + + +; +const items = [ + { + title: 'Item 1', + icon: Icon, + subItems: [ + { + title: 'Sub Item 1', + icon: Icon, + component:
Sub Item 1
+ }, + { + title: 'Sub Item 2', + icon: Icon, + }, + { + title: 'Sub Item 3', + icon: Icon, + } + ], + component:
Item 1
+ }, + { + title: 'Item 2', + icon: Icon, + }, + { + title: 'Item 3', + icon: Icon, + } +] + +ReactDOM.createRoot(document.getElementById('root')!).render( + +
+ + + +
, )