diff --git a/src/components/file/file.mdx b/src/components/file/file.mdx new file mode 100644 index 0000000000..8194dc1bbe --- /dev/null +++ b/src/components/file/file.mdx @@ -0,0 +1,13 @@ +--- +name: File +route: /file +menu: Components +--- + +# File + + + +## Example + + diff --git a/src/components/file/file.tsx b/src/components/file/file.tsx new file mode 100644 index 0000000000..af205a12fc --- /dev/null +++ b/src/components/file/file.tsx @@ -0,0 +1,142 @@ +import { MDCTextField } from '@lime-material/textfield'; +import { Component, Element, Event, EventEmitter, Prop } from '@stencil/core'; +import { createRandomString } from '../../util/random-string'; +import { Chip } from '../chip-set/chip'; + +const CHIP_SET_TAG_NAME = 'limel-chip-set'; +const DEFAULT_FILE_CHIP: Chip = { + id: null, + text: null, + removable: true, + icon: 'note', + iconColor: 'var(--lime-light-grey)', +}; + +@Component({ + tag: 'limel-file', + shadow: true, +}) +export class File { + @Prop() + public value: Chip; + + @Prop({ reflectToAttr: true }) + public label: string; + + @Prop({ reflectToAttr: true }) + public required: boolean = false; + + /** + * True if the input should be disabled + */ + @Prop({ reflectToAttr: true }) + public disabled: boolean = false; + + /** + * Dispatched when a file is selected/deselected + */ + @Event() + private change: EventEmitter; + + @Element() + private element: HTMLElement; + + private fileInput: HTMLInputElement; + private fileInputId = createRandomString(); + private chipSet; + private mdcTextField; + + constructor() { + this.handleFileSelection = this.handleFileSelection.bind(this); + this.handleFileChange = this.handleFileChange.bind(this); + this.handleChipSetChange = this.handleChipSetChange.bind(this); + this.handleFileDrop = this.handleFileDrop.bind(this); + } + + public componentDidLoad() { + this.fileInput = this.element.shadowRoot.getElementById( + this.fileInputId + ) as HTMLInputElement; + this.chipSet = this.element.shadowRoot.querySelector(CHIP_SET_TAG_NAME); + this.mdcTextField = new MDCTextField( + this.chipSet.shadowRoot.querySelector('.mdc-text-field') + ); + } + + public componentDidUnload() { + if (this.mdcTextField) { + this.mdcTextField.destroy(); + } + } + + public render() { + const chipArray = this.value + ? [{ ...DEFAULT_FILE_CHIP, ...this.value }] + : []; + return [ + , + , + ]; + } + + private handleFileSelection(event) { + event.stopPropagation(); + if (!this.value) { + this.fileInput.click(); + } + } + + private handleFileChange(event) { + if (this.fileInput.files.length > 0) { + event.stopPropagation(); + this.handleFile(this.fileInput.files[0]); + } + } + + private handleFile(file) { + this.change.emit(file); + this.chipSet.blur(); + this.mdcTextField.valid = true; + } + + private handleChipSetChange(event) { + event.stopPropagation(); + const chip = !event.detail.length ? event.detail[0] : null; + this.chipSet.blur(); + if (!chip) { + this.fileInput.value = ''; + this.change.emit(chip); + if (this.required) { + this.mdcTextField.valid = false; + } + } + } + + private handleFileDrop(event) { + this.preventAndStop(event); + const dataTransfer = event.dataTransfer; + this.handleFile(dataTransfer.files[0]); + } + + private preventAndStop(event) { + event.stopPropagation(); + event.preventDefault(); + } +} diff --git a/src/examples/file/file.tsx b/src/examples/file/file.tsx new file mode 100644 index 0000000000..721983e6fe --- /dev/null +++ b/src/examples/file/file.tsx @@ -0,0 +1,39 @@ +import { Component, State } from '@stencil/core'; +import { Chip } from '../../components/chip-set/chip'; + +@Component({ + tag: 'limel-example-file', + shadow: true, +}) +export class FileExample { + @State() + private value: Chip = { id: 'bla', text: 'bla.jpg' }; + + @State() + private required = false; + + public render() { + return [ + { + this.required = !this.required; + }} + />, + , + ]; + } + + private handleChange(event) { + console.log('onChange', event.detail); + this.value = event.detail + ? { id: event.detail.name, text: event.detail.name } + : null; + } +}