From f1bd857289598b0653654d67e52d63007780e3e3 Mon Sep 17 00:00:00 2001 From: Chen Asraf Date: Sat, 27 Jan 2018 16:13:40 +0200 Subject: [PATCH] Add Tab component --- scaffold/component/{{Name}}.tsx | 3 +- src/components/Header/Header.tsx | 11 +-- src/components/Tabs/Tabs.css | 36 ++++++++++ src/components/Tabs/Tabs.css.d.ts | 6 ++ src/components/Tabs/Tabs.module.d.ts | 22 ++++++ src/components/Tabs/Tabs.tsx | 102 +++++++++++++++++++++++++++ 6 files changed, 175 insertions(+), 5 deletions(-) create mode 100644 src/components/Tabs/Tabs.css create mode 100644 src/components/Tabs/Tabs.css.d.ts create mode 100644 src/components/Tabs/Tabs.module.d.ts create mode 100644 src/components/Tabs/Tabs.tsx diff --git a/scaffold/component/{{Name}}.tsx b/scaffold/component/{{Name}}.tsx index c753ff5..6b7d96c 100644 --- a/scaffold/component/{{Name}}.tsx +++ b/scaffold/component/{{Name}}.tsx @@ -14,7 +14,8 @@ class {{Name}} extends React.Component { return (
- {{Name}} Component +

{{Name}} Component

+ {this.props.children}
) } diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx index 7b9c83f..5bbaae4 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Header/Header.tsx @@ -3,6 +3,7 @@ import * as css from './Header.css' import * as I from './Header.module' import AddressBar from 'components/AddressBar/AddressBar' import RequestPayload from 'components/RequestPayload/RequestPayload' +import { Tab, TabContainer } from 'components/Tabs/Tabs' import SelectBox, { Option, styles as selectBoxStyle } from 'components/SelectBox/SelectBox' import Button from 'components/Button/Button' import axios, { AxiosResponse } from 'axios' @@ -25,10 +26,12 @@ class Header extends React.Component { -
- - -
+ + + + + + ) } diff --git a/src/components/Tabs/Tabs.css b/src/components/Tabs/Tabs.css new file mode 100644 index 0000000..fe34c62 --- /dev/null +++ b/src/components/Tabs/Tabs.css @@ -0,0 +1,36 @@ +@import "../../_variables.css"; + +.TabsContainer { + /* */ +} + +.tab-strip { + border-bottom: 1px solid #ccc; + margin-top: -1px; + margin-bottom: 10px; +} + +.tab-label { + padding: 7px 16px; + border-radius: 3px 3px 0 0; + border: 1px solid #ccc; + display: inline-block; + background: #f0f0f0; + text-align: center; + margin-bottom: -1px; + margin-right: 5px; + cursor: pointer; + + &.active { + background: white; + border-bottom-color: transparent; + } + + &:hover { + background: white; + } +} + +.tab-content { + width: 100%; +} diff --git a/src/components/Tabs/Tabs.css.d.ts b/src/components/Tabs/Tabs.css.d.ts new file mode 100644 index 0000000..60a81ef --- /dev/null +++ b/src/components/Tabs/Tabs.css.d.ts @@ -0,0 +1,6 @@ +export const TabsContainer: string; +export const tabsContainer: string; +export const tabStrip: string; +export const tabLabel: string; +export const active: string; +export const tabContent: string; diff --git a/src/components/Tabs/Tabs.module.d.ts b/src/components/Tabs/Tabs.module.d.ts new file mode 100644 index 0000000..a2b1154 --- /dev/null +++ b/src/components/Tabs/Tabs.module.d.ts @@ -0,0 +1,22 @@ +import * as React from 'react' + +export interface ContainerProps { + className?: string + children: JSX.Element[] | JSX.Element + collapsible?: boolean +} + +export interface ContainerState { + children: JSX.Element[] + activeIdx: number + collapsed: boolean +} + +export interface TabProps { + className?: string + children: any | any[] + label: string + onClick?: (event: React.MouseEvent) => void +} + +export type Tab = React.ComponentElement> diff --git a/src/components/Tabs/Tabs.tsx b/src/components/Tabs/Tabs.tsx new file mode 100644 index 0000000..8ccb789 --- /dev/null +++ b/src/components/Tabs/Tabs.tsx @@ -0,0 +1,102 @@ +import * as React from 'react' +import * as css from './Tabs.css' +import * as I from './Tabs.module' +import * as classNames from 'classnames' + +export const Tab = (props: I.TabProps) => { + const children = (props.children.constructor === Array + ? props.children : [props.children]) as I.Tab[] + const newProps = Object.assign({}, props) + delete newProps.children + + return React.createElement('div', newProps, children) +} + +export class TabContainer extends React.Component { + constructor(props: I.ContainerProps) { + const children = (props.children.constructor === Array + ? props.children : [props.children]) as JSX.Element[] + + super(props) + this.checkChildrenTypes(this.props.children, this.constructor.name) + this.state = { + children, + activeIdx: 0, + collapsed: false, + } + } + + private checkChildrenTypes(children: React.ReactNode | JSX.Element[] | JSX.Element, componentName: string) { + let error: Error | null = null + + React.Children.forEach(children, function (child: React.ReactElement) { + if (child.type !== Tab) { + throw new Error('`' + componentName + '` children should be of type `Tab`.') + } + }) + + return true + } + + private tabStrip() { + return this.state.children.map((child, idx) => { + const cls = classNames(css.tabLabel, { + [css.active]: idx === this.state.activeIdx + }) + const { onClick }: I.TabProps = child.props + const onTabClick = this.onTabClick(idx, onClick) + + return ( +
+ {child.props.label} +
+ ) + }) + } + + private tabContents() { + const Child = this.state.children[this.state.activeIdx] + const { children, className: tabClasses, ...rest }: I.TabProps = Child.props + const cls = classNames(tabClasses, css.tabContent) + + return ( + + {children} + + ) + } + + private onTabClick(idx: number, callback?: (e: React.MouseEvent) => void) { + const commonClick = (e: React.MouseEvent) => { + this.setState({ activeIdx: idx }) + } + + if (callback) { + return (e: React.MouseEvent) => { + commonClick(e) + callback(e) + } + } + + return (e: React.MouseEvent) => { + commonClick(e) + } + } + + render() { + const className = classNames(css.TabsContainer, this.props.className) + + return ( +
+
+ {this.tabStrip()} +
+ {this.tabContents()} +
+ ) + } +} + +export default TabContainer