Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ImageUpload component #237

Closed
wants to merge 3 commits into from
Closed
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
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,8 @@ DATABASE_URL="file:./dev.db"
AUTH_SECRET="Replace me with `openssl rand -base64 32` generated secret"

NEXT_PUBLIC_IS_DEMO="false"

# Cloudinary integration
# Make sure that the upload preset supports unsigned upload
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=
NEXT_PUBLIC_CLOUDINARY_UPLOAD_PRESET=
3 changes: 3 additions & 0 deletions .env.validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ const envSchema = z.object({

NEXT_PUBLIC_DEV_ENV_NAME: z.string().optional(),
NEXT_PUBLIC_DEV_ENV_COLOR_SCHEME: z.string().optional(),

NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME: z.string().optional(),
NEXT_PUBLIC_CLOUDINARY_UPLOAD_PRESET: z.string().optional(),
});

/**
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
"@formkit/auto-animate": "1.0.0-beta.6",
"@lukemorales/query-key-factory": "1.2.0",
"@prisma/client": "4.15.0",
"@rpldy/upload-drop-zone": "1.4.1",
"@rpldy/uploady": "1.4.1",
"@tanstack/react-query": "4.29.12",
"@tanstack/react-query-devtools": "4.29.12",
"axios": "1.4.0",
Expand Down
127 changes: 127 additions & 0 deletions src/components/FieldImageUpload/docs.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { Box, Button, Center, Stack, Tag, Text } from '@chakra-ui/react';
import { Formiz, useForm } from '@formiz/core';
import { FiImage } from 'react-icons/fi';

import { FieldImageUpload } from '@/components/FieldImageUpload';
import { Icon } from '@/components/Icons';

export default {
title: 'Fields/FieldImageUpload',
};

export const Default = () => {
const form = useForm({
onValidSubmit(formValues) {
console.log(formValues);
},
});

return (
<Formiz connect={form} autoForm>
<Stack spacing={6}>
<FieldImageUpload
name="demo"
label="Profil picture"
helper="This is an helper"
required="Profil picture is required"
width="360px"
/>
<FieldImageUpload
name="demo-default-value"
label="Default value"
defaultValue="https://bit.ly/dan-abramov"
width="360px"
/>
<FieldImageUpload
name="demo-ratio"
label="Custom aspect ratio and size"
imageUploadProps={{ ratio: 1 }}
w="240px"
/>
<Box>
<Button type="submit">Submit</Button>
</Box>
</Stack>
</Formiz>
);
};

export const CustomPlaceholder = () => {
const form = useForm({
onValidSubmit(formValues) {
console.log(formValues);
},
});

const PlaceholderComponent = () => (
<Center bgColor="gray.50" overflow="hidden">
<Stack textAlign="center" spacing={2}>
<Icon
fontSize="48px"
textColor="gray.400"
icon={FiImage}
alignSelf="center"
/>
<Text textColor="gray.600" fontWeight="medium" fontSize="md">
Upload a photo
</Text>
</Stack>
</Center>
);

return (
<Formiz connect={form} autoForm>
<Stack spacing={6}>
<FieldImageUpload
name="demo"
label="Image with placeholder"
imageUploadProps={{ placeholder: <PlaceholderComponent /> }}
w="480px"
/>
<Box>
<Button type="submit">Submit</Button>
</Box>
</Stack>
</Formiz>
);
};

export const InvalidateFormWhileUploading = () => {
const form = useForm({
onValidSubmit(formValues) {
console.log(formValues);
},
});

return (
<Formiz connect={form} autoForm>
<Stack spacing={2}>
<FieldImageUpload
name="demo"
label="Invalidate during uploading"
w="360px"
imageUploadProps={{
onUploadStateChange: (isUploading) =>
isUploading && form.setErrors({ demo: 'Image is uploading' }),
}}
/>
<Box>
{form.isValid ? (
<Tag size="lg" colorScheme="green">
Valid
</Tag>
) : (
<Tag size="lg" colorScheme="red">
Invalid
</Tag>
)}
</Box>
<Box>
<Button type="submit" isDisabled={!form.isValid && form.isSubmitted}>
Submit
</Button>
</Box>
</Stack>
</Formiz>
);
};
62 changes: 62 additions & 0 deletions src/components/FieldImageUpload/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React, { FC, useEffect, useState } from 'react';

import { InputGroup } from '@chakra-ui/react';
import { FieldProps, useField } from '@formiz/core';

import { FormGroup, FormGroupProps } from '@/components/FormGroup';
import { ImageUpload, ImageUploadProps } from '@/components/ImageUpload';

export type FieldImageUploadProps = FieldProps &
Omit<FormGroupProps, 'placeholder'> & {
imageUploadProps?: Omit<ImageUploadProps, 'value' | 'onChange'>;
};

export const FieldImageUpload: FC<FieldImageUploadProps> = (props) => {
const {
errorMessage,
id,
isValid,
isSubmitted,
resetKey,
setValue,
value,
otherProps,
} = useField(props);

const { children, label, helper, color, imageUploadProps, ...rest } =
otherProps;
const { required } = props;
const [isTouched, setIsTouched] = useState(false);
const showError = !isValid && (isTouched || isSubmitted);

useEffect(() => {
setIsTouched(false);
}, [resetKey]);

const formGroupProps = {
errorMessage,
helper,
id,
isRequired: !!required,
label,
showError,
...rest,
};

return (
<FormGroup {...formGroupProps}>
<InputGroup>
<ImageUpload
flex="1"
id={id}
value={value ?? ''}
onChange={(e) => setValue(e)}
onBlur={() => setIsTouched(true)}
bgColor={color}
{...imageUploadProps}
/>
</InputGroup>
{children}
</FormGroup>
);
};
27 changes: 27 additions & 0 deletions src/components/ImageUpload/DefaultImagePlaceholder.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { chakra } from '@chakra-ui/react';

export const DefaultImagePlaceholder: React.FC = (props) => (
<chakra.svg width="auto" viewBox="0 0 1600 900" bgColor="#F1F5F9" {...props}>
<g clipPath="url(#a)">
<path d="M1600 0 0 900M0 0l1600 900" stroke="#CBD5E1" strokeWidth={5} />
<path fill="#F1F5F9" d="M616 266h368v368H616z" />
<path
fillRule="evenodd"
clipRule="evenodd"
d="M691.875 327.5c-8.491 0-15.375 6.884-15.375 15.375v215.25c0 8.491 6.884 15.375 15.375 15.375h215.25c8.491 0 15.375-6.884 15.375-15.375v-215.25c0-8.491-6.884-15.375-15.375-15.375h-215.25Zm-46.125 15.375c0-25.474 20.651-46.125 46.125-46.125h215.25c25.474 0 46.125 20.651 46.125 46.125v215.25c0 25.474-20.651 46.125-46.125 46.125h-215.25c-25.474 0-46.125-20.651-46.125-46.125v-215.25Z"
fill="#94A3B8"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M745.688 389a7.688 7.688 0 1 0 0 15.376 7.688 7.688 0 0 0 0-15.376Zm-38.438 7.688c0-21.229 17.209-38.438 38.438-38.438 21.228 0 38.437 17.209 38.437 38.438 0 21.228-17.209 38.437-38.437 38.437-21.229 0-38.438-17.209-38.438-38.437ZM850.128 408.878c6.005-6.004 15.739-6.004 21.744 0l76.875 76.875c6.004 6.005 6.004 15.739 0 21.744-6.005 6.004-15.739 6.004-21.744 0L861 441.494 702.747 599.747c-6.005 6.004-15.739 6.004-21.744 0-6.004-6.004-6.004-15.739 0-21.744l169.125-169.125Z"
fill="#94A3B8"
/>
</g>
<defs>
<clipPath id="a">
<path fill="#fff" d="M0 0h1600v900H0z" />
</clipPath>
</defs>
</chakra.svg>
);
9 changes: 9 additions & 0 deletions src/components/ImageUpload/ImageUpload.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Input, InputProps, forwardRef } from '@chakra-ui/react';
import { useFileInput } from '@rpldy/uploady';

export const ImageUploadInput = forwardRef<InputProps, 'input'>(
(props, ref) => {
useFileInput(ref as ExplicitAny);
return <Input ref={ref} type="file" display="none" {...props} />;
}
);
67 changes: 67 additions & 0 deletions src/components/ImageUpload/docs.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React, { useState } from 'react';

import { Center, Stack, Text } from '@chakra-ui/react';
import { FiImage } from 'react-icons/fi';

import { Icon } from '@/components/Icons';
import { ImageUpload } from '@/components/ImageUpload';

export default {
title: 'Components/ImageUpload',
};

export const Default = () => {
const [imageUrl, setImageUrl] = useState<string>('');

return (
<Stack>
<ImageUpload
value={imageUrl}
onChange={(e) => setImageUrl(e.toString())}
w="240px"
/>
<ImageUpload
value={imageUrl}
onChange={(e) => setImageUrl(e.toString())}
w="360px"
/>
<ImageUpload
value={imageUrl}
onChange={(e) => setImageUrl(e.toString())}
w="480px"
ratio={1}
/>
</Stack>
);
};

export const CustomPlaceholder = () => {
const PlaceholderComponent = () => (
<Center bgColor="gray.50" overflow="hidden">
<Stack textAlign="center" spacing={2}>
<Icon
fontSize="48px"
textColor="gray.400"
icon={FiImage}
alignSelf="center"
/>
<Text textColor="gray.600" fontWeight="medium" fontSize="md">
Upload a photo
</Text>
</Stack>
</Center>
);

const [imageUrl, setImageUrl] = useState<string>('');

return (
<Stack>
<ImageUpload
value={imageUrl}
onChange={(e) => setImageUrl(e.toString())}
placeholder={<PlaceholderComponent />}
w="360px"
/>
</Stack>
);
};
Loading