fix: lemon file input to support button cta (#35040)

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Paul D'Ambra
2025-07-15 15:33:58 +01:00
committed by GitHub
parent 5505f7fbfc
commit db4a88bb74
17 changed files with 68 additions and 55 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 KiB

After

Width:  |  Height:  |  Size: 194 KiB

View File

@@ -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({})

View File

@@ -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) => (

View File

@@ -34,7 +34,7 @@ export function OrganizationLogo(): JSX.Element {
onChange={setFilesToUpload}
loading={uploading}
value={filesToUpload}
disabled={!!restrictionReason}
disabledReason={restrictionReason}
callToAction={
<>
<div className="relative">