Skip to content

Commit

Permalink
Merge pull request #89 from perimetre/7.8.0
Browse files Browse the repository at this point in the history
7.8.0
  • Loading branch information
AssisrMatheus authored Sep 15, 2022
2 parents 110fa51 + 4f2dbde commit 6073ac6
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 2 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

## [7.8.0] 2022-09-15

### Added

- Added `ButtonStack` component

## [7.7.2] 2022-08-26

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@perimetre/ui",
"description": "A component library made by @perimetre",
"version": "7.7.2",
"version": "7.8.0",
"repository": {
"type": "git",
"url": "git+https://github.com/perimetre/ui.git"
Expand Down
124 changes: 124 additions & 0 deletions src/components/ButtonStack/ButtonStack.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import React from 'react';
import { useState } from 'react';
// also exported from '@storybook/react' if you can deal with breaking changes in 6.1
import { Meta, Story } from '@storybook/react/types-6-0';
import classnames from 'classnames';
import { ButtonStack, ButtonStackProps } from '.';
import { colorOptions } from '../../prebuiltTailwindTheme';
import { puiColorClassnameMap } from '../../storybookMappers';
import { BellIcon } from '../Icons';

export default {
title: 'Components/ButtonStack',
component: ButtonStack,
argTypes: {
color: {
defaultValue: 'pui-primary',
control: {
type: 'select',
options: colorOptions
}
},
disabled: {
defaultValue: false,
control: {
type: 'boolean'
}
},
className: {
control: {
type: 'text'
}
},
onClick: { action: 'onClick' }
}
} as Meta;

/**
* A story that displays a button stack example
*
* @param props the story props
* @param props.color the color property set on controls
* @param props.disabled the disabled color set on controls
*/
const WithTooltipTemplate: Story<ButtonStackProps & { border?: string; color?: string; disabled?: boolean }> = ({
color,
disabled,
...props
}) => {
const [activeKey, setActiveKey] = useState('button-1');

const dropdownContentItems = Array(4)
.fill(null)
.map((_, i) => ({
key: `button-${i + 1}`,
/**
* The icon example
*/
icon: () => <BellIcon />,
buttonProps: {
/**
* Callback for when the button is clicked
*/
onClick: () => setActiveKey(`button-${i + 1}`),
disabled
}
}));

return (
<ButtonStack
{...props}
containerClassname={classnames({
[puiColorClassnameMap[color || 'transparent']]: color !== 'pui-primary'
})}
activeKey={activeKey}
items={dropdownContentItems}
/>
);
};

export const WithTooltip = WithTooltipTemplate.bind({});

/**
* A story that displays a button stack example
*
* @param props the story props
* @param props.color the color property set on controls
* @param props.disabled the disabled color set on controls
*/
const NoTooltipTemplate: Story<ButtonStackProps & { border?: string; color?: string; disabled?: boolean }> = ({
color,
disabled,
...props
}) => {
const [activeKey, setActiveKey] = useState('button-1');

const dropdownContentItems = Array(4)
.fill(null)
.map((_, i) => ({
key: `button-${i + 1}`,
/**
* The icon example
*/
icon: () => <BellIcon />,
/**
* Callback for when the button is clicked
*/
onClick: () => setActiveKey(`button-${i + 1}`),
disabled
}));

return (
<ButtonStack
{...props}
hasTooltip={false}
containerClassname={classnames({
[puiColorClassnameMap[color || 'transparent']]: color !== 'pui-primary'
})}
activeKey={activeKey}
items={dropdownContentItems}
/>
);
};

export const NoTooltip = NoTooltipTemplate.bind({});
87 changes: 87 additions & 0 deletions src/components/ButtonStack/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import classNames from 'classnames';
import { startCase } from 'lodash';
import React from 'react';
import { Button, ButtonProps } from '../Button';
import { Tooltip, TooltipProps } from '../Tooltip';

export type ButtonStackItem = {
key: string;
icon: () => React.ReactNode;
};

export type ButtonStackProps = {
/** The list of buttons that should be displayed */
items: (ButtonStackItem & (Omit<TooltipProps, 'ref'> | Omit<ButtonProps, 'ref'>))[];
/** Which button key, from the list of provided `items` should be active */
activeKey?: string;
/** Whether or not it should have a tooltip on hover or not */
hasTooltip?: boolean;
/** Classname for the container that wrapps all the buttons */
containerClassname?: string;
};

/**
* A list of buttons besides each other
*
* @param props The component props
* @param props.items The list of buttons that should be displayed
* @param props.activeKey Which button key, from the list of provided `items` should be active
* @param props.hasTooltip Whether or not it should have a tooltip on hover or not
* @param props.containerClassname Classname for the container that wrapps all the buttons
*/
export const ButtonStack: React.FC<ButtonStackProps> = ({
items,
activeKey,
hasTooltip = true,
containerClassname
}) => (
<div className={classNames('mb-2 flex items-center', containerClassname)}>
{hasTooltip
? items.map(({ key, icon, ...props }, index) => {
const tooltipProps = props as Omit<TooltipProps, 'ref'>;

// ! If any change is done to some of these props, remember to also apply to the <Button> version when `hasTooltip` is false
return (
<Tooltip
key={key}
content={<p className="text-center">{startCase(key)}</p>}
{...tooltipProps}
buttonProps={{
...tooltipProps.buttonProps,
className: classNames(
'pui-btn-bordered flex items-center justify-center w-9 px-1',
{
'bg-pui-placeholder-color text-pui-paragraph-0':
!tooltipProps.buttonProps?.disabled && activeKey === key, // If active
'rounded-l-none': index > 0, // All BUT first
'rounded-r-none border-r-0': index < items.length - 1 // All BUT last
},
tooltipProps.buttonProps?.className
)
}}
>
<>{icon()}</>
</Tooltip>
);
})
: items.map(({ key, icon, ...props }, index) => {
const buttonProps = props as Omit<ButtonProps, 'ref'>;

// ! If any change is done to some of these props, remember to also apply to the <Tooltip> version when `hasTooltip` is true
return (
<Button
key={key}
variant="bordered"
{...buttonProps}
className={classNames('pui-btn-bordered flex items-center justify-center w-9 px-1', {
'bg-pui-placeholder-color text-pui-paragraph-0': !buttonProps?.disabled && activeKey === key, // If active
'rounded-l-none': index > 0, // All BUT first
'rounded-r-none border-r-0': index < items.length - 1 // All BUT last
})}
>
<>{icon()}</>
</Button>
);
})}
</div>
);
2 changes: 1 addition & 1 deletion src/components/Tooltip/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export type TooltipProps = TippyProps & {
* @param props.content The tooltip content
* @param props.children The provided children
*/
export const Tooltip: React.FC<TooltipProps> = forwardRef<Element, TooltipProps>(
export const Tooltip = forwardRef<Element, TooltipProps>(
({ children, content, buttonProps, arrow = true, contentClassName, ...props }, ref) => (
<Tippy
{...props}
Expand Down
1 change: 1 addition & 0 deletions src/components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ export * from './ProgramCard';
export * from './BaseCard';
export * from './HorizontalResizeablePanel';
export * from './FormikSubmitOnChange';
export * from './ButtonStack';

0 comments on commit 6073ac6

Please sign in to comment.