Skip to content

Commit

Permalink
feat: add useFileInput hook (#624)
Browse files Browse the repository at this point in the history
  • Loading branch information
korvin89 authored Apr 14, 2023
1 parent 2aae0d2 commit eb47759
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,4 @@ export * from './utils/useForkRef';
export * from './utils/setRef';
export {useOnFocusOutside} from './utils/useOnFocusOutside';
export * from './utils/xpath';
export * from './utils/useFileInput/useFileInput';
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';
import {Meta, Story} from '@storybook/react';
import {Button} from '../../../Button';
import {useFileInput} from '../useFileInput';

export default {title: 'Hooks/useFileInput'} as Meta;

const DefaultTemplate: Story = () => {
const onUpdate = React.useCallback((files: File[]) => console.log(files), []);
const {controlProps, triggerProps} = useFileInput({onUpdate});

return (
<React.Fragment>
<input {...controlProps} />
<Button {...triggerProps}>Upload</Button>
</React.Fragment>
);
};

export const Default = DefaultTemplate.bind({});
74 changes: 74 additions & 0 deletions src/components/utils/useFileInput/useFileInput.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/* eslint-disable valid-jsdoc */
import React from 'react';

export type UseFileInputProps = {
onUpdate?: (files: File[]) => void;
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
};

export type UseFileInputOutput = {
controlProps: React.DetailedHTMLProps<
React.InputHTMLAttributes<HTMLInputElement>,
HTMLInputElement
>;
triggerProps: {
onClick: () => void;
};
};

/**
* Used to shape props for input with type "file".
*
* Usage example:
```tsx
import React from 'react';
import {Button, useFileInput} from '@gravity-ui/uikit';
const Component = () => {
const onUpdate = React.useCallback((files: File[]) => console.log(files), []);
const {controlProps, triggerProps} = useFileInput({onUpdate});
return (
<React.Fragment>
<input {...controlProps} />
<Button {...triggerProps}>Upload</Button>
</React.Fragment>
);
};
```
*/
export function useFileInput({onUpdate, onChange}: UseFileInputProps): UseFileInputOutput {
const ref = React.useRef<HTMLInputElement>(null);

const handleChange = React.useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
onChange?.(event);
onUpdate?.(Array.from(event.target.files || []));
// https://stackoverflow.com/a/12102992
event.target.value = '';
},
[onChange, onUpdate],
);

const openDeviceStorage = React.useCallback(() => {
ref.current?.click();
}, []);

const result: UseFileInputOutput = React.useMemo(
() => ({
controlProps: {
ref,
type: 'file',
tabIndex: -1,
style: {opacity: 0, position: 'absolute', width: 1, height: 1},
onChange: handleChange,
},
triggerProps: {
onClick: openDeviceStorage,
},
}),
[handleChange, openDeviceStorage],
);

return result;
}

0 comments on commit eb47759

Please sign in to comment.