Skip to content

Commit

Permalink
Merge pull request #93 from contentful/create-tab-component
Browse files Browse the repository at this point in the history
feat(Tabs): Created Tabs component
  • Loading branch information
suevalov authored Feb 18, 2019
2 parents 3fa6060 + 53789e6 commit 29fcc6e
Show file tree
Hide file tree
Showing 11 changed files with 754 additions and 134 deletions.
12 changes: 12 additions & 0 deletions packages/forma-36-react-components/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
/// <reference path="./dist/components/Modal/Modal/Modal.d.ts" />
/// <reference path="./dist/components/Modal/ModalConfirm/ModalConfirm.d.ts" />
/// <reference path="./dist/components/Notification/index.d.ts" />
/// <reference path="./dist/components/Tabs/Tabs.d.ts" />
/// <reference path="./dist/components/Tabs/Tab.d.ts" />
/// <reference path="./dist/components/Tabs/TabPanel.d.ts" />

import * as React from 'react';

Expand Down Expand Up @@ -57,6 +60,9 @@ import FormComponent from './dist/components/Form/Form/Form';
import ModalComponent from './dist/components/Modal/Modal/Modal';
import ModalConfirmComponent from './dist/components/Modal/ModalConfirm/ModalConfirm';
import NotificationAPI from './dist/components/Notification/index';
import TabsComponent from './dist/components/Tabs/Tabs';
import TabComponent from './dist/components/Tabs/Tab';
import TabPanelComponent from './dist/components/Tabs/TabPanel';

export const Button: typeof ButtonComponent;
export const Spinner: typeof SpinnerComponent;
Expand Down Expand Up @@ -85,7 +91,13 @@ export const FieldGroup: typeof FieldGroupComponent;
export const Form: typeof FormComponent;
export const Modal: typeof ModalComponent;
export const ModalConfirm: typeof ModalConfirmComponent;
<<<<<<< HEAD
export const Notification: typeof NotificationAPI;
=======
export const Tabs: typeof TabsComponent;
export const Tab: typeof TabComponent;
export const TabPanel: typeof TabPanelComponent;
>>>>>>> feat(Tabs): Created Tabs component

