Skip to content

Commit

Permalink
feat(file-input): add file list (advanced file picker)
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinbuhmann committed Dec 20, 2024
1 parent 38862d4 commit 919e34f
Show file tree
Hide file tree
Showing 18 changed files with 874 additions and 110 deletions.
153 changes: 153 additions & 0 deletions .storybook/stories/file-input/advanced-file-input-states.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* Copyright (c) 2016-2024 Broadcom. All Rights Reserved.
* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/

import { FormsModule } from '@angular/forms';
import { ClrFileInputModule, ClrFormLayout, ClrFormsModule } from '@clr/angular';
import { moduleMetadata, StoryContext, StoryFn, StoryObj } from '@storybook/angular';

import { clearFiles, selectFiles } from '../../../projects/angular/src/forms/file-input/file-input.helpers';

export default {
title: 'File Input/Advanced File Input States',
decorators: [
moduleMetadata({
imports: [FormsModule, ClrFormsModule, ClrFileInputModule],
}),
],
argTypes: {
// form inputs
clrLayout: { control: false },
},
};

const advancedFileInputStatesTemplate: StoryFn = args => ({
template: `
<form clrForm [clrLayout]="clrLayout">
<div>${advancedFileInputTemplateFn('Success State')}</div>
<div>${advancedFileInputTemplateFn('Empty Error State')}</div>
<div>${advancedFileInputTemplateFn('Multiple Files Success State')}</div>
<div>${advancedFileInputTemplateFn('Multiple Files Error State')}</div>
<div>${advancedFileInputTemplateFn('Multiple Files Mixed State')}</div>
<div>${advancedFileInputTemplateFn('Long Filename')}</div>
<clr-file-input-container>
<label>Disabled</label>
<input type="file" clrFileInput disabled />
</clr-file-input-container>
</form>
`,
props: { ...args },
});

function advancedFileInputTemplateFn(label: string) {
const id = `${label.toLowerCase().replace(/\s+/g, '-')}-file-input`;
const ngModel = `${label[0].toLowerCase()}${label.substring(1).replace(/\s+/g, '')}File`;

return `
<clr-file-input-container>
<label>${label}</label>
<input
id="${id}"
name="${id}"
type="file"
accept=".txt,text/plain"
multiple
required
clrFileInput
[clrMinFileSize]="50"
[clrMaxFileSize]="500"
[(ngModel)]="${ngModel}"
#${ngModel}NgModel="ngModel"
/>
<clr-control-helper>Helper text for file input control</clr-control-helper>
<clr-control-success>Success message for file input control</clr-control-success>
<clr-control-error *clrIfError="'required'">Required</clr-control-error>
<clr-control-error *ngIf="${ngModel}NgModel.invalid && !${ngModel}NgModel.control.hasError('required')">Error message for file input control</clr-control-error>
<!-- This makes this file input an "advanced" file input. -->
<clr-file-list>
<ng-template clr-file-messages let-file let-errors="errors">
<clr-file-info>Info text for {{ file.name }}</clr-file-info>
<clr-file-success>Success message for {{ file.name }}</clr-file-success>
<clr-file-error *ngIf="errors.accept">File type not accepted</clr-file-error>
<clr-file-error *ngIf="errors.minFileSize">File size too small</clr-file-error>
<clr-file-error *ngIf="errors.maxFileSize">File size too large</clr-file-error>
</ng-template>
</clr-file-list>
</clr-file-input-container>
`;
}

export const VerticalAdvancedFileInputStates: StoryObj = {
render: advancedFileInputStatesTemplate,
play: fileInputStatesPlayFn,
args: {
clrLayout: ClrFormLayout.VERTICAL,
},
};

export const HorizontalAdvancedFileInputStates: StoryObj = {
render: advancedFileInputStatesTemplate,
play: fileInputStatesPlayFn,
args: {
clrLayout: ClrFormLayout.HORIZONTAL,
},
};

export const CompactAdvancedFileInputStates: StoryObj = {
render: advancedFileInputStatesTemplate,
play: fileInputStatesPlayFn,
args: {
clrLayout: ClrFormLayout.COMPACT,
},
};

