[WEB-3128] improvement: made tab list items modular for independent use and added few icons (#6387)
* chore: added bar and tree map icons * improvement: made tab list items modular for independent use
This commit is contained in:
parent
c779fc095c
commit
7183cffdb7
6 changed files with 127 additions and 45 deletions
20
packages/ui/src/icons/bar-icon.tsx
Normal file
20
packages/ui/src/icons/bar-icon.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import * as React from "react";
|
||||
|
||||
import { ISvgIcons } from "./type";
|
||||
|
||||
export const BarIcon: React.FC<ISvgIcons> = ({ className = "", ...rest }) => (
|
||||
<svg className={className} viewBox="0 0 24 24" fill="currentColor" xmlns="http://www.w3.org/2000/svg" {...rest}>
|
||||
<path
|
||||
d="M0 12.5859C0 11.4814 0.89543 10.5859 2 10.5859H3.64706C4.75163 10.5859 5.64706 11.4814 5.64706 12.5859V23.9977H0V12.5859Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M9.17773 2C9.17773 0.89543 10.0732 0 11.1777 0H12.8248C13.9294 0 14.8248 0.895431 14.8248 2V24H9.17773V2Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M18.3535 8.35156C18.3535 7.247 19.2489 6.35156 20.3535 6.35156H22.0006C23.1051 6.35156 24.0006 7.24699 24.0006 8.35156V23.9986H18.3535V8.35156Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
|
@ -49,3 +49,5 @@ export * from "./off-track-icon";
|
|||
export * from "./at-risk-icon";
|
||||
export * from "./multiple-sticky";
|
||||
export * from "./sticky-note-icon";
|
||||
export * from "./bar-icon";
|
||||
export * from "./tree-map-icon";
|
||||
|
|
|
|||
16
packages/ui/src/icons/tree-map-icon.tsx
Normal file
16
packages/ui/src/icons/tree-map-icon.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import * as React from "react";
|
||||
|
||||
import { ISvgIcons } from "./type";
|
||||
|
||||
export const TreeMapIcon: React.FC<ISvgIcons> = ({ className = "", ...rest }) => (
|
||||
<svg className={className} viewBox="0 0 27 22" fill="currentColor" xmlns="http://www.w3.org/2000/svg" {...rest}>
|
||||
<rect width="10" height="10" rx="1" fill="currentColor" />
|
||||
<rect x="11.5" width="10" height="6" rx="1" fill="currentColor" />
|
||||
<rect y="12" width="10" height="10" rx="1" fill="currentColor" />
|
||||
<rect x="11.5" y="16" width="10" height="6" rx="1" fill="currentColor" />
|
||||
<rect x="11.5" y="8" width="10" height="6" rx="1" fill="currentColor" />
|
||||
<rect x="23" width="4" height="11" rx="1" fill="currentColor" />
|
||||
<rect x="23" y="13" width="4" height="4" rx="1" fill="currentColor" />
|
||||
<rect x="23" y="19" width="4" height="3" rx="1" fill="currentColor" />
|
||||
</svg>
|
||||
);
|
||||
|
|
@ -1 +1,2 @@
|
|||
export * from "./tabs";
|
||||
export * from "./tab-list";
|
||||
|
|
|
|||
71
packages/ui/src/tabs/tab-list.tsx
Normal file
71
packages/ui/src/tabs/tab-list.tsx
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import { Tab } from "@headlessui/react";
|
||||
import { LucideProps } from "lucide-react";
|
||||
import React, { FC } from "react";
|
||||
// helpers
|
||||
import { cn } from "../../helpers";
|
||||
|
||||
export type TabListItem = {
|
||||
key: string;
|
||||
icon?: FC<LucideProps>;
|
||||
label?: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
onClick?: () => void;
|
||||
};
|
||||
|
||||
type TTabListProps = {
|
||||
tabs: TabListItem[];
|
||||
tabListClassName?: string;
|
||||
tabClassName?: string;
|
||||
size?: "sm" | "md" | "lg";
|
||||
selectedTab?: string;
|
||||
onTabChange?: (key: string) => void;
|
||||
};
|
||||
|
||||
export const TabList: FC<TTabListProps> = ({
|
||||
tabs,
|
||||
tabListClassName,
|
||||
tabClassName,
|
||||
size = "md",
|
||||
selectedTab,
|
||||
onTabChange,
|
||||
}) => (
|
||||
<Tab.List
|
||||
as="div"
|
||||
className={cn(
|
||||
"flex w-full min-w-fit items-center justify-between gap-1.5 rounded-md text-sm p-0.5 bg-custom-background-80/60",
|
||||
tabListClassName
|
||||
)}
|
||||
>
|
||||
{tabs.map((tab) => (
|
||||
<Tab
|
||||
className={({ selected }) =>
|
||||
cn(
|
||||
"flex items-center justify-center p-1 min-w-fit w-full font-medium text-custom-text-100 outline-none focus:outline-none cursor-pointer transition-all rounded",
|
||||
(selectedTab ? selectedTab === tab.key : selected)
|
||||
? "bg-custom-background-100 text-custom-text-100 shadow-sm"
|
||||
: tab.disabled
|
||||
? "text-custom-text-400 cursor-not-allowed"
|
||||
: "text-custom-text-400 hover:text-custom-text-300 hover:bg-custom-background-80/60",
|
||||
{
|
||||
"text-xs": size === "sm",
|
||||
"text-sm": size === "md",
|
||||
"text-base": size === "lg",
|
||||
},
|
||||
tabClassName
|
||||
)
|
||||
}
|
||||
key={tab.key}
|
||||
onClick={() => {
|
||||
if (!tab.disabled) {
|
||||
onTabChange?.(tab.key);
|
||||
tab.onClick?.();
|
||||
}
|
||||
}}
|
||||
disabled={tab.disabled}
|
||||
>
|
||||
{tab.icon && <tab.icon className="size-4" />}
|
||||
{tab.label}
|
||||
</Tab>
|
||||
))}
|
||||
</Tab.List>
|
||||
);
|
||||
|
|
@ -1,19 +1,17 @@
|
|||
import React, { FC, Fragment, useEffect, useState } from "react";
|
||||
import { Tab } from "@headlessui/react";
|
||||
import { LucideProps } from "lucide-react";
|
||||
import React, { FC, Fragment, useEffect, useState } from "react";
|
||||
// helpers
|
||||
import { useLocalStorage } from "@plane/hooks";
|
||||
import { cn } from "../../helpers";
|
||||
// types
|
||||
import { TabList, TabListItem } from "./tab-list";
|
||||
|
||||
type TabItem = {
|
||||
key: string;
|
||||
icon?: FC<LucideProps>;
|
||||
label?: React.ReactNode;
|
||||
export type TabContent = {
|
||||
content: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
onClick?: () => void;
|
||||
};
|
||||
|
||||
export type TabItem = TabListItem & TabContent;
|
||||
|
||||
type TTabsProps = {
|
||||
tabs: TabItem[];
|
||||
storageKey?: string;
|
||||
|
|
@ -58,48 +56,22 @@ export const Tabs: FC<TTabsProps> = (props: TTabsProps) => {
|
|||
|
||||
const currentTabIndex = (tabKey: string): number => tabs.findIndex((tab) => tab.key === tabKey);
|
||||
|
||||
const handleTabChange = (key: string) => {
|
||||
setSelectedTab(key);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full h-full">
|
||||
<Tab.Group defaultIndex={currentTabIndex(selectedTab)}>
|
||||
<div className={cn("flex flex-col w-full h-full gap-2", containerClassName)}>
|
||||
<div className={cn("flex w-full items-center gap-4", tabListContainerClassName)}>
|
||||
<Tab.List
|
||||
as="div"
|
||||
className={cn(
|
||||
"flex w-full min-w-fit items-center justify-between gap-1.5 rounded-md text-sm p-0.5 bg-custom-background-80/60",
|
||||
tabListClassName
|
||||
)}
|
||||
>
|
||||
{tabs.map((tab) => (
|
||||
<Tab
|
||||
className={({ selected }) =>
|
||||
cn(
|
||||
`flex items-center justify-center p-1 min-w-fit w-full font-medium text-custom-text-100 outline-none focus:outline-none cursor-pointer transition-all rounded`,
|
||||
selected
|
||||
? "bg-custom-background-100 text-custom-text-100 shadow-sm"
|
||||
: tab.disabled
|
||||
? "text-custom-text-400 cursor-not-allowed"
|
||||
: "text-custom-text-400 hover:text-custom-text-300 hover:bg-custom-background-80/60",
|
||||
{
|
||||
"text-xs": size === "sm",
|
||||
"text-sm": size === "md",
|
||||
"text-base": size === "lg",
|
||||
},
|
||||
tabClassName
|
||||
)
|
||||
}
|
||||
key={tab.key}
|
||||
onClick={() => {
|
||||
if (!tab.disabled) setSelectedTab(tab.key);
|
||||
tab.onClick?.();
|
||||
}}
|
||||
disabled={tab.disabled}
|
||||
>
|
||||
{tab.icon && <tab.icon className="size-4" />}
|
||||
{tab.label}
|
||||
</Tab>
|
||||
))}
|
||||
</Tab.List>
|
||||
<TabList
|
||||
tabs={tabs}
|
||||
tabListClassName={tabListClassName}
|
||||
tabClassName={tabClassName}
|
||||
size={size}
|
||||
onTabChange={handleTabChange}
|
||||
/>
|
||||
{actions && <div className="flex-grow">{actions}</div>}
|
||||
</div>
|
||||
<Tab.Panels as={Fragment}>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue