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 classNames from 'classnames';
import { ButtonProps, Options } from '@/components/elements/button/types';
import styles from './style.module.css';
export type ButtonProps = JSX.IntrinsicElements['button'] & {
square?: boolean;
small?: boolean;
}
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ children, square, small, className, ...rest }, ref) => {
({ children, shape, size, variant, className, ...rest }, ref) => {
return (
<button
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}
>
{children}
@ -31,6 +37,12 @@ const DangerButton = forwardRef<HTMLButtonElement, ButtonProps>(({ className, ..
<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;

View file

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

View file

@ -1,38 +1,73 @@
.button {
@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 hover:bg-blue-500 active:bg-blue-500;
&.square {
@apply p-2;
}
&:focus {
@apply ring-[3px] ring-blue-500 ring-offset-2 ring-offset-neutral-700;
}
@apply rounded text-base font-semibold transition-all duration-100;
@apply focus:ring-[3px] focus:ring-offset-2 focus:ring-offset-gray-700 focus:ring-opacity-50;
/* Sizing Controls */
&.small {
@apply px-3 py-1 font-normal focus:ring-2;
@apply px-4 py-1 font-normal text-sm focus:ring-2;
}
&.square {
@apply p-1;
&.large {
@apply px-5 py-3;
}
&.secondary {
@apply text-gray-50 bg-transparent;
&:disabled {
@apply bg-transparent;
}
}
&:disabled {
@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 {
@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 {
@apply hover:bg-transparent text-gray-300;
@apply bg-gray-500/75 text-gray-200/75;
}
}
.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. */}
{!hideCloseIcon &&
<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'}/>
</Button.Text>
</div>

View file

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

View file

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

View file

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

View file

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