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 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;
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
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. */}
|
{/* 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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue