chore: input component (#84)

This commit is contained in:
Camila Sosa Morales 2023-01-30 12:10:47 -05:00 committed by GitHub
parent e81132a9b8
commit 4e9023ce3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 360 additions and 4 deletions

View File

@ -1,13 +1,17 @@
import { IoLogoGithub } from '@react-icons/all-files/io5/IoLogoGithub';
import { IoArrowBackCircleSharp } from '@react-icons/all-files/io5/IoArrowBackCircleSharp';
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';
export const IconLibrary = Object.freeze({
back: IoArrowBackCircleSharp,
check: AiOutlineCheck,
ethereum: EthereumIcon,
github: IoLogoGithub,
info: IoInformationCircleSharp,
upload: IoCloudUploadSharp,
metamask: MetamaskIcon, //remove if not used
});

View File

@ -0,0 +1 @@
export * from './input';

View File

@ -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>}
</>
);
}
);

View File

@ -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 />
</>
);
};

View File

@ -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;

View File

@ -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>
</>
);
};

View File

@ -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',
},
},
},
});
}

View File

@ -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>;
}

View File

@ -0,0 +1 @@
export * from './form';

View File

@ -1,2 +1,3 @@
export * from './core';
export * from './layout';
export * from './form';

View File

@ -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>;

View File

@ -14,10 +14,12 @@ export const spacing = {
8: '2rem',
9: '2.25rem',
10: '2.5rem',
11: '2.75rem',
12: '3rem',
14: '3.5rem',
16: '4rem',
20: '5rem',
20: '5rem', // 80px
22: '5.5rem', // 88px
24: '6rem',
28: '7rem',
32: '8rem',

View File

@ -1,7 +1,7 @@
import { globalCss } from '@stitches/react';
export const themeGlobals = globalCss({
'html, body': {
'html, body, #root': {
height: '100%',
padding: 0,
margin: '25px 50px',

View File

@ -61,6 +61,9 @@ export const createDripStitches = <
...darkColors, // TODO: replace with light colors once it's done the light mode
...(theme?.colors || {}),
},
borderWidths: {
default: '1px',
},
space: {
..._spacing,
...(theme?.space || {}),

View File

@ -1,10 +1,22 @@
import { Flex } from '@/components';
import React from 'react';
import { Form } from '@/components';
import React, { useState } from 'react';
export const Home = () => {
const [file, setFile] = useState<File | null>(null);
return (
<Flex css={{ justifyContent: 'center' }}>
<Flex
css={{
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
}}
>
<h1>Home</h1>
<Form.Field css={{ width: '$24' }}>
<Form.Label>Label</Form.Label>
<Form.LogoFileInput value={file} onChange={(file) => setFile(file)} />
</Form.Field>
</Flex>
);
};