react-packages

@react-packages/file-upload

React component which extends HTML file input by adding a view for the uploaded file(s). The view is to make sure that we’re uploading the correct file(s). It supports view for image, video and text file. For audio file, it will show the player so that you can hear the audio. Possibly, in the next releases, we try to support the view of more file types.

File Upload Video

Basic usage

import FileUpload from '@react-packages/file-upload';

...

<FileUpload name='uploadedFiles' multiple className='align-bottom' />
...

The above example shows how to use the component using the default options. You may apply the props of HTML input element to FileUpload element, except type prop is always "file".

More options

To change some options, we must use createInput function as the following example:

import {createInput} from '@react-packages/file-upload';
const FileUpload = createInput({
    ...
});

...

<FileUpload name='uploadedFiles' multiple className='align-bottom' />
...

createInput function takes one parameter which is an object that is defined as follows:

type Params = {
    ListPopup?: React.ComponentType<{
        files: File[],
        setViewIndex: (index: number) => void,
        setVisible: (visible: boolean) => void,
        viewIndex: number,
        visible?: boolean,
    }> | null,
    maxViewSize?: number,
    moreFile?: string | React.ComponentType<{count: number}> | null,
    noFileImage?: React.ReactNode,
    noFileName?: React.ReactNode,
    noViewImage?: React.ReactNode,
    ratioX?: number,
    ratioY?: number,
    styles?: {
        audio?: TStyle,
        bgView: TStyle,
        bgViewSingle?: TStyle,
        buttonMore: TStyle,
        buttonNav: TStyle,
        container: TStyle,
        containerView: TStyle,
        fileItem?: TStyle,
        fileItemViewed?: TStyle,
        fileItemText?: TStyle,
        fileList?: TStyle,
        fileListBackdrop?: TStyle,
        fileName: TStyle,
        fileNameSingle?: TStyle,
        image?: TStyle,
        input: TStyle,
        text?: TStyle,
        video?: TStyle,
        onLoad?: (styles: Omit<NonNullable<Params['styles']>, 'onLoad'>) => void,
    },
}

where TStyle is

{
    className?: string,
    style?: CSSProperties,
}

The structure of elements

Below, the elements rendered by FileUpload component:

  <div className={props.className} style={ { display:'inline-block', width:'20rem', ...props.style} }>
      <div {...styles.container}>
          <div {...styles.containerView}>
              <button
                  {...styles.buttonNav}
                  type='button'
              >
                  <span>&lt;</span>
              </button>
              <div {...styles.bgView}>
                  <Rect
                    ratioX={ratioX}
                    ratioY={ratioY}
                  >
                      <Content file={files[viewIndex] || null} />
                  </Rect>
              </div>
             <button
                  {...styles.buttonNav}
                  type='button'
              >
                  <span>&gt;</span>
              </button>
          </div>
          <div {...styles.input}>
              <button
                  {...styles.fileName}
                  type='button'
              >
                  {files[viewIndex]?.name ?? noFileName}
              </button>
              <button 
                  {...styles.buttonMore}
                  type='button'
              >
                  <MoreFile count={files.length} />
              </button>
          </div>
          <input
              {...props}
              className={undefined}
              style={ {display: 'none'} }
              type='file'
          />
          <ListPopup .../>
      </div>
  </div>

The elements rendered by Content (see the code above) component will vary depending on the type of file. The following list shows the file types and their representing elenent(s):

If you don’t set ListPopup, there is a simple propup available for you. The popup has structure of elements:

<div popover='auto' style={ {
    backgroundColor: 'transparent',
    display: 'fixed',
    height: '100%',
    left: 0,
    top: 0,
    width: '100%',
} }>
    <div {...styles.fileListBackdrop}></div>
    <div style={ {
        alignItems: 'center',
        backgroundColor: 'transparent',
        bottom: 0,
        display: 'flex',
        justifyContent: 'center',
        left: 0,
        overflow: 'auto',
        position: 'absolute',
        right: 0,
        top: 0,
    } }>
        <ul {...styles.fileList}>
           <li key={0} {...styles.fileItem}>
                <span {...styles.fileItemText}>{file0.name}</span>
            </li>
            <li key={1} {...styles.fileItem}>
                <span {...styles.fileItemText}>{file1.name}</span>
            </li>
            ...
        </ul>
    </div>
</div>

CSS tricks

Altering CSS width and display of the FileUpload input

From the structure of elements, we can see that className and style prop are applied to the top div element. However, this element has two default CSS property values, those are:

display: inline-block;
width: 20rem;

These default CSS properties are set via style prop. Therefore, if you want to override the default value(s), you should set it via style prop. But, if you insists to utilize className prop, you must use !important flag after the value in your CSS class rule defition. For example, if you use Tailwind CSS and want to set width to be 16rem then you must use CSS class !w-3xs, instead of w-3xs.

To set width greater than 20rem, you must also set styles.container because its default max-width is 20rem.

Altering the content of navigation buttons

There are two navigation buttons beside the view area. These buttons are visible if there are more one uploded file. The buttons are to change the view to another file. Remember, only one file on the view at one time.

The content of the left navigation button is < character and the right one contains >. If you want to change these contents, you must do it by CSS. There is a CSS property called content that you can use for this purpose.

Firstly, you must use createInput function to set the CSS class name for the navigation button. For example, we use class name file-upload-btn-nav

const FileUpload = createInput({
    ...
    styles: {
        buttonNav: {
            className: 'file-upload-btn-nav',
        },
        ...
    }
});

Then, declare some CSS rules as follows (you may use <style> tag or create a separate CSS file):

/* Common style for navigation buttons */
.file-upload-btn-nav {
    align-items: center;
    background-color: black;
    border-radius: 0.25rem;
    box-sizing: border-box;
    color: white;
    cursor: pointer;
    display: flex;
    flex: none;
    font-size: 1.25rem;
    font-weight: bold;
    height: 1.5rem;
    justify-content: center;
    opacity: 0.3;
    width: 2rem;
    z-index: 1;
}
.file-upload-view:hover .file-upload-btn-nav {
    opacity: 1;
}

/* Hide the default content of navigation buttons */
.file-upload-btn-nav span {
    display: none;
}

/* The content of left navigation button */
.file-upload-btn-nav:first-child::before {
    content: "«";
}

/* The content of right navigation button */
.file-upload-btn-nav:last-child::before {
    content: "»";
}

/**
 * Hide the navigation buttons when:
 * - Left button: the first file is being viewed
 * - Right button: the last file is being viewed
 * - Both buttons: the input is disabled (`disabled` prop is set)
 */
.file-upload-btn-nav:disabled {
    visibility: hidden;
}

:first-child selector refers to the element which is the first child of its parent. The left navigation button meets this condition. Similarly, :last-child will match the right navigation button. The combination of ::before selector and content property will insert a new content before the actual content. Because the actual content is hidden by the third rule, we will only see the new content.

CSS content property can also display an image. Please consult the further information here.

Notice the last rule, we use :disabled selector. This selector will apply to the disabled form elements. The navigation buttons are button element and it’s a form element. The left button will be disabled when the first uploaded file is being viewed because there is no previous file in the list. Whereas, the right button is disabled when the last uploaded file is being viewed.