diff --git a/apps/client/src/common/components/buttons/BaseButtonStyles.module.scss b/apps/client/src/common/components/buttons/BaseButtonStyles.module.scss index 73f442121..ac0be6ef4 100644 --- a/apps/client/src/common/components/buttons/BaseButtonStyles.module.scss +++ b/apps/client/src/common/components/buttons/BaseButtonStyles.module.scss @@ -81,6 +81,25 @@ } .ghosted { + background: transparent; + color: $blue-500; + + &:hover:not(:disabled):not(:active) { + background: $gray-1000; + color: $blue-500; + } + + &:active:not(:disabled) { + background: $gray-1100; + border-color: $gray-1250; + } + + &:disabled { + opacity: $opacity-disabled; + } +} + +.ghosted-white { background: transparent; color: $ui-white; diff --git a/apps/client/src/common/components/buttons/Button.module.scss b/apps/client/src/common/components/buttons/Button.module.scss index a43aaddd0..52ceb1fa5 100644 --- a/apps/client/src/common/components/buttons/Button.module.scss +++ b/apps/client/src/common/components/buttons/Button.module.scss @@ -2,6 +2,7 @@ @import './BaseButtonStyles.module.scss'; .baseButton { + position: relative; display: flex; align-items: center; justify-content: center; @@ -25,6 +26,36 @@ outline: 2px solid $blue-500; outline-offset: 2px; } + + &.loading { + cursor: wait; + .content { + opacity: 0; + } + } +} + +.content { + display: flex; + align-items: center; + gap: inherit; +} + +.loadingOverlay { + position: absolute; + display: grid; + place-content: center; +} + +.spinner { + animation: spin 1s linear infinite; + stroke-dasharray: 8; +} + +@keyframes spin { + 100% { + transform: rotate(360deg); + } } .small { diff --git a/apps/client/src/common/components/buttons/Button.tsx b/apps/client/src/common/components/buttons/Button.tsx index f6e85d700..a23c29319 100644 --- a/apps/client/src/common/components/buttons/Button.tsx +++ b/apps/client/src/common/components/buttons/Button.tsx @@ -1,29 +1,44 @@ import { ButtonHTMLAttributes, forwardRef } from 'react'; +import { IoEllipseOutline } from 'react-icons/io5'; import { cx } from '../../utils/styleUtils'; import style from './Button.module.scss'; interface ButtonProps extends ButtonHTMLAttributes { - variant?: 'primary' | 'subtle' | 'subtle-white' | 'destructive' | 'subtle-destructive' | 'ghosted'; + variant?: 'primary' | 'subtle' | 'subtle-white' | 'destructive' | 'subtle-destructive' | 'ghosted' | 'ghosted-white'; size?: 'small' | 'medium' | 'large' | 'xlarge'; fluid?: boolean; + loading?: boolean; } -const Button = forwardRef((props, ref) => { - const { className, children, variant = 'subtle', size = 'medium', fluid, ...buttonProps } = props; - - return ( - - ); -}); +const Button = forwardRef( + ({ className, children, variant = 'subtle', size = 'medium', fluid, loading, ...buttonProps }, ref) => { + return ( + + ); + }, +); Button.displayName = 'Button'; diff --git a/apps/client/src/common/components/buttons/IconButton.tsx b/apps/client/src/common/components/buttons/IconButton.tsx index abc7a2c0c..6492b6d3b 100644 --- a/apps/client/src/common/components/buttons/IconButton.tsx +++ b/apps/client/src/common/components/buttons/IconButton.tsx @@ -5,7 +5,7 @@ import { cx } from '../../utils/styleUtils'; import style from './IconButton.module.scss'; interface IconButtonProps extends ButtonHTMLAttributes { - variant?: 'primary' | 'subtle' | 'subtle-white' | 'destructive' | 'subtle-destructive' | 'ghosted'; + variant?: 'primary' | 'subtle' | 'subtle-white' | 'destructive' | 'subtle-destructive' | 'ghosted' | 'ghosted-white'; size?: 'small' | 'medium' | 'large' | 'xlarge'; } diff --git a/apps/client/src/common/components/input/textarea/Textarea.module.scss b/apps/client/src/common/components/input/textarea/Textarea.module.scss new file mode 100644 index 000000000..c4f68d291 --- /dev/null +++ b/apps/client/src/common/components/input/textarea/Textarea.module.scss @@ -0,0 +1,47 @@ +@use '../../../../theme/viewerDefs' as *; + +.textarea { + box-sizing: border-box; + + display: block; + font-size: 1rem; + font-weight: 400; + color: $gray-200; + border-radius: $component-border-radius-md; + border: 1px solid transparent; + + padding-inline: 0.5em; + outline: none; + + &:hover:not(:disabled) { + background-color: $gray-1100; + } + + &:focus:not(:read-only) { + background-color: $gray-1000; + border: 1px solid $blue-500; + } + + &:disabled { + opacity: 0.4; + cursor: not-allowed; + } + + &::placeholder { + color: $gray-500; + letter-spacing: 0; + } +} + +.subtle { + background-color: $gray-1200; +} + +.ghosted { + background-color: transparent; + padding: 0; +} + +.fluid { + width: 100%; +} diff --git a/apps/client/src/common/components/input/textarea/Textarea.tsx b/apps/client/src/common/components/input/textarea/Textarea.tsx new file mode 100644 index 000000000..5220d599d --- /dev/null +++ b/apps/client/src/common/components/input/textarea/Textarea.tsx @@ -0,0 +1,31 @@ +import { forwardRef, TextareaHTMLAttributes } from 'react'; + +import { cx } from '../../../utils/styleUtils'; + +import style from './Textarea.module.scss'; + +export interface TextareaProps extends TextareaHTMLAttributes { + variant?: 'subtle' | 'ghosted'; + fluid?: boolean; + resize?: 'none' | 'both' | 'horizontal' | 'vertical'; +} + +const Textarea = forwardRef(function TextArea( + { className, variant = 'subtle', fluid, rows = 5, resize = 'none', style: customStyle, ...textareaProps }, + ref, +) { + return ( +