fix: lemon file input to support button cta (#35040)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 180 KiB After Width: | Height: | Size: 180 KiB |
|
Before Width: | Height: | Size: 195 KiB After Width: | Height: | Size: 194 KiB |
@@ -20,49 +20,52 @@ export default meta
|
||||
|
||||
const Template: StoryFn<typeof LemonFileInput> = (props) => {
|
||||
const [singleValue, setSingleValue] = useState([] as any[])
|
||||
const [multipleValue, setMultipleValue] = useState([] as any[])
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<LemonFileInput
|
||||
loading={props.loading}
|
||||
{...props}
|
||||
value={singleValue}
|
||||
onChange={(newValue) => setSingleValue(newValue)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const SingleUploadAccepted: Story = Template.bind({})
|
||||
|
||||
export const MultiUploadAccepted: Story = Template.bind({})
|
||||
MultiUploadAccepted.args = {
|
||||
multiple: true,
|
||||
}
|
||||
|
||||
export const SpecificType: Story = Template.bind({})
|
||||
SpecificType.args = {
|
||||
accept: 'image/*',
|
||||
}
|
||||
|
||||
export const CustomCTA: Story = Template.bind({})
|
||||
CustomCTA.args = {
|
||||
callToAction: <div>i am a custom CTA, i could be any valid element</div>,
|
||||
}
|
||||
|
||||
export const ExtraDragAndDropTarget: StoryFn<typeof LemonFileInput> = (props) => {
|
||||
const [extraTargetValue, setExtraTargetValue] = useState([] as any[])
|
||||
|
||||
const additionalDragTarget = createRef<HTMLDivElement>()
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div>
|
||||
<h5>Single file input</h5>
|
||||
<LemonFileInput
|
||||
loading={props.loading}
|
||||
{...props}
|
||||
multiple={false}
|
||||
value={singleValue}
|
||||
onChange={(newValue) => setSingleValue(newValue)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h5>Multi file input</h5>
|
||||
<LemonFileInput
|
||||
loading={props.loading}
|
||||
{...props}
|
||||
multiple={true}
|
||||
value={multipleValue}
|
||||
onChange={(newValue) => setMultipleValue(newValue)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h5>Extra drag and drop target</h5>
|
||||
<div ref={additionalDragTarget} className="h-12 w-full border flex items-center justify-center">
|
||||
This area is also a drag target
|
||||
</div>
|
||||
<LemonFileInput
|
||||
loading={props.loading}
|
||||
{...props}
|
||||
multiple={true}
|
||||
value={extraTargetValue}
|
||||
onChange={(newValue) => setExtraTargetValue(newValue)}
|
||||
alternativeDropTargetRef={additionalDragTarget}
|
||||
/>
|
||||
<div ref={additionalDragTarget} className="h-12 w-full border flex items-center justify-center">
|
||||
This area is also a drag target
|
||||
</div>
|
||||
<LemonFileInput
|
||||
{...props}
|
||||
value={extraTargetValue}
|
||||
onChange={(newValue) => setExtraTargetValue(newValue)}
|
||||
alternativeDropTargetRef={additionalDragTarget}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const Default: Story = Template.bind({})
|
||||
|
||||
@@ -2,6 +2,7 @@ import './LemonFileInput.scss'
|
||||
|
||||
import clsx from 'clsx'
|
||||
import { IconUploadFile } from 'lib/lemon-ui/icons'
|
||||
import { LemonButton } from 'lib/lemon-ui/LemonButton'
|
||||
import { LemonTag } from 'lib/lemon-ui/LemonTag/LemonTag'
|
||||
import { Spinner } from 'lib/lemon-ui/Spinner/Spinner'
|
||||
import { ChangeEvent, createRef, RefObject, useEffect, useState } from 'react'
|
||||
@@ -13,8 +14,8 @@ export interface LemonFileInputProps extends Pick<HTMLInputElement, 'multiple' |
|
||||
* are the files currently being uploaded?
|
||||
*/
|
||||
loading?: boolean
|
||||
/** Whether input field is disabled */
|
||||
disabled?: boolean
|
||||
/** Like plain `disabled`, except we enforce a reason to be shown in the tooltip. */
|
||||
disabledReason?: string | null
|
||||
/** if this is not provided then this component is the drop target
|
||||
* and is styled when a file is dragged over it
|
||||
* if this alternativeDropTargetRef is provided,
|
||||
@@ -37,7 +38,7 @@ export const LemonFileInput = ({
|
||||
onChange,
|
||||
multiple,
|
||||
loading,
|
||||
disabled,
|
||||
disabledReason,
|
||||
// e.g. '.json' or 'image/*'
|
||||
accept,
|
||||
alternativeDropTargetRef,
|
||||
@@ -51,6 +52,7 @@ export const LemonFileInput = ({
|
||||
let dragCounter = 0
|
||||
const [drag, setDrag] = useState(false)
|
||||
const dropRef = createRef<HTMLDivElement>()
|
||||
const fileInputRef = createRef<HTMLInputElement>()
|
||||
|
||||
useEffect(() => {
|
||||
if (value && value !== files) {
|
||||
@@ -58,6 +60,12 @@ export const LemonFileInput = ({
|
||||
}
|
||||
}, [value])
|
||||
|
||||
const handleCallToActionClick = (): void => {
|
||||
if (disabledReason === undefined && fileInputRef.current) {
|
||||
fileInputRef.current.click()
|
||||
}
|
||||
}
|
||||
|
||||
const onInputChange = (e: ChangeEvent<HTMLInputElement>): void => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
@@ -145,29 +153,31 @@ export const LemonFileInput = ({
|
||||
'FileDropTarget flex flex-col gap-1',
|
||||
!alternativeDropTargetRef?.current && drag && 'FileDropTarget--active'
|
||||
)}
|
||||
aria-disabled={disabled}
|
||||
aria-disabled={!!disabledReason}
|
||||
>
|
||||
<label
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
className="hidden"
|
||||
type="file"
|
||||
multiple={multiple}
|
||||
accept={accept}
|
||||
onChange={onInputChange}
|
||||
disabled={!!disabledReason}
|
||||
/>
|
||||
<div
|
||||
className={clsx(
|
||||
'text-secondary inline-flex flow-row items-center gap-1',
|
||||
disabled ? 'cursor-not-allowed' : 'cursor-pointer'
|
||||
disabledReason ? 'cursor-not-allowed' : 'cursor-pointer'
|
||||
)}
|
||||
onClick={handleCallToActionClick}
|
||||
>
|
||||
<input
|
||||
className="hidden"
|
||||
type="file"
|
||||
multiple={multiple}
|
||||
accept={accept}
|
||||
onChange={onInputChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{callToAction || (
|
||||
<>
|
||||
<IconUploadFile className="text-2xl" /> Click or drag and drop to upload
|
||||
<LemonButton icon={<IconUploadFile />} type="tertiary" disabledReason={disabledReason}>
|
||||
Click or drag and drop to upload
|
||||
{accept ? ` ${acceptToDisplayName(accept)}` : ''}
|
||||
</>
|
||||
</LemonButton>
|
||||
)}
|
||||
</label>
|
||||
</div>
|
||||
{files.length > 0 && showUploadedFiles && (
|
||||
<div className="flex flex-row gap-2">
|
||||
{files.map((x, i) => (
|
||||
|
||||
@@ -34,7 +34,7 @@ export function OrganizationLogo(): JSX.Element {
|
||||
onChange={setFilesToUpload}
|
||||
loading={uploading}
|
||||
value={filesToUpload}
|
||||
disabled={!!restrictionReason}
|
||||
disabledReason={restrictionReason}
|
||||
callToAction={
|
||||
<>
|
||||
<div className="relative">
|
||||
|
||||