Skip to content

Commit

Permalink
feat: add two modes(single or multiple) for Tabs, close #9
Browse files Browse the repository at this point in the history
  • Loading branch information
pearmini committed Jan 18, 2021
1 parent 88642e1 commit 19b06a2
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 33 deletions.
73 changes: 56 additions & 17 deletions src/components/Tab.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
import React, { createContext, useState } from "react";
import styled from "styled-components";
import classNames from "classnames";

import { tuple } from "../utils/type";
import TabPanel, { TabPanelProps } from "./TabPanel";
import Icon from "./Icon";

const TabTypes = tuple("line", "card", "editable-card");
type TabType = typeof TabTypes[number];

const TabModes = tuple("single", "multiple");
type TabMode = typeof TabModes[number];

export interface TabProps {
type?: TabType;
defaultActiveIndex?: string;
onEdit?: (tartgetKey: string, action: string) => void;
onChange?: (activeKey: string) => void;
onExapnd?: () => void;
children?: React.ReactNode;
expandable?: boolean;
defaultMode?: TabMode;
className?: string;
}

export interface TabContext {
activeIndex?: string;
mode?: TabMode;
}

export const TabContext = createContext<TabContext>({ activeIndex: "0" });
Expand All @@ -25,42 +35,66 @@ const Container = styled.div``;

const Header = styled.ul`
display: flex;
position: relative;
`;

const Item = styled.li<{ type?: TabType }>``;

const Body = styled.div``;

const ActionIcon = styled(Icon)`
position: absolute;
top: 50%;
right: 0.5rem;
transform: translate(0, -50%);
`;

const InternalTab: React.FC<TabProps> = ({
children,
type,
defaultActiveIndex,
type = "line",
defaultActiveIndex = "0",
onChange,
expandable = false,
defaultMode = "single",
className,
onExapnd,
...restProps
}) => {
const [activeIndex, setActiveIndex] = useState(defaultActiveIndex);
const [activeIndex, setActiveIndex] = useState<string>(defaultActiveIndex);
const [mode, setMode] = useState<TabMode>(defaultMode);
const tabContextValue: TabContext = {
activeIndex,
mode,
};
const classes = classNames(className, "h-full");

function handleClickItem(index: string) {
setActiveIndex(index);
onChange && onChange(index);
}

function handleClickActionIcon() {
mode === "single" ? setMode("multiple") : setMode("single");
onExapnd && onExapnd();
}

function toItem(child: React.ReactNode, childIndex: number) {
const childElement = child as React.FunctionComponentElement<TabPanelProps>;
const { displayName } = childElement.type;
if (displayName === "TabPanel") {
const { text, index } = childElement.props;
const { label, index } = childElement.props;
const key = index ? index : childIndex.toString();
const classes = classNames(
{
"text-gray-400": key !== activeIndex,
"hover:text-gray-600": key !== activeIndex,
"flex-1": mode === "multiple",
},
"p-2 cursor-pointer transition-all duration-150 text-sm"
);
return (
<Item
type={type}
onClick={() => handleClickItem(key)}
className="p-1 cursor-pointer transition duration-150 hover:bg-white hover:shadow-sm"
>
{text}
<Item type={type} onClick={() => handleClickItem(key)} className={classes}>
{label}
</Item>
);
} else {
Expand All @@ -84,10 +118,19 @@ const InternalTab: React.FC<TabProps> = ({
}

return (
<Container {...restProps}>
<Container {...restProps} className={classes}>
<TabContext.Provider value={tabContextValue}>
<Header className="bg-gray-50">{React.Children.map(children, toItem)}</Header>
<Body>{React.Children.map(children, toPane)}</Body>
<Header className="bg-gray-50 border-b border-gray-200">
{React.Children.map(children, toItem)}
{expandable && (
<ActionIcon
icon={mode === "single" ? "expand" : "compress"}
className="text-gray-600 cursor-pointer"
onClick={handleClickActionIcon}
/>
)}
</Header>
<Body className="flex h-full">{React.Children.map(children, toPane)}</Body>
</TabContext.Provider>
</Container>
);
Expand All @@ -101,9 +144,5 @@ const Tab = InternalTab as OuternalTab;

Tab.TabPanel = TabPanel;
Tab.displayName = "Tab";
Tab.defaultProps = {
type: "line",
defaultActiveIndex: "0",
};

export default Tab;
23 changes: 13 additions & 10 deletions src/components/TabPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,33 @@
import React, { useContext } from "react";
import { TabContext } from "./Tab";
import styled from "styled-components";
import classNames from "classnames";

export interface TabPanelProps {
text?: string;
label?: string;
index?: string;
children?: React.ReactNode;
}

const Container = styled.div<{ active?: boolean }>`
display: ${(props) => (props.active ? "block" : "none")};
`;
const Container = styled.div``;

const TabPanel: React.FC<TabPanelProps> = ({ children, index }) => {
const { activeIndex } = useContext(TabContext);
return (
<Container active={activeIndex === index} className="h-full">
{children}
</Container>
const { activeIndex, mode } = useContext(TabContext);
const classes = classNames(
{
hidden: activeIndex !== index && mode === "single",
"flex-1": mode === "multiple",
"border-l border-gray-200": mode === "multiple",
},
"h-full transition-all duration-150"
);
return <Container className={classes}>{children}</Container>;
};

TabPanel.displayName = "TabPanel";

TabPanel.defaultProps = {
text: "Tab",
label: "Tab",
};

export default TabPanel;
6 changes: 5 additions & 1 deletion src/helpers/icon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
faTrashAlt,
faUserCircle,
faUsers,
faExpand,
faCompress,
} from "@fortawesome/free-solid-svg-icons";

library.add(
Expand All @@ -24,5 +26,7 @@ library.add(
faTrashAlt,
faBuilding,
faEllipsisH,
faCalendar
faCalendar,
faExpand,
faCompress,
);
10 changes: 5 additions & 5 deletions src/pages/editor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ const EditorPage: React.FC<EditorPageProps> = () => {
<Toolbar />
<Body className="w-full h-full bg-gray-200">
<LeftPanel resizable>
<Tab>
<TabPanel text="Outline">
<Tab expandable>
<TabPanel label="Outline">
<OutlinePanel />
</TabPanel>
<TabPanel text="Assets">
<TabPanel label="Assets">
<AssetsPanel />
</TabPanel>
</Tab>
Expand All @@ -58,10 +58,10 @@ const EditorPage: React.FC<EditorPageProps> = () => {
</MainPanel>
<RightPanel>
<Tab>
<TabPanel text="Style">
<TabPanel label="Style">
<StylePanel />
</TabPanel>
<TabPanel text="Effects">
<TabPanel label="Effects">
<EffectsPanel />
</TabPanel>
</Tab>
Expand Down

0 comments on commit 19b06a2

Please sign in to comment.