function fileInputStatesPlayFn({ canvasElement }: StoryContext) {
const successStateFileInputElement = canvasElement.querySelector<HTMLInputElement>('#success-state-file-input');
selectFiles(successStateFileInputElement, [new File(['.'.repeat(50)], 'file.txt')]);

const emptyErrorStateFileInputElement = canvasElement.querySelector<HTMLInputElement>(
'#empty-error-state-file-input'
);
selectFiles(emptyErrorStateFileInputElement, [new File([''], 'file.txt')]);
clearFiles(emptyErrorStateFileInputElement);

const multipleFilesSuccessStateFileInputElement = canvasElement.querySelector<HTMLInputElement>(
'#multiple-files-success-state-file-input'
);
selectFiles(multipleFilesSuccessStateFileInputElement, [
new File(['.'.repeat(50)], 'file-1.txt'),
new File(['.'.repeat(50)], 'file-2.txt'),
new File(['.'.repeat(50)], 'file-3.txt'),
]);

const multipleFilesErrorStateFileInputElement = canvasElement.querySelector<HTMLInputElement>(
'#multiple-files-error-state-file-input'
);
selectFiles(multipleFilesErrorStateFileInputElement, [
new File(['.'.repeat(25)], 'file-1.txt'),
new File(['.'.repeat(501)], 'file-2.txt'),
new File(['.'.repeat(50)], 'file-3.png'),
]);

const multipleFilesMixedStateFileInputElement = canvasElement.querySelector<HTMLInputElement>(
'#multiple-files-mixed-state-file-input'
);
selectFiles(multipleFilesMixedStateFileInputElement, [
new File(['.'.repeat(25)], 'file-1.txt'),
new File(['.'.repeat(50)], 'file-2.txt'),
new File(['.'.repeat(501)], 'file-3.txt'),
]);

const longFilenameFileInputElement = canvasElement.querySelector<HTMLInputElement>('#long-filename-file-input');
selectFiles(longFilenameFileInputElement, [
new File(
[''],
'long-filename-lorem-ipsum-dolor-sit-amet-consectetur-adipiscing-elit-mauris-a-ante-pharetra-hendrerit-turpis-nec-volutpat-leo-duis-consectetur-tincidunt-risus-non-lobortis.txt'
),
]);
}
75 changes: 75 additions & 0 deletions .storybook/stories/file-input/advanced-file-input.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright (c) 2016-2024 Broadcom. All Rights Reserved.
* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
* This software is released under MIT license.
* The full license information can be found in LICENSE in the root directory of this project.
*/

import { FormsModule } from '@angular/forms';
import { ClrFileInputModule, ClrFormLayout, commonStringsDefault } from '@clr/angular';
import { moduleMetadata, StoryFn, StoryObj } from '@storybook/angular';

export default {
title: 'File Input/Advanced File Input',
decorators: [
moduleMetadata({
imports: [FormsModule, ClrFileInputModule],
}),
],
argTypes: {
// inputs
clrButtonLabel: { type: 'string' },
// form inputs
clrLayout: { control: false },
},
args: {
// inputs
clrButtonLabel: commonStringsDefault.browse,
},
};

const advancedAdvancedFileInputTemplate = `
<form clrForm [clrLayout]="clrLayout">
<clr-file-input-container [clrButtonLabel]="clrButtonLabel">
<label>File</label>
<input type="file" name="file" [(ngModel)]="file" clrFileInput required multiple />
<clr-control-helper>Helper message</clr-control-helper>
<clr-control-success>Success message</clr-control-success>
<clr-control-error *clrIfError="'required'">Required</clr-control-error>
<!-- This makes this file input an "advanced" file input. -->
<clr-file-list>
<ng-template clr-file-messages let-file let-errors="errors">
<clr-file-info>Info text for {{ file.name }}</clr-file-info>
<clr-file-success>Success message for {{ file.name }}</clr-file-success>
</ng-template>
</clr-file-list>
</clr-file-input-container>
</form>
`;

