Skip to content

Commit 28c1ca2

Browse files
authored
Merge pull request #3171 from SMassola/issue-3169
feat: add configurability to file name and actions
2 parents f83a70f + a5f0235 commit 28c1ca2

File tree

9 files changed

+349
-31
lines changed

9 files changed

+349
-31
lines changed

src/file-uploader/file-uploader.component.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,10 @@ const noop = () => { };
7373
<ng-container *ngFor="let fileItem of files">
7474
<cds-file
7575
[fileItem]="fileItem"
76-
(remove)="removeFile(fileItem)"
77-
[size]="fileItemSize">
76+
[nameTpl]="fileNameTpl"
77+
[actionsTpl]="fileActionsTpl"
78+
[size]="fileItemSize"
79+
(remove)="removeFile(fileItem)">
7880
</cds-file>
7981
</ng-container>
8082
</div>
@@ -164,6 +166,14 @@ export class FileUploader implements ControlValueAccessor {
164166
* Set to `true` to disable upload button
165167
*/
166168
@Input() disabled = false;
169+
/**
170+
* Custom template used to render the file name of uploaded files
171+
*/
172+
@Input() fileNameTpl: TemplateRef<unknown>;
173+
/**
174+
* Custom template used to render the file actions of uploaded files
175+
*/
176+
@Input() fileActionsTpl: TemplateRef<unknown>;
167177

168178
@Output() filesChange = new EventEmitter<any>();
169179

src/file-uploader/file-uploader.stories.ts

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
/* tslint:disable variable-name */
2-
31
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
4-
import { moduleMetadata, Meta } from "@storybook/angular";
5-
import { FileUploaderModule, FileUploader } from "./";
6-
import { NotificationModule } from "../notification";
2+
import { Meta, moduleMetadata } from "@storybook/angular";
73
import { ButtonModule } from "../button";
4+
import { IconModule } from "../icon";
5+
import { NotificationModule } from "../notification";
6+
import { FileUploader, FileUploaderModule } from "./";
87
import {
8+
CustomFileIconsModule,
9+
DragAndDropStory,
910
FileUploaderStory,
11+
FileUploaderWithCustomFileStory,
1012
NgModelFileUploaderStory,
11-
DragAndDropStory,
1213
ReactiveFormsStory
1314
} from "./stories";
1415

@@ -18,16 +19,19 @@ export default {
1819
moduleMetadata({
1920
declarations: [
2021
FileUploaderStory,
22+
FileUploaderWithCustomFileStory,
2123
NgModelFileUploaderStory,
2224
DragAndDropStory,
2325
ReactiveFormsStory
2426
],
2527
imports: [
28+
ButtonModule,
29+
CustomFileIconsModule,
2630
FileUploaderModule,
2731
FormsModule,
28-
ReactiveFormsModule,
32+
IconModule,
2933
NotificationModule,
30-
ButtonModule
34+
ReactiveFormsModule
3135
]
3236
})
3337
],
@@ -81,6 +85,37 @@ const Template = (args) => ({
8185
});
8286
export const Basic = Template.bind({});
8387

88+
const CustomFile = (args) => ({
89+
props: args,
90+
template: `
91+
<!--
92+
app-* components are for demo purposes only.
93+
You can create your own implementation by using the component source found at:
94+
https://github.com/IBM/carbon-components-angular/tree/master/src/file-uploader/stories/uploader-custom-file.component.ts
95+
-->
96+
<app-file-uploader-with-custom-file
97+
[title]="title"
98+
[description]="description"
99+
[buttonText]="buttonText"
100+
[buttonType]="buttonType"
101+
[accept]="accept"
102+
[multiple]="multiple"
103+
[size]="size"
104+
[fileItemSize]="fileItemSize"
105+
[disabled]="disabled">
106+
</app-file-uploader-with-custom-file>
107+
`
108+
});
109+
export const UploaderWithCustomFile = CustomFile.bind({});
110+
UploaderWithCustomFile.argTypes = {
111+
size: {
112+
control: false
113+
},
114+
buttonType: {
115+
control: false
116+
}
117+
};
118+
84119
const DragAndDropTemplate = (args) => ({
85120
props: args,
86121
template: `

src/file-uploader/file.component.ts

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import {
22
Component,
3-
Input,
4-
Output,
53
EventEmitter,
64
HostBinding,
7-
OnDestroy
5+
Input,
6+
OnDestroy,
7+
Output,
8+
TemplateRef
89
} from "@angular/core";
910

1011
import { I18n } from "carbon-components-angular/i18n";
@@ -13,43 +14,64 @@ import { FileItem } from "./file-item.interface";
1314
@Component({
1415
selector: "cds-file, ibm-file",
1516
template: `
16-
<p class="cds--file-filename" [title]="fileItem.file.name">{{fileItem.file.name}}</p>
17-
<span
18-
*ngIf="fileItem.state === 'edit'"
19-
class="cds--file__state-container">
17+
<p class="cds--file-filename" [title]="fileItem.file.name">
18+
<ng-template
19+
*ngIf="isTemplate(nameTpl); else defaultName"
20+
[ngTemplateOutlet]="nameTpl"
21+
[ngTemplateOutletContext]="{ $implicit: fileItem }">
22+
</ng-template>
23+
<ng-template #defaultName>{{ fileItem.file.name }}</ng-template>
24+
</p>
25+
<span *ngIf="fileItem.state === 'edit'" class="cds--file__state-container">
2026
<svg
2127
*ngIf="isInvalidText"
2228
cdsIcon="warning--filled"
2329
class="cds--file--invalid"
2430
size="16">
2531
</svg>
26-
<button
27-
type="button"
28-
class="cds--file-close"
29-
[attr.aria-label]="translations.REMOVE_BUTTON"
30-
tabindex="0"
31-
(click)="remove.emit()"
32-
(keyup.enter)="remove.emit()"
33-
(keyup.space)="remove.emit()">
34-
<svg cdsIcon="close" size="16"></svg>
35-
</button>
32+
<ng-template
33+
*ngIf="isTemplate(actionsTpl); else defaultActions"
34+
[ngTemplateOutlet]="actionsTpl">
35+
</ng-template>
36+
<ng-template #defaultActions>
37+
<button
38+
type="button"
39+
cdsButton="ghost"
40+
iconOnly="true"
41+
[size]="size"
42+
[attr.aria-label]="translations.REMOVE_BUTTON"
43+
(click)="remove.emit()"
44+
(keyup.enter)="remove.emit()"
45+
(keyup.space)="remove.emit()">
46+
<svg cdsIcon="trash-can" size="16"></svg>
47+
</button>
48+
</ng-template>
3649
</span>
3750
<span *ngIf="fileItem.state === 'upload'">
3851
<div class="cds--inline-loading__animation">
3952
<cds-loading size="sm"></cds-loading>
4053
</div>
4154
</span>
42-
<span *ngIf="fileItem.state === 'complete'" class="cds--file__state-container">
55+
<span
56+
*ngIf="fileItem.state === 'complete'"
57+
class="cds--file__state-container">
4358
<svg
4459
cdsIcon="checkmark--filled"
4560
size="16"
4661
class="cds--file-complete"
4762
[ariaLabel]="translations.CHECKMARK">
4863
</svg>
4964
</span>
50-
<div class="cds--form-requirement" role="alert" *ngIf="fileItem.invalid">
51-
<div class="cds--form-requirement__title">{{fileItem.invalidTitle}}</div>
52-
<p class="cds--form-requirement__supplement">{{fileItem.invalidText}}</p>
65+
<div
66+
class="cds--form-requirement"
67+
role="alert"
68+
*ngIf="fileItem.invalid">
69+
<div class="cds--form-requirement__title">
70+
{{ fileItem.invalidTitle }}
71+
</div>
72+
<p class="cds--form-requirement__supplement">
73+
{{ fileItem.invalidText }}
74+
</p>
5375
</div>
5476
`
5577
})
@@ -65,6 +87,16 @@ export class FileComponent implements OnDestroy {
6587

6688
@Input() size: "sm" | "md" | "lg" = "lg";
6789

90+
/**
91+
* A custom template for the file name
92+
*/
93+
@Input() nameTpl: TemplateRef<unknown>;
94+
95+
/**
96+
* A custom template for the available file actions
97+
*/
98+
@Input() actionsTpl: TemplateRef<unknown>;
99+
68100
@Output() remove = new EventEmitter();
69101

70102
@HostBinding("class.cds--file__selected-file") selectedFile = true;
@@ -84,8 +116,13 @@ export class FileComponent implements OnDestroy {
84116
@HostBinding("class.cds--file__selected-file--lg") get fileSizeLarge() {
85117
return this.size === "lg";
86118
}
119+
87120
constructor(protected i18n: I18n) {}
88121

122+
public isTemplate(value: unknown): boolean {
123+
return value instanceof TemplateRef;
124+
}
125+
89126
ngOnDestroy() {
90127
this.remove.emit();
91128
}

src/file-uploader/file.stories.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { Meta, moduleMetadata } from "@storybook/angular";
2+
3+
import { ButtonModule } from "../button";
4+
import { IconModule } from "../icon";
5+
import { FileComponent, FileUploaderModule } from "./";
6+
import { BasicFileStory, CustomFileIconsModule, CustomFileStory } from "./stories";
7+
8+
export default {
9+
title: "Components/File",
10+
decorators: [
11+
moduleMetadata({
12+
declarations: [BasicFileStory, CustomFileStory],
13+
imports: [
14+
ButtonModule,
15+
CustomFileIconsModule,
16+
FileUploaderModule,
17+
IconModule
18+
]
19+
})
20+
],
21+
args: {
22+
size: "md"
23+
},
24+
argTypes: {
25+
size: {
26+
options: ["sm", "md", "lg"],
27+
control: "radio"
28+
}
29+
},
30+
component: FileComponent
31+
} as Meta;
32+
33+
const BasicFileTemplate = (args) => ({
34+
props: args,
35+
size: {
36+
options: ["sm", "md", "lg"],
37+
control: "radio"
38+
},
39+
template: `
40+
<!--
41+
app-* components are for demo purposes only.
42+
You can create your own implementation by using the component source found at:
43+
https://github.com/IBM/carbon-components-angular/tree/master/src/file-uploader/stories/basic-file.component.ts
44+
-->
45+
<app-basic-file [size]="size"></app-basic-file>
46+
`
47+
});
48+
export const BasicFile = BasicFileTemplate.bind({});
49+
50+
const CustomFileTemplate = (args) => ({
51+
props: args,
52+
size: {
53+
options: ["sm", "md", "lg"],
54+
control: "radio"
55+
},
56+
template: `
57+
<!--
58+
app-* components are for demo purposes only.
59+
You can create your own implementation by using the component source found at:
60+
https://github.com/IBM/carbon-components-angular/tree/master/src/file-uploader/stories/custom-file.component.ts
61+
-->
62+
<app-custom-file [size]="size"></app-custom-file>
63+
`
64+
});
65+
export const CustomFile = CustomFileTemplate.bind({});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Component, Input } from "@angular/core";
2+
3+
@Component({
4+
selector: "app-basic-file",
5+
template: `<cds-file [size]="size" [fileItem]="fileItem"></cds-file>`
6+
})
7+
export class BasicFileStory {
8+
@Input() size = "sm";
9+
10+
fileItem = {
11+
file: new File(["foo"], "Lorem ipsum dolor sit amet.txt", { type: "text/plain" }),
12+
state: "edit"
13+
};
14+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { CommonModule } from "@angular/common";
2+
import { NgModule } from "@angular/core";
3+
import Download16 from "@carbon/icons/es/download/16";
4+
import View16 from "@carbon/icons/es/view/16";
5+
6+
import { IconModule, IconService } from "../../icon";
7+
8+
@NgModule({
9+
imports: [CommonModule, IconModule]
10+
})
11+
export class CustomFileIconsModule {
12+
constructor(private iconService: IconService) {
13+
this.iconService.registerAll([Download16, View16]);
14+
}
15+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { Component, Input } from "@angular/core";
2+
3+
@Component({
4+
selector: "app-custom-file",
5+
template: `
6+
<cds-file
7+
[size]="size"
8+
[fileItem]="fileItem"
9+
[nameTpl]="nameTpl"
10+
[actionsTpl]="actionsTpl">
11+
</cds-file>
12+
13+
<ng-template #nameTpl let-fileItem>
14+
<a href="#" cdsLink>{{ fileItem.file.name }}</a>
15+
</ng-template>
16+
17+
<ng-template #actionsTpl>
18+
<button
19+
ibmButton="ghost"
20+
iconOnly="true"
21+
aria-label="View"
22+
[size]="size">
23+
<svg ibmIcon="view" size="16"></svg>
24+
</button>
25+
<button
26+
ibmButton="ghost"
27+
iconOnly="true"
28+
aria-label="Download"
29+
[size]="size">
30+
<svg ibmIcon="download" size="16"></svg>
31+
</button>
32+
</ng-template>
33+
`,
34+
styles: []
35+
})
36+
export class CustomFileStory {
37+
@Input() size = "sm";
38+
39+
fileItem = {
40+
file: new File(["foo"], "Lorem ipsum dolor sit amet.txt", {
41+
type: "text/plain"
42+
}),
43+
state: "edit"
44+
};
45+
}

src/file-uploader/stories/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1+
export * from "./basic-file.component";
2+
export * from "./custom-file-icons.module";
3+
export * from "./custom-file.component";
14
export * from "./drag-drop.component";
5+
export * from "./uploader-custom-file.component";
26
export * from "./uploader-form.component";
37
export * from "./uploader-reactive-form.component";
48
export * from "./uploader.component";

0 commit comments

Comments
 (0)