File Upload
A component that allows users to upload files.
'use client'
import { Trash2Icon } from 'lucide-react'
import { Button } from '~/components/ui/button'
import { FileUpload } from '~/components/ui/file-upload'
import { IconButton } from '~/components/ui/icon-button'
export const Demo = (props: FileUpload.RootProps) => {
return (
<FileUpload.Root maxFiles={3} {...props}>
<FileUpload.Dropzone>
<FileUpload.Label>Drop your files here</FileUpload.Label>
<FileUpload.Trigger asChild>
<Button size="sm">Open Dialog</Button>
</FileUpload.Trigger>
</FileUpload.Dropzone>
<FileUpload.ItemGroup>
<FileUpload.Context>
{({ acceptedFiles }) =>
acceptedFiles.map((file, id) => (
<FileUpload.Item key={id} file={file}>
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<FileUpload.ItemDeleteTrigger asChild>
<IconButton variant="link" size="sm">
<Trash2Icon />
</IconButton>
</FileUpload.ItemDeleteTrigger>
</FileUpload.Item>
))
}
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.Root>
)
}
import { Trash2Icon } from 'lucide-solid'
import { For } from 'solid-js'
import { Button } from '~/components/ui/button'
import { FileUpload } from '~/components/ui/file-upload'
import { IconButton } from '~/components/ui/icon-button'
export const Demo = (props: FileUpload.RootProps) => {
return (
<FileUpload.Root maxFiles={3} {...props}>
<FileUpload.Dropzone>
<FileUpload.Label>Drop your files here</FileUpload.Label>
<FileUpload.Trigger
asChild={(triggerProps) => (
<Button size="sm" {...triggerProps()}>
Open Dialog
</Button>
)}
/>
</FileUpload.Dropzone>
<FileUpload.ItemGroup>
<FileUpload.Context>
{(fileUpload) => (
<For each={fileUpload().acceptedFiles}>
{(file) => (
<FileUpload.Item file={file}>
<FileUpload.ItemPreview type="image/*">
<FileUpload.ItemPreviewImage />
</FileUpload.ItemPreview>
<FileUpload.ItemName />
<FileUpload.ItemSizeText />
<FileUpload.ItemDeleteTrigger
asChild={(triggerProps) => (
<IconButton variant="link" size="sm" {...triggerProps()}>
<Trash2Icon />
</IconButton>
)}
/>
</FileUpload.Item>
)}
</For>
)}
</FileUpload.Context>
</FileUpload.ItemGroup>
<FileUpload.HiddenInput />
</FileUpload.Root>
)
}
Usage
import { FileUpload } from '~/components/ui/file-upload'
Installation
npx @park-ui/cli components add file-upload
1
Add Styled Primitive
Copy the code snippet below into ~/components/ui/styled/file-upload.tsx
'use client'
import type { Assign } from '@ark-ui/react'
import { FileUpload } from '@ark-ui/react/file-upload'
import { type FileUploadVariantProps, fileUpload } from 'styled-system/recipes'
import type { ComponentProps, HTMLStyledProps } from 'styled-system/types'
import { createStyleContext } from './utils/create-style-context'
const { withProvider, withContext } = createStyleContext(fileUpload)
export type RootProviderProps = ComponentProps<typeof RootProvider>
export const RootProvider = withProvider<
HTMLDivElement,
Assign<Assign<HTMLStyledProps<'div'>, FileUpload.RootProviderBaseProps>, FileUploadVariantProps>
>(FileUpload.RootProvider, 'root')
export type RootProps = ComponentProps<typeof Root>
export const Root = withProvider<
HTMLDivElement,
Assign<Assign<HTMLStyledProps<'div'>, FileUpload.RootBaseProps>, FileUploadVariantProps>
>(FileUpload.Root, 'root')
export const Dropzone = withContext<
HTMLDivElement,
Assign<HTMLStyledProps<'div'>, FileUpload.DropzoneBaseProps>
>(FileUpload.Dropzone, 'dropzone')
export const ItemDeleteTrigger = withContext<
HTMLButtonElement,
Assign<HTMLStyledProps<'button'>, FileUpload.ItemDeleteTriggerBaseProps>
>(FileUpload.ItemDeleteTrigger, 'itemDeleteTrigger')
export const ItemGroup = withContext<
HTMLUListElement,
Assign<HTMLStyledProps<'ul'>, FileUpload.ItemGroupBaseProps>
>(FileUpload.ItemGroup, 'itemGroup')
export const ItemName = withContext<
HTMLDivElement,
Assign<HTMLStyledProps<'div'>, FileUpload.ItemNameBaseProps>
>(FileUpload.ItemName, 'itemName')
export const ItemPreviewImage = withContext<
HTMLImageElement,
Assign<HTMLStyledProps<'img'>, FileUpload.ItemPreviewImageBaseProps>
>(FileUpload.ItemPreviewImage, 'itemPreviewImage')
export const ItemPreview = withContext<
HTMLImageElement,
Assign<HTMLStyledProps<'div'>, FileUpload.ItemPreviewBaseProps>
>(FileUpload.ItemPreview, 'itemPreview')
export const Item = withContext<
HTMLLIElement,
Assign<HTMLStyledProps<'li'>, FileUpload.ItemBaseProps>
>(FileUpload.Item, 'item')
export const ItemSizeText = withContext<
HTMLDivElement,
Assign<HTMLStyledProps<'div'>, FileUpload.ItemSizeTextBaseProps>
>(FileUpload.ItemSizeText, 'itemSizeText')
export const Label = withContext<
HTMLLabelElement,
Assign<HTMLStyledProps<'label'>, FileUpload.LabelBaseProps>
>(FileUpload.Label, 'label')
export const Trigger = withContext<
HTMLButtonElement,
Assign<HTMLStyledProps<'button'>, FileUpload.TriggerBaseProps>
>(FileUpload.Trigger, 'trigger')
export {
FileUploadContext as Context,
FileUploadHiddenInput as HiddenInput,
} from '@ark-ui/react/file-upload'
import { type Assign, FileUpload } from '@ark-ui/solid'
import type { ComponentProps } from 'solid-js'
import { type FileUploadVariantProps, fileUpload } from 'styled-system/recipes'
import type { HTMLStyledProps } from 'styled-system/types'
import { createStyleContext } from './utils/create-style-context'
const { withProvider, withContext } = createStyleContext(fileUpload)
export type RootProviderProps = ComponentProps<typeof RootProvider>
export const RootProvider = withProvider<
Assign<Assign<HTMLStyledProps<'div'>, FileUpload.RootProviderBaseProps>, FileUploadVariantProps>
>(FileUpload.RootProvider, 'root')
export type RootProps = ComponentProps<typeof Root>
export const Root = withProvider<
Assign<Assign<HTMLStyledProps<'div'>, FileUpload.RootBaseProps>, FileUploadVariantProps>
>(FileUpload.Root, 'root')
export const Dropzone = withContext<Assign<HTMLStyledProps<'div'>, FileUpload.DropzoneBaseProps>>(
FileUpload.Dropzone,
'dropzone',
)
export const ItemDeleteTrigger = withContext<
Assign<HTMLStyledProps<'button'>, FileUpload.ItemDeleteTriggerBaseProps>
>(FileUpload.ItemDeleteTrigger, 'itemDeleteTrigger')
export const ItemGroup = withContext<Assign<HTMLStyledProps<'ul'>, FileUpload.ItemGroupBaseProps>>(
FileUpload.ItemGroup,
'itemGroup',
)
export const ItemName = withContext<Assign<HTMLStyledProps<'div'>, FileUpload.ItemNameBaseProps>>(
FileUpload.ItemName,
'itemName',
)
export const ItemPreviewImage = withContext<
Assign<HTMLStyledProps<'img'>, FileUpload.ItemPreviewImageBaseProps>
>(FileUpload.ItemPreviewImage, 'itemPreviewImage')
export const ItemPreview = withContext<
Assign<HTMLStyledProps<'div'>, FileUpload.ItemPreviewBaseProps>
>(FileUpload.ItemPreview, 'itemPreview')
export const Item = withContext<Assign<HTMLStyledProps<'li'>, FileUpload.ItemBaseProps>>(
FileUpload.Item,
'item',
)
export const ItemSizeText = withContext<
Assign<HTMLStyledProps<'div'>, FileUpload.ItemSizeTextBaseProps>
>(FileUpload.ItemSizeText, 'itemSizeText')
export const Label = withContext<Assign<HTMLStyledProps<'label'>, FileUpload.LabelBaseProps>>(
FileUpload.Label,
'label',
)
export const Trigger = withContext<Assign<HTMLStyledProps<'button'>, FileUpload.TriggerBaseProps>>(
FileUpload.Trigger,
'trigger',
)
export {
FileUploadContext as Context,
FileUploadHiddenInput as HiddenInput,
} from '@ark-ui/solid'
No snippet found
2
Add Re-Export
To improve the developer experience, re-export the styled primitives in~/components/ui/file-upload.tsx
.
export * as FileUpload from './styled/file-upload'
export * as FileUpload from './styled/file-upload'
3
Integrate Recipe
If you're not using @park-ui/preset
, add the following recipe to yourpanda.config.ts
:
import { fileUploadAnatomy } from '@ark-ui/anatomy'
import { defineSlotRecipe } from '@pandacss/dev'
export const fileUpload = defineSlotRecipe({
className: 'fileUpload',
slots: fileUploadAnatomy.keys(),
base: {
root: {
display: 'flex',
flexDirection: 'column',
gap: '4',
width: '100%',
},
label: {
fontWeight: 'medium',
textStyle: 'sm',
},
dropzone: {
alignItems: 'center',
background: 'bg.default',
borderRadius: 'l3',
borderWidth: '1px',
display: 'flex',
flexDirection: 'column',
gap: '3',
justifyContent: 'center',
minHeight: 'xs',
px: '6',
py: '4',
},
item: {
animation: 'fadeIn 0.25s ease-out',
background: 'bg.default',
borderRadius: 'l3',
borderWidth: '1px',
columnGap: '3',
display: 'grid',
gridTemplateColumns: 'auto 1fr auto',
gridTemplateAreas: `
"preview name delete"
"preview size delete"
`,
p: '4',
},
itemGroup: {
display: 'flex',
flexDirection: 'column',
gap: '3',
},
itemName: {
color: 'fg.default',
fontWeight: 'medium',
gridArea: 'name',
textStyle: 'sm',
},
itemSizeText: {
color: 'fg.muted',
gridArea: 'size',
textStyle: 'sm',
},
itemDeleteTrigger: {
alignSelf: 'flex-start',
gridArea: 'delete',
},
itemPreview: {
gridArea: 'preview',
},
itemPreviewImage: {
aspectRatio: '1',
height: '10',
objectFit: 'scale-down',
width: '10',
},
},
})