Button styling cleanup, prop consistency

This commit is contained in:
DaneEveritt 2022-06-20 12:38:23 -04:00
parent 7dd74ecc9d
commit 7b0e2ce99d
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
9 changed files with 121 additions and 46 deletions

View file

@ -1,18 +1,24 @@
import React, { forwardRef } from 'react'; import React, { forwardRef } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { ButtonProps, Options } from '@/components/elements/button/types';
import styles from './style.module.css'; import styles from './style.module.css';
export type ButtonProps = JSX.IntrinsicElements['button'] & {
square?: boolean;
small?: boolean;
}
const Button = forwardRef<HTMLButtonElement, ButtonProps>( const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ children, square, small, className, ...rest }, ref) => { ({ children, shape, size, variant, className, ...rest }, ref) => {
return ( return (
<button <button
ref={ref} ref={ref}
className={classNames(styles.button, { [styles.square]: square, [styles.small]: small }, className)} className={classNames(
styles.button,
styles.primary,
{
[styles.secondary]: variant === Options.Variant.Secondary,
[styles.square]: shape === Options.Shape.IconSquare,
[styles.small]: size === Options.Size.Small,
[styles.large]: size === Options.Size.Large,
},
className,
)}
{...rest} {...rest}
> >
{children} {children}
@ -31,6 +37,12 @@ const DangerButton = forwardRef<HTMLButtonElement, ButtonProps>(({ className, ..
<Button ref={ref} className={classNames(styles.danger, className)} {...props} /> <Button ref={ref} className={classNames(styles.danger, className)} {...props} />
)); ));
const _Button = Object.assign(Button, { Text: TextButton, Danger: DangerButton }); const _Button = Object.assign(Button, {
Sizes: Options.Size,
Shapes: Options.Shape,
Variants: Options.Variant,
Text: TextButton,
Danger: DangerButton,
});
export default _Button; export default _Button;

View file

@ -1,2 +1,3 @@
export { ButtonProps } from './types';
export { default as Button } from './Button'; export { default as Button } from './Button';
export { default as styles } from './style.module.css'; export { default as styles } from './style.module.css';

View file

@ -1,38 +1,73 @@
.button { .button {
@apply px-4 py-2 inline-flex items-center justify-center; @apply px-4 py-2 inline-flex items-center justify-center;
@apply bg-blue-600 rounded text-base font-semibold text-blue-50 transition-all duration-100; @apply rounded text-base font-semibold transition-all duration-100;
@apply hover:bg-blue-500 active:bg-blue-500; @apply focus:ring-[3px] focus:ring-offset-2 focus:ring-offset-gray-700 focus:ring-opacity-50;
&.square {
@apply p-2;
}
&:focus {
@apply ring-[3px] ring-blue-500 ring-offset-2 ring-offset-neutral-700;
}
/* Sizing Controls */ /* Sizing Controls */
&.small { &.small {
@apply px-3 py-1 font-normal focus:ring-2; @apply px-4 py-1 font-normal text-sm focus:ring-2;
}
&.square { &.large {
@apply p-1; @apply px-5 py-3;
}
&.secondary {
@apply text-gray-50 bg-transparent;
&:disabled {
@apply bg-transparent;
} }
} }
&:disabled { &:disabled {
@apply cursor-not-allowed; @apply cursor-not-allowed;
} }
&.square {
@apply p-0 w-12 h-12;
&.small {
@apply w-8 h-8;
}
}
}
.primary {
@apply bg-blue-600 text-blue-50;
@apply hover:bg-blue-500 active:bg-blue-500 focus:ring-blue-400 focus:ring-opacity-75;
&.secondary {
@apply hover:bg-blue-600 active:bg-blue-600;
}
&:disabled {
@apply bg-blue-500/75 text-blue-200/75;
}
} }
.text { .text {
@apply text-gray-50 bg-transparent focus:ring-neutral-300 focus:ring-opacity-50 hover:bg-neutral-500 active:bg-neutral-500; @apply bg-gray-500 text-gray-50;
@apply hover:bg-gray-400 active:bg-gray-400 focus:ring-gray-300 focus:ring-opacity-50;
&.secondary {
@apply hover:bg-gray-500 active:bg-gray-500;
}
&:disabled { &:disabled {
@apply hover:bg-transparent text-gray-300; @apply bg-gray-500/75 text-gray-200/75;
} }
} }
.danger { .danger {
@apply bg-red-600 hover:bg-red-500 active:bg-red-500 focus:ring-red-500 text-red-50; @apply bg-red-600 text-gray-50;
@apply hover:bg-red-500 active:bg-red-500 focus:ring-red-400 focus:ring-opacity-75;
&.secondary {
@apply hover:bg-red-600 active:bg-red-600;
}
&:disabled {
@apply bg-red-600/75 text-red-50/75;
}
} }

View file

@ -0,0 +1,23 @@
enum Shape {
Default,
IconSquare,
}
enum Size {
Default,
Small,
Large,
}
enum Variant {
Primary,
Secondary,
}
export const Options = { Shape, Size, Variant };
export type ButtonProps = JSX.IntrinsicElements['button'] & {
shape?: Shape;
size?: Size;
variant?: Variant;
}

View file

@ -78,7 +78,12 @@ const Dialog = ({ open, title, description, onClose, hideCloseIcon, children }:
{/* Keep this below the other buttons so that it isn't the default focus if they're present. */} {/* Keep this below the other buttons so that it isn't the default focus if they're present. */}
{!hideCloseIcon && {!hideCloseIcon &&
<div className={'absolute right-0 top-0 m-4'}> <div className={'absolute right-0 top-0 m-4'}>
<Button.Text square small onClick={onClose} className={'hover:rotate-90'}> <Button.Text
size={Button.Sizes.Small}
shape={Button.Shapes.IconSquare}
onClick={onClose}
className={'hover:rotate-90'}
>
<XIcon className={'w-5 h-5'}/> <XIcon className={'w-5 h-5'}/>
</Button.Text> </Button.Text>
</div> </div>

View file

@ -30,6 +30,13 @@ const PaginationFooter = ({ pagination, className, onPageSelect }: Props) => {
return null; return null;
} }
const buttonProps = (page: number) => ({
size: Button.Sizes.Small,
shape: Button.Shapes.IconSquare,
variant: Button.Variants.Secondary,
onClick: () => onPageSelect(page),
});
return ( return (
<div className={classNames('flex items-center justify-between my-2', className)}> <div className={classNames('flex items-center justify-between my-2', className)}>
<p className={'text-sm text-neutral-500'}> <p className={'text-sm text-neutral-500'}>
@ -42,21 +49,21 @@ const PaginationFooter = ({ pagination, className, onPageSelect }: Props) => {
</p> </p>
{pagination.totalPages > 1 && {pagination.totalPages > 1 &&
<div className={'flex space-x-1'}> <div className={'flex space-x-1'}>
<Button.Text small disabled={pages.previous.length !== 2} onClick={() => onPageSelect(1)}> <Button.Text {...buttonProps(1)} disabled={pages.previous.length !== 2}>
<ChevronDoubleLeftIcon className={'w-3 h-3'}/> <ChevronDoubleLeftIcon className={'w-3 h-3'}/>
</Button.Text> </Button.Text>
{pages.previous.reverse().map((value) => ( {pages.previous.reverse().map((value) => (
<Button.Text small key={`previous-${value}`} onClick={() => onPageSelect(value)}> <Button.Text key={`previous-${value}`} {...buttonProps(value)}>
{value} {value}
</Button.Text> </Button.Text>
))} ))}
<Button small disabled>{current}</Button> <Button size={Button.Sizes.Small} shape={Button.Shapes.IconSquare}>{current}</Button>
{pages.next.map((value) => ( {pages.next.map((value) => (
<Button.Text small key={`next-${value}`} onClick={() => onPageSelect(value)}> <Button.Text key={`next-${value}`} {...buttonProps(value)}>
{value} {value}
</Button.Text> </Button.Text>
))} ))}
<Button.Text small disabled={pages.next.length !== 2} onClick={() => onPageSelect(total)}> <Button.Text {...buttonProps(total)} disabled={pages.next.length !== 2}>
<ChevronDoubleRightIcon className={'w-3 h-3'}/> <ChevronDoubleRightIcon className={'w-3 h-3'}/>
</Button.Text> </Button.Text>
</div> </div>

View file

@ -6,7 +6,7 @@ import { Actions, useStoreActions } from 'easy-peasy';
import { ApplicationStore } from '@/state'; import { ApplicationStore } from '@/state';
import { httpErrorToHuman } from '@/api/http'; import { httpErrorToHuman } from '@/api/http';
import tw from 'twin.macro'; import tw from 'twin.macro';
import Button from '@/components/elements/Button'; import { Button } from '@/components/elements/button/index';
import { Dialog } from '@/components/elements/dialog'; import { Dialog } from '@/components/elements/dialog';
export default () => { export default () => {
@ -57,14 +57,9 @@ export default () => {
</strong> </strong>
</p> </p>
<div css={tw`mt-6 text-right`}> <div css={tw`mt-6 text-right`}>
<Button <Button.Danger variant={Button.Variants.Secondary} onClick={() => setModalVisible(true)}>
type={'button'}
color={'red'}
isSecondary
onClick={() => setModalVisible(true)}
>
Reinstall Server Reinstall Server
</Button> </Button.Danger>
</div> </div>
</TitledGreyBox> </TitledGreyBox>
); );

View file

@ -9,7 +9,7 @@ import { object, string } from 'yup';
import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
import { ApplicationStore } from '@/state'; import { ApplicationStore } from '@/state';
import { httpErrorToHuman } from '@/api/http'; import { httpErrorToHuman } from '@/api/http';
import Button from '@/components/elements/Button'; import { Button } from '@/components/elements/button/index';
import tw from 'twin.macro'; import tw from 'twin.macro';
interface Values { interface Values {

View file

@ -9,11 +9,11 @@ import ReinstallServerBox from '@/components/server/settings/ReinstallServerBox'
import tw from 'twin.macro'; import tw from 'twin.macro';
import Input from '@/components/elements/Input'; import Input from '@/components/elements/Input';
import Label from '@/components/elements/Label'; import Label from '@/components/elements/Label';
import { LinkButton } from '@/components/elements/Button';
import ServerContentBlock from '@/components/elements/ServerContentBlock'; import ServerContentBlock from '@/components/elements/ServerContentBlock';
import isEqual from 'react-fast-compare'; import isEqual from 'react-fast-compare';
import CopyOnClick from '@/components/elements/CopyOnClick'; import CopyOnClick from '@/components/elements/CopyOnClick';
import { formatIp } from '@/helpers'; import { formatIp } from '@/helpers';
import { Button } from '@/components/elements/button/index';
export default () => { export default () => {
const username = useStoreState(state => state.user.data!.username); const username = useStoreState(state => state.user.data!.username);
@ -58,12 +58,9 @@ export default () => {
</div> </div>
</div> </div>
<div css={tw`ml-4`}> <div css={tw`ml-4`}>
<LinkButton <a href={`sftp://${username}.${id}@${formatIp(sftp.ip)}:${sftp.port}`}>
isSecondary <Button.Text variant={Button.Variants.Secondary}>Launch SFTP</Button.Text>
href={`sftp://${username}.${id}@${formatIp(sftp.ip)}:${sftp.port}`} </a>
>
Launch SFTP
</LinkButton>
</div> </div>
</div> </div>
</TitledGreyBox> </TitledGreyBox>