feat(toolbar): new Toolbar to enable reactive state synchronization (#3983)
This commit is contained in:
1 parent
79d5c36bda
commit
566b25a544
155 files changed
+6311
-6577
No files matched your search
@@ -11,42 +11,29 @@ import ListMenu from '../ListMenu';
|
||||
const baseClasses = {
|
||||
Button: 'flex items-center rounded-md border-transparent cursor-pointer group/button',
|
||||
Primary:
|
||||
// By default border on left, top and bottom for hover effect and only rounded on left side.
|
||||
// Extra padding on right to compensate for no right border.
|
||||
'h-full border-l-2 border-t-2 border-b-2 rounded-tl-md rounded-bl-md group/primary !pl-2 !py-2',
|
||||
Secondary:
|
||||
'h-full flex items-center justify-center rounded-tr-md rounded-br-md w-4 border-2 border-transparent group/secondary',
|
||||
SecondaryIcon: 'w-4 h-full stroke-1',
|
||||
Separator: 'border-l py-2.5',
|
||||
Separator: 'border-l py-3 ml-0.5',
|
||||
Content: 'absolute z-10 top-0 mt-12',
|
||||
};
|
||||
|
||||
const classes = {
|
||||
Button: ({ isExpanded, primary }) =>
|
||||
Button: ({ isExpanded }) =>
|
||||
classNames(
|
||||
baseClasses.Button,
|
||||
!isExpanded && !primary.isActive && 'hover:!bg-primary-dark hover:border-primary-dark'
|
||||
!isExpanded && 'hover:!bg-primary-dark hover:border-primary-dark'
|
||||
),
|
||||
Interface: 'h-full flex flex-row items-center',
|
||||
Primary: ({ primary, isExpanded }) =>
|
||||
Primary: ({ isExpanded, isActive }) =>
|
||||
classNames(
|
||||
baseClasses.Primary,
|
||||
primary.isActive
|
||||
isActive
|
||||
? isExpanded
|
||||
? 'border-primary-dark !bg-primary-dark hover:border-primary-dark !text-primary-light'
|
||||
: `${
|
||||
primary.isToggle
|
||||
? 'border-secondary-dark bg-secondary-light'
|
||||
: 'border-primary-light bg-primary-light'
|
||||
}
|
||||
border-2 rounded-md !p-2` // Full, rounded border with less right padding when active.
|
||||
: `focus:!text-black focus:!rounded-md focus:!border-primary-light focus:!bg-primary-light
|
||||
${
|
||||
isExpanded
|
||||
? 'border-primary-dark bg-primary-dark !text-primary-light'
|
||||
: 'border-secondary-dark bg-secondary-dark group-hover/button:border-primary-dark group-hover/button:text-primary-light hover:!bg-primary-dark hover:border-primary-dark focus:!text-black'
|
||||
}
|
||||
`
|
||||
: 'border-primary-light bg-primary-light border-2 rounded-md !p-2'
|
||||
: `focus:!text-black focus:!rounded-md focus:!border-primary-light focus:!bg-primary-light ${isExpanded ? 'border-primary-dark bg-primary-dark !text-primary-light' : 'border-secondary-dark bg-secondary-dark group-hover/button:border-primary-dark group-hover/button:text-primary-light hover:!bg-primary-dark hover:border-primary-dark focus:!text-black'}`
|
||||
),
|
||||
Secondary: ({ isExpanded, primary }) =>
|
||||
classNames(
|
||||
@@ -54,13 +41,15 @@ const classes = {
|
||||
isExpanded
|
||||
? 'bg-primary-light !rounded-tr-md !rounded-br-md'
|
||||
: primary.isActive
|
||||
? 'bg-secondary-dark'
|
||||
: 'hover:bg-primary-dark bg-secondary-dark group-hover/button:border-primary-dark'
|
||||
? 'bg-secondary-dark'
|
||||
: 'hover:bg-primary-dark bg-secondary-dark group-hover/button:border-primary-dark'
|
||||
),
|
||||
SecondaryIcon: ({ isExpanded }) =>
|
||||
classNames(
|
||||
baseClasses.SecondaryIcon,
|
||||
isExpanded ? 'text-primary-dark' : 'text-[#348cfd] group-hover/secondary:text-primary-light'
|
||||
isExpanded
|
||||
? 'text-primary-dark'
|
||||
: 'text-primary-active group-hover/secondary:text-primary-light'
|
||||
),
|
||||
Separator: ({ primary, isExpanded, isHovering }) =>
|
||||
classNames(
|
||||
@@ -70,14 +59,40 @@ const classes = {
|
||||
Content: ({ isExpanded }) => classNames(baseClasses.Content, isExpanded ? 'block' : 'hidden'),
|
||||
};
|
||||
|
||||
const DefaultListItemRenderer = props => {
|
||||
const { t, icon, label, className, isActive } = props;
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'flex h-8 w-full flex-row items-center p-3',
|
||||
'whitespace-pre text-base',
|
||||
className,
|
||||
`${isActive ? 'hover:opacity-80' : 'hover:bg-primary-dark '}`
|
||||
)}
|
||||
>
|
||||
{icon && (
|
||||
<span className="mr-4">
|
||||
<Icon
|
||||
name={icon}
|
||||
className="h-5 w-5"
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
<span className="mr-5">{t?.(label)}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a more generic version of SplitButton without the isActive
|
||||
* and other interaction props
|
||||
*/
|
||||
const SplitButton = ({
|
||||
isToggle,
|
||||
groupId,
|
||||
primary,
|
||||
secondary,
|
||||
items,
|
||||
renderer,
|
||||
isActive,
|
||||
onInteraction,
|
||||
Component,
|
||||
}) => {
|
||||
@@ -88,19 +103,14 @@ const SplitButton = ({
|
||||
const setHover = hovering => setState({ ...state, isHovering: hovering });
|
||||
const collapse = () => setState({ ...state, isExpanded: false });
|
||||
|
||||
const renderPrimaryButton = () => (
|
||||
<Component
|
||||
key={primary.id}
|
||||
{...primary}
|
||||
isActive={isActive}
|
||||
onInteraction={onInteraction}
|
||||
rounded="none"
|
||||
className={classes.Primary({ ...state, primary: { isActive, isToggle } })}
|
||||
data-tool={primary.id}
|
||||
data-cy={`${groupId}-split-button-primary`}
|
||||
/>
|
||||
const listItemRenderer = renderer || DefaultListItemRenderer;
|
||||
const primaryClassNames = classNames(
|
||||
classes.Primary({
|
||||
isExpanded: state.isExpanded,
|
||||
isActive: primary.isActive,
|
||||
}),
|
||||
primary.className
|
||||
);
|
||||
|
||||
return (
|
||||
<OutsideClickHandler
|
||||
onOutsideClick={collapse}
|
||||
@@ -111,16 +121,26 @@ const SplitButton = ({
|
||||
className="relative"
|
||||
>
|
||||
<div
|
||||
className={classes.Button({ ...state, primary: { isActive } })}
|
||||
className={classes.Button({ ...state })}
|
||||
style={{ height: '40px' }}
|
||||
onMouseEnter={() => setHover(true)}
|
||||
onMouseLeave={() => setHover(false)}
|
||||
>
|
||||
<div className={classes.Interface}>
|
||||
<div onClick={collapse}>{renderPrimaryButton()}</div>
|
||||
<div className={classes.Separator({ ...state, primary: { isActive } })}></div>
|
||||
<div onClick={collapse}>
|
||||
<Component
|
||||
key={primary.id}
|
||||
{...primary}
|
||||
onInteraction={onInteraction}
|
||||
rounded="none"
|
||||
className={primaryClassNames}
|
||||
data-tool={primary.id}
|
||||
data-cy={`${groupId}-split-button-primary`}
|
||||
/>
|
||||
</div>
|
||||
<div className={classes.Separator({ ...state, primary })}></div>
|
||||
<div
|
||||
className={classes.Secondary({ ...state, primary: { isActive } })}
|
||||
className={classes.Secondary({ ...state, primary })}
|
||||
onClick={toggleExpanded}
|
||||
data-cy={`${groupId}-split-button-secondary`}
|
||||
>
|
||||
@@ -131,7 +151,7 @@ const SplitButton = ({
|
||||
>
|
||||
<Icon
|
||||
name={secondary.icon}
|
||||
className={classes.SecondaryIcon({ ...state, primary: { isActive } })}
|
||||
className={classes.SecondaryIcon({ ...state })}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
@@ -144,7 +164,7 @@ const SplitButton = ({
|
||||
<ListMenu
|
||||
items={items}
|
||||
onClick={collapse}
|
||||
renderer={args => renderer({ ...args, t })}
|
||||
renderer={args => listItemRenderer({ ...args, t })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -162,6 +182,7 @@ SplitButton.propTypes = {
|
||||
isActive: PropTypes.bool,
|
||||
onInteraction: PropTypes.func.isRequired,
|
||||
Component: PropTypes.elementType,
|
||||
interactionType: PropTypes.oneOf(['action', 'tool', 'toggle']),
|
||||
};
|
||||
|
||||
SplitButton.defaultProps = {
|
||||
|
||||
@@ -2,6 +2,7 @@ import SplitButton from '../SplitButton';
|
||||
|
||||
import { ArgsTable, Story, Canvas, Meta } from '@storybook/addon-docs';
|
||||
import { createComponentTemplate } from '../../../storybook/functions/create-component-story';
|
||||
import ToolbarButton from '../../ToolbarButton';
|
||||
|
||||
export const argTypes = {
|
||||
component: SplitButton,
|
||||
@@ -30,7 +31,7 @@ export const SplitButtonTemplate = args => (
|
||||
|
||||
## Overview
|
||||
|
||||
SplitButton is a component that renders the SplitButtons.
|
||||
SplitButton is a component that renders the SplitButtons
|
||||
|
||||
<Canvas>
|
||||
<Story
|
||||
@@ -63,6 +64,7 @@ SplitButton is a component that renders the SplitButtons.
|
||||
tooltip: 'Bidirectional',
|
||||
},
|
||||
],
|
||||
Component: ToolbarButton,
|
||||
}}
|
||||
>
|
||||
{SplitButtonTemplate.bind({})}
|
||||
|
||||
Reference in new issue
Block a user