export interface CopyButtonProps {
extraClassNames?: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { storiesOf, StoryDecorator } from '@storybook/react';
import { text, boolean } from '@storybook/addon-knobs';
import { StateDecorator, Store } from '@sambego/storybook-state';
import { host } from 'storybook-host';
Expand All @@ -20,8 +20,7 @@ storiesOf('Components|CheckboxField', module)
cropMarks: false,
}),
)
// @ts-ignore
.addDecorator(StateDecorator(store))
.addDecorator(StateDecorator(store) as StoryDecorator)
.add(
'default',
withInfo()(() => (
Expand Down
86 changes: 86 additions & 0 deletions packages/forma-36-react-components/src/components/Tabs/Tab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import React, { Component, CSSProperties } from 'react';
import classNames from 'classnames';

const styles = require('./Tabs.css');

interface TabProps {
id: string;
onSelect?: Function;
selected?: boolean;
href?: string;
target?: string;
disabled?: boolean;
tabIndex?: number;
style?: CSSProperties;
extraClassNames?: string;
testId?: string;
children: React.ReactNode;
}

export class Tab extends Component<TabProps> {
static defaultProps = {
onSelect: () => {},
onKeyPress: () => {},
selected: false,
disabled: false,
testId: 'cf-ui-tab',
tabIndex: 0,
};

onClick = () => {
this.props.onSelect(this.props.id);
};

onKeyPress = e => {
if (e.key === 'Enter') {
this.props.onSelect(this.props.id);
e.preventDefault();
}
};

render() {
const {
id,
disabled,
extraClassNames,
href,
style,
testId,
selected,
children,
tabIndex,
} = this.props;
let elementProps = {
className: classNames(
styles.Tab,
{
[styles['Tab__selected']]: selected,
},
extraClassNames,
),
onClick: this.onClick,
onKeyPress: this.onKeyPress,
style: style,
'data-test-id': testId,
tabIndex,
};

if (disabled) {
elementProps['aria-disabled'] = true;
}
if (href) {
elementProps['href'] = href;
if (selected) {
elementProps['aria-current'] = 'page';
}
return <a {...elementProps}>{children}</a>;
} else {
elementProps['aria-selected'] = selected;
elementProps['role'] = 'tab';
elementProps['aria-controls'] = id;
return <div {...elementProps}>{children}</div>;
}
}
}

export default Tab;
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React, { Component } from 'react';

interface TabPanelProps
extends React.DetailedHTMLProps<
React.HTMLAttributes<HTMLDivElement>,
HTMLDivElement
> {
id: string;
extraClassNames?: string;
testId?: string;
children: React.ReactNode;
}

export class TabPanel extends Component<TabPanelProps> {
static defaultProps = {
testId: 'cf-ui-tab-panel',
};

render() {
const { testId, extraClassNames, children, id, ...rest } = this.props;
return (
<div
{...rest}
id={id}
role="tabpanel"
data-test-id={testId}
className={extraClassNames}
>
{children}
</div>
);
}
}

export default TabPanel;
57 changes: 57 additions & 0 deletions packages/forma-36-react-components/src/components/Tabs/Tabs.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
@import 'settings/colors';
@import 'settings/typography';
@import 'settings/transitions';
@import 'settings/dimensions';

.Tabs {
display: flex;
}

.Tabs .Tab {
margin-right: var(--spacing-l);
}

.Tab {
white-space: nowrap;
color: var(--color-text-dark);
position: relative;
cursor: pointer;
padding: 0 var(--spacing-s);
height: 56px;
line-height: 56px;
font-size: var(--font-size-m);
font-family: var(--font-stack-primary);
font-weight: var(--font-weight-normal);
outline: none;
text-decoration: none;
}

.Tabs .Tab:last-child {
margin-right: 0;
}

.Tab__selected {
font-weight: var(--font-weight-demi-bold);
}

.Tab:before {
content: '';
position: absolute;
background: var(--color-primary);
opacity: 0;
bottom: 0;
left: 0;
right: 0;
height: 3px;
}

.Tab:hover:before,
.Tab:focus:before {
opacity: 0.5;
}

.Tab__selected:hover:before,
.Tab__selected:focus:before,
.Tab__selected:before {
opacity: 1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import React from 'react';
import { storiesOf, StoryDecorator } from '@storybook/react';
import { StateDecorator, Store } from '@sambego/storybook-state';
import { action } from '@storybook/addon-actions';
import { host } from 'storybook-host';
import { text } from '@storybook/addon-knobs';
import { withInfo } from '@storybook/addon-info';

import Tabs from './Tabs';
import Tab from './Tab';
import TabPanel from './TabPanel';

const store = new Store({
selected: 'first',
});

storiesOf('Components|Tabs', module)
.addDecorator((story, context) => withInfo()(story)(context))
.addDecorator(
host({
align: 'center middle',
cropMarks: false,
}),
)
.addDecorator(StateDecorator(store) as StoryDecorator)
.add('default', () => (
<div>
<Tabs extraClassNames={text('extraClassNames', '')}>
<Tab
id="first"
selected={store.state.selected === 'first'}
onSelect={id => {
action('onSelect')(id);
store.set({ selected: id });
}}
>
First
</Tab>
<Tab
id="second"
selected={store.state.selected === 'second'}
onSelect={id => {
action('onSelect')(id);
store.set({ selected: id });
}}
>
Second
</Tab>
<Tab
id="third"
selected={store.state.selected === 'third'}
onSelect={id => {
action('onSelect')(id);
store.set({ selected: id });
}}
>
Third
</Tab>
</Tabs>
{store.state.selected === 'first' && (
<TabPanel id="first">content first tab</TabPanel>
)}
{store.state.selected === 'second' && (
<TabPanel id="second">content second tab</TabPanel>
)}
{store.state.selected === 'third' && (
<TabPanel id="third">content third tab</TabPanel>
)}
</div>
))
.add('as navigation', () => (
<Tabs role="navigation" extraClassNames={text('extraClassNames', '')}>
<Tab
id="first"
href="https://contentful.com"
selected={store.state.selected === 'first'}
onSelect={id => {
action('onSelect')(id);
store.set({ selected: id });
}}
>
First
</Tab>
<Tab
id="second"
href="https://contentful.com"
selected={store.state.selected === 'second'}
onSelect={id => {
action('onSelect')(id);
store.set({ selected: id });
}}
>
Second
</Tab>
<Tab
id="third"
href="https://contentful.com"
selected={store.state.selected === 'third'}
onSelect={id => {
action('onSelect')(id);
store.set({ selected: id });
}}
>
Third
</Tab>
</Tabs>
));
Loading

0 comments on commit 29fcc6e

Please sign in to comment.