Direct image upload #184
14
src/components/Inputs/Error.tsx
Normal file
14
src/components/Inputs/Error.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
type InputErrorProps = {
|
||||
message: string
|
||||
}
|
||||
|
||||
export const InputError = ({ message }: InputErrorProps) => {
|
||||
if (!message) return null
|
||||
|
||||
return (
|
||||
<div className='errorMain'>
|
||||
<div className='errorMainColor'></div>
|
||||
<p className='errorMainText'>{message}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
14
src/components/Inputs/MediaInputError.module.scss
Normal file
14
src/components/Inputs/MediaInputError.module.scss
Normal file
@ -0,0 +1,14 @@
|
||||
.accordion-button::after {
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
color: rgba(255, 255, 255, 0.5) !important;
|
||||
|
||||
top: unset !important;
|
||||
bottom: unset !important;
|
||||
}
|
||||
.accordion-body > * {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.accordion-item + .accordion-item {
|
||||
margin-top: 10px;
|
||||
}
|
64
src/components/Inputs/MediaInputError.tsx
Normal file
64
src/components/Inputs/MediaInputError.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import { FileError } from 'react-dropzone'
|
||||
import styles from './MediaInputError.module.scss'
|
||||
|
||||
type MediaInputErrorProps = {
|
||||
rootId: string
|
||||
index: number
|
||||
message: string
|
||||
errors?: readonly FileError[] | undefined
|
||||
}
|
||||
|
||||
export const MediaInputError = ({
|
||||
rootId,
|
||||
index,
|
||||
message,
|
||||
errors
|
||||
}: MediaInputErrorProps) => {
|
||||
if (!message) return null
|
||||
|
||||
return (
|
||||
<div className={['accordion-item', styles['accordion-item']].join(' ')}>
|
||||
<h2 className='accordion-header' role='tab'>
|
||||
<button
|
||||
className={[
|
||||
'accordion-button collapsed',
|
||||
styles['accordion-button']
|
||||
].join(' ')}
|
||||
type='button'
|
||||
data-bs-toggle='collapse'
|
||||
data-bs-target={`#${rootId} .item-${index}`}
|
||||
aria-expanded='false'
|
||||
aria-controls={`${rootId} .item-${index}`}
|
||||
>
|
||||
<div className='errorMain'>
|
||||
<div className='errorMainColor'></div>
|
||||
<p className='errorMainText'>{message}</p>
|
||||
</div>
|
||||
</button>
|
||||
</h2>
|
||||
{errors && (
|
||||
<div
|
||||
className={`accordion-collapse collapse item-${index}`}
|
||||
role='tabpanel'
|
||||
data-bs-parent={`#${rootId}`}
|
||||
>
|
||||
<div
|
||||
className={['accordion-body', styles['accordion-body']].join(' ')}
|
||||
>
|
||||
{errors.map((e) => {
|
||||
return typeof e === 'string' ? (
|
||||
<div className='errorMain' key={e}>
|
||||
{e}
|
||||
</div>
|
||||
) : (
|
||||
<div className='errorMain' key={e.code}>
|
||||
{e.message}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
45
src/components/Inputs/MediaInputPopover.module.scss
Normal file
45
src/components/Inputs/MediaInputPopover.module.scss
Normal file
@ -0,0 +1,45 @@
|
||||
.popover {
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 0 16px 0px rgb(0 0 0 / 15%);
|
||||
background: #232323;
|
||||
z-index: 2;
|
||||
}
|
||||
.content {
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
padding: 25px;
|
||||
> *:not(:first-child) {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
.trigger {
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
right: 25px;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.mediaInputError {
|
||||
--bs-accordion-color: unset;
|
||||
--bs-accordion-bg: unset;
|
||||
--bs-accordion-transition: unset;
|
||||
--bs-accordion-border-color: unset;
|
||||
--bs-accordion-border-width: unset;
|
||||
--bs-accordion-border-radius: unset;
|
||||
--bs-accordion-inner-border-radius: unset;
|
||||
--bs-accordion-btn-padding-x: unset;
|
||||
--bs-accordion-btn-padding-y: unset;
|
||||
--bs-accordion-btn-color: unset;
|
||||
--bs-accordion-btn-bg: unset;
|
||||
--bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='gray'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
|
||||
--bs-accordion-btn-icon-width: 1.25rem;
|
||||
--bs-accordion-btn-icon-transform: rotate(-180deg);
|
||||
--bs-accordion-btn-icon-transition: transform 0.2s ease-in-out;
|
||||
--bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='gray'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
|
||||
--bs-accordion-btn-focus-border-color: unset;
|
||||
--bs-accordion-btn-focus-box-shadow: unset;
|
||||
--bs-accordion-body-padding-x: unset;
|
||||
--bs-accordion-body-padding-y: unset;
|
||||
--bs-accordion-active-color: unset;
|
||||
--bs-accordion-active-bg: unset;
|
||||
}
|
108
src/components/Inputs/MediaInputPopover.tsx
Normal file
108
src/components/Inputs/MediaInputPopover.tsx
Normal file
@ -0,0 +1,108 @@
|
||||
import * as Popover from '@radix-ui/react-popover'
|
||||
import { useMemo } from 'react'
|
||||
import { FileRejection, FileWithPath } from 'react-dropzone'
|
||||
import { MediaInputError } from './MediaInputError'
|
||||
import { InputSuccess } from './Success'
|
||||
import styles from './MediaInputPopover.module.scss'
|
||||
|
||||
interface MediaInputPopoverProps {
|
||||
name: string
|
||||
acceptedFiles: readonly FileWithPath[]
|
||||
fileRejections: readonly FileRejection[]
|
||||
}
|
||||
|
||||
export const MediaInputPopover = ({
|
||||
name,
|
||||
acceptedFiles,
|
||||
fileRejections
|
||||
}: MediaInputPopoverProps) => {
|
||||
const acceptedFileItems = useMemo(
|
||||
() =>
|
||||
acceptedFiles.map((file) => (
|
||||
<InputSuccess
|
||||
key={file.path}
|
||||
message={`${file.path} - ${file.size} bytes`}
|
||||
/>
|
||||
)),
|
||||
[acceptedFiles]
|
||||
)
|
||||
const fileRejectionItems = useMemo(() => {
|
||||
const id = `${name}-errors`
|
||||
return (
|
||||
<div
|
||||
className={`accordion accordion-flush ${styles.mediaInputError}`}
|
||||
role='tablist'
|
||||
id={id}
|
||||
>
|
||||
{fileRejections.map(({ file, errors }, index) => (
|
||||
<MediaInputError
|
||||
rootId={id}
|
||||
index={index}
|
||||
key={file.path}
|
||||
message={`${file.path} - ${file.size} bytes`}
|
||||
errors={errors}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}, [fileRejections, name])
|
||||
|
||||
if (acceptedFiles.length === 0 && fileRejections.length === 0) return null
|
||||
|
||||
return (
|
||||
<Popover.Root>
|
||||
<Popover.Trigger asChild>
|
||||
<div className={styles.trigger}>
|
||||
{acceptedFiles.length > 0 ? (
|
||||
<svg
|
||||
width='1.5em'
|
||||
height='1.5em'
|
||||
fill='currentColor'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='0 0 576 512'
|
||||
>
|
||||
<path d='M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 38.6C310.1 219.5 256 287.4 256 368c0 59.1 29.1 111.3 73.7 143.3c-3.2 .5-6.4 .7-9.7 .7L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zM288 368a144 144 0 1 1 288 0 144 144 0 1 1 -288 0zm211.3-43.3c-6.2-6.2-16.4-6.2-22.6 0L416 385.4l-28.7-28.7c-6.2-6.2-16.4-6.2-22.6 0s-6.2 16.4 0 22.6l40 40c6.2 6.2 16.4 6.2 22.6 0l72-72c6.2-6.2 6.2-16.4 0-22.6z' />
|
||||
</svg>
|
||||
) : (
|
||||
<svg
|
||||
width='1.5em'
|
||||
height='1.5em'
|
||||
fill='tomato'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='0 0 576 512'
|
||||
>
|
||||
<path d='M0 64C0 28.7 28.7 0 64 0L224 0l0 128c0 17.7 14.3 32 32 32l128 0 0 38.6C310.1 219.5 256 287.4 256 368c0 59.1 29.1 111.3 73.7 143.3c-3.2 .5-6.4 .7-9.7 .7L64 512c-35.3 0-64-28.7-64-64L0 64zm384 64l-128 0L256 0 384 128zm48 96a144 144 0 1 1 0 288 144 144 0 1 1 0-288zm0 240a24 24 0 1 0 0-48 24 24 0 1 0 0 48zm0-192c-8.8 0-16 7.2-16 16l0 80c0 8.8 7.2 16 16 16s16-7.2 16-16l0-80c0-8.8-7.2-16-16-16z' />
|
||||
</svg>
|
||||
)}
|
||||
</div>
|
||||
</Popover.Trigger>
|
||||
<Popover.Portal>
|
||||
<Popover.Content className={styles.popover} sideOffset={5}>
|
||||
<div className='popUpMainCardTop'>
|
||||
<div className='popUpMainCardTopInfo'>
|
||||
<h3>Selected files</h3>
|
||||
</div>
|
||||
<Popover.Close asChild aria-label='Close'>
|
||||
<div className='popUpMainCardTopClose'>
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='-96 0 512 512'
|
||||
width='1em'
|
||||
height='1em'
|
||||
fill='currentColor'
|
||||
style={{ zIndex: 1 }}
|
||||
>
|
||||
<path d='M310.6 361.4c12.5 12.5 12.5 32.75 0 45.25C304.4 412.9 296.2 416 288 416s-16.38-3.125-22.62-9.375L160 301.3L54.63 406.6C48.38 412.9 40.19 416 32 416S15.63 412.9 9.375 406.6c-12.5-12.5-12.5-32.75 0-45.25l105.4-105.4L9.375 150.6c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0L160 210.8l105.4-105.4c12.5-12.5 32.75-12.5 45.25 0s12.5 32.75 0 45.25l-105.4 105.4L310.6 361.4z'></path>
|
||||
</svg>
|
||||
</div>
|
||||
</Popover.Close>
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
{acceptedFileItems}
|
||||
{fileRejectionItems}
|
||||
</div>
|
||||
</Popover.Content>
|
||||
</Popover.Portal>
|
||||
</Popover.Root>
|
||||
)
|
||||
}
|
14
src/components/Inputs/Success.tsx
Normal file
14
src/components/Inputs/Success.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
type InputSuccessProps = {
|
||||
message: string
|
||||
}
|
||||
|
||||
export const InputSuccess = ({ message }: InputSuccessProps) => {
|
||||
if (!message) return null
|
||||
|
||||
return (
|
||||
<div className='successMain'>
|
||||
<div className='successMainColor'></div>
|
||||
<p className='successMainText'>{message}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -661,6 +661,7 @@ a:hover {
|
||||
padding-right: 50px;
|
||||
}
|
||||
|
||||
.successMain,
|
||||
.errorMain {
|
||||
width: 100%;
|
||||
border-radius: 10px;
|
||||
@ -671,13 +672,17 @@ a:hover {
|
||||
flex-direction: row;
|
||||
grid-gap: 10px;
|
||||
}
|
||||
|
||||
.successMainColor,
|
||||
.errorMainColor {
|
||||
width: 5px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.errorMainColor {
|
||||
background: tomato;
|
||||
}
|
||||
|
||||
.successMainColor {
|
||||
background: #60ae60;
|
||||
}
|
||||
.errorMainText {
|
||||
}
|
||||
|
||||
@ -709,12 +714,14 @@ a:hover {
|
||||
.uploadBoxMain {
|
||||
background: hsl(0deg 0% 0% / 10%);
|
||||
border-radius: 10px;
|
||||
height: 10px;
|
||||
height: 150px;
|
||||
padding: 10px;
|
||||
border: solid 1px hsl(0deg 0% 100% / 5%);
|
||||
transition: padding ease-in-out 0.4s;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.uploadBoxMain:hover > .uploadBoxMainInside {
|
||||
.uploadBoxMain:hover {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
@ -729,4 +736,5 @@ a:hover {
|
||||
align-items: center;
|
||||
color: hsl(0deg 0% 100% / 20%);
|
||||
grid-gap: 10px;
|
||||
cursor: pointer;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user