Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"plugin:react/recommended",
"plugin:jsx-a11y/recommended",
"plugin:@next/next/recommended",
"plugin:prettier/recommended"
"plugin:prettier/recommended",
"plugin:react-hooks/recommended"
],
"rules": {
"prettier/prettier": [
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ yarn-error.log*

.env
.eslintcache
dist
dist
14 changes: 10 additions & 4 deletions components/atoms/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import cx from 'classix';
import React from 'react';

const sizeClassNames = {
content: '',
medium: 'py-2 px-5 text-base',
small: 'py-1 px-3 text-sm',
};
Expand All @@ -19,14 +20,16 @@ const iconVariantClassNames: Record<keyof typeof variantClassNames, string> = {
};

const iconSizeClassNames: Record<keyof typeof sizeClassNames, string> = {
content: 'h-4 w-4',
medium: 'h-4 w-4',
small: 'h-3 w-3',
};

export const Button: React.FC<{
interface Props {
size?: keyof typeof sizeClassNames;
variant?: keyof typeof variantClassNames;
type?: 'button' | 'submit';
as?: 'button' | 'div';
className?: string;
disabled?: boolean;
isLoading?: boolean;
Expand All @@ -38,13 +41,16 @@ export const Button: React.FC<{
onClick?: React.MouseEventHandler<any>;
onClickPrefix?: React.MouseEventHandler<any>;
onClickSuffix?: React.MouseEventHandler<any>;
}> = ({
}

export const Button = ({
variant = 'primary',
size = 'medium',
className = '',
prefixIconClassName = '',
suffixIconClassName = '',
type = 'button',
as = 'button',
disabled = false,
isLoading = false,
children,
Expand All @@ -53,7 +59,7 @@ export const Button: React.FC<{
suffixIcon,
onClickSuffix,
...props
}) => (
}: Props) => (
<button
className={cx(
'flex items-center justify-center gap-2',
Expand Down Expand Up @@ -89,7 +95,7 @@ export const Button: React.FC<{
</div>
)}

{children && <p>{children}</p>}
{children && <p>{children}</p>}

{suffixIcon && (
<div onClick={onClickSuffix}>
Expand Down
52 changes: 52 additions & 0 deletions components/atoms/Checkbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import cx from 'classix';
import React, { useEffect, useRef } from 'react';

interface CheckboxProps {
onChange?: (checked: boolean) => void;
label?: string;
value?: boolean;
indeterminate?: boolean;
rounded?: boolean;
className?: string;
}

const Checkbox: React.FC<CheckboxProps> = ({
label,
value = true,
indeterminate = false,
onChange,
rounded = false,
className,
}: CheckboxProps) => {
const inputRef = useRef();

useEffect(() => {
// if (inputRef.current) {
// inputRef.current.indeterminate = indeterminate;
// }
}, [inputRef, indeterminate]);

return (
<>
{label && <p>{label}</p>}
<input
ref={inputRef}
type="checkbox"
checked={value}
onChange={(event) => {
if (onChange) onChange(event.target.checked);
}}
readOnly={onChange == undefined}
className={cx(
rounded ? 'rounded-xl' : 'rounded-md',
'h-6 w-6 border-black outline-none',
'text-green cursor-pointer focus:ring-0 focus:ring-offset-0',
'transition duration-300 ease-in-out',
className,
)}
/>
</>
);
};

export default Checkbox;
83 changes: 63 additions & 20 deletions components/atoms/Input.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
/* eslint-disable @typescript-eslint/no-empty-function */
import cx from 'classix';
import React, { ComponentPropsWithoutRef, PropsWithoutRef, RefAttributes } from 'react';
import React, { ComponentPropsWithoutRef, HTMLInputTypeAttribute, PropsWithoutRef, RefAttributes } from 'react';
import { useMemo } from 'react';
import { UseFormRegisterReturn } from 'react-hook-form';
import { FieldError, UseFormRegisterReturn } from 'react-hook-form';
import { useSsr } from 'usehooks-ts';

import { useI18n } from '../../i18n/useI18n';
import { translateErrorMessage } from '../../i18n/zod';

const variantClassNames = {
transparent: 'w-full',
normal: 'border border-dark-10 focus-within:border-dark-30 rounded-md',
};

Expand All @@ -15,10 +18,8 @@ const sizeClassNames = {
};

const variantInputClassNames: Record<keyof typeof variantClassNames, string> = {
transparent:
'peer h-10 w-full text-black dark:text-white placeholder-transparent focus:placeholder-dark-10 focus:outline-none bg-transparent !border-0 outline-none !shadow-none !ring-transparent',
normal:
'peer h-10 w-full text-black dark:text-white focus:outline-none bg-transparent !border-0 outline-none !shadow-none !ring-transparent',
'peer h-10 w-full text-white dark:text-black focus:outline-none bg-transparent !border-0 outline-none !shadow-none !ring-transparent',
};

const sizeInputClassNames: Record<keyof typeof sizeClassNames, string> = {
Expand All @@ -27,10 +28,18 @@ const sizeInputClassNames: Record<keyof typeof sizeClassNames, string> = {
};

const variantLabelClassNames: Record<keyof typeof variantClassNames, string> = {
transparent: '',
normal: '',
};

const iconVariantClassNames: Record<keyof typeof variantClassNames, string> = {
normal: 'bg-black',
};

const iconSizeClassNames: Record<keyof typeof sizeClassNames, string> = {
medium: 'h-4 w-4',
small: 'h-3 w-3',
};

export type InputSize = keyof typeof sizeClassNames;

//@ts-ignore
Expand All @@ -41,8 +50,8 @@ export interface InputProps extends ComponentPropsWithoutRef<'input'> {
variant?: keyof typeof variantClassNames;
size?: InputSize;
className?: string;
type?: string;
error?;
type?: HTMLInputTypeAttribute;
error?: FieldError;
register?: UseFormRegisterReturn;
prefixIcon?: string;
suffixIcon?: string;
Expand All @@ -52,7 +61,8 @@ export interface InputProps extends ComponentPropsWithoutRef<'input'> {
inputClassName?: string;
helperText?: string;
disabled?: boolean;
onClick?: React.MouseEventHandler<any>;
onPrefixClick?: React.MouseEventHandler<any>;
onSuffixClick?: React.MouseEventHandler<any>;
inputRef?: React.LegacyRef<HTMLInputElement>;
}

Expand All @@ -74,15 +84,21 @@ export const Input: React.FC<PropsWithoutRef<InputProps> & RefAttributes<HTMLInp
error,
register = {},
inputRef,
onClick,
onPrefixClick,
onSuffixClick,
helperText,
required,
...props
}) => {
const { t } = useI18n();
const isError = useMemo(() => {
return !!error;
}, [error]);

const translatedError = error?.message ? translateErrorMessage({ message: error?.message }, t) : '';

if (!!error && !translatedError) console.warn(`No translation was found for the key '${error.message}'`);

return (
<div className={cx('relative block max-w-xl', className)}>
{label && (
Expand Down Expand Up @@ -116,28 +132,26 @@ export const Input: React.FC<PropsWithoutRef<InputProps> & RefAttributes<HTMLInp
'icon bg-dark-100 block h-5 w-5',
`icon-${prefixIcon}`,
prefixIconClassName,
disabled ? 'cursor-not-allowed' : 'cursor-pointer',
onPrefixClick ? (disabled ? 'cursor-not-allowed' : 'cursor-pointer') : '',
)}
onClick={onClick}
onClick={onPrefixClick}
/>
</div>
)}
<div className="flex-grow">
<div className="grow">
<input
id={name}
name={name}
type={type}
className={cx(
'px-0',
variantInputClassNames[variant],
sizeInputClassNames[size],
'placeholder-white placeholder-opacity-60',
disabled ? 'cursor-not-allowed' : '',
props.contentEditable === false && 'cursor-default caret-transparent',
inputClassName,
)}
disabled={disabled}
placeholder={placeholder || ''}
ref={inputRef}
{...register}
{...props}
/>
Expand All @@ -149,19 +163,48 @@ export const Input: React.FC<PropsWithoutRef<InputProps> & RefAttributes<HTMLInp
'icon block h-5 w-5 bg-white',
`icon-${suffixIcon}`,
suffixIconClassName,
disabled ? 'cursor-not-allowed' : 'cursor-pointer',
onSuffixClick ? (disabled ? 'cursor-not-allowed' : 'cursor-pointer') : '',
)}
onClick={onClick}
onClick={onSuffixClick}
/>
</div>
)}
</div>
{(!!error || helperText) && (
<p
className={cx('mt-1 text-sm', isError ? '!border-error !text-error' : 'text-white text-opacity-80')}
dangerouslySetInnerHTML={{ __html: error || helperText }}
dangerouslySetInnerHTML={{ __html: error! || helperText }}
></p>
)}
</div>
);
};

type PasswordInputProps = InputProps & {
isVisible: boolean;
onVisibilityClick: () => void;
hideAutofill?: boolean;
};

export const PasswordInput = ({ isVisible, onVisibilityClick, hideAutofill, ...props }: PasswordInputProps) => {
const { isBrowser } = useSsr();
const isChrome = useMemo(() => {
if (!isBrowser) {
return false;
}

return /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
}, [isBrowser]);

return (
<Input
type={isVisible ? 'text' : hideAutofill && isChrome ? 'text' : 'password'}
autoComplete={hideAutofill ? 'off' : props.autoComplete}
suffixIcon={cx('icon', isVisible ? 'icon-eye' : 'icon-eye-off')}
suffixIconClassName={cx(!isVisible && 'mt-4', 'cursor-pointer')}
onSuffixClick={onVisibilityClick}
inputClassName={cx(props.inputClassName || '', hideAutofill && !isVisible && isChrome ? 'text-security' : '')}
{...props}
/>
);
};
4 changes: 2 additions & 2 deletions components/atoms/Modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const Modal: FC<{
<Dialog.Overlay />
<div
className="fixed inset-0 bg-zinc-900 bg-opacity-50 backdrop-blur backdrop-filter transition-opacity"
onClick={() => onClose()}
onClick={onClose}
/>

{/* This element is to trick the browser into centering the modal contents. */}
Expand Down Expand Up @@ -58,7 +58,7 @@ const Modal: FC<{
></i>
</span>
<div>
<Dialog.Title className="line-clamp-1 text-2xl font-bold text-white">{title}</Dialog.Title>
<Dialog.Title className="text-2xl font-bold text-white line-clamp-1">{title}</Dialog.Title>
</div>
<div>
<button
Expand Down
Loading