mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-16 12:53:32 +00:00
refactor: migrate related components
This commit is contained in:
committed by
Carlos Valente
parent
cd8e014f08
commit
84b73fc02a
@@ -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;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<HTMLButtonElement> {
|
||||
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<HTMLButtonElement, ButtonProps>((props, ref) => {
|
||||
const { className, children, variant = 'subtle', size = 'medium', fluid, ...buttonProps } = props;
|
||||
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
className={cx([style.baseButton, style[variant], style[size], fluid && style.fluid, className])}
|
||||
type='button'
|
||||
{...buttonProps}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
});
|
||||
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, children, variant = 'subtle', size = 'medium', fluid, loading, ...buttonProps }, ref) => {
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
className={cx([
|
||||
style.baseButton,
|
||||
style[variant],
|
||||
style[size],
|
||||
fluid && style.fluid,
|
||||
loading && style.loading,
|
||||
className,
|
||||
])}
|
||||
type='button'
|
||||
disabled={loading || buttonProps.disabled}
|
||||
{...buttonProps}
|
||||
>
|
||||
<span className={style.content}>{children}</span>
|
||||
{loading && (
|
||||
<div className={style.loadingOverlay}>
|
||||
<IoEllipseOutline className={style.spinner} />
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Button.displayName = 'Button';
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { cx } from '../../utils/styleUtils';
|
||||
import style from './IconButton.module.scss';
|
||||
|
||||
interface IconButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
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';
|
||||
}
|
||||
|
||||
|
||||
@@ -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%;
|
||||
}
|
||||
@@ -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<HTMLTextAreaElement> {
|
||||
variant?: 'subtle' | 'ghosted';
|
||||
fluid?: boolean;
|
||||
resize?: 'none' | 'both' | 'horizontal' | 'vertical';
|
||||
}
|
||||
|
||||
const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(function TextArea(
|
||||
{ className, variant = 'subtle', fluid, rows = 5, resize = 'none', style: customStyle, ...textareaProps },
|
||||
ref,
|
||||
) {
|
||||
return (
|
||||
<textarea
|
||||
ref={ref}
|
||||
autoCorrect='off'
|
||||
autoComplete='off'
|
||||
spellCheck='false'
|
||||
rows={rows}
|
||||
style={{ ...customStyle, resize }}
|
||||
className={cx([style.textarea, style[variant], fluid && style.fluid, className])}
|
||||
{...textareaProps}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default Textarea;
|
||||
@@ -17,11 +17,11 @@
|
||||
font-size: calc(1rem - 2px);
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
&:hover:not([data-disabled]) {
|
||||
background-color: $gray-1100;
|
||||
}
|
||||
|
||||
&:active {
|
||||
&:active:not([data-disabled]) {
|
||||
background-color: $gray-1000;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
background-color: $gray-1000;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
&[data-disabled] {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@@ -4,25 +4,17 @@ import { Select as BaseSelect } from '@base-ui-components/react/select';
|
||||
|
||||
import styles from './Select.module.scss';
|
||||
|
||||
interface SelectProps<T extends string | null = string> {
|
||||
defaultValue?: T;
|
||||
interface SelectProps<T> extends Omit<BaseSelect.Root.Props<T>, 'items'> {
|
||||
// overload items to not allow undefined values
|
||||
options: {
|
||||
value: NonNullable<T>;
|
||||
value: T;
|
||||
label: string;
|
||||
disabled?: boolean; // exposed to allow creating a non-selectable option
|
||||
}[];
|
||||
value?: T;
|
||||
onChange?: (value: NonNullable<T>) => void;
|
||||
}
|
||||
|
||||
export default function Select<T extends string | null = string>({
|
||||
defaultValue,
|
||||
options,
|
||||
value,
|
||||
onChange,
|
||||
}: SelectProps<T>) {
|
||||
export default function Select<T>({ options, ...selectRootProps }: SelectProps<T>) {
|
||||
return (
|
||||
<BaseSelect.Root items={options} defaultValue={defaultValue} onValueChange={onChange} value={value}>
|
||||
<BaseSelect.Root items={options} {...selectRootProps}>
|
||||
<BaseSelect.Trigger className={styles.select}>
|
||||
<BaseSelect.Value />
|
||||
<BaseSelect.Icon className={styles.selectIcon}>
|
||||
@@ -33,16 +25,14 @@ export default function Select<T extends string | null = string>({
|
||||
<BaseSelect.Positioner side='bottom' align='start'>
|
||||
<BaseSelect.ScrollUpArrow className={styles.scrollArrow} />
|
||||
<BaseSelect.Popup className={styles.popup}>
|
||||
{options.map((option) => {
|
||||
return (
|
||||
<BaseSelect.Item key={option.value} className={styles.item} value={option.value}>
|
||||
<BaseSelect.ItemIndicator className={styles.itemIndicator}>
|
||||
<IoCheckmark className={styles.itemIndicatorIcon} />
|
||||
</BaseSelect.ItemIndicator>
|
||||
<BaseSelect.ItemText className={styles.itemLabel}>{option.label}</BaseSelect.ItemText>
|
||||
</BaseSelect.Item>
|
||||
);
|
||||
})}
|
||||
{options.map(({ label, value }) => (
|
||||
<BaseSelect.Item key={String(value)} className={styles.item} value={value}>
|
||||
<BaseSelect.ItemIndicator className={styles.itemIndicator}>
|
||||
<IoCheckmark className={styles.itemIndicatorIcon} />
|
||||
</BaseSelect.ItemIndicator>
|
||||
<BaseSelect.ItemText className={styles.itemLabel}>{label}</BaseSelect.ItemText>
|
||||
</BaseSelect.Item>
|
||||
))}
|
||||
</BaseSelect.Popup>
|
||||
<BaseSelect.ScrollDownArrow className={styles.scrollArrow} />
|
||||
</BaseSelect.Positioner>
|
||||
|
||||
Reference in New Issue
Block a user