const advancedAdvancedFileInputStoryFn: StoryFn = args => ({
template: advancedAdvancedFileInputTemplate,
props: { ...args },
});

export const VerticalAdvancedFileInput: StoryObj = {
render: advancedAdvancedFileInputStoryFn,
args: {
clrLayout: ClrFormLayout.VERTICAL,
},
};

export const HorizontalAdvancedFileInput: StoryObj = {
render: advancedAdvancedFileInputStoryFn,
args: {
clrLayout: ClrFormLayout.HORIZONTAL,
},
};

export const CompactAdvancedFileInput: StoryObj = {
render: advancedAdvancedFileInputStoryFn,
args: {
clrLayout: ClrFormLayout.COMPACT,
},
};
89 changes: 27 additions & 62 deletions .storybook/stories/file-input/file-input-states.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,68 +27,10 @@ export default {
const fileInputStatesTemplate: StoryFn = args => ({
template: `
<form clrForm [clrLayout]="clrLayout">
<clr-file-input-container>
<label>Success State</label>
<input
id="success-state-file-input"
type="file"
name="success-state-file"
[(ngModel)]="successStateFile"
clrFileInput
required
multiple
/>
<clr-control-helper>Helper message</clr-control-helper>
<clr-control-success>Success message</clr-control-success>
<clr-control-error *clrIfError="'required'">Required</clr-control-error>
</clr-file-input-container>
<clr-file-input-container>
<label>Error State</label>
<input
id="error-state-file-input"
type="file"
name="error-state-file"
[(ngModel)]="errorStateFile"
clrFileInput
required
multiple
/>
<clr-control-helper>Helper message</clr-control-helper>
<clr-control-error *clrIfError="'required'">Required</clr-control-error>
</clr-file-input-container>
<clr-file-input-container>
<label>Multiple Files</label>
<input
id="multiple-files-file-input"
type="file"
name="multiple-files-file"
[(ngModel)]="multipleFilesFile"
clrFileInput
required
multiple
/>
<clr-control-helper>Helper message</clr-control-helper>
<clr-control-success>Success message</clr-control-success>
<clr-control-error *clrIfError="'required'">Required</clr-control-error>
</clr-file-input-container>
<clr-file-input-container>
<label>Long Filename</label>
<input
id="long-filename-file-input"
type="file"
name="long-filename-file"
[(ngModel)]="longFilenameFile"
clrFileInput
required
multiple
/>
<clr-control-helper>Helper message</clr-control-helper>
<clr-control-success>Success message</clr-control-success>
<clr-control-error *clrIfError="'required'">Required</clr-control-error>
</clr-file-input-container>
<div>${fileInputTemplateFn('Success State')}</div>
<div>${fileInputTemplateFn('Error State')}</div>
<div>${fileInputTemplateFn('Multiple Files')}</div>
<div>${fileInputTemplateFn('Long Filename')}</div>
<clr-file-input-container>
<label>Disabled</label>
Expand All @@ -99,6 +41,29 @@ const fileInputStatesTemplate: StoryFn = args => ({
props: { ...args },
});

function fileInputTemplateFn(label: string) {
const id = `${label.toLowerCase().replace(/\s+/g, '-')}-file-input`;
const ngModel = `${label[0].toLowerCase()}${label.substring(1).replace(/\s+/g, '')}File`;

return `
<clr-file-input-container>
<label>${label}</label>
<input
id="${id}"
name="${id}"
type="file"
multiple
required
clrFileInput
[(ngModel)]="${ngModel}"
/>
<clr-control-helper>Helper message</clr-control-helper>
<clr-control-success>Success message</clr-control-success>
<clr-control-error *clrIfError="'required'">Required</clr-control-error>
</clr-file-input-container>
`;
}

export const VerticalFileInputStates: StoryObj = {
render: fileInputStatesTemplate,
play: fileInputStatesPlayFn,
Expand Down
Loading

0 comments on commit 919e34f

Please sign in to comment.