Skip to content

Commit

Permalink
feat: add Button group component
Browse files Browse the repository at this point in the history
  • Loading branch information
bacali95 committed Mar 13, 2022
1 parent a56572d commit 1f8b8fd
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 5 deletions.
10 changes: 10 additions & 0 deletions public/images/button-group-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions public/images/button-group-light.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 9 additions & 1 deletion src/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
HiHome,
HiMenuAlt1,
} from 'react-icons/hi';
import { BsGithub, BsImages } from 'react-icons/bs';
import { BsCreditCard2FrontFill, BsGithub, BsImages } from 'react-icons/bs';
import { FaSpinner } from 'react-icons/fa';
import { Route, Routes } from 'react-router-dom';

Expand All @@ -22,6 +22,7 @@ const AccordionPage = lazy(() => import('./pages/AccordionPage'));
const BadgesPage = lazy(() => import('./pages/BadgesPage'));
const BreadcrumbPage = lazy(() => import('./pages/BreadcrumbPage'));
const ButtonsPage = lazy(() => import('./pages/ButtonsPage'));
const ButtonGroupPage = lazy(() => import('./pages/ButtonGroupPage'));
const CardPage = lazy(() => import('./pages/CardPage'));
const CarouselPage = lazy(() => import('./pages/CarouselPage'));
const SpinnersPage = lazy(() => import('./pages/SpinnersPage'));
Expand Down Expand Up @@ -70,6 +71,12 @@ export const Root: FC = () => {
{
group: false,
icon: HiDuplicate,
title: 'Button group',
href: '/button-group',
},
{
group: false,
icon: BsCreditCard2FrontFill,
title: 'Card',
href: '/card',
},
Expand Down Expand Up @@ -133,6 +140,7 @@ export const Root: FC = () => {
<Route path="badges" element={<BadgesPage />} />
<Route path="breadcrumb" element={<BreadcrumbPage />} />
<Route path="buttons" element={<ButtonsPage />} />
<Route path="button-group" element={<ButtonGroupPage />} />
<Route path="card" element={<CardPage />} />
<Route path="carousel" element={<CarouselPage />} />
<Route path="spinners" element={<SpinnersPage />} />
Expand Down
19 changes: 15 additions & 4 deletions src/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type GradientDuoTone =
| 'tealToLime'
| 'redToYellow';
type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
type PositionInGroup = 'start' | 'middle' | 'end';

export type ButtonProps = ComponentProps<'button'> & {
pill?: boolean;
Expand All @@ -22,6 +23,7 @@ export type ButtonProps = ComponentProps<'button'> & {
icon?: FC<ComponentProps<'svg'>>;
gradientMonochrome?: GradientMonochrome;
gradientDuoTone?: GradientDuoTone;
positionInGroup?: PositionInGroup;
};

const colorClasses: Record<Color, string> = {
Expand Down Expand Up @@ -88,6 +90,7 @@ const iconSizeClasses: Record<Size, string> = {

export const Button: FC<ButtonProps> = ({
children,
className,
pill,
outline,
disabled = false,
Expand All @@ -96,30 +99,38 @@ export const Button: FC<ButtonProps> = ({
color = 'blue',
gradientMonochrome,
gradientDuoTone,
positionInGroup,
...props
}) => {
return (
<button
disabled={disabled}
className={classNames(
'group flex h-min w-fit items-center justify-center p-0.5 text-center font-medium',
'group flex h-min w-fit items-center justify-center p-0.5 text-center font-medium focus:z-10',
pill ? 'rounded-full' : 'rounded-lg',
!gradientMonochrome && !gradientDuoTone && colorClasses[color],
!gradientDuoTone && gradientMonochrome && gradientMonochromeClasses[gradientMonochrome],
gradientDuoTone && gradientDuoToneClasses[gradientDuoTone],
{
'border border-gray-900 dark:border-white': color === 'alternative' && outline,
'cursor-not-allowed opacity-50': disabled,
'focus:!ring-2': !!positionInGroup,
'rounded-r-none': positionInGroup === 'start',
'rounded-none border-l-0 pl-0': positionInGroup === 'middle',
'rounded-l-none border-l-0 pl-0': positionInGroup === 'end',
},
className,
)}
type="button"
{...props}
>
<span
className={classNames('flex items-center', sizeClasses[size], {
className={classNames('flex items-center', sizeClasses[size], outline && pill ? 'rounded-full' : 'rounded-md', {
'bg-white text-gray-900 transition-all duration-75 ease-in group-hover:bg-opacity-0 group-hover:text-inherit dark:bg-gray-900 dark:text-white':
outline,
'rounded-md': outline && !pill,
'rounded-full': outline && pill,
'rounded-r-none': positionInGroup === 'start',
'rounded-none': positionInGroup === 'middle',
'rounded-l-none': positionInGroup === 'end',
[iconSizeClasses[size]]: !!Icon,
})}
>
Expand Down
29 changes: 29 additions & 0 deletions src/components/ButtonGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Children, cloneElement, FC, ReactElement, useMemo } from 'react';

import { ButtonProps } from './Button';

export type ButtonGroupProps = {
pill?: boolean;
outline?: boolean;
};

export const ButtonGroup: FC<ButtonGroupProps> = ({ children, pill, outline }) => {
const items = useMemo(
() =>
Children.map(children as ReactElement<ButtonProps>[], (child, index) =>
cloneElement(child, {
pill,
outline,
positionInGroup:
index === 0 ? 'start' : index === (children as ReactElement<ButtonProps>[]).length - 1 ? 'end' : 'middle',
}),
),
[children, outline, pill],
);

return (
<div className="inline-flex" role="group">
{items}
</div>
);
};
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from './Accordion';
export * from './Badge';
export * from './Breadcrumb';
export * from './Button';
export * from './ButtonGroup';
export * from './Card';
export * from './Carousel';
export * from './DarkThemeToggle';
Expand Down
124 changes: 124 additions & 0 deletions src/pages/ButtonGroupPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { FC } from 'react';
import { HiAdjustments, HiCloudDownload, HiUserCircle } from 'react-icons/hi';

import { CodeExample, DemoPage } from './DemoPage';
import { Button, ButtonGroup } from '../components';

const ButtonGroupPage: FC = () => {
const examples: CodeExample[] = [
{
title: 'Default example',
code: (
<ButtonGroup>
<Button color="alternative">Profile</Button>
<Button color="alternative">Settings</Button>
<Button color="alternative">Messages</Button>
</ButtonGroup>
),
},
{
title: 'Group buttons with icons',
code: (
<ButtonGroup>
<Button color="alternative">
<HiUserCircle className="mr-3 h-4 w-4" /> Profile
</Button>
<Button color="alternative">
<HiAdjustments className="mr-3 h-4 w-4" /> Settings
</Button>
<Button color="alternative">
<HiCloudDownload className="mr-3 h-4 w-4" /> Messages
</Button>
</ButtonGroup>
),
},
{
title: 'All colors',
code: (
<div className="flex flex-wrap gap-2">
<ButtonGroup>
<Button color="blue">Profile</Button>
<Button color="blue">Settings</Button>
<Button color="blue">Messages</Button>
</ButtonGroup>
<ButtonGroup>
<Button gradientMonochrome="blue">Profile</Button>
<Button gradientMonochrome="blue">Settings</Button>
<Button gradientMonochrome="blue">Messages</Button>
</ButtonGroup>
<ButtonGroup>
<Button gradientDuoTone="greenToBlue">Profile</Button>
<Button gradientDuoTone="greenToBlue">Settings</Button>
<Button gradientDuoTone="greenToBlue">Messages</Button>
</ButtonGroup>
</div>
),
},
{
title: 'Outline',
code: (
<div className="flex flex-wrap gap-2">
<ButtonGroup outline>
<Button color="alternative">Profile</Button>
<Button color="alternative">Settings</Button>
<Button color="alternative">Messages</Button>
</ButtonGroup>
<ButtonGroup outline>
<Button gradientMonochrome="blue">Profile</Button>
<Button gradientMonochrome="blue">Settings</Button>
<Button gradientMonochrome="blue">Messages</Button>
</ButtonGroup>
<ButtonGroup outline>
<Button gradientDuoTone="cyanToBlue">Profile</Button>
<Button gradientDuoTone="cyanToBlue">Settings</Button>
<Button gradientDuoTone="cyanToBlue">Messages</Button>
</ButtonGroup>
</div>
),
},
{
title: 'Outline with icons',
code: (
<div className="flex flex-wrap gap-2">
<ButtonGroup outline>
<Button color="alternative">
<HiUserCircle className="mr-3 h-4 w-4" /> Profile
</Button>
<Button color="alternative">
<HiAdjustments className="mr-3 h-4 w-4" /> Settings
</Button>
<Button color="alternative">
<HiCloudDownload className="mr-3 h-4 w-4" /> Messages
</Button>
</ButtonGroup>
<ButtonGroup outline>
<Button gradientMonochrome="blue">
<HiUserCircle className="mr-3 h-4 w-4" /> Profile
</Button>
<Button gradientMonochrome="blue">
<HiAdjustments className="mr-3 h-4 w-4" /> Settings
</Button>
<Button gradientMonochrome="blue">
<HiCloudDownload className="mr-3 h-4 w-4" /> Messages
</Button>
</ButtonGroup>
<ButtonGroup outline>
<Button gradientDuoTone="cyanToBlue">
<HiUserCircle className="mr-3 h-4 w-4" /> Profile
</Button>
<Button gradientDuoTone="cyanToBlue">
<HiAdjustments className="mr-3 h-4 w-4" /> Settings
</Button>
<Button gradientDuoTone="cyanToBlue">
<HiCloudDownload className="mr-3 h-4 w-4" /> Messages
</Button>
</ButtonGroup>
</div>
),
},
];

return <DemoPage examples={examples} />;
};

export default ButtonGroupPage;
6 changes: 6 additions & 0 deletions src/pages/DashboardPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ const DashboardPage: FC = () => {
className: 'w-24',
images: { light: 'buttons.svg', dark: 'buttons.svg' },
},
{
title: 'Button group',
href: '/button-group',
className: 'w-56',
images: { light: 'button-group-light.svg', dark: 'button-group-dark.svg' },
},
{
title: 'Card',
href: '/card',
Expand Down

0 comments on commit 1f8b8fd

Please sign in to comment.