├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .nvmrc ├── .prettierrc ├── .yarnrc.yml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── dist ├── constants │ ├── events.d.ts │ ├── events.d.ts.map │ ├── file.d.ts │ ├── file.d.ts.map │ ├── images.d.ts │ ├── images.d.ts.map │ ├── style.d.ts │ ├── style.d.ts.map │ ├── text.d.ts │ └── text.d.ts.map ├── file-upload-with-preview.d.ts ├── file-upload-with-preview.d.ts.map ├── file-upload-with-preview.jpg ├── index.cjs ├── index.d.ts ├── index.d.ts.map ├── index.iife.js ├── index.js ├── style.css ├── types │ ├── events.d.ts │ ├── events.d.ts.map │ ├── options.d.ts │ └── options.d.ts.map └── utils │ ├── file.d.ts │ └── file.d.ts.map ├── docs ├── assets │ ├── custom-image-de30ffc0.svg │ ├── index-0a8e647e.css │ └── index-86f31695.js ├── index.html └── typedoc │ ├── .nojekyll │ ├── assets │ ├── highlight.css │ ├── main.js │ ├── search.js │ └── style.css │ ├── classes │ └── FileUploadWithPreview.html │ ├── enums │ └── Events.html │ ├── index.html │ ├── interfaces │ ├── ClearButtonClickedEvent.html │ ├── ClearButtonClickedEventDetail.html │ ├── ImageAddedEvent.html │ ├── ImageAddedEventDetail.html │ ├── ImageDeletedEvent.html │ ├── ImageDeletedEventDetail.html │ ├── ImageMultiItemClickedEvent.html │ ├── ImageMultiItemClickedEventDetail.html │ ├── Images.html │ ├── Options.html │ └── Text.html │ ├── modules.html │ ├── types │ ├── PresetFiles.html │ └── RequiredOptions.html │ └── variables │ ├── DEFAULT_BACKGROUND_IMAGE.html │ ├── DEFAULT_BASE_IMAGE.html │ ├── DEFAULT_BROWSE_TEXT.html │ ├── DEFAULT_CHOOSE_FILE_TEXT.html │ ├── DEFAULT_FILES_SELECTED_TEXT.html │ ├── DEFAULT_LABEL_TEXT.html │ ├── DEFAULT_SUCCESS_FILE_ALT_IMAGE.html │ ├── DEFAULT_SUCCESS_PDF_IMAGE.html │ ├── DEFAULT_SUCCESS_VIDEO_IMAGE.html │ ├── MULTI_ITEM_CLEAR_ANIMATION_CLASS.html │ └── UNIQUE_ID_IDENTIFIER.html ├── example ├── custom-image.svg ├── favicon.png ├── index.html ├── index.scss └── index.ts ├── globals.d.ts ├── jest.config.cjs ├── jest ├── constants │ └── file.ts └── style-mock.ts ├── package.json ├── public └── file-upload-with-preview.jpg ├── src ├── assets │ └── images │ │ ├── background.svg │ │ ├── base-image.svg │ │ ├── file-alt-success.svg │ │ ├── pdf-success.svg │ │ └── video-success.svg ├── constants │ ├── events.ts │ ├── file.ts │ ├── images.ts │ ├── style.ts │ └── text.ts ├── file-upload-with-preview.spec.ts ├── file-upload-with-preview.ts ├── index.scss ├── index.ts ├── types │ ├── events.ts │ └── options.ts └── utils │ └── file.ts ├── tsconfig.json ├── typedoc.json ├── vite.config.app.ts ├── vite.config.library.ts └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | docs 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "env": { 5 | "browser": true, 6 | "jest": true, 7 | "node": true 8 | }, 9 | "extends": [ 10 | "eslint:recommended", 11 | "plugin:@typescript-eslint/eslint-recommended", 12 | "plugin:@typescript-eslint/recommended", 13 | "prettier" 14 | ], 15 | "plugins": ["@typescript-eslint", "simple-import-sort", "prettier"], 16 | "rules": { 17 | "@typescript-eslint/ban-ts-comment": "off", 18 | "import/extensions": "off", 19 | "import/prefer-default-export": "off", 20 | "no-console": "off", 21 | "no-prototype-builtins": "off", 22 | "prettier/prettier": 2, 23 | "simple-import-sort/exports": "error", 24 | "simple-import-sort/imports": "error", 25 | "sort-keys": ["error", "asc", { "caseSensitive": true, "natural": false, "minKeys": 2 }] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | 4 | # https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored 5 | .pnp.* 6 | .yarn/* 7 | !.yarn/patches 8 | !.yarn/plugins 9 | !.yarn/releases 10 | !.yarn/sdks 11 | !.yarn/versions 12 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 16.17.0 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "semi": true, 4 | "singleQuote": true, 5 | "tabWidth": 2, 6 | "trailingComma": "all", 7 | "useTabs": false 8 | } 9 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | enableTelemetry: false 2 | nodeLinker: node-modules 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG.md 2 | 3 | ## 6.0.2 4 | 5 | - Use vite for serving app and building library 6 | - Remove rollup 7 | - Remove babel 8 | - Update package.json exports 9 | 10 | ## 5.0.8 11 | 12 | - Fix index bug. Thank you @tasinttttttt. 13 | 14 | ## 5.0.6 15 | 16 | - Revamped library 17 | - Added TypeScript 18 | - Replaced webpack with vite for example 19 | - Updated all deps 20 | - Added prettier and updated eslint 21 | - Simplified library logic 22 | - Updated SCSS styling 23 | 24 | ## 4.3.0 25 | 26 | - Added image click event and updated deps. Thanks @tasinttttttt. 27 | 28 | ## 4.1.0 29 | 30 | - Added index on delete event. Updates deps. Thank you @tasinttttttt. 31 | 32 | ## 4.0.1, 4.0.2 33 | 34 | - Removing unneeded wrapper divs in preview panels 35 | - Adding `token` attribute on each preview panel 36 | 37 | ## 4.0.0 38 | 39 | - Refactor of basically entire library. Many methods changed, properties changed. Please read the README and update your code accordingly. This refactoring was necessary due to the need for additional methods that allow for more control and replacement of the `cachedFileArray`. 40 | - Removed `rollup-plugin-buble` in favor of `rollup-plugin-bable` due to the former not fully supporting `async/await`. 41 | 42 | ## 3.2.0 43 | 44 | - Moves from Gulp to Webpack 45 | 46 | ## 3.1.0 47 | 48 | - Made updates for accessibility. 49 | - Added `text` object to `options` so that the input copy can be changed. 50 | - Updated the README accordingly with these changes. 51 | 52 | ## 3.0.1 53 | 54 | - Removed the option to _not_ show a grid of files when setting `multiple` on the input. The logic messiness wasn't worth it considering we don't really need the option now that we have a grid view for multiple images. 55 | - Cleaned up logic flow. 56 | - _Added_ buttons for deleting indivudual files in the grid view. Also includes the option to disable this feature. 57 | - Added a deletion event that gets triggered when a file is deleted. 58 | - Updated the README accordingly with these changes. 59 | 60 | ## 3.0.0 61 | 62 | - Rewrite of the library code. Made the handling of multiple images a bit more clear. Tried to clean up the logic and set properties only a single time when needed. Should all be a bit more clear now. 63 | - Showing the grid of images in the case of multiple is now the default setting. To stop that, pass 64 | `{showMultiple: false}` when initializing the class. 65 | - All further options are now set up to be passed in a single `options` object during initialization. This will be good for any further additions. 66 | - Restructured how the `imageSelected` event is done as to make it a bit easier to use - see updated documentation on how to use that event. 67 | - `custom-file-container__image-preview__active` was changed to `custom-file-container__image-preview--active` to be inline with BEM style. 68 | - Added overflow to multiple image pane, so when there's a ton of images it doesn't just keep growing in height. 69 | 70 | ## 2.1.2 (2018-09-21) 71 | 72 | - Merged in #12 from @firstor - support for .gifs in both single and multiple previews. 73 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) John Datserakis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | # file-upload-with-preview 4 | 5 | 🖼 Simple file-upload utility that shows a preview of the uploaded image. Written in TypeScript. No dependencies. Works well with or without a framework. 6 | 7 |

8 | NPM Version 9 | NPM Downloads 10 | License 11 | 12 | Tweet 13 |

14 | 15 | ## Links 16 | 17 | - [Demo](https://johndatserakis.github.io/file-upload-with-preview) 18 | - [npm](https://www.npmjs.com/package/file-upload-with-preview) 19 | - [GitHub](https://github.com/johndatserakis/file-upload-with-preview#readme) 20 | - [CodeSandbox - TypeScript](https://codesandbox.io/s/file-upload-with-preview-4ypil8?file=/src/index.ts) 21 | - [CodeSandbox - Browser](https://codesandbox.io/s/file-upload-with-preview-browser-446nc8?file=/src/index.js) 22 | - [Docs](https://johndatserakis.github.io/file-upload-with-preview/typedoc/) 23 | 24 | ## Install 25 | 26 | ```bash 27 | yarn add file-upload-with-preview 28 | ``` 29 | 30 | Or, you can include it through the browser. 31 | 32 | ```html 33 | 38 | 39 | 40 | ``` 41 | 42 | ## About 43 | 44 | This is a simple frontend utility meant to help the file-upload process on your website. 45 | 46 | It is written in pure JavaScript using TypeScript and has no dependencies. You can check out the live demo [here](https://johndatserakis.github.io/file-upload-with-preview). 47 | 48 | For the most part, browsers do a good job of handling image-uploads. That being said - I find the ability to show our users a preview of their upload can go a long way in increasing the confidence in their upload. 49 | 50 | `file-upload-with-preview` aims to address the issue of showing a preview of a user's uploaded image in a simple to use package. 51 | 52 | ## Features 53 | 54 | - Shows the actual image preview in the case of a single uploaded .jpg, .jpeg, .gif, or .png image. Shows a _success-image_ in the case of an uploaded .pdf file, uploaded video, or other un-renderable file - so the user knows their image was collected successfully. In the case of multiple selected files, the user's selected images will be shown in a grid. 55 | - Shows the image name in the input bar. Shows the count of selected images in the case of multiple selections within the same input. 56 | - Allows the user to clear their upload and clear individual images in the `multiple` grid 57 | - Looks great 58 | - Framework agnostic - to access the uploaded file/files just use the `cachedFileArray` (always will be an array) property of your `FileUploadWithPreview` object. 59 | - For every file-group you want just initialize another `FileUploadWithPreview` object with its own `uniqueId` - this way you can have multiple file-uploads on the same page. You also can just use a single input designated with a `multiple` property to allow multiple files on the same input. 60 | 61 | ## Usage 62 | 63 | This library looks for a specific HTML element to display the file-upload. Simply add the below `div` to your HTML. Make sure to populate your unique id in the `data-upload-id` attribute. 64 | 65 | ```html 66 |
67 | ``` 68 | 69 | Then, initialize your file-upload in the JavaScript like below: 70 | 71 | ```javascript 72 | import { FileUploadWithPreview } from 'file-upload-with-preview'; 73 | import 'file-upload-with-preview/dist/style.css'; 74 | 75 | const upload = new FileUploadWithPreview('my-unique-id'); 76 | ``` 77 | 78 | Usage with Next.js / Tailwind : 79 | 80 | ```typescript 81 | 'use client'; 82 | import { useEffect } from 'react'; 83 | import { FileUploadWithPreview } from 'file-upload-with-preview'; 84 | import 'file-upload-with-preview/dist/style.css'; 85 | 86 | export default function Uploader() { 87 | useEffect(() => { 88 | const upload = new FileUploadWithPreview('my-unique-id'); 89 | }, []); 90 | 91 | return
; 92 | } 93 | ``` 94 | 95 | If you're importing directly in the browser, use the following instead: 96 | 97 | ```html 98 | 99 | 100 | 101 | 102 | 107 | 108 | 109 |
110 | 111 | 112 | 113 | ``` 114 | 115 | Then initialize like this: 116 | 117 | ```javascript 118 | const upload = new FileUploadWithPreview.FileUploadWithPreview('my-unique-id'); 119 | ``` 120 | 121 | Then when you're ready to use the user's file for an API call or whatever, just access the user's uploaded file/files in the `cachedFileArray` property of your initialized object like this: 122 | 123 | ```javascript 124 | upload.cachedFileArray; 125 | ``` 126 | 127 | You can optionally trigger the image browser and clear selected images programmatically. There are additional methods on the class if you'd like to take a look at the source code. 128 | 129 | ```javascript 130 | upload.emulateInputSelection(); // to open image browser 131 | upload.resetPreviewPanel(); // clear all selected images 132 | ``` 133 | 134 | You may also want to capture the event when an image is selected. 135 | 136 | ```javascript 137 | import { Events, ImageAddedEvent } from 'file-upload-with-preview'; 138 | 139 | window.addEventListener(Events.IMAGE_ADDED, (e: Event) => { 140 | const { detail } = e as unknown as ImageAddedEvent; 141 | 142 | console.log('detail', detail); 143 | }); 144 | ``` 145 | 146 | ### Note 147 | 148 | The `cachedFileArray` property is always an array. So if you are only allowing the user to upload a single file, you can access that file at `cachedFileArray[0]` - otherwise just send the entire array to your backend to handle it normally. 149 | 150 | Make sure to pass in `multiple: true` in your options if you want to allow the user to select multiple images. 151 | 152 | ## Docs 153 | 154 | View the full docs [here](https://johndatserakis.github.io/file-upload-with-preview/typedoc/). 155 | 156 | ## Full Example 157 | 158 | See the full example in the `./example/index.ts` folder. See the top of this README for some links to a few live CodeSandbox's. 159 | 160 | ## Browser Support 161 | 162 | If you are supporting a browser like IE11, one way to add a polyfill for `fetch` and `promise` is by adding the following in the bottom of your `index.html`: 163 | 164 | ```html 165 | 166 | 167 | ``` 168 | 169 | ## Development 170 | 171 | ```bash 172 | # Install dependencies 173 | yarn 174 | 175 | # Watch changes during local development 176 | yarn dev 177 | 178 | # Run tests 179 | yarn test 180 | 181 | # Build library 182 | yarn build 183 | ``` 184 | 185 | ## Other 186 | 187 | Go ahead and fork the project! Submit an issue if needed. Have fun! 188 | 189 | ## License 190 | 191 | [MIT](http://opensource.org/licenses/MIT) 192 | -------------------------------------------------------------------------------- /dist/constants/events.d.ts: -------------------------------------------------------------------------------- 1 | export declare enum Events { 2 | IMAGE_ADDED = "fileUploadWithPreview:imagesAdded", 3 | IMAGE_DELETED = "fileUploadWithPreview:imageDeleted", 4 | CLEAR_BUTTON_CLICKED = "fileUploadWithPreview:clearButtonClicked", 5 | IMAGE_MULTI_ITEM_CLICKED = "fileUploadWithPreview:imageMultiItemClicked" 6 | } 7 | //# sourceMappingURL=events.d.ts.map -------------------------------------------------------------------------------- /dist/constants/events.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../../src/constants/events.ts"],"names":[],"mappings":"AAAA,oBAAY,MAAM;IAChB,WAAW,sCAAsC;IACjD,aAAa,uCAAuC;IACpD,oBAAoB,6CAA6C;IACjE,wBAAwB,gDAAgD;CACzE"} -------------------------------------------------------------------------------- /dist/constants/file.d.ts: -------------------------------------------------------------------------------- 1 | export declare const UNIQUE_ID_IDENTIFIER = ":upload:"; 2 | //# sourceMappingURL=file.d.ts.map -------------------------------------------------------------------------------- /dist/constants/file.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../../src/constants/file.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oBAAoB,aAAa,CAAC"} -------------------------------------------------------------------------------- /dist/constants/images.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"images.d.ts","sourceRoot":"","sources":["../../../src/constants/images.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,m0DACmyD,CAAC;AAEn0D,eAAO,MAAM,yBAAyB,uqFACgoF,CAAC;AAEvqF,eAAO,MAAM,2BAA2B,m0MAC0xM,CAAC;AAEn0M,eAAO,MAAM,8BAA8B,mhMACu+L,CAAC;AAEnhM,eAAO,MAAM,wBAAwB,2uIACqsI,CAAC"} -------------------------------------------------------------------------------- /dist/constants/style.d.ts: -------------------------------------------------------------------------------- 1 | export declare const MULTI_ITEM_CLEAR_ANIMATION_CLASS = "multi-item-clear-animation"; 2 | //# sourceMappingURL=style.d.ts.map -------------------------------------------------------------------------------- /dist/constants/style.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"style.d.ts","sourceRoot":"","sources":["../../../src/constants/style.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gCAAgC,+BAA+B,CAAC"} -------------------------------------------------------------------------------- /dist/constants/text.d.ts: -------------------------------------------------------------------------------- 1 | export declare const DEFAULT_CHOOSE_FILE_TEXT = "Choose file..."; 2 | export declare const DEFAULT_BROWSE_TEXT = "Browse"; 3 | export declare const DEFAULT_FILES_SELECTED_TEXT = "files selected"; 4 | export declare const DEFAULT_LABEL_TEXT = "Upload"; 5 | //# sourceMappingURL=text.d.ts.map -------------------------------------------------------------------------------- /dist/constants/text.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../../src/constants/text.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,wBAAwB,mBAAmB,CAAC;AACzD,eAAO,MAAM,mBAAmB,WAAW,CAAC;AAC5C,eAAO,MAAM,2BAA2B,mBAAmB,CAAC;AAC5D,eAAO,MAAM,kBAAkB,WAAW,CAAC"} -------------------------------------------------------------------------------- /dist/file-upload-with-preview.d.ts: -------------------------------------------------------------------------------- 1 | import { Options, PresetFiles, RequiredOptions } from './types/options'; 2 | export declare class FileUploadWithPreview { 3 | /** 4 | * Currently selected files 5 | * 6 | * @default [] 7 | */ 8 | cachedFileArray: File[]; 9 | /** 10 | * Button to reset the instance 11 | */ 12 | clearButton: Element; 13 | /** 14 | * Main container for the instance 15 | */ 16 | el: Element; 17 | /** 18 | * Display panel for the images 19 | */ 20 | imagePreview: HTMLDivElement; 21 | /** 22 | * Hidden input 23 | */ 24 | inputHidden: HTMLInputElement; 25 | /** 26 | * Visible input 27 | */ 28 | inputVisible: Element; 29 | options: RequiredOptions; 30 | /** 31 | * The `id` you set for the instance 32 | */ 33 | uploadId: string; 34 | constructor(uploadId: string, options?: Options); 35 | bindClickEvents(): void; 36 | addImagesFromPath(presetFiles: PresetFiles): Promise; 37 | addFiles(files: FileList | File[]): void; 38 | addFileToPreviewPanel(file: File): void; 39 | replaceFiles(files: File[]): void; 40 | replaceFileAtIndex(file: File, index: number): void; 41 | deleteFileAtIndex(index: number): void; 42 | refreshPreviewPanel(): void; 43 | addBrowseButton(text: string): void; 44 | emulateInputSelection(): void; 45 | resetPreviewPanel(): void; 46 | } 47 | //# sourceMappingURL=file-upload-with-preview.d.ts.map -------------------------------------------------------------------------------- /dist/file-upload-with-preview.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"file-upload-with-preview.d.ts","sourceRoot":"","sources":["../../src/file-upload-with-preview.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGxE,qBAAa,qBAAqB;IAChC;;;;OAIG;IACH,eAAe,EAAE,IAAI,EAAE,CAAC;IACxB;;OAEG;IACH,WAAW,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,EAAE,EAAE,OAAO,CAAC;IACZ;;OAEG;IACH,YAAY,EAAE,cAAc,CAAC;IAC7B;;OAEG;IACH,WAAW,EAAE,gBAAgB,CAAC;IAC9B;;OAEG;IACH,YAAY,EAAE,OAAO,CAAC;IACtB,OAAO,EAAE,eAAe,CAmBtB;IACF;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;gBAEL,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,OAAY;IAwFnD,eAAe;IAgET,iBAAiB,CAAC,WAAW,EAAE,WAAW;IAoBhD,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,EAAE;IA4CjC,qBAAqB,CAAC,IAAI,EAAE,IAAI;IAwEhC,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE;IAS1B,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM;IAS5C,iBAAiB,CAAC,KAAK,EAAE,MAAM;IAuB/B,mBAAmB;IAoBnB,eAAe,CAAC,IAAI,EAAE,MAAM;IAI5B,qBAAqB;IAIrB,iBAAiB;CAQlB"} -------------------------------------------------------------------------------- /dist/file-upload-with-preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndatserakis/file-upload-with-preview/c0c27abcf8368f733079ecbd131e671d811614d2/dist/file-upload-with-preview.jpg -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './constants/events'; 2 | export * from './constants/file'; 3 | export * from './constants/images'; 4 | export * from './constants/style'; 5 | export * from './constants/text'; 6 | export * from './types/events'; 7 | export * from './types/options'; 8 | export * from './file-upload-with-preview'; 9 | //# sourceMappingURL=index.d.ts.map -------------------------------------------------------------------------------- /dist/index.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAC;AAGtB,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AAGjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAGhC,cAAc,4BAA4B,CAAC"} -------------------------------------------------------------------------------- /dist/style.css: -------------------------------------------------------------------------------- 1 | .custom-file-container{box-sizing:border-box;position:relative;display:block}.custom-file-container *{box-sizing:border-box}.custom-file-container .label-container{align-items:center;display:flex;justify-content:space-between;margin-bottom:4px}.custom-file-container .clear-button{color:#333;font-size:26px;height:26px;line-height:26px;text-decoration:none;transition:color .2s ease-in-out}.custom-file-container .clear-button:hover{color:#777}.custom-file-container .input-container{display:inline-block;height:40px;margin-bottom:8px;position:relative;width:100%}.custom-file-container .input-container:hover{cursor:pointer}.custom-file-container .input-hidden{height:40px;margin:0;max-width:100%;min-width:300px;opacity:0}.custom-file-container .input-visible{background-clip:padding-box;background-color:#fff;border-radius:4px;border:1px solid #c0c0af;color:#333;height:40px;left:0;line-height:1.5;overflow:hidden;padding:8px 12px;position:absolute;right:0;top:0;-webkit-user-select:none;user-select:none;z-index:5}.custom-file-container .browse-button{background-color:#edede8;border-left:1px solid #c0c0af;color:#333;display:block;height:38px;padding:8px 12px;position:absolute;right:0;top:0;z-index:6}.custom-file-container .image-preview{background-color:#edede8;background-position:center center;background-repeat:no-repeat;background-size:cover;border-radius:4px;height:250px;overflow:auto;padding:4px;transition:background .2s ease-in-out;width:100%}.custom-file-container .image-preview-item{background-position:center center;background-repeat:no-repeat;background-size:cover;border-radius:4px;box-shadow:0 4px 10px #33333340;float:left;height:90px;margin:1.858736059%;position:relative;transition:background .2s ease-in-out,opacity .2s ease-in-out;width:29.615861214%}.custom-file-container .image-preview-item.multi-item-clear-animation{opacity:0}.custom-file-container .image-preview-item-clear{background:#edede8;border-radius:50%;box-shadow:0 4px 10px #33333340;height:20px;left:-6px;margin-top:-6px;position:absolute;text-align:center;transition:background .2s ease-in-out,color .2s ease-in-out;width:20px}.custom-file-container .image-preview-item-clear:hover{background:#e2e2da;cursor:pointer}.custom-file-container .image-preview-item-clear-icon{color:#333;display:block;margin-top:-2px} 2 | -------------------------------------------------------------------------------- /dist/types/events.d.ts: -------------------------------------------------------------------------------- 1 | export interface ImageAddedEventDetail { 2 | addedFilesCount: number; 3 | cachedFileArray: File[]; 4 | files: FileList | File[]; 5 | uploadId: string; 6 | } 7 | export interface ImageAddedEvent { 8 | detail: ImageAddedEventDetail; 9 | } 10 | export interface ImageDeletedEventDetail { 11 | cachedFileArray: File[]; 12 | currentFileCount: number; 13 | index: number; 14 | uploadId: string; 15 | } 16 | export interface ImageDeletedEvent { 17 | detail: ImageDeletedEventDetail; 18 | } 19 | export interface ClearButtonClickedEventDetail { 20 | uploadId: string; 21 | } 22 | export interface ClearButtonClickedEvent { 23 | detail: ClearButtonClickedEventDetail; 24 | } 25 | export interface ImageMultiItemClickedEventDetail { 26 | cachedFileArray: File[]; 27 | file: File; 28 | index: number; 29 | uploadId: string; 30 | } 31 | export interface ImageMultiItemClickedEvent { 32 | detail: ImageMultiItemClickedEventDetail; 33 | } 34 | //# sourceMappingURL=events.d.ts.map -------------------------------------------------------------------------------- /dist/types/events.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../../src/types/events.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,qBAAqB;IACpC,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,IAAI,EAAE,CAAC;IACxB,KAAK,EAAE,QAAQ,GAAG,IAAI,EAAE,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;CAClB;AACD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,qBAAqB,CAAC;CAC/B;AAED,MAAM,WAAW,uBAAuB;IACtC,eAAe,EAAE,IAAI,EAAE,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AACD,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,uBAAuB,CAAC;CACjC;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,EAAE,MAAM,CAAC;CAClB;AACD,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,6BAA6B,CAAC;CACvC;AAED,MAAM,WAAW,gCAAgC;IAC/C,eAAe,EAAE,IAAI,EAAE,CAAC;IACxB,IAAI,EAAE,IAAI,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AACD,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,gCAAgC,CAAC;CAC1C"} -------------------------------------------------------------------------------- /dist/types/options.d.ts: -------------------------------------------------------------------------------- 1 | export interface Text { 2 | /** 3 | * Browse button text 4 | * 5 | * @default "Browse" 6 | */ 7 | browse?: string; 8 | /** 9 | * Placeholder text 10 | * 11 | * @default "Choose file..." 12 | */ 13 | chooseFile?: string; 14 | /** 15 | * Main input label text 16 | * 17 | * @default "Upload" 18 | */ 19 | label?: string; 20 | /** 21 | * Count descriptor text. Defaults to `${ n } files selected`. 22 | * 23 | * @default "files selected" 24 | */ 25 | selectedCount?: string; 26 | } 27 | export interface Images { 28 | /** 29 | * Background image for image grid 30 | * 31 | * @default DEFAULT_BACKGROUND_IMAGE 32 | */ 33 | backgroundImage?: string; 34 | /** 35 | * Placeholder image 36 | * 37 | * @default DEFAULT_BASE_IMAGE 38 | */ 39 | baseImage?: string; 40 | /** 41 | * Alternate file upload image 42 | * 43 | * @default DEFAULT_SUCCESS_FILE_ALT_IMAGE 44 | */ 45 | successFileAltImage?: string; 46 | /** 47 | * PDF upload image 48 | * 49 | * @default DEFAULT_SUCCESS_PDF_IMAGE 50 | */ 51 | successPdfImage?: string; 52 | /** 53 | * Video upload image 54 | * 55 | * @default DEFAULT_SUCCESS_VIDEO_IMAGE 56 | */ 57 | successVideoImage?: string; 58 | } 59 | export type PresetFiles = string[]; 60 | /** 61 | * Options to customize the library 62 | */ 63 | export interface Options { 64 | /** 65 | * Type of files to accept in your input 66 | * 67 | * @default '*' 68 | */ 69 | accept?: HTMLInputElement['accept']; 70 | /** 71 | * Configurable images for the library 72 | */ 73 | images?: Images; 74 | /** 75 | * Set a maximum number of files you'd like the component to deal with. Must be `> 0` if set. By default there is no limit. 76 | * 77 | * @default 0 78 | */ 79 | maxFileCount?: number; 80 | /** 81 | * Set to `true` if you want to allow the user to selected multiple images. Will use grid view in the image preview if set. 82 | * 83 | * @default false 84 | */ 85 | multiple?: boolean; 86 | /** 87 | * Provide an array of image paths to be automatically uploaded and displayed on page load (can be images hosted on server or URLs) 88 | * 89 | * @default [] 90 | */ 91 | presetFiles?: PresetFiles; 92 | /** 93 | * Show a delete button on images in the grid 94 | * 95 | * @default true 96 | */ 97 | showDeleteButtonOnImages?: boolean; 98 | /** 99 | * Configurable text for the library 100 | */ 101 | text?: Text; 102 | } 103 | export type RequiredOptions = Required & { 104 | images: Required; 105 | text: Required; 106 | }; 107 | //# sourceMappingURL=options.d.ts.map -------------------------------------------------------------------------------- /dist/types/options.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../../src/types/options.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,IAAI;IACnB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,MAAM;IACrB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,MAAM,WAAW,GAAG,MAAM,EAAE,CAAC;AAEnC;;GAEG;AACH,MAAM,WAAW,OAAO;IACtB;;;;OAIG;IACH,MAAM,CAAC,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACpC;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;;OAIG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC;;OAEG;IACH,IAAI,CAAC,EAAE,IAAI,CAAC;CACb;AAED,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG;IAChD,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACzB,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;CACtB,CAAC"} -------------------------------------------------------------------------------- /dist/utils/file.d.ts: -------------------------------------------------------------------------------- 1 | export declare const generateUniqueId: () => string; 2 | //# sourceMappingURL=file.d.ts.map -------------------------------------------------------------------------------- /dist/utils/file.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../../src/utils/file.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,cAA4C,CAAC"} -------------------------------------------------------------------------------- /docs/assets/custom-image-de30ffc0.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /docs/assets/index-0a8e647e.css: -------------------------------------------------------------------------------- 1 | html{min-height:100%;position:relative}body{color:#333;font-family:Montserrat,sans-serif;font-size:16px;height:100%;line-height:1.5;margin:0;width:100%}a{color:#3157b3}a:hover{color:#3f7ebd}pre{background-color:#edede8;border-radius:4px;color:#333;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:14px;padding:8px 16px}.header-container{align-items:center;display:flex;flex-direction:column;margin-bottom:16px}.header-container .content{font-size:2.5rem;margin-bottom:8px;text-align:center}.header-container .code{margin-bottom:8px}.demo-upload-container{align-items:center;display:flex;flex-direction:column;gap:32px;justify-content:center;margin-bottom:32px}@media (min-width: 992px){.demo-upload-container{flex-direction:row;gap:64px}}.demo-info-container{align-items:center;background:#3157b3;border-radius:4px;color:#fff;display:flex;flex-direction:column;gap:12px;justify-content:center;margin:0 auto 16px;padding:8px 12px;width:300px}.demo-info-container a{color:#fff}.demo-info-container a:hover{color:#e6e6e6}.github-corner:hover .octo-arm{animation:octocat-wave .56s ease-in-out}@keyframes octocat-wave{0%,to{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width: 500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave .56s ease-in-out}}.custom-file-container{box-sizing:border-box;position:relative;display:block}.custom-file-container *{box-sizing:border-box}.custom-file-container .label-container{align-items:center;display:flex;justify-content:space-between;margin-bottom:4px}.custom-file-container .clear-button{color:#333;font-size:26px;height:26px;line-height:26px;text-decoration:none;transition:color .2s ease-in-out}.custom-file-container .clear-button:hover{color:#777}.custom-file-container .input-container{display:inline-block;height:40px;margin-bottom:8px;position:relative;width:100%}.custom-file-container .input-container:hover{cursor:pointer}.custom-file-container .input-hidden{height:40px;margin:0;max-width:100%;min-width:300px;opacity:0}.custom-file-container .input-visible{background-clip:padding-box;background-color:#fff;border-radius:4px;border:1px solid #c0c0af;color:#333;height:40px;left:0;line-height:1.5;overflow:hidden;padding:8px 12px;position:absolute;right:0;top:0;-webkit-user-select:none;user-select:none;z-index:5}.custom-file-container .browse-button{background-color:#edede8;border-left:1px solid #c0c0af;color:#333;display:block;height:38px;padding:8px 12px;position:absolute;right:0;top:0;z-index:6}.custom-file-container .image-preview{background-color:#edede8;background-position:center center;background-repeat:no-repeat;background-size:cover;border-radius:4px;height:250px;overflow:auto;padding:4px;transition:background .2s ease-in-out;width:100%}.custom-file-container .image-preview-item{background-position:center center;background-repeat:no-repeat;background-size:cover;border-radius:4px;box-shadow:0 4px 10px #33333340;float:left;height:90px;margin:1.858736059%;position:relative;transition:background .2s ease-in-out,opacity .2s ease-in-out;width:29.615861214%}.custom-file-container .image-preview-item.multi-item-clear-animation{opacity:0}.custom-file-container .image-preview-item-clear{background:#edede8;border-radius:50%;box-shadow:0 4px 10px #33333340;height:20px;left:-6px;margin-top:-6px;position:absolute;text-align:center;transition:background .2s ease-in-out,color .2s ease-in-out;width:20px}.custom-file-container .image-preview-item-clear:hover{background:#e2e2da;cursor:pointer}.custom-file-container .image-preview-item-clear-icon{color:#333;display:block;margin-top:-2px} 2 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | file-upload-with-preview | John Datserakis 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 41 | 42 | 43 |
44 |

file-upload-with-preview

45 |
46 |
yarn add file-upload-with-preview
47 |
48 | View the source code on GitHub 51 |
52 |
53 | 54 | 55 |
56 |
57 |
58 |
59 | 60 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /docs/typedoc/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /docs/typedoc/assets/highlight.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --light-hl-0: #795E26; 3 | --dark-hl-0: #DCDCAA; 4 | --light-hl-1: #000000; 5 | --dark-hl-1: #D4D4D4; 6 | --light-hl-2: #A31515; 7 | --dark-hl-2: #CE9178; 8 | --light-hl-3: #800000; 9 | --dark-hl-3: #808080; 10 | --light-hl-4: #800000; 11 | --dark-hl-4: #569CD6; 12 | --light-hl-5: #E50000; 13 | --dark-hl-5: #9CDCFE; 14 | --light-hl-6: #0000FF; 15 | --dark-hl-6: #CE9178; 16 | --light-hl-7: #000000FF; 17 | --dark-hl-7: #D4D4D4; 18 | --light-hl-8: #AF00DB; 19 | --dark-hl-8: #C586C0; 20 | --light-hl-9: #001080; 21 | --dark-hl-9: #9CDCFE; 22 | --light-hl-10: #0000FF; 23 | --dark-hl-10: #569CD6; 24 | --light-hl-11: #0070C1; 25 | --dark-hl-11: #4FC1FF; 26 | --light-hl-12: #267F99; 27 | --dark-hl-12: #4EC9B0; 28 | --light-hl-13: #008000; 29 | --dark-hl-13: #6A9955; 30 | --light-code-background: #FFFFFF; 31 | --dark-code-background: #1E1E1E; 32 | } 33 | 34 | @media (prefers-color-scheme: light) { :root { 35 | --hl-0: var(--light-hl-0); 36 | --hl-1: var(--light-hl-1); 37 | --hl-2: var(--light-hl-2); 38 | --hl-3: var(--light-hl-3); 39 | --hl-4: var(--light-hl-4); 40 | --hl-5: var(--light-hl-5); 41 | --hl-6: var(--light-hl-6); 42 | --hl-7: var(--light-hl-7); 43 | --hl-8: var(--light-hl-8); 44 | --hl-9: var(--light-hl-9); 45 | --hl-10: var(--light-hl-10); 46 | --hl-11: var(--light-hl-11); 47 | --hl-12: var(--light-hl-12); 48 | --hl-13: var(--light-hl-13); 49 | --code-background: var(--light-code-background); 50 | } } 51 | 52 | @media (prefers-color-scheme: dark) { :root { 53 | --hl-0: var(--dark-hl-0); 54 | --hl-1: var(--dark-hl-1); 55 | --hl-2: var(--dark-hl-2); 56 | --hl-3: var(--dark-hl-3); 57 | --hl-4: var(--dark-hl-4); 58 | --hl-5: var(--dark-hl-5); 59 | --hl-6: var(--dark-hl-6); 60 | --hl-7: var(--dark-hl-7); 61 | --hl-8: var(--dark-hl-8); 62 | --hl-9: var(--dark-hl-9); 63 | --hl-10: var(--dark-hl-10); 64 | --hl-11: var(--dark-hl-11); 65 | --hl-12: var(--dark-hl-12); 66 | --hl-13: var(--dark-hl-13); 67 | --code-background: var(--dark-code-background); 68 | } } 69 | 70 | :root[data-theme='light'] { 71 | --hl-0: var(--light-hl-0); 72 | --hl-1: var(--light-hl-1); 73 | --hl-2: var(--light-hl-2); 74 | --hl-3: var(--light-hl-3); 75 | --hl-4: var(--light-hl-4); 76 | --hl-5: var(--light-hl-5); 77 | --hl-6: var(--light-hl-6); 78 | --hl-7: var(--light-hl-7); 79 | --hl-8: var(--light-hl-8); 80 | --hl-9: var(--light-hl-9); 81 | --hl-10: var(--light-hl-10); 82 | --hl-11: var(--light-hl-11); 83 | --hl-12: var(--light-hl-12); 84 | --hl-13: var(--light-hl-13); 85 | --code-background: var(--light-code-background); 86 | } 87 | 88 | :root[data-theme='dark'] { 89 | --hl-0: var(--dark-hl-0); 90 | --hl-1: var(--dark-hl-1); 91 | --hl-2: var(--dark-hl-2); 92 | --hl-3: var(--dark-hl-3); 93 | --hl-4: var(--dark-hl-4); 94 | --hl-5: var(--dark-hl-5); 95 | --hl-6: var(--dark-hl-6); 96 | --hl-7: var(--dark-hl-7); 97 | --hl-8: var(--dark-hl-8); 98 | --hl-9: var(--dark-hl-9); 99 | --hl-10: var(--dark-hl-10); 100 | --hl-11: var(--dark-hl-11); 101 | --hl-12: var(--dark-hl-12); 102 | --hl-13: var(--dark-hl-13); 103 | --code-background: var(--dark-code-background); 104 | } 105 | 106 | .hl-0 { color: var(--hl-0); } 107 | .hl-1 { color: var(--hl-1); } 108 | .hl-2 { color: var(--hl-2); } 109 | .hl-3 { color: var(--hl-3); } 110 | .hl-4 { color: var(--hl-4); } 111 | .hl-5 { color: var(--hl-5); } 112 | .hl-6 { color: var(--hl-6); } 113 | .hl-7 { color: var(--hl-7); } 114 | .hl-8 { color: var(--hl-8); } 115 | .hl-9 { color: var(--hl-9); } 116 | .hl-10 { color: var(--hl-10); } 117 | .hl-11 { color: var(--hl-11); } 118 | .hl-12 { color: var(--hl-12); } 119 | .hl-13 { color: var(--hl-13); } 120 | pre, code { background: var(--code-background); } 121 | -------------------------------------------------------------------------------- /docs/typedoc/enums/Events.html: -------------------------------------------------------------------------------- 1 | Events | file-upload-with-preview
2 |
3 | 10 |
11 |
12 |
13 |
14 | 17 |

Enumeration Events

20 |
21 |
22 |
23 | 24 |
25 |
26 |

Enumeration Members

27 |
32 |
33 |

Enumeration Members

34 |
35 | 36 |
CLEAR_BUTTON_CLICKED: "fileUploadWithPreview:clearButtonClicked"
39 |
40 | 41 |
IMAGE_ADDED: "fileUploadWithPreview:imagesAdded"
44 |
45 | 46 |
IMAGE_DELETED: "fileUploadWithPreview:imageDeleted"
49 |
50 | 51 |
IMAGE_MULTI_ITEM_CLICKED: "fileUploadWithPreview:imageMultiItemClicked"
54 |
83 |
84 |

Generated using TypeDoc

85 |
-------------------------------------------------------------------------------- /docs/typedoc/interfaces/ClearButtonClickedEvent.html: -------------------------------------------------------------------------------- 1 | ClearButtonClickedEvent | file-upload-with-preview
2 |
3 | 10 |
11 |
12 |
13 |
14 | 17 |

Interface ClearButtonClickedEvent

18 |
19 |

Hierarchy

20 |
    21 |
  • ClearButtonClickedEvent
24 |
25 |
26 |
27 | 28 |
29 |
30 |

Properties

31 |
detail 32 |
33 |
34 |

Properties

35 |
36 | 37 |
40 |
66 |
67 |

Generated using TypeDoc

68 |
-------------------------------------------------------------------------------- /docs/typedoc/interfaces/ClearButtonClickedEventDetail.html: -------------------------------------------------------------------------------- 1 | ClearButtonClickedEventDetail | file-upload-with-preview
2 |
3 | 10 |
11 |
12 |
13 |
14 | 17 |

Interface ClearButtonClickedEventDetail

18 |
19 |

Hierarchy

20 |
    21 |
  • ClearButtonClickedEventDetail
24 |
25 |
26 |
27 | 28 |
29 |
30 |

Properties

31 |
uploadId 32 |
33 |
34 |

Properties

35 |
36 | 37 |
uploadId: string
40 |
66 |
67 |

Generated using TypeDoc

68 |
-------------------------------------------------------------------------------- /docs/typedoc/interfaces/ImageAddedEvent.html: -------------------------------------------------------------------------------- 1 | ImageAddedEvent | file-upload-with-preview
2 |
3 | 10 |
11 |
12 |
13 |
14 | 17 |

Interface ImageAddedEvent

18 |
19 |

Hierarchy

20 |
    21 |
  • ImageAddedEvent
24 |
25 |
26 |
27 | 28 |
29 |
30 |

Properties

31 |
detail 32 |
33 |
34 |

Properties

35 |
36 | 37 |
40 |
66 |
67 |

Generated using TypeDoc

68 |
-------------------------------------------------------------------------------- /docs/typedoc/interfaces/ImageAddedEventDetail.html: -------------------------------------------------------------------------------- 1 | ImageAddedEventDetail | file-upload-with-preview
2 |
3 | 10 |
11 |
12 |
13 |
14 | 17 |

Interface ImageAddedEventDetail

18 |
19 |

Hierarchy

20 |
    21 |
  • ImageAddedEventDetail
24 |
25 |
26 |
27 | 28 |
29 |
30 |

Properties

31 |
36 |
37 |

Properties

38 |
39 | 40 |
addedFilesCount: number
43 |
44 | 45 |
cachedFileArray: File[]
48 |
49 | 50 |
files: File[] | FileList
53 |
54 | 55 |
uploadId: string
58 |
87 |
88 |

Generated using TypeDoc

89 |
-------------------------------------------------------------------------------- /docs/typedoc/interfaces/ImageDeletedEvent.html: -------------------------------------------------------------------------------- 1 | ImageDeletedEvent | file-upload-with-preview
2 |
3 | 10 |
11 |
12 |
13 |
14 | 17 |

Interface ImageDeletedEvent

18 |
19 |

Hierarchy

20 |
    21 |
  • ImageDeletedEvent
24 |
25 |
26 |
27 | 28 |
29 |
30 |

Properties

31 |
detail 32 |
33 |
34 |

Properties

35 |
36 | 37 |
40 |
66 |
67 |

Generated using TypeDoc

68 |
-------------------------------------------------------------------------------- /docs/typedoc/interfaces/ImageDeletedEventDetail.html: -------------------------------------------------------------------------------- 1 | ImageDeletedEventDetail | file-upload-with-preview
2 |
3 | 10 |
11 |
12 |
13 |
14 | 17 |

Interface ImageDeletedEventDetail

18 |
19 |

Hierarchy

20 |
    21 |
  • ImageDeletedEventDetail
24 |
25 |
26 |
27 | 28 |
29 |
30 |

Properties

31 |
36 |
37 |

Properties

38 |
39 | 40 |
cachedFileArray: File[]
43 |
44 | 45 |
currentFileCount: number
48 |
49 | 50 |
index: number
53 |
54 | 55 |
uploadId: string
58 |
87 |
88 |

Generated using TypeDoc

89 |
-------------------------------------------------------------------------------- /docs/typedoc/interfaces/ImageMultiItemClickedEvent.html: -------------------------------------------------------------------------------- 1 | ImageMultiItemClickedEvent | file-upload-with-preview
2 |
3 | 10 |
11 |
12 |
13 |
14 | 17 |

Interface ImageMultiItemClickedEvent

18 |
19 |

Hierarchy

20 |
    21 |
  • ImageMultiItemClickedEvent
24 |
25 |
26 |
27 | 28 |
29 |
30 |

Properties

31 |
detail 32 |
33 |
34 |

Properties

35 |
36 | 37 |
40 |
66 |
67 |

Generated using TypeDoc

68 |
-------------------------------------------------------------------------------- /docs/typedoc/interfaces/ImageMultiItemClickedEventDetail.html: -------------------------------------------------------------------------------- 1 | ImageMultiItemClickedEventDetail | file-upload-with-preview
2 |
3 | 10 |
11 |
12 |
13 |
14 | 17 |

Interface ImageMultiItemClickedEventDetail

18 |
19 |

Hierarchy

20 |
    21 |
  • ImageMultiItemClickedEventDetail
24 |
25 |
26 |
27 | 28 |
29 |
30 |

Properties

31 |
cachedFileArray 32 | file 33 | index 34 | uploadId 35 |
36 |
37 |

Properties

38 |
39 | 40 |
cachedFileArray: File[]
43 |
44 | 45 |
file: File
48 |
49 | 50 |
index: number
53 |
54 | 55 |
uploadId: string
58 |
87 |
88 |

Generated using TypeDoc

89 |
-------------------------------------------------------------------------------- /docs/typedoc/interfaces/Text.html: -------------------------------------------------------------------------------- 1 | Text | file-upload-with-preview
2 |
3 | 10 |
11 |
12 |
13 |
14 | 17 |

Interface Text

18 |
19 |

Hierarchy

20 |
    21 |
  • Text
24 |
25 |
26 |
27 | 28 |
29 |
30 |

Properties

31 |
36 |
37 |

Properties

38 |
39 | 40 |
browse?: string
41 |

Browse button text

42 | 43 |

Default

"Browse"

44 |
47 |
48 | 49 |
chooseFile?: string
50 |

Placeholder text

51 | 52 |

Default

"Choose file..."

53 |
56 |
57 | 58 |
label?: string
59 |

Main input label text

60 | 61 |

Default

"Upload"

62 |
65 |
66 | 67 |
selectedCount?: string
68 |

Count descriptor text. Defaults to ${ n } files selected.

69 | 70 |

Default

"files selected"

71 |
74 |
103 |
104 |

Generated using TypeDoc

105 |
-------------------------------------------------------------------------------- /example/custom-image.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /example/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndatserakis/file-upload-with-preview/c0c27abcf8368f733079ecbd131e671d811614d2/example/favicon.png -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | file-upload-with-preview | John Datserakis 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 39 | 40 | 41 |
42 |

file-upload-with-preview

43 |
44 |
yarn add file-upload-with-preview
45 |
46 | View the source code on GitHub 49 |
50 |
51 | 52 | 53 |
54 |
55 |
56 |
57 | 58 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /example/index.scss: -------------------------------------------------------------------------------- 1 | $grey: #edede8; 2 | 3 | html { 4 | min-height: 100%; 5 | position: relative; 6 | } 7 | 8 | body { 9 | color: #333; 10 | font-family: 'Montserrat', sans-serif; 11 | font-size: 16px; 12 | height: 100%; 13 | line-height: 1.5; 14 | margin: 0; 15 | width: 100%; 16 | } 17 | 18 | a { 19 | color: #3157b3; 20 | } 21 | 22 | a:hover { 23 | color: #3f7ebd; 24 | } 25 | 26 | pre { 27 | background-color: #edede8; 28 | border-radius: 4px; 29 | color: #333; 30 | font-family: SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; 31 | font-size: 14px; 32 | padding: 8px 16px; 33 | } 34 | 35 | .header-container { 36 | align-items: center; 37 | display: flex; 38 | flex-direction: column; 39 | margin-bottom: 16px; 40 | 41 | .content { 42 | font-size: 2.5rem; 43 | margin-bottom: 8px; 44 | text-align: center; 45 | } 46 | 47 | .code { 48 | margin-bottom: 8px; 49 | } 50 | } 51 | 52 | .demo-upload-container { 53 | align-items: center; 54 | display: flex; 55 | flex-direction: column; 56 | gap: 32px; 57 | justify-content: center; 58 | margin-bottom: 32px; 59 | 60 | @media (min-width: 992px) { 61 | flex-direction: row; 62 | gap: 64px; 63 | } 64 | } 65 | 66 | .demo-info-container { 67 | align-items: center; 68 | background: #3157b3; 69 | border-radius: 4px; 70 | color: white; 71 | display: flex; 72 | flex-direction: column; 73 | gap: 12px; 74 | justify-content: center; 75 | margin: 0 auto 16px; 76 | padding: 8px 12px; 77 | width: 300px; 78 | 79 | a { 80 | color: white; 81 | 82 | &:hover { 83 | color: darken(white, 10%); 84 | } 85 | } 86 | } 87 | 88 | // GitHub Banner 89 | .github-corner:hover .octo-arm { 90 | animation: octocat-wave 560ms ease-in-out; 91 | } 92 | 93 | @keyframes octocat-wave { 94 | 0%, 95 | 100% { 96 | transform: rotate(0); 97 | } 98 | 99 | 20%, 100 | 60% { 101 | transform: rotate(-25deg); 102 | } 103 | 104 | 40%, 105 | 80% { 106 | transform: rotate(10deg); 107 | } 108 | } 109 | 110 | @media (max-width: 500px) { 111 | .github-corner:hover .octo-arm { 112 | animation: none; 113 | } 114 | 115 | .github-corner .octo-arm { 116 | animation: octocat-wave 560ms ease-in-out; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /example/index.ts: -------------------------------------------------------------------------------- 1 | import './index.scss'; 2 | import '../src/index.scss'; 3 | 4 | import { 5 | ClearButtonClickedEvent, 6 | Events, 7 | FileUploadWithPreview, 8 | ImageAddedEvent, 9 | ImageDeletedEvent, 10 | ImageMultiItemClickedEvent, 11 | } from '../src/index'; 12 | import importedBaseImage from './custom-image.svg'; 13 | 14 | const firstUpload = new FileUploadWithPreview('myFirstImage'); 15 | 16 | const secondUpload = new FileUploadWithPreview('mySecondImage', { 17 | images: { 18 | baseImage: importedBaseImage, 19 | }, 20 | maxFileCount: 5, 21 | multiple: true, 22 | presetFiles: [ 23 | 'https://images.unsplash.com/photo-1557090495-fc9312e77b28?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=668&q=80', 24 | importedBaseImage, 25 | 'https://images.unsplash.com/photo-1632333650998-8842b63f5cfc?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2787&q=80', 26 | ], 27 | text: { 28 | browse: 'Choose', 29 | chooseFile: 'Take your pick...', 30 | label: 'Choose Files to Upload', 31 | }, 32 | }); 33 | 34 | const firstUploadInfoButton = document.querySelector('.upload-info-button-first'); 35 | const secondUploadInfoButton = document.querySelector('.upload-info-button-second'); 36 | 37 | if (firstUploadInfoButton) { 38 | firstUploadInfoButton.addEventListener('click', () => { 39 | console.log('First upload:', firstUpload, firstUpload.cachedFileArray); 40 | }); 41 | } 42 | 43 | if (secondUploadInfoButton) { 44 | secondUploadInfoButton.addEventListener('click', () => { 45 | console.log('Second upload:', secondUpload, secondUpload.cachedFileArray); 46 | }); 47 | } 48 | 49 | window.addEventListener(Events.IMAGE_ADDED, (e: Event) => { 50 | const { detail } = e as unknown as ImageAddedEvent; 51 | 52 | console.log('detail', detail); 53 | }); 54 | 55 | window.addEventListener(Events.IMAGE_DELETED, (e: Event) => { 56 | const { detail } = e as unknown as ImageDeletedEvent; 57 | 58 | console.log('detail', detail); 59 | }); 60 | 61 | window.addEventListener(Events.CLEAR_BUTTON_CLICKED, (e: Event) => { 62 | const { detail } = e as unknown as ClearButtonClickedEvent; 63 | 64 | console.log('detail', detail); 65 | }); 66 | 67 | window.addEventListener(Events.IMAGE_MULTI_ITEM_CLICKED, (e: Event) => { 68 | const { detail } = e as unknown as ImageMultiItemClickedEvent; 69 | 70 | console.log('detail', detail); 71 | }); 72 | -------------------------------------------------------------------------------- /globals.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' { 2 | const content: string; 3 | export default content; 4 | } 5 | -------------------------------------------------------------------------------- /jest.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: ['js', 'ts'], 3 | testEnvironment: 'jsdom', 4 | transform: { 5 | '^.+\\.(scss|less|svg|png)$': './jest/style-mock.ts', 6 | '^.+\\.[t|j]sx?$': 'ts-jest', 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /jest/style-mock.ts: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process() { 3 | return ''; 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "John Datserakis ", 3 | "bugs": { 4 | "url": "https://github.com/johndatserakis/file-upload-with-preview/issues" 5 | }, 6 | "description": "Simple file-upload utility that shows a preview of the uploaded image. Written in TypeScript. No dependencies. Works well with or without a framework.", 7 | "devDependencies": { 8 | "@types/jest": "^29.5.0", 9 | "@types/node": "^18.15.3", 10 | "@typescript-eslint/eslint-plugin": "^5.55.0", 11 | "@typescript-eslint/parser": "^5.55.0", 12 | "eslint": "^8.36.0", 13 | "eslint-config-prettier": "^8.7.0", 14 | "eslint-plugin-import": "^2.27.5", 15 | "eslint-plugin-prettier": "^4.2.1", 16 | "eslint-plugin-simple-import-sort": "^10.0.0", 17 | "jest": "^27.3.1", 18 | "prettier": "^2.8.4", 19 | "rimraf": "^4.4.0", 20 | "sass": "^1.59.3", 21 | "ts-jest": "^27.1.4", 22 | "typedoc": "^0.23.27", 23 | "typescript": "^4.9.4", 24 | "vite": "^4.0.0", 25 | "vite-plugin-dts": "^2.1.0" 26 | }, 27 | "engines": { 28 | "node": ">=10" 29 | }, 30 | "exports": { 31 | "./package.json": "./package.json", 32 | ".": { 33 | "import": "./dist/index.js", 34 | "require": "./dist/index.cjs", 35 | "types": "./dist/index.d.ts" 36 | }, 37 | "./dist/style.css": "./dist/style.css" 38 | }, 39 | "files": [ 40 | "dist" 41 | ], 42 | "keywords": [ 43 | "upload", 44 | "uploader", 45 | "preview", 46 | "image", 47 | "file", 48 | "bootstrap" 49 | ], 50 | "license": "MIT", 51 | "main": "./dist/index.js", 52 | "name": "file-upload-with-preview", 53 | "repository": { 54 | "type": "git", 55 | "url": "git+https://github.com/johndatserakis/file-upload-with-preview.git" 56 | }, 57 | "scripts": { 58 | "build": "yarn clear:dist && yarn clear:docs && yarn typescript:check-types && yarn lint:fix && yarn prettier:format && yarn test && yarn build:example && yarn build:library && yarn build:typedoc", 59 | "build:example": "vite build --config vite.config.app.ts", 60 | "build:library": "vite build --config vite.config.library.ts", 61 | "build:typedoc": "yarn typedoc", 62 | "clear:dist": "rimraf ./dist", 63 | "clear:docs": "rimraf ./docs ", 64 | "dev": "vite --config vite.config.app.ts", 65 | "lint": "eslint .", 66 | "lint:fix": "npm run lint -- --fix", 67 | "prettier:format": "prettier 'src/**/*.ts' --write", 68 | "test": "jest", 69 | "typescript:check-types": "tsc --noEmit" 70 | }, 71 | "sideEffects": false, 72 | "style": "./dist/style.css", 73 | "type": "module", 74 | "types": "./dist/index.d.ts", 75 | "version": "6.1.2" 76 | } 77 | -------------------------------------------------------------------------------- /public/file-upload-with-preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndatserakis/file-upload-with-preview/c0c27abcf8368f733079ecbd131e671d811614d2/public/file-upload-with-preview.jpg -------------------------------------------------------------------------------- /src/assets/images/background.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | background-image 7 | Created with Sketch. 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/images/base-image.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | base-image 7 | Created with Sketch. 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/images/file-alt-success.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | file-alt-success 7 | Created with Sketch. 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/images/pdf-success.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | pdf-success 7 | Created with Sketch. 8 | 9 | 10 | 11 | .PDF 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/assets/images/video-success.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | video-success 7 | Created with Sketch. 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/constants/events.ts: -------------------------------------------------------------------------------- 1 | export enum Events { 2 | IMAGE_ADDED = 'fileUploadWithPreview:imagesAdded', 3 | IMAGE_DELETED = 'fileUploadWithPreview:imageDeleted', 4 | CLEAR_BUTTON_CLICKED = 'fileUploadWithPreview:clearButtonClicked', 5 | IMAGE_MULTI_ITEM_CLICKED = 'fileUploadWithPreview:imageMultiItemClicked', 6 | } 7 | -------------------------------------------------------------------------------- /src/constants/file.ts: -------------------------------------------------------------------------------- 1 | export const UNIQUE_ID_IDENTIFIER = ':upload:'; 2 | -------------------------------------------------------------------------------- /src/constants/style.ts: -------------------------------------------------------------------------------- 1 | export const MULTI_ITEM_CLEAR_ANIMATION_CLASS = 'multi-item-clear-animation'; 2 | -------------------------------------------------------------------------------- /src/constants/text.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_CHOOSE_FILE_TEXT = 'Choose file...'; 2 | export const DEFAULT_BROWSE_TEXT = 'Browse'; 3 | export const DEFAULT_FILES_SELECTED_TEXT = 'files selected'; 4 | export const DEFAULT_LABEL_TEXT = 'Upload'; 5 | -------------------------------------------------------------------------------- /src/file-upload-with-preview.spec.ts: -------------------------------------------------------------------------------- 1 | import { DEFAULT_INITIALIZED_OBJECT_OPTIONS } from '../jest/constants/file'; 2 | import { FileUploadWithPreview } from './index'; 3 | 4 | const TEST_ID = 'myTestImage'; 5 | 6 | describe('Module Actions', () => { 7 | beforeAll(() => { 8 | document.body.innerHTML = 9 | '
'; 10 | }); 11 | 12 | it('loads module', () => { 13 | expect.assertions(1); 14 | 15 | expect(FileUploadWithPreview).toBeTruthy(); 16 | }); 17 | 18 | it('initializes default options', () => { 19 | expect.assertions(2); 20 | 21 | const upload = new FileUploadWithPreview(TEST_ID); 22 | 23 | expect(upload.uploadId).toBe(TEST_ID); 24 | expect(upload.options).toMatchObject(DEFAULT_INITIALIZED_OBJECT_OPTIONS); 25 | }); 26 | 27 | // Testing an actual file upload is tough - so we'll at least check that 28 | // the cachedFileArray can be pushed to. 29 | it('test that the cachedFileArray can be pushed to', () => { 30 | expect.assertions(2); 31 | 32 | const upload = new FileUploadWithPreview(TEST_ID); 33 | 34 | const file = new Blob([''], { type: 'image/jpeg' }); 35 | const file1 = new Blob([''], { type: 'image/jpeg' }); 36 | upload.cachedFileArray.push(file as File); 37 | upload.cachedFileArray.push(file1 as File); 38 | 39 | expect(upload.cachedFileArray.length).toBe(2); 40 | expect(upload.uploadId).toBe(TEST_ID); 41 | }); 42 | 43 | it('clears an added file when the clear button is clicked', () => { 44 | expect.assertions(3); 45 | 46 | const upload = new FileUploadWithPreview(TEST_ID); 47 | 48 | const file = new Blob([''], { type: 'image/jpeg' }); 49 | upload.cachedFileArray.push(file as File); 50 | expect(upload.cachedFileArray.length).toBe(1); 51 | 52 | const event = new Event('click', { 53 | bubbles: true, 54 | cancelable: true, 55 | }); 56 | upload.clearButton.dispatchEvent(event); 57 | 58 | expect(upload.uploadId).toBe(TEST_ID); 59 | expect(upload.cachedFileArray).toEqual([]); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /src/index.scss: -------------------------------------------------------------------------------- 1 | @function toPx($number) { 2 | @return $number + 0px; 3 | } 4 | 5 | $grey: #edede8; 6 | $font-color: #333; 7 | $white: #fff; 8 | $border-color: darken($grey, 20%); 9 | $black: #333; 10 | $hover: #777; 11 | 12 | $input-min-width: 300; 13 | $input-height: 40; 14 | $image-preview-height: 250; 15 | $border-radius: 4; 16 | $box-shadow: 0 4px 10px 0 rgba($black, 0.25); 17 | 18 | .custom-file-container { 19 | box-sizing: border-box; 20 | position: relative; 21 | display: block; 22 | 23 | * { 24 | box-sizing: border-box; 25 | } 26 | 27 | .label-container { 28 | align-items: center; 29 | display: flex; 30 | justify-content: space-between; 31 | margin-bottom: 4px; 32 | } 33 | 34 | .clear-button { 35 | color: $font-color; 36 | font-size: 26px; 37 | height: 26px; 38 | line-height: 26px; 39 | text-decoration: none; 40 | transition: color 0.2s ease-in-out; 41 | 42 | &:hover { 43 | color: $hover; 44 | } 45 | } 46 | 47 | .input-container { 48 | display: inline-block; 49 | height: 40px; 50 | margin-bottom: 8px; 51 | position: relative; 52 | width: 100%; 53 | 54 | &:hover { 55 | cursor: pointer; 56 | } 57 | } 58 | 59 | .input-hidden { 60 | height: toPx($input-height); 61 | margin: 0; 62 | max-width: 100%; 63 | min-width: toPx($input-min-width); 64 | opacity: 0; 65 | } 66 | 67 | .input-visible { 68 | background-clip: padding-box; 69 | background-color: $white; 70 | border-radius: toPx($border-radius); 71 | border: 1px solid $border-color; 72 | color: $font-color; 73 | height: toPx($input-height); 74 | left: 0; 75 | line-height: 1.5; 76 | overflow: hidden; 77 | padding: 8px 12px; 78 | position: absolute; 79 | right: 0; 80 | top: 0; 81 | user-select: none; 82 | z-index: 5; 83 | } 84 | 85 | .browse-button { 86 | background-color: $grey; 87 | border-left: 1px solid $border-color; 88 | color: $font-color; 89 | display: block; 90 | height: toPx($input-height - 2); 91 | padding: 8px 12px; 92 | position: absolute; 93 | right: 0; 94 | top: 0; 95 | z-index: 6; 96 | } 97 | 98 | .image-preview { 99 | background-color: $grey; 100 | background-position: center center; 101 | background-repeat: no-repeat; 102 | background-size: cover; 103 | border-radius: toPx($border-radius); 104 | height: toPx($image-preview-height); 105 | overflow: auto; 106 | padding: 4px; 107 | transition: background 0.2s ease-in-out; 108 | width: 100%; 109 | } 110 | 111 | .image-preview-item { 112 | background-position: center center; 113 | background-repeat: no-repeat; 114 | background-size: cover; 115 | border-radius: toPx($border-radius); 116 | box-shadow: $box-shadow; 117 | float: left; 118 | height: 90px; 119 | margin: 1.858736059%; 120 | position: relative; 121 | transition: background 0.2s ease-in-out, opacity 0.2s ease-in-out; 122 | width: 29.615861214%; 123 | 124 | &.multi-item-clear-animation { 125 | opacity: 0; 126 | } 127 | } 128 | 129 | .image-preview-item-clear { 130 | background: $grey; 131 | border-radius: 50%; 132 | box-shadow: $box-shadow; 133 | height: 20px; 134 | left: -6px; 135 | margin-top: -6px; 136 | position: absolute; 137 | text-align: center; 138 | transition: background 0.2s ease-in-out, color 0.2s ease-in-out; 139 | width: 20px; 140 | 141 | &:hover { 142 | background: darken($grey, 5%); 143 | cursor: pointer; 144 | } 145 | } 146 | 147 | .image-preview-item-clear-icon { 148 | color: $font-color; 149 | display: block; 150 | margin-top: -2px; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import './index.scss'; // Importing the CSS allows it to be exported in the build 2 | 3 | // Constants 4 | export * from './constants/events'; 5 | export * from './constants/file'; 6 | export * from './constants/images'; 7 | export * from './constants/style'; 8 | export * from './constants/text'; 9 | 10 | // Types 11 | export * from './types/events'; 12 | export * from './types/options'; 13 | 14 | // Lib 15 | export * from './file-upload-with-preview'; 16 | -------------------------------------------------------------------------------- /src/types/events.ts: -------------------------------------------------------------------------------- 1 | export interface ImageAddedEventDetail { 2 | addedFilesCount: number; 3 | cachedFileArray: File[]; 4 | files: FileList | File[]; 5 | uploadId: string; 6 | } 7 | export interface ImageAddedEvent { 8 | detail: ImageAddedEventDetail; 9 | } 10 | 11 | export interface ImageDeletedEventDetail { 12 | cachedFileArray: File[]; 13 | currentFileCount: number; 14 | index: number; 15 | uploadId: string; 16 | } 17 | export interface ImageDeletedEvent { 18 | detail: ImageDeletedEventDetail; 19 | } 20 | 21 | export interface ClearButtonClickedEventDetail { 22 | uploadId: string; 23 | } 24 | export interface ClearButtonClickedEvent { 25 | detail: ClearButtonClickedEventDetail; 26 | } 27 | 28 | export interface ImageMultiItemClickedEventDetail { 29 | cachedFileArray: File[]; 30 | file: File; 31 | index: number; 32 | uploadId: string; 33 | } 34 | export interface ImageMultiItemClickedEvent { 35 | detail: ImageMultiItemClickedEventDetail; 36 | } 37 | -------------------------------------------------------------------------------- /src/types/options.ts: -------------------------------------------------------------------------------- 1 | export interface Text { 2 | /** 3 | * Browse button text 4 | * 5 | * @default "Browse" 6 | */ 7 | browse?: string; 8 | /** 9 | * Placeholder text 10 | * 11 | * @default "Choose file..." 12 | */ 13 | chooseFile?: string; 14 | /** 15 | * Main input label text 16 | * 17 | * @default "Upload" 18 | */ 19 | label?: string; 20 | /** 21 | * Count descriptor text. Defaults to `${ n } files selected`. 22 | * 23 | * @default "files selected" 24 | */ 25 | selectedCount?: string; 26 | } 27 | 28 | export interface Images { 29 | /** 30 | * Background image for image grid 31 | * 32 | * @default DEFAULT_BACKGROUND_IMAGE 33 | */ 34 | backgroundImage?: string; 35 | /** 36 | * Placeholder image 37 | * 38 | * @default DEFAULT_BASE_IMAGE 39 | */ 40 | baseImage?: string; 41 | /** 42 | * Alternate file upload image 43 | * 44 | * @default DEFAULT_SUCCESS_FILE_ALT_IMAGE 45 | */ 46 | successFileAltImage?: string; 47 | /** 48 | * PDF upload image 49 | * 50 | * @default DEFAULT_SUCCESS_PDF_IMAGE 51 | */ 52 | successPdfImage?: string; 53 | /** 54 | * Video upload image 55 | * 56 | * @default DEFAULT_SUCCESS_VIDEO_IMAGE 57 | */ 58 | successVideoImage?: string; 59 | } 60 | 61 | export type PresetFiles = string[]; 62 | 63 | /** 64 | * Options to customize the library 65 | */ 66 | export interface Options { 67 | /** 68 | * Type of files to accept in your input 69 | * 70 | * @default '*' 71 | */ 72 | accept?: HTMLInputElement['accept']; 73 | /** 74 | * Configurable images for the library 75 | */ 76 | images?: Images; 77 | /** 78 | * Set a maximum number of files you'd like the component to deal with. Must be `> 0` if set. By default there is no limit. 79 | * 80 | * @default 0 81 | */ 82 | maxFileCount?: number; 83 | /** 84 | * Set to `true` if you want to allow the user to selected multiple images. Will use grid view in the image preview if set. 85 | * 86 | * @default false 87 | */ 88 | multiple?: boolean; 89 | /** 90 | * Provide an array of image paths to be automatically uploaded and displayed on page load (can be images hosted on server or URLs) 91 | * 92 | * @default [] 93 | */ 94 | presetFiles?: PresetFiles; 95 | /** 96 | * Show a delete button on images in the grid 97 | * 98 | * @default true 99 | */ 100 | showDeleteButtonOnImages?: boolean; 101 | /** 102 | * Configurable text for the library 103 | */ 104 | text?: Text; 105 | } 106 | 107 | export type RequiredOptions = Required & { 108 | images: Required; 109 | text: Required; 110 | }; 111 | -------------------------------------------------------------------------------- /src/utils/file.ts: -------------------------------------------------------------------------------- 1 | export const generateUniqueId = () => Math.random().toString(16).slice(2); 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "declaration": true, 5 | "declarationDir": "dist/types", 6 | "declarationMap": true, 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "module": "CommonJS", 10 | "moduleResolution": "node", 11 | "noFallthroughCasesInSwitch": true, 12 | "noImplicitAny": true, 13 | "noImplicitReturns": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "sourceMap": true, 17 | "strict": true, 18 | "target": "ES6", 19 | "types": ["vite/client", "jest", "node"] 20 | }, 21 | "include": ["src/**/*"], 22 | "exclude": ["node_modules", "dist", "docs"], 23 | "files": ["example/index.ts"] 24 | } 25 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": ["src/index.ts"], 3 | "out": "docs/typedoc" 4 | } 5 | -------------------------------------------------------------------------------- /vite.config.app.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | 3 | export default defineConfig({ 4 | base: '/file-upload-with-preview/', // For GitHub docs support 5 | build: { 6 | outDir: '../docs', // Actual root "docs" folder because we're in "root" context here 7 | }, 8 | root: 'example', 9 | }); 10 | -------------------------------------------------------------------------------- /vite.config.library.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | import { defineConfig } from 'vite'; 3 | import dts from 'vite-plugin-dts'; 4 | 5 | /** 6 | * Vite Library Mode: https://vitejs.dev/guide/build.html#library-mode 7 | */ 8 | export default defineConfig({ 9 | build: { 10 | lib: { 11 | entry: resolve(__dirname, 'src/index.ts'), 12 | fileName: 'index', 13 | formats: ['es', 'cjs', 'iife'], 14 | name: 'FileUploadWithPreview', 15 | }, 16 | outDir: './dist', 17 | rollupOptions: { 18 | external: [], // 'react', 'vue' 19 | output: { 20 | globals: {}, // react: 'React' 21 | }, 22 | }, 23 | }, 24 | plugins: [dts()], 25 | }); 26 | --------------------------------------------------------------------------------