chore: input component (#84)
This commit is contained in:
parent
e81132a9b8
commit
4e9023ce3f
|
|
@ -1,13 +1,17 @@
|
||||||
import { IoLogoGithub } from '@react-icons/all-files/io5/IoLogoGithub';
|
import { IoLogoGithub } from '@react-icons/all-files/io5/IoLogoGithub';
|
||||||
import { IoArrowBackCircleSharp } from '@react-icons/all-files/io5/IoArrowBackCircleSharp';
|
import { IoArrowBackCircleSharp } from '@react-icons/all-files/io5/IoArrowBackCircleSharp';
|
||||||
import { IoInformationCircleSharp } from '@react-icons/all-files/io5/IoInformationCircleSharp';
|
import { IoInformationCircleSharp } from '@react-icons/all-files/io5/IoInformationCircleSharp';
|
||||||
|
import { IoCloudUploadSharp } from '@react-icons/all-files/io5/IoCloudUploadSharp';
|
||||||
|
import { AiOutlineCheck } from '@react-icons/all-files/ai/AiOutlineCheck';
|
||||||
import { MetamaskIcon, EthereumIcon } from './custom';
|
import { MetamaskIcon, EthereumIcon } from './custom';
|
||||||
|
|
||||||
export const IconLibrary = Object.freeze({
|
export const IconLibrary = Object.freeze({
|
||||||
back: IoArrowBackCircleSharp,
|
back: IoArrowBackCircleSharp,
|
||||||
|
check: AiOutlineCheck,
|
||||||
ethereum: EthereumIcon,
|
ethereum: EthereumIcon,
|
||||||
github: IoLogoGithub,
|
github: IoLogoGithub,
|
||||||
info: IoInformationCircleSharp,
|
info: IoInformationCircleSharp,
|
||||||
|
upload: IoCloudUploadSharp,
|
||||||
metamask: MetamaskIcon, //remove if not used
|
metamask: MetamaskIcon, //remove if not used
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './input';
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
import { Flex } from '../../layout';
|
||||||
|
import { dripStitches } from '../../../theme';
|
||||||
|
import { forwardRef, useRef, useState } from 'react';
|
||||||
|
import { Icon } from '../icon';
|
||||||
|
import { Form } from '../../../components/form/form';
|
||||||
|
|
||||||
|
const { styled } = dripStitches;
|
||||||
|
|
||||||
|
const BorderInput = styled('div', {
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderColor: '$gray7',
|
||||||
|
width: '$22',
|
||||||
|
height: '$22',
|
||||||
|
transition: 'border-color 0.2s ease-in-out',
|
||||||
|
borderWidth: '$default',
|
||||||
|
borderRadius: '$lg',
|
||||||
|
zIndex: '$docked',
|
||||||
|
my: '$1h',
|
||||||
|
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: '$gray8',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const DEFAULT_MAX_FILE_SIZE = 10; // in KB
|
||||||
|
|
||||||
|
// The file size must be capped to a size that the contract can handle
|
||||||
|
const validateFileSize = (
|
||||||
|
file: File,
|
||||||
|
maxSize = DEFAULT_MAX_FILE_SIZE
|
||||||
|
): boolean => {
|
||||||
|
return file.size <= 1024 * maxSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
type InputFileProps = {
|
||||||
|
value: File | null;
|
||||||
|
onChange: (file: File | null) => void;
|
||||||
|
} & React.ComponentProps<typeof Flex>;
|
||||||
|
|
||||||
|
export const StyledInputFile = forwardRef<HTMLDivElement, InputFileProps>(
|
||||||
|
({ value: file, onChange, css, ...props }, ref) => {
|
||||||
|
const inputFileRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setErrorMessage(null);
|
||||||
|
|
||||||
|
if (e.target.files && e.target.files.length > 0) {
|
||||||
|
if (validateFileSize(e.target.files[0])) onChange(e.target.files[0]);
|
||||||
|
else {
|
||||||
|
onChange(null);
|
||||||
|
setErrorMessage('File size is too big');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Flex
|
||||||
|
css={{
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
cursor: 'pointer',
|
||||||
|
...(css || {}),
|
||||||
|
}}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
onClick={() => inputFileRef.current?.click()}
|
||||||
|
>
|
||||||
|
{file ? (
|
||||||
|
<img
|
||||||
|
className="absolute w-14 h-14"
|
||||||
|
src={URL.createObjectURL(file)}
|
||||||
|
alt="logo"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Icon name="upload" size="md" css={{ position: 'absolute' }} />
|
||||||
|
)}
|
||||||
|
<BorderInput />
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
className="hidden"
|
||||||
|
accept={'.svg'}
|
||||||
|
ref={inputFileRef}
|
||||||
|
onChange={handleFileChange}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
{errorMessage && <Form.Error>{errorMessage}</Form.Error>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { Input } from './';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Components/Input',
|
||||||
|
component: Input,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Variants = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Input size="sm" placeholder="Small" />
|
||||||
|
<Input size="md" placeholder="Medium" required />
|
||||||
|
<Input size="md" placeholder="Medium Invaild" aria-invalid />
|
||||||
|
<Input size="lg" placeholder="Large" />
|
||||||
|
<Input size="sm" placeholder="Small" disabled />
|
||||||
|
<Input size="md" placeholder="Medium" disabled />
|
||||||
|
<Input size="lg" placeholder="Large" disabled />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { dripStitches } from '../../../theme';
|
||||||
|
import { StyledInputFile } from './input-file';
|
||||||
|
const { styled } = dripStitches;
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
all: 'unset',
|
||||||
|
width: '100%',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
minWidth: '$0',
|
||||||
|
color: '$slate12',
|
||||||
|
my: '$1h',
|
||||||
|
|
||||||
|
transition: 'border-color 0.2s ease-in-out',
|
||||||
|
borderWidth: '$default',
|
||||||
|
borderColor: '$slate7',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: '$gray8',
|
||||||
|
},
|
||||||
|
'&:focus': {
|
||||||
|
outline: 'none',
|
||||||
|
borderColor: '$blue9',
|
||||||
|
},
|
||||||
|
'&[aria-invalid=true], &[data-invalid]': {
|
||||||
|
borderColor: '$red9',
|
||||||
|
},
|
||||||
|
'&:disabled': {
|
||||||
|
color: '$slate8',
|
||||||
|
borderColor: '$slate6',
|
||||||
|
backgroundColor: '$slate2',
|
||||||
|
'&::placeholder': {
|
||||||
|
color: '$slate8',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
sm: {
|
||||||
|
borderRadius: '$md',
|
||||||
|
fontSize: '$xs',
|
||||||
|
lineHeight: '$4',
|
||||||
|
p: '$1h',
|
||||||
|
},
|
||||||
|
md: {
|
||||||
|
borderRadius: '$lg',
|
||||||
|
fontSize: '$sm',
|
||||||
|
p: '$3 $3h',
|
||||||
|
},
|
||||||
|
lg: {
|
||||||
|
borderRadius: '$xl',
|
||||||
|
fontSize: '$md',
|
||||||
|
p: '$4 $5',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
size: 'md',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Input = styled('input', styles);
|
||||||
|
|
||||||
|
export const Textarea = styled('textarea', styles);
|
||||||
|
|
||||||
|
export const LogoFileInput = StyledInputFile;
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { Form } from './form';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Components/Form',
|
||||||
|
component: Form,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Fields = () => {
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Form.Field>
|
||||||
|
<Form.Label>Label</Form.Label>
|
||||||
|
<Form.Input placeholder="Input" />
|
||||||
|
<Form.Error>Input error</Form.Error>
|
||||||
|
</Form.Field>
|
||||||
|
<Form.Field>
|
||||||
|
<Form.Label>Label</Form.Label>
|
||||||
|
<Form.Textarea placeholder="Textarea" />
|
||||||
|
<Form.Error>Textarea error</Form.Error>
|
||||||
|
</Form.Field>
|
||||||
|
<Form.Field css={{ width: '$24' }}>
|
||||||
|
<Form.Label>Label</Form.Label>
|
||||||
|
<Form.LogoFileInput value={file} onChange={(file) => setFile(file)} />
|
||||||
|
</Form.Field>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { dripStitches } from '../../theme';
|
||||||
|
import { Flex } from '../layout';
|
||||||
|
|
||||||
|
const { styled } = dripStitches;
|
||||||
|
export abstract class FormStyles {
|
||||||
|
static readonly Field = styled(Flex, {
|
||||||
|
flexDirection: 'column',
|
||||||
|
});
|
||||||
|
|
||||||
|
static readonly Label = styled('label', {
|
||||||
|
color: '$slate11',
|
||||||
|
|
||||||
|
'&:disabled': {
|
||||||
|
color: '$slate8',
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
sm: {
|
||||||
|
fontSize: '$xs', //TODO check with royce font size
|
||||||
|
},
|
||||||
|
md: {
|
||||||
|
fontSize: '$xs',
|
||||||
|
},
|
||||||
|
lg: {
|
||||||
|
fontSize: '$md',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
size: 'md',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
static readonly ErrorMessage = styled('span', {
|
||||||
|
color: '$red11',
|
||||||
|
fontSize: '0.625rem',
|
||||||
|
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
lg: {
|
||||||
|
fontSize: '$sm',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
import React, { forwardRef } from 'react';
|
||||||
|
import { Input, LogoFileInput, Textarea } from '../core/input';
|
||||||
|
import { FormStyles } from './form.styles';
|
||||||
|
|
||||||
|
export abstract class Form {
|
||||||
|
static readonly Field = forwardRef<HTMLDivElement, Form.FieldProps>(
|
||||||
|
({ children, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<FormStyles.Field ref={ref} {...props}>
|
||||||
|
{children}
|
||||||
|
</FormStyles.Field>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
static readonly Label = forwardRef<HTMLLabelElement, Form.LabelProps>(
|
||||||
|
({ children, ...props }, ref) => (
|
||||||
|
<FormStyles.Label ref={ref} {...props}>
|
||||||
|
{children}
|
||||||
|
</FormStyles.Label>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
static readonly Error = forwardRef<HTMLDivElement, Form.ErrorProps>(
|
||||||
|
({ children, ...props }, ref) => (
|
||||||
|
<FormStyles.ErrorMessage ref={ref} {...props}>
|
||||||
|
{children}
|
||||||
|
</FormStyles.ErrorMessage>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
static readonly Input = forwardRef<HTMLInputElement, Form.InputProps>(
|
||||||
|
(props, ref) => {
|
||||||
|
return <Input ref={ref} {...props} />;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
static readonly Textarea = forwardRef<
|
||||||
|
HTMLTextAreaElement,
|
||||||
|
Form.TextareaProps
|
||||||
|
>((props, ref) => {
|
||||||
|
return <Textarea ref={ref} {...props} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
static readonly LogoFileInput = forwardRef<
|
||||||
|
HTMLInputElement,
|
||||||
|
Form.LogoFileInputProps
|
||||||
|
>((props, ref) => {
|
||||||
|
return <LogoFileInput ref={ref} {...props} />;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Form {
|
||||||
|
export type FieldProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
} & React.ComponentProps<typeof FormStyles.Field>;
|
||||||
|
|
||||||
|
export type LabelProps = React.ComponentProps<typeof FormStyles.Label>;
|
||||||
|
|
||||||
|
export type ErrorProps = React.ComponentProps<typeof FormStyles.ErrorMessage>;
|
||||||
|
|
||||||
|
export type InputProps = React.ComponentProps<typeof Input>;
|
||||||
|
|
||||||
|
export type TextareaProps = React.ComponentProps<typeof Textarea>;
|
||||||
|
|
||||||
|
export type LogoFileInputProps = React.ComponentProps<typeof LogoFileInput>;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './form';
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
export * from './core';
|
export * from './core';
|
||||||
export * from './layout';
|
export * from './layout';
|
||||||
|
export * from './form';
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { dripStitches } from '../../theme/stitches'; //TODO replace with absolute path
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const { styled } = dripStitches;
|
||||||
|
|
||||||
|
export const Flex = styled('div', {
|
||||||
|
display: 'flex',
|
||||||
|
});
|
||||||
|
|
||||||
|
export type FlexProps = React.ComponentProps<typeof Flex>;
|
||||||
|
|
@ -14,10 +14,12 @@ export const spacing = {
|
||||||
8: '2rem',
|
8: '2rem',
|
||||||
9: '2.25rem',
|
9: '2.25rem',
|
||||||
10: '2.5rem',
|
10: '2.5rem',
|
||||||
|
11: '2.75rem',
|
||||||
12: '3rem',
|
12: '3rem',
|
||||||
14: '3.5rem',
|
14: '3.5rem',
|
||||||
16: '4rem',
|
16: '4rem',
|
||||||
20: '5rem',
|
20: '5rem', // 80px
|
||||||
|
22: '5.5rem', // 88px
|
||||||
24: '6rem',
|
24: '6rem',
|
||||||
28: '7rem',
|
28: '7rem',
|
||||||
32: '8rem',
|
32: '8rem',
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { globalCss } from '@stitches/react';
|
import { globalCss } from '@stitches/react';
|
||||||
|
|
||||||
export const themeGlobals = globalCss({
|
export const themeGlobals = globalCss({
|
||||||
'html, body': {
|
'html, body, #root': {
|
||||||
height: '100%',
|
height: '100%',
|
||||||
padding: 0,
|
padding: 0,
|
||||||
margin: '25px 50px',
|
margin: '25px 50px',
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,9 @@ export const createDripStitches = <
|
||||||
...darkColors, // TODO: replace with light colors once it's done the light mode
|
...darkColors, // TODO: replace with light colors once it's done the light mode
|
||||||
...(theme?.colors || {}),
|
...(theme?.colors || {}),
|
||||||
},
|
},
|
||||||
|
borderWidths: {
|
||||||
|
default: '1px',
|
||||||
|
},
|
||||||
space: {
|
space: {
|
||||||
..._spacing,
|
..._spacing,
|
||||||
...(theme?.space || {}),
|
...(theme?.space || {}),
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,22 @@
|
||||||
import { Flex } from '@/components';
|
import { Flex } from '@/components';
|
||||||
import React from 'react';
|
import { Form } from '@/components';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
export const Home = () => {
|
export const Home = () => {
|
||||||
|
const [file, setFile] = useState<File | null>(null);
|
||||||
return (
|
return (
|
||||||
<Flex css={{ justifyContent: 'center' }}>
|
<Flex
|
||||||
|
css={{
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<h1>Home</h1>
|
<h1>Home</h1>
|
||||||
|
<Form.Field css={{ width: '$24' }}>
|
||||||
|
<Form.Label>Label</Form.Label>
|
||||||
|
<Form.LogoFileInput value={file} onChange={(file) => setFile(file)} />
|
||||||
|
</Form.Field>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue