feat(toolbar): new Toolbar to enable reactive state synchronization (#3983)

This commit is contained in:
Alireza authored and GitHub committed 2024-03-27 16:01:32 -04:00
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({})}