Skip to content

Commit

Permalink
feat(limel-file): add component
Browse files Browse the repository at this point in the history
fix #303
  • Loading branch information
BregenzerK authored and adrianschmidt committed Sep 10, 2019
1 parent 7178ed2 commit 05e4926
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 0 deletions.
13 changes: 13 additions & 0 deletions src/components/file/file.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
name: File
route: /file
menu: Components
---

# File

<limel-props name="limel-file" />

## Example

<limel-example name="limel-example-file" />
165 changes: 165 additions & 0 deletions src/components/file/file.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { MDCTextField } from '@lime-material-16px/textfield';
import { Component, Element, Event, EventEmitter, Prop } from '@stencil/core';
import { createRandomString } from '../../util/random-string';
import { Chip } from '../chip-set/chip.types';
import { FileInfo } from './file.types';

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 {
/**
* The selected file.
*/
@Prop()
public value: FileInfo;

/**
* The input label.
*/
@Prop({ reflectToAttr: true })
public label: string;

/**
* Set to `true` to indicate that the field is required.
* Defaults to `false`
*/
@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<FileInfo>;

@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,
text: this.value.filename,
id: this.value.id,
},
]
: [];
return [
<input
id={this.fileInputId}
type="file"
onChange={this.handleFileChange}
hidden={true}
/>,
<limel-chip-set
disabled={this.disabled}
label={this.label}
required={this.required}
type="input"
value={chipArray}
onFocus={this.handleFileSelection}
onChange={this.handleChipSetChange}
onInteract={this.preventAndStop}
onDrop={this.handleFileDrop}
onDragEnter={this.preventAndStop}
onDragOver={this.preventAndStop}
/>,
];
}

private handleFileSelection(event: FocusEvent) {
event.stopPropagation();
if (!this.value) {
this.fileInput.click();
}
}

private handleFileChange(event: Event) {
if (this.fileInput.files.length > 0) {
event.stopPropagation();
this.handleFile(this.fileInput.files[0]);
}
}

private handleFile(file) {
const limeFile: FileInfo = {
id: createRandomString(),
filename: file.name,
contentType: file.type,
size: file.size,
};
this.change.emit(limeFile);
this.chipSet.blur();
this.mdcTextField.valid = true;
}

private handleChipSetChange(event: CustomEvent) {
event.stopPropagation();
const file = !event.detail.length ? event.detail[0] : null;
this.chipSet.blur();
if (!file) {
this.fileInput.value = '';
this.change.emit(file);
if (this.required) {
this.mdcTextField.valid = false;
}
}
}

private handleFileDrop(event: DragEvent) {
this.preventAndStop(event);
const dataTransfer = event.dataTransfer;
this.handleFile(dataTransfer.files[0]);
}

private preventAndStop(event: Event) {
event.stopPropagation();
event.preventDefault();
}
}
30 changes: 30 additions & 0 deletions src/components/file/file.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export interface FileInfo {
/**
* ID of the file. Must be unique.
*/
id: number | string;
/**
* Name of file.
*/
filename: string;

/**
* Extension of file.
*/
extension?: string;

/**
* Content type of file.
*/
contentType?: string;

/**
* Date of last modification.
*/
lastModified?: Date;

/**
* Size of file.
*/
size?: number;
}
44 changes: 44 additions & 0 deletions src/examples/file/file.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Component, State } from '@stencil/core';
import { FileInfo } from '../../components/file/file.types';

@Component({
tag: 'limel-example-file',
shadow: true,
})
export class FileExample {
@State()
private value: FileInfo = { filename: 'bla.jpg', id: 123 };

@State()
private required = false;

constructor() {
this.handleChange = this.handleChange.bind(this);
this.toggleRequired = this.toggleRequired.bind(this);
}

public render() {
return [
<limel-switch
label="Toggle required"
value={this.required}
onChange={this.toggleRequired}
/>,
<limel-file
label="File"
value={this.value}
required={this.required}
onChange={this.handleChange}
/>,
];
}

private handleChange(event) {
this.value = event.detail;
console.log('onChange', this.value);
}

private toggleRequired() {
this.required = !this.required;
}
}

0 comments on commit 05e4926

Please sign in to comment.