Button styling cleanup, prop consistency
This commit is contained in:
parent
7dd74ecc9d
commit
7b0e2ce99d
9 changed files with 121 additions and 46 deletions
|
@ -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;
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export { ButtonProps } from './types';
|
||||
export { default as Button } from './Button';
|
||||
export { default as styles } from './style.module.css';
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
23
resources/scripts/components/elements/button/types.ts
Normal file
23
resources/scripts/components/elements/button/types.ts
Normal 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;
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue