mirror of
https://github.com/storybookjs/storybook.git
synced 2025-04-07 04:41:07 +08:00
213 lines
5.1 KiB
JavaScript
213 lines
5.1 KiB
JavaScript
import React, { Children, Component, Fragment } from 'react';
|
|
import PropTypes from 'prop-types';
|
|
import { styled } from '@storybook/theming';
|
|
|
|
import { Placeholder } from '../placeholder/placeholder';
|
|
import { FlexBar } from '../bar/bar';
|
|
import { TabButton } from '../bar/button';
|
|
|
|
const Wrapper = styled.div(
|
|
({ theme, bordered }) =>
|
|
bordered
|
|
? {
|
|
backgroundClip: 'padding-box',
|
|
borderRadius: 4,
|
|
border: `1px solid ${theme.mainBorderColor}`,
|
|
}
|
|
: {},
|
|
({ absolute }) =>
|
|
absolute
|
|
? {
|
|
width: '100%',
|
|
height: '100%',
|
|
boxSizing: 'border-box',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
}
|
|
: {
|
|
display: 'block',
|
|
}
|
|
);
|
|
|
|
export const TabBar = styled.div(
|
|
{
|
|
whiteSpace: 'nowrap',
|
|
height: '100%',
|
|
|
|
'&:first-child': {
|
|
marginLeft: 0,
|
|
},
|
|
},
|
|
({ scroll }) => ({
|
|
// super lame, hiding of scrollbar
|
|
paddingBottom: 50,
|
|
overflowX: scroll ? 'auto' : 'visisble',
|
|
overflowY: scroll ? 'hidden' : 'visisble',
|
|
}),
|
|
({ flex }) =>
|
|
flex
|
|
? {
|
|
flex: typeof flex === 'number' ? flex : 1,
|
|
}
|
|
: {}
|
|
);
|
|
|
|
const Content = styled.div(
|
|
{
|
|
display: 'block',
|
|
position: 'relative',
|
|
},
|
|
({ theme }) => ({
|
|
fontSize: `${theme.typography.size.s2}px`,
|
|
}),
|
|
({ absolute }) =>
|
|
absolute
|
|
? {
|
|
position: 'relative',
|
|
overflow: 'auto',
|
|
flex: 1,
|
|
width: '100%',
|
|
}
|
|
: {}
|
|
);
|
|
|
|
const VisuallyHidden = styled.div(({ active }) =>
|
|
active ? { display: 'block' } : { display: 'none' }
|
|
);
|
|
export const TabWrapper = ({ active, render, children }) => (
|
|
<VisuallyHidden active={active}>{render ? render() : children}</VisuallyHidden>
|
|
);
|
|
TabWrapper.propTypes = {
|
|
active: PropTypes.bool.isRequired,
|
|
render: PropTypes.func,
|
|
children: PropTypes.node,
|
|
};
|
|
TabWrapper.defaultProps = {
|
|
render: undefined,
|
|
children: undefined,
|
|
};
|
|
|
|
export const panelProps = {
|
|
active: PropTypes.bool,
|
|
};
|
|
|
|
const childrenToList = (children, selected) =>
|
|
Children.toArray(children).map(({ props: { title, id, children: childrenOfChild } }, index) => {
|
|
const content = Array.isArray(childrenOfChild) ? childrenOfChild[0] : childrenOfChild;
|
|
return {
|
|
active: selected ? id === selected : index === 0,
|
|
title,
|
|
id,
|
|
render:
|
|
typeof content === 'function'
|
|
? content
|
|
: // eslint-disable-next-line react/prop-types
|
|
({ active, key }) => (
|
|
<VisuallyHidden key={key} active={active} role="tabpanel">
|
|
{content}
|
|
</VisuallyHidden>
|
|
),
|
|
};
|
|
});
|
|
|
|
export const Tabs = React.memo(
|
|
({ children, selected, actions, absolute, bordered, scroll, tools, id: htmlId }) => {
|
|
const list = childrenToList(children, selected);
|
|
|
|
return list.length ? (
|
|
<Wrapper absolute={absolute} bordered={bordered} id={htmlId}>
|
|
<FlexBar border scroll={!scroll}>
|
|
<TabBar role="tablist">
|
|
{list.map(({ title, id, active }) => (
|
|
<TabButton
|
|
type="button"
|
|
key={id}
|
|
active={active}
|
|
onClick={e => e.preventDefault() || actions.onSelect(id)}
|
|
role="tab"
|
|
>
|
|
{typeof title === 'function' ? title() : title}
|
|
</TabButton>
|
|
))}
|
|
</TabBar>
|
|
{tools ? <Fragment>{tools}</Fragment> : null}
|
|
</FlexBar>
|
|
<Content absolute={absolute}>
|
|
{list.map(({ id, active, render }) => render({ key: id, active }))}
|
|
</Content>
|
|
</Wrapper>
|
|
) : (
|
|
<Placeholder>
|
|
<Fragment key="title">Nothing found</Fragment>
|
|
</Placeholder>
|
|
);
|
|
}
|
|
);
|
|
Tabs.displayName = 'Tabs';
|
|
Tabs.propTypes = {
|
|
id: PropTypes.string,
|
|
children: PropTypes.node,
|
|
tools: PropTypes.node,
|
|
selected: PropTypes.string,
|
|
actions: PropTypes.shape({
|
|
onSelect: PropTypes.func.isRequired,
|
|
}).isRequired,
|
|
absolute: PropTypes.bool,
|
|
bordered: PropTypes.bool,
|
|
scroll: PropTypes.bool,
|
|
};
|
|
Tabs.defaultProps = {
|
|
id: null,
|
|
children: null,
|
|
tools: null,
|
|
selected: null,
|
|
absolute: false,
|
|
bordered: false,
|
|
scroll: true,
|
|
};
|
|
|
|
// eslint-disable-next-line react/no-multi-comp
|
|
export class TabsState extends Component {
|
|
static propTypes = {
|
|
children: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.node, PropTypes.func])),
|
|
initial: PropTypes.string,
|
|
absolute: PropTypes.bool,
|
|
bordered: PropTypes.bool,
|
|
scroll: PropTypes.bool,
|
|
};
|
|
|
|
static defaultProps = {
|
|
children: [],
|
|
initial: null,
|
|
absolute: false,
|
|
bordered: false,
|
|
scroll: true,
|
|
};
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
|
|
this.state = {
|
|
selected: props.initial,
|
|
};
|
|
}
|
|
|
|
render() {
|
|
const { bordered = false, absolute = false, children, scroll = true } = this.props;
|
|
const { selected } = this.state;
|
|
return (
|
|
<Tabs
|
|
bordered={bordered}
|
|
absolute={absolute}
|
|
selected={selected}
|
|
scroll={scroll}
|
|
actions={{
|
|
onSelect: id => this.setState({ selected: id }),
|
|
}}
|
|
>
|
|
{children}
|
|
</Tabs>
|
|
);
|
|
}
|
|
}
|