Skip to content

Commit 3c8f659

Browse files
init
0 parents  commit 3c8f659

File tree

129 files changed

+7249
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

129 files changed

+7249
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Defguard React UI Components Collection
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { useMemo } from 'react';
2+
import { FieldValues, useController, UseControllerProps } from 'react-hook-form';
3+
4+
import { CheckBox, CheckBoxProps } from '../../layout/Checkbox/CheckBox';
5+
6+
interface Props<T extends FieldValues> extends Partial<CheckBoxProps> {
7+
controller: UseControllerProps<T>;
8+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
9+
customValue?: (context: any) => boolean;
10+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
11+
customOnChange?: (context: any) => unknown;
12+
}
13+
14+
export const FormCheckBox = <T extends FieldValues>({
15+
controller,
16+
customValue,
17+
customOnChange,
18+
...rest
19+
}: Props<T>) => {
20+
const {
21+
field: { value, onChange },
22+
} = useController(controller);
23+
const checkBoxValue = useMemo(() => {
24+
if (customValue) {
25+
return customValue(value);
26+
}
27+
return value;
28+
}, [customValue, value]);
29+
return (
30+
<CheckBox
31+
data-testid={`field-${controller.name}`}
32+
{...rest}
33+
value={checkBoxValue}
34+
onChange={(e) => {
35+
if (customOnChange) {
36+
onChange(customOnChange(value));
37+
} else {
38+
onChange(e);
39+
}
40+
}}
41+
/>
42+
);
43+
};
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import './style.scss';
2+
3+
import { DevTool } from '@hookform/devtools';
4+
import ReactDOM from 'react-dom';
5+
import { Control } from 'react-hook-form';
6+
7+
interface Props {
8+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
9+
control: Control<any, object>;
10+
}
11+
12+
export const DevTools: React.FC<Props> = ({ control }) => {
13+
const element = document.querySelector('#root');
14+
if (!element) return null;
15+
return ReactDOM.createPortal(
16+
<div className="dev-tools">
17+
<DevTool control={control} />
18+
</div>,
19+
element,
20+
);
21+
};
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
@use '../../../scss/base/variables/' as v;
2+
3+
.dev-tools {
4+
& > div {
5+
z-index: 999999999999999999;
6+
}
7+
8+
p {
9+
color: v.$white;
10+
}
11+
12+
td {
13+
color: v.$white;
14+
}
15+
16+
code {
17+
color: v.$white;
18+
}
19+
}
20+
21+
.ReactQueryDevtools {
22+
color: v.$white;
23+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { isUndefined } from 'lodash-es';
2+
import { useMemo } from 'react';
3+
import { FieldValues, useController, UseControllerProps } from 'react-hook-form';
4+
5+
import { Input } from '../../layout/Input/Input';
6+
import { InputFloatingErrors, InputProps } from '../../layout/Input/types';
7+
8+
interface Props<T extends FieldValues> extends Omit<InputProps, 'floatingErrors'> {
9+
controller: UseControllerProps<T>;
10+
floatingErrors?: {
11+
title?: string;
12+
errorMessages?: string[];
13+
};
14+
}
15+
export const FormInput = <T extends FieldValues>({
16+
controller,
17+
floatingErrors,
18+
...rest
19+
}: Props<T>) => {
20+
const {
21+
field,
22+
fieldState: { isDirty, isTouched, error },
23+
formState: { isSubmitted },
24+
} = useController(controller);
25+
26+
const isInvalid = useMemo(() => {
27+
if (
28+
(!isUndefined(error) && (isDirty || isTouched)) ||
29+
(!isUndefined(error) && isSubmitted)
30+
) {
31+
return true;
32+
}
33+
return false;
34+
}, [error, isDirty, isSubmitted, isTouched]);
35+
36+
const floatingErrorsData = useMemo((): InputFloatingErrors | undefined => {
37+
if (floatingErrors && floatingErrors.title && error && error.types && isInvalid) {
38+
let errors: string[] = [];
39+
for (const val of Object.values(error.types)) {
40+
if (typeof val === 'string') {
41+
errors.push(val);
42+
}
43+
if (Array.isArray(val)) {
44+
errors = [...errors, ...val];
45+
}
46+
}
47+
if (floatingErrors.errorMessages && floatingErrors.errorMessages.length) {
48+
errors = [...errors, ...floatingErrors.errorMessages];
49+
}
50+
return {
51+
title: floatingErrors.title,
52+
errorMessages: errors,
53+
};
54+
}
55+
return undefined;
56+
}, [error, floatingErrors, isInvalid]);
57+
58+
return (
59+
<Input
60+
data-testid={`field-${controller.name}`}
61+
{...rest}
62+
{...field}
63+
invalid={isInvalid}
64+
errorMessage={error?.message}
65+
floatingErrors={floatingErrorsData}
66+
/>
67+
);
68+
};
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { isUndefined } from 'lodash-es';
2+
import { useMemo } from 'react';
3+
import { FieldValues, useController, UseControllerProps } from 'react-hook-form';
4+
5+
import {
6+
Select,
7+
SelectOption,
8+
SelectProps,
9+
SelectValue,
10+
} from '../../layout/Select/Select';
11+
12+
interface Props<T extends FieldValues, Y extends SelectValue>
13+
extends Omit<SelectProps<Y>, 'onChange'> {
14+
controller: UseControllerProps<T>;
15+
}
16+
17+
export const FormSelect = <T extends FieldValues, Y extends SelectValue>({
18+
controller,
19+
...rest
20+
}: Props<T, Y>) => {
21+
const {
22+
field,
23+
fieldState: { isDirty, isTouched, error },
24+
formState: { isSubmitted },
25+
} = useController(controller);
26+
27+
const isInvalid = useMemo(() => {
28+
if (
29+
(!isUndefined(error) && (isDirty || isTouched)) ||
30+
(!isUndefined(error) && isSubmitted)
31+
) {
32+
return true;
33+
}
34+
return false;
35+
}, [error, isDirty, isSubmitted, isTouched]);
36+
37+
const isValid = useMemo(
38+
() => !isInvalid && (isTouched || isDirty || isSubmitted),
39+
[isDirty, isInvalid, isSubmitted, isTouched],
40+
);
41+
42+
return (
43+
<Select
44+
{...rest}
45+
selected={field.value as SelectOption<Y> | SelectOption<Y>[]}
46+
valid={isValid}
47+
invalid={isInvalid}
48+
errorMessage={error?.message}
49+
onChange={(res) => field.onChange(res)}
50+
inForm={true}
51+
/>
52+
);
53+
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { FieldValues, useController, UseControllerProps } from 'react-hook-form';
2+
3+
import { Toggle } from '../../layout/Toggle/Toggle';
4+
import { ToggleProps } from '../../layout/Toggle/types';
5+
6+
interface Props<T extends FieldValues, D>
7+
extends Omit<ToggleProps<D>, 'onChange' | 'selected'> {
8+
controller: UseControllerProps<T>;
9+
}
10+
11+
export const FormToggle = <T extends FieldValues, D>({
12+
controller,
13+
...rest
14+
}: Props<T, D>) => {
15+
const {
16+
field: { onChange, value },
17+
} = useController(controller);
18+
return <Toggle {...rest} selected={value} onChange={(v) => onChange(v)} />;
19+
};
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import './style.scss';
2+
3+
import classNames from 'classnames';
4+
import { HTMLMotionProps, motion, TargetAndTransition } from 'framer-motion';
5+
import { useMemo, useState } from 'react';
6+
7+
import SvgIconCopy from '../../svg/IconCopy';
8+
import SvgIconDownload from '../../svg/IconDownload';
9+
import SvgIconQr from '../../svg/IconQr';
10+
import { useTheme } from '../hooks/theme/useTheme';
11+
import { ActionButtonVariant } from './types';
12+
13+
type Props = HTMLMotionProps<'button'> & {
14+
variant: ActionButtonVariant;
15+
disabled?: boolean;
16+
className?: string;
17+
active?: boolean;
18+
onClick?: () => void;
19+
};
20+
21+
/**
22+
* Styled button holding icon, created for usage with ExpandableCard and RowBox
23+
* **/
24+
export const ActionButton = ({
25+
variant,
26+
className,
27+
onClick,
28+
disabled = false,
29+
active = false,
30+
...rest
31+
}: Props) => {
32+
const { colors } = useTheme();
33+
34+
const getIcon = useMemo(() => {
35+
switch (variant) {
36+
case ActionButtonVariant.COPY:
37+
return <SvgIconCopy />;
38+
case ActionButtonVariant.QRCODE:
39+
return <SvgIconQr />;
40+
case ActionButtonVariant.DOWNLOAD:
41+
return <SvgIconDownload />;
42+
}
43+
}, [variant]);
44+
45+
const cn = useMemo(
46+
() =>
47+
classNames(
48+
'action-button',
49+
className,
50+
{
51+
disabled,
52+
active,
53+
},
54+
`variant-${variant.valueOf()}`,
55+
),
56+
[className, disabled, variant, active],
57+
);
58+
59+
const [hovered, setHovered] = useState(false);
60+
61+
const getAnimate = useMemo((): TargetAndTransition => {
62+
const res: TargetAndTransition = {
63+
backgroundColor: colors.surfaceButton,
64+
opacity: 1,
65+
transition: {
66+
duration: 0.25,
67+
},
68+
};
69+
70+
if (disabled) {
71+
res.opacity = 0.5;
72+
return res;
73+
}
74+
75+
if (hovered || active) {
76+
res.backgroundColor = colors.surfaceMainPrimary;
77+
}
78+
79+
return res;
80+
}, [colors.surfaceButton, colors.surfaceMainPrimary, disabled, hovered, active]);
81+
82+
return (
83+
<motion.button
84+
{...rest}
85+
onHoverStart={() => setHovered(true)}
86+
onHoverEnd={() => setHovered(false)}
87+
className={cn}
88+
disabled={disabled}
89+
initial={false}
90+
animate={getAnimate}
91+
onClick={() => {
92+
if (!disabled && onClick) {
93+
onClick();
94+
}
95+
}}
96+
>
97+
{getIcon}
98+
</motion.button>
99+
);
100+
};

0 commit comments

Comments
 (0)