This commit is contained in:
Sacha Weatherstone
2022-02-23 14:14:33 +11:00
parent 3b85422ab2
commit 4cf326661f
54 changed files with 403 additions and 255 deletions
+44 -9
View File
@@ -1,21 +1,56 @@
import type React from 'react';
import { m } from 'framer-motion';
import Draggable from 'react-draggable';
export interface CardProps {
className?: string;
title?: string;
actions?: React.ReactNode;
children: React.ReactNode;
border?: boolean;
draggable?: boolean;
}
export const Card = ({ className, children }: CardProps): JSX.Element => {
export const Card = ({
className,
title,
actions,
draggable,
border,
children,
}: CardProps): JSX.Element => {
return (
<m.div
className={`flex select-none rounded-md bg-white p-4 shadow-md dark:bg-primaryDark ${className}`}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
{children}
</m.div>
<Draggable handle=".handle" disabled={!draggable}>
<div
className={`flex h-full w-full flex-col rounded-md shadow-md ${
border ? 'border border-gray-300 dark:border-gray-600' : ''
} ${className ?? ''}`}
>
{(title || actions) && (
<div
className={`w-full select-none justify-between rounded-t-md border-b border-gray-300 bg-gray-200 p-2 px-2 text-lg font-medium dark:border-gray-600 dark:bg-primaryDark dark:text-white ${
draggable ? 'cursor-move' : ''
}`}
>
<div className="handle flex h-8 justify-between">
<div className="my-auto ml-2 truncate">{title}</div>
{actions}
</div>
</div>
)}
<m.div
className={`flex flex-grow select-none flex-col gap-4 p-4 ${
title || actions ? 'rounded-b-md backdrop-blur-xl' : 'rounded-md'
} ${draggable ? '' : 'bg-white dark:bg-primaryDark'}`}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
{children}
</m.div>
</div>
</Draggable>
);
};
+4 -3
View File
@@ -1,4 +1,5 @@
import React from 'react';
import type React from 'react';
import { useState } from 'react';
import { FiActivity, FiAperture, FiTag } from 'react-icons/fi';
@@ -13,8 +14,8 @@ export const ContextMenu = ({
items,
children,
}: ContextMenuProps): JSX.Element => {
const [visible, setVisible] = React.useState(false);
const [position, setPosition] = React.useState({ x: 0, y: 0 });
const [visible, setVisible] = useState(false);
const [position, setPosition] = useState({ x: 0, y: 0 });
return (
<div
+10 -8
View File
@@ -37,16 +37,18 @@ export const Modal = ({
&#8203;
</span>
<div className="inline-block w-full max-w-3xl align-middle">
<Card className="relative flex-col gap-4 border border-gray-300 dark:border-gray-600">
<div className="flex justify-between">
<div className="text-2xl font-medium dark:text-white">
{title}
</div>
<div className="flex gap-2">
<Card
border
draggable
title={title}
actions={
<>
{actions}
<IconButton tooltip="Close" icon={<FiX />} onClick={onClose} />
</div>
</div>
</>
}
className="relative flex-col gap-4 "
>
{children}
</Card>
</div>
@@ -1,4 +1,5 @@
import React from 'react';
import type React from 'react';
import { useState } from 'react';
import { AnimatePresence, m } from 'framer-motion';
import { FiArrowUp } from 'react-icons/fi';
@@ -14,13 +15,15 @@ export const CollapsibleSection = ({
icon,
children,
}: CollapsibleSectionProps): JSX.Element => {
const [open, setOpen] = React.useState(false);
const [open, setOpen] = useState(false);
const toggleOpen = (): void => setOpen(!open);
return (
<m.div>
<m.div
layout
className="w-full cursor-pointer select-none overflow-hidden border-b border-gray-300 p-2 text-sm font-medium dark:border-primaryDark dark:bg-secondaryDark dark:text-gray-400"
className={`w-full cursor-pointer select-none overflow-hidden border-l-4 border-b border-gray-300 p-2 text-sm font-medium dark:border-primaryDark dark:bg-secondaryDark dark:text-gray-400 ${
open ? 'dark:border-l-primary' : 'dark:border-secondaryDark'
}`}
>
<m.div
layout
@@ -1,20 +1,23 @@
import React from 'react';
import type React from 'react';
import { useState } from 'react';
import { m } from 'framer-motion';
import { FiExternalLink } from 'react-icons/fi';
import { FiChevronRight } from 'react-icons/fi';
export interface ExternalSectionProps {
title: string;
icon?: JSX.Element;
active?: boolean;
onClick: () => void;
}
export const ExternalSection = ({
title,
icon,
active,
onClick,
}: ExternalSectionProps): JSX.Element => {
const [open, setOpen] = React.useState(false);
const [open, setOpen] = useState(false);
const toggleOpen = (): void => setOpen(!open);
return (
<m.div
@@ -24,7 +27,9 @@ export const ExternalSection = ({
>
<m.div
layout
className="w-full cursor-pointer select-none overflow-hidden dark:bg-secondaryDark dark:text-gray-400"
className={`w-full cursor-pointer select-none overflow-hidden border-l-4 dark:bg-secondaryDark dark:text-gray-400 ${
active ? 'border-primary' : 'dark:border-secondaryDark'
}`}
>
<m.div
layout
@@ -38,7 +43,7 @@ export const ExternalSection = ({
{title}
</m.div>
<m.div className="my-auto">
<FiExternalLink />
<FiChevronRight />
</m.div>
</m.div>
</m.div>
+19 -13
View File
@@ -1,35 +1,37 @@
import React from 'react';
import type React from 'react';
import { useState } from 'react';
import { m } from 'framer-motion';
import { FiCheck } from 'react-icons/fi';
type DefaultButtonProps = JSX.IntrinsicElements['button'];
export enum ButtonSize {
Small = 'small',
Medium = 'medium',
Large = 'large',
}
export interface ButtonProps extends DefaultButtonProps {
export interface ButtonProps {
icon?: JSX.Element;
active?: boolean;
border?: boolean;
className?: string;
disabled?: boolean;
children?: React.ReactNode;
size?: ButtonSize;
onClick?: () => void;
confirmAction?: () => void;
}
export const Button = ({
icon,
className,
active,
border,
size = ButtonSize.Medium,
confirmAction,
onClick,
disabled,
children,
...props
}: ButtonProps): JSX.Element => {
const [hasConfirmed, setHasConfirmed] = React.useState(false);
const [hasConfirmed, setHasConfirmed] = useState(false);
const handleConfirm = (): void => {
if (typeof confirmAction == 'function') {
@@ -44,9 +46,11 @@ export const Button = ({
};
return (
<button
<m.button
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.97 }}
onClick={handleConfirm}
className={`flex select-none items-center space-x-3 rounded-md border border-transparent text-sm transition duration-200 ease-in-out focus-within:border-primary focus-within:shadow-border active:scale-95 dark:text-white dark:focus-within:border-primary
className={`flex select-none items-center space-x-3 rounded-md border border-transparent text-sm focus-within:border-primary focus-within:shadow-border dark:text-white dark:focus-within:border-primary
${
size === ButtonSize.Small
? 'p-0'
@@ -58,8 +62,10 @@ export const Button = ({
disabled
? 'cursor-not-allowed bg-white dark:bg-primaryDark'
: 'cursor-pointer hover:bg-gray-100 hover:shadow-md dark:hover:bg-secondaryDark'
} ${border ? 'border-gray-400 dark:border-gray-200' : ''} ${className}`}
{...props}
} ${border ? 'border-gray-400 dark:border-gray-200' : ''} ${
className ?? ''
}`}
onClickCapture={onClick}
>
{icon && (
<div className="text-gray-500 dark:text-gray-400">
@@ -68,6 +74,6 @@ export const Button = ({
)}
<span>{children}</span>
</button>
</m.button>
);
};
+15 -7
View File
@@ -1,5 +1,7 @@
import type React from 'react';
import { m } from 'framer-motion';
import { Tooltip } from '@components/generic/Tooltip';
type DefaulButtonProps = JSX.IntrinsicElements['button'];
@@ -7,26 +9,32 @@ type DefaulButtonProps = JSX.IntrinsicElements['button'];
export interface IconButtonProps extends DefaulButtonProps {
icon: React.ReactNode;
tooltip?: string;
nested?: boolean;
active?: boolean;
}
export const IconButton = ({
icon,
active,
tooltip,
nested,
active,
disabled,
...props
}: IconButtonProps): JSX.Element => {
return (
<Tooltip disabled={!tooltip} content={tooltip}>
<div className="my-auto text-gray-500 dark:text-gray-400">
<m.div
whileHover={{ scale: 1.01 }}
whileTap={{ scale: 0.97 }}
className="my-auto text-gray-500 dark:text-gray-400"
>
<button
type="button"
disabled={disabled}
className={`rounded-md p-2 transition duration-200 ease-in-out active:scale-95 ${
active
? 'bg-gray-200 dark:bg-gray-600'
: 'hover:bg-gray-200 dark:hover:bg-gray-600'
className={`rounded-md p-2 hover:bg-gray-200 ${
active ? 'bg-gray-200 dark:bg-secondaryDark' : ''
} ${
nested ? 'dark:hover:bg-primaryDark' : 'dark:hover:bg-secondaryDark'
} ${
disabled
? 'cursor-not-allowed text-gray-400 dark:text-gray-700'
@@ -37,7 +45,7 @@ export const IconButton = ({
{icon}
<span className="sr-only">Refresh</span>
</button>
</div>
</m.div>
</Tooltip>
);
};
+3 -2
View File
@@ -1,4 +1,5 @@
import React from 'react';
import type React from 'react';
import { forwardRef } from 'react';
import { Label } from '@components/generic/form/Label';
@@ -12,7 +13,7 @@ export interface CheckboxProps extends DefaultInputProps {
error?: boolean;
}
export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
function Input(
{ label, valid, validationMessage, id, error, ...props }: CheckboxProps,
ref,
+25 -23
View File
@@ -1,4 +1,5 @@
import React from 'react';
import type React from 'react';
import { forwardRef } from 'react';
import { InputWrapper } from '@components/generic/form/InputWrapper';
import { Label } from '@components/generic/form/Label';
@@ -13,25 +14,26 @@ export interface InputProps extends DefaultInputProps {
suffix?: string;
}
export const Input = React.forwardRef<HTMLInputElement, InputProps>(
function Input({ label, error, action, suffix, ...props }: InputProps, ref) {
return (
<div className="w-full">
{label && <Label label={label} error={error} />}
<InputWrapper error={error} disabled={props.disabled}>
<input
ref={ref}
className="h-10 w-full bg-transparent px-3 py-2 focus:outline-none disabled:cursor-not-allowed dark:text-white"
{...props}
/>
{suffix && (
<span className="my-auto mr-3 text-sm font-medium text-gray-500 dark:text-gray-400">
{suffix}
</span>
)}
{action && <div className="mr-1 flex">{action}</div>}
</InputWrapper>
</div>
);
},
);
export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
{ label, error, action, suffix, ...props }: InputProps,
ref,
) {
return (
<div className="w-full">
{label && <Label label={label} error={error} />}
<InputWrapper error={error} disabled={props.disabled}>
<input
ref={ref}
className="h-10 w-full bg-transparent px-3 py-2 focus:outline-none disabled:cursor-not-allowed dark:text-white"
{...props}
/>
{suffix && (
<span className="my-auto mr-3 text-sm font-medium text-gray-500 dark:text-gray-400">
{suffix}
</span>
)}
{action && <div className="mr-1 flex">{action}</div>}
</InputWrapper>
</div>
);
});
+3 -2
View File
@@ -1,4 +1,5 @@
import React from 'react';
import type React from 'react';
import { forwardRef } from 'react';
import { InputWrapper } from '@components/generic/form/InputWrapper';
import { Label } from '@components/generic/form/Label';
@@ -16,7 +17,7 @@ export interface SelectProps extends DefaultSelectProps {
small?: boolean;
}
export const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
export const Select = forwardRef<HTMLSelectElement, SelectProps>(
({ options, optionsEnum, label, error, small, ...props }, ref) => {
const optionsEnumValues = optionsEnum
? Object.entries(optionsEnum).filter(