45 changed files with 1603 additions and 1350 deletions
File diff suppressed because it is too large
@ -0,0 +1,16 @@ |
|||
import type React from 'react'; |
|||
|
|||
import type { FallbackProps } from 'react-error-boundary'; |
|||
|
|||
export const ErrorFallback = ({ |
|||
error, |
|||
resetErrorBoundary, |
|||
}: FallbackProps): JSX.Element => { |
|||
return ( |
|||
<div role="alert"> |
|||
<p>Something went wrong:</p> |
|||
<pre>{error.message}</pre> |
|||
<button onClick={resetErrorBoundary}>Try again</button> |
|||
</div> |
|||
); |
|||
}; |
|||
@ -1,61 +0,0 @@ |
|||
import React from 'react'; |
|||
|
|||
import { FiCheck } from 'react-icons/fi'; |
|||
|
|||
type DefaultButtonProps = JSX.IntrinsicElements['button']; |
|||
|
|||
interface ButtonProps extends DefaultButtonProps { |
|||
icon?: JSX.Element; |
|||
active?: boolean; |
|||
border?: boolean; |
|||
padding?: string; |
|||
confirmAction?: () => void; |
|||
} |
|||
|
|||
export const Button = ({ |
|||
icon, |
|||
className, |
|||
active, |
|||
border, |
|||
confirmAction, |
|||
disabled, |
|||
children, |
|||
padding = '2', |
|||
...props |
|||
}: ButtonProps): JSX.Element => { |
|||
const [hasConfirmed, setHasConfirmed] = React.useState(false); |
|||
|
|||
const handleConfirm = (): void => { |
|||
if (typeof confirmAction == 'function') { |
|||
if (hasConfirmed) { |
|||
void confirmAction(); |
|||
} |
|||
setHasConfirmed(true); |
|||
setTimeout(() => { |
|||
setHasConfirmed(false); |
|||
}, 3000); |
|||
} |
|||
}; |
|||
|
|||
return ( |
|||
<button |
|||
onClick={handleConfirm} |
|||
className={`items-center select-none flex border border-transparent dark:text-white active:scale-95 transition duration-200 ease-in-out focus-within:border-primary dark:focus-within:border-primary focus-within:shadow-border rounded-md p-${padding} space-x-3 text-sm ${ |
|||
active && !disabled ? 'bg-gray-100 dark:bg-gray-700' : '' |
|||
} ${ |
|||
disabled |
|||
? 'cursor-not-allowed dark:bg-primaryDark bg-white' |
|||
: 'cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700 hover:shadow-md' |
|||
} ${border ? 'border-gray-400 dark:border-gray-200' : ''} ${className}`}
|
|||
{...props} |
|||
> |
|||
{icon && ( |
|||
<div className="text-gray-500 dark:text-gray-400"> |
|||
{hasConfirmed ? <FiCheck /> : icon} |
|||
</div> |
|||
)} |
|||
|
|||
<span>{children}</span> |
|||
</button> |
|||
); |
|||
}; |
|||
@ -1,57 +0,0 @@ |
|||
import type React from 'react'; |
|||
|
|||
import { Loading } from '@components/generic/Loading'; |
|||
|
|||
type DefaultDivProps = JSX.IntrinsicElements['div']; |
|||
|
|||
interface CardProps extends DefaultDivProps { |
|||
title?: string; |
|||
description?: string | JSX.Element; |
|||
buttons?: JSX.Element; |
|||
lgPlaceholder?: JSX.Element; |
|||
loading?: boolean; |
|||
} |
|||
|
|||
export const Card = ({ |
|||
title, |
|||
description, |
|||
buttons, |
|||
children, |
|||
className, |
|||
lgPlaceholder, |
|||
loading, |
|||
...props |
|||
}: CardProps): JSX.Element => { |
|||
return ( |
|||
<div |
|||
className={`relative flex flex-col flex-auto dark:text-white border-y md:border shadow-md select-none bg-white dark:bg-primaryDark border-gray-300 dark:border-gray-600 md:rounded-md ${className}`} |
|||
{...props} |
|||
> |
|||
{loading && <Loading />} |
|||
{(title || description) && ( |
|||
<div className="flex items-center justify-between mx-10 mt-10"> |
|||
<div className="flex flex-col"> |
|||
{title && ( |
|||
<div className="mr-4 text-2xl font-semibold leading-7 tracking-tight text-black md:text-3xl dark:text-white"> |
|||
{title} |
|||
</div> |
|||
)} |
|||
|
|||
{description && ( |
|||
<div className="font-medium text-gray-400">{description}</div> |
|||
)} |
|||
</div> |
|||
{buttons} |
|||
</div> |
|||
)} |
|||
<div className="flex"> |
|||
<div className={`${lgPlaceholder ? 'w-full xl:w-2/3' : 'w-full'}`}> |
|||
{children} |
|||
</div> |
|||
{lgPlaceholder && ( |
|||
<div className="hidden w-1/3 xl:flex">{lgPlaceholder}</div> |
|||
)} |
|||
</div> |
|||
</div> |
|||
); |
|||
}; |
|||
@ -1,35 +0,0 @@ |
|||
import type React from 'react'; |
|||
|
|||
type DefaulButtonProps = JSX.IntrinsicElements['button']; |
|||
|
|||
export interface IconButtonProps extends DefaulButtonProps { |
|||
icon: React.ReactNode; |
|||
active?: boolean; |
|||
} |
|||
|
|||
export const IconButton = ({ |
|||
icon, |
|||
active, |
|||
disabled, |
|||
...props |
|||
}: IconButtonProps): JSX.Element => { |
|||
return ( |
|||
<div className="my-auto text-gray-500 dark:text-gray-400"> |
|||
<button |
|||
type="button" |
|||
disabled={disabled} |
|||
className={`p-2 transition duration-200 ease-in-out rounded-md active:scale-95 ${ |
|||
active |
|||
? 'bg-gray-200 dark:bg-gray-600' |
|||
: 'hover:bg-gray-200 dark:hover:bg-gray-600' |
|||
} ${ |
|||
disabled ? 'text-gray-400 dark:text-gray-700 cursor-not-allowed' : '' |
|||
}`}
|
|||
{...props} |
|||
> |
|||
{icon} |
|||
<span className="sr-only">Refresh</span> |
|||
</button> |
|||
</div> |
|||
); |
|||
}; |
|||
@ -0,0 +1,89 @@ |
|||
import type React from 'react'; |
|||
|
|||
import type { Noop, RefCallBack } from 'react-hook-form'; |
|||
import type { Theme } from 'react-select'; |
|||
import ReactSelect from 'react-select'; |
|||
|
|||
import { bitwiseDecode, bitwiseEncode } from '@app/core/utils/bitwise'; |
|||
import { useAppSelector } from '@app/hooks/redux.js'; |
|||
|
|||
import { Label } from './Label'; |
|||
|
|||
export interface BiwiseSelectProps { |
|||
label: string; |
|||
error?: string; |
|||
value: number; |
|||
optionsEnum: { [s: string]: string | number }; |
|||
onChange: (...event: unknown[]) => void; |
|||
onBlur: Noop; |
|||
name: string; |
|||
ref: RefCallBack; |
|||
} |
|||
|
|||
export const BitwiseSelect = ({ |
|||
label, |
|||
error, |
|||
value, |
|||
optionsEnum, |
|||
onChange, |
|||
ref, |
|||
}: BiwiseSelectProps): JSX.Element => { |
|||
const darkMode = useAppSelector((state) => state.app.darkMode); |
|||
|
|||
return ( |
|||
<div className="w-full"> |
|||
{label && <Label label={label} error={error} />} |
|||
<ReactSelect |
|||
ref={ref} |
|||
isMulti |
|||
// styles={{
|
|||
// control: (provided, state) => ({
|
|||
// ...provided,
|
|||
// // color: state.isFocused ? 'blue' : 'red',
|
|||
// // borderColor: state.isFocused ? 'blue' : 'red',
|
|||
// }),
|
|||
// }}
|
|||
theme={(theme): Theme => ({ |
|||
...theme, |
|||
borderRadius: 7, |
|||
colors: { |
|||
...theme.colors, |
|||
primary: '#67ea94', //focus border color
|
|||
// primary75: 'red',
|
|||
// primary50: 'red',
|
|||
// primary25: 'red',
|
|||
// danger: 'red',
|
|||
// dangerLight: 'red',
|
|||
neutral0: darkMode ? 'rgb(30 41 59)' : 'white', //bg color
|
|||
// neutral5: 'red',
|
|||
neutral10: darkMode ? 'rgb(75 85 99)' : 'rgb(229 231 235)', //tag bg color
|
|||
neutral20: darkMode ? 'rgb(229 231 235)' : 'rgb(156 163 175)', //border color
|
|||
neutral30: '#67ea94', //border hover
|
|||
// neutral40: 'red',
|
|||
// neutral50: 'red',
|
|||
// neutral60: 'red',
|
|||
// neutral70: 'red',
|
|||
neutral80: darkMode ? 'white' : 'black', //tag text color
|
|||
// neutral90: 'red',
|
|||
}, |
|||
})} |
|||
value={bitwiseDecode(value, optionsEnum).map((flag) => { |
|||
return { |
|||
value: flag, |
|||
label: (optionsEnum[flag] as string).replace('POS_', ''), |
|||
}; |
|||
})} |
|||
options={Object.entries(optionsEnum) |
|||
.filter((value) => typeof value[1] !== 'number') |
|||
.filter((value) => parseInt(value[0]) !== optionsEnum.POS_UNDEFINED) |
|||
.map((value) => { |
|||
return { |
|||
value: parseInt(value[0]), |
|||
label: value[1].toString().replace('POS_', ''), |
|||
}; |
|||
})} |
|||
onChange={(e): void => onChange(bitwiseEncode(e.map((v) => v.value)))} |
|||
/> |
|||
</div> |
|||
); |
|||
}; |
|||
@ -1,48 +0,0 @@ |
|||
import React from 'react'; |
|||
|
|||
import { Label } from './Label'; |
|||
|
|||
type DefaultInputProps = JSX.IntrinsicElements['input']; |
|||
|
|||
interface CheckboxProps extends DefaultInputProps { |
|||
action?: (enabled: boolean) => void; |
|||
label: string; |
|||
valid?: boolean; |
|||
validationMessage?: string; |
|||
error?: boolean; |
|||
} |
|||
|
|||
export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>( |
|||
function Input( |
|||
{ label, valid, validationMessage, id, error, ...props }: CheckboxProps, |
|||
ref, |
|||
) { |
|||
return ( |
|||
<div className="flex flex-col w-full"> |
|||
<Label label={label} /> |
|||
<div className="ml-auto"> |
|||
<input |
|||
ref={ref} |
|||
type="checkbox" |
|||
id={id} |
|||
className={`appearance-none w-8 h-8 border rounded-md focus:outline-none focus-within:shadow-border checked:bg-primary checked:border-transparent transition duration-200 ease-in-out border-gray-400 dark:border-gray-200 ${ |
|||
props.disabled |
|||
? 'bg-gray-200 text-gray-500 dark:bg-gray-800 dark:text-gray-400 border-gray-400 dark:border-gray-700' |
|||
: '' |
|||
} ${ |
|||
error |
|||
? 'border-red-500' |
|||
: props.disabled |
|||
? 'border-gray-200' |
|||
: 'focus-within:border-primary dark:focus-within:border-primary hover:border-primary dark:hover:border-primary' |
|||
}`}
|
|||
{...props} |
|||
/> |
|||
</div> |
|||
{!valid && ( |
|||
<div className="text-sm text-gray-600">{validationMessage}</div> |
|||
)} |
|||
</div> |
|||
); |
|||
}, |
|||
); |
|||
@ -1,37 +0,0 @@ |
|||
import React from 'react'; |
|||
|
|||
import { InputWrapper } from './InputWrapper'; |
|||
import { Label } from './Label'; |
|||
|
|||
type DefaultInputProps = JSX.IntrinsicElements['input']; |
|||
|
|||
interface InputProps extends DefaultInputProps { |
|||
label?: string; |
|||
error?: string; |
|||
action?: JSX.Element; |
|||
prefix?: string; |
|||
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="w-full h-10 px-3 py-2 bg-transparent focus:outline-none" |
|||
{...props} |
|||
/> |
|||
{suffix && ( |
|||
<span className="my-auto mr-3 text-sm font-medium text-gray-500 dark:text-gray-400"> |
|||
{suffix} |
|||
</span> |
|||
)} |
|||
{action && <div className="flex mr-1">{action}</div>} |
|||
</InputWrapper> |
|||
</div> |
|||
); |
|||
}, |
|||
); |
|||
@ -1,29 +0,0 @@ |
|||
import type React from 'react'; |
|||
|
|||
export interface LabelProps { |
|||
error?: string; |
|||
disabled?: boolean; |
|||
children: React.ReactNode; |
|||
} |
|||
|
|||
export const InputWrapper = ({ |
|||
error, |
|||
disabled, |
|||
children, |
|||
}: LabelProps): JSX.Element => ( |
|||
<div |
|||
className={`flex w-full border-gray-400 dark:border-gray-200 border-y border rounded-md transition duration-200 ease-in-out ${ |
|||
disabled |
|||
? 'bg-gray-200 text-gray-500 dark:bg-gray-800 dark:text-gray-400 border-gray-400 dark:border-gray-700' |
|||
: '' |
|||
} ${ |
|||
error |
|||
? 'border-red-500' |
|||
: disabled |
|||
? 'border-gray-200' |
|||
: ' focus-within:border-primary dark:focus-within:border-primary hover:border-primary dark:hover:border-primary focus-within:shadow-border' |
|||
}`}
|
|||
> |
|||
{children} |
|||
</div> |
|||
); |
|||
@ -1,66 +0,0 @@ |
|||
import React from 'react'; |
|||
|
|||
import { InputWrapper } from './InputWrapper'; |
|||
import { Label } from './Label'; |
|||
|
|||
type DefaultSelectProps = JSX.IntrinsicElements['select']; |
|||
|
|||
interface SelectProps extends DefaultSelectProps { |
|||
options?: { |
|||
name: string | number; |
|||
value: string | number; |
|||
}[]; |
|||
optionsEnum?: { [s: string]: string | number }; |
|||
label?: string; |
|||
error?: string; |
|||
small?: boolean; |
|||
} |
|||
|
|||
export const Select = React.forwardRef<HTMLSelectElement, SelectProps>( |
|||
({ options, optionsEnum, label, error, small, ...props }, ref) => { |
|||
const optionsEnumValues = optionsEnum |
|||
? Object.entries(optionsEnum).filter( |
|||
(value) => typeof value[1] === 'number', |
|||
) |
|||
: []; |
|||
return ( |
|||
<div> |
|||
{label && <Label label={label} error={error} />} |
|||
<InputWrapper> |
|||
<select |
|||
ref={ref} |
|||
className={`w-full rounded-md bg-transparent focus:outline-none focus:border-primary ${ |
|||
small ? 'm-1' : 'h-10 mx-2' |
|||
}`}
|
|||
disabled={ |
|||
props.disabled |
|||
? true |
|||
: !(optionsEnumValues.length || options?.length) |
|||
} |
|||
{...props} |
|||
> |
|||
{!(optionsEnumValues.length || options?.length) && ( |
|||
<option className="dark:bg-gray-700">Loading</option> |
|||
)} |
|||
{optionsEnumValues.length && |
|||
optionsEnumValues.map(([name, value]) => ( |
|||
<option className="dark:bg-gray-700" key={value} value={value}> |
|||
{name} |
|||
</option> |
|||
))} |
|||
{options && |
|||
options.map((option) => ( |
|||
<option |
|||
className="dark:bg-gray-700" |
|||
key={option.value} |
|||
value={option.value} |
|||
> |
|||
{option.name} |
|||
</option> |
|||
))} |
|||
</select> |
|||
</InputWrapper> |
|||
</div> |
|||
); |
|||
}, |
|||
); |
|||
@ -0,0 +1,9 @@ |
|||
export const bitwiseEncode = (enumValues: number[]): number => { |
|||
return enumValues.reduce((acc, curr) => acc | curr, 0); |
|||
}; |
|||
|
|||
export const bitwiseDecode = (value: number, decodeEnum: object): number[] => { |
|||
const enumValues = Object.keys(decodeEnum).map(Number).filter(Boolean); |
|||
|
|||
return enumValues.map((b) => value & b).filter(Boolean); |
|||
}; |
|||
Loading…
Reference in new issue