Skip to content

Commit

Permalink
feat: add prefab file list with dropzone
Browse files Browse the repository at this point in the history
  • Loading branch information
remadex committed Dec 17, 2024
1 parent ea85b32 commit d9b682c
Show file tree
Hide file tree
Showing 16 changed files with 557 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Controller from '@ember/controller';
import { action } from '@ember/object';
import { ImmerChangeset } from 'ember-immer-changeset';
import type { Owner } from '@ember/test-helpers/build-owner';

export default class DocsEmberInputValidationPrefabsFileListController extends Controller {
changeset = new ImmerChangeset({
files: [new File([], 'file.txt')],
disabled: [new File([], 'file.txt')],
error: '',
});

constructor(owner: Owner) {
super(owner);
this.changeset.addError({
message: 'This is an error message',
value: '',
originalValue: '',
key: 'error',
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,4 @@ export default class DocsEmberInputValidationPrefabsFileController extends Contr
key: 'error',
});
}

}
1 change: 1 addition & 0 deletions doc-app/app/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ Router.map(function (this: RouterDSL) {
this.route('datepicker');
this.route('datepicker-range');
this.route('file');
this.route('file-list');
});
this.route('installation');
this.route('checkbox');
Expand Down
8 changes: 6 additions & 2 deletions doc-app/app/templates/docs.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
{{! prefab section }}

{{! validation section }}
{{!-- <nav.section @label="Ember Input Validation" />
{{! <nav.section @label="Ember Input Validation" />
<nav.item
@label="Install"
@route="docs.ember-input-validation.installation"
Expand All @@ -32,7 +32,7 @@
@route="docs.ember-input-validation.radio-group"
/>
<nav.item @label="Select" @route="docs.ember-input-validation.select" />
<nav.item @label="Textarea" @route="docs.ember-input-validation.textarea" /> --}}
<nav.item @label="Textarea" @route="docs.ember-input-validation.textarea" /> }}

{{! form section }}
<nav.section @label="Ember Input Validation" />
Expand All @@ -59,6 +59,10 @@
@route="docs.ember-input-validation.prefabs.email"
/>
<nav.item @label="File" @route="docs.ember-input-validation.prefabs.file" />
<nav.item
@label="File List"
@route="docs.ember-input-validation.prefabs.file-list"
/>
<nav.item @label="Iban" @route="docs.ember-input-validation.prefabs.iban" />
<nav.item
@label="Input"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Input File

This is an input with a dropzone that allows to upload multiple files. You can also find a list of uploaded files and dowload/delete theme.

<DocsDemo as |demo|>
<demo.example @name="prefab-tpk-file.hbs">
<Prefabs::TpkValidationFileList
@label="File"
@changeset={{this.changeset}}
@validationField="files"
@placeholder="Glisser-déposer un fichier ou cliquer pour sélectionner un fichier (max 10Mo)"
/>
<Prefabs::TpkValidationFileList
@label="Disabled"
@changeset={{this.changeset}}
@validationField="disabled"
@placeholder="Glisser-déposer un fichier ou cliquer pour sélectionner un fichier (max 10Mo)"
@disabled=true
/>
<Prefabs::TpkValidationFileList
@label="Error"
@changeset={{this.changeset}}
@validationField="error"
@placeholder="Glisser-déposer un fichier ou cliquer pour sélectionner un fichier (max 10Mo)"
@mandatory=true
/>
</demo.example>
<demo.snippet @name="prefab-tpk-file.hbs"/>
</DocsDemo>

## Mandatory properties

- `@validationField`: The field name in the changeset for validation.
- `@changeset`: The changeset object for form validation.

## Optional properties

- `@label`: The label for the input field.
- `@disabled`: Whether the input field is disabled.
- `@mandatory`: Whether the input file multiple field is mandatory.
- `@onChange`: The action to be called when the selection changes.
- `@disableDownload`: Whether the download button is disabled.
- `@placeholder`: The placeholder for the dropzone area.
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ This is an input with type File
- `@disabled`: Whether the input field is disabled.
- `@mandatory`: Whether the textarea field is mandatory.
- `@onChange`: The action to be called when the selection changes.
- `@changeEvent`: The event to trigger the onChange action.
- `@changeEvent`: The event to trigger the onChange action.
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, triggerEvent } from '@ember/test-helpers';
import { ImmerChangeset } from 'ember-immer-changeset';
import { setupIntl } from 'ember-intl/test-support';
import TpkValidationFileList from '@triptyk/ember-input-validation/components/prefabs/tpk-validation-file-list';
import { click } from '@ember/test-helpers';
import { assertTpkCssClassesExist } from '../generic-test-functions/assert-tpk-css-classes-exist';
import { a11yAudit } from 'ember-a11y-testing/test-support';
import { settled } from '@ember/test-helpers';


module(
'Integration | Component | Prefabs | tpk-validation-file-list',
function (hooks) {
setupRenderingTest(hooks);
setupIntl(hooks, 'fr-fr');

function setupChangeset({
files = [],
}: {
files: File[]
}) {
return new ImmerChangeset<{
files: File[];
}>({
files,
});
}

async function renderComponent(params: { changeset: ImmerChangeset; disabled?: boolean; disableDownload?: boolean; }) {
await render(
<template>
<TpkValidationFileList
@label="label"
@changeset={{params.changeset}}
@validationField="files"
@disableDownload={{params.disableDownload}}
@disabled={{params.disabled}}
@placeholder="Glisser-déposer des fichiers images (max 2mb)"
/>
</template>,
);
}


test('Should show download and delete buttons by default when there are files', async function (assert) {
const changeset = setupChangeset({
files: [new File(['Ember Rules!'], 'file.txt')]
});
await renderComponent({changeset});
assert.dom('.tpk-file-list-list-item-action-download').exists();
assert.dom('.tpk-file-list-list-item-action-delete').exists();
});

test('Should hide download and delete buttons when disableDownload is true and disabled', async function (assert) {
const changeset = setupChangeset({
files: [new File(['Ember Rules!'], 'file.txt')]
});
await renderComponent({changeset, disabled: true, disableDownload: true});
assert.dom('.tpk-file-list-list-item-action-download').doesNotExist();
assert.dom('.tpk-file-list-list-item-action-delete').doesNotExist();
});

test('Drag and drop files should add them to the changeset and show them in the list', async function (assert) {
const changeset = setupChangeset({
files: []
});
await renderComponent({ changeset });
await triggerEvent('.tpk-file-list-placeholder-container', 'drop', {
dataTransfer: {
files: [new File(['Ember Rules!'], 'file.txt'), new File(['Ember Rules!'], 'loempia.txt')],
}
});
assert.dom('.tpk-file-list-list-item').exists({ count: 2 });
assert.strictEqual(changeset.get('files').length, 2);
});

test('Drop a file with a default file in changeset should add the file to the changeset and not remove the default file', async function (assert) {
const changeset = setupChangeset({
files: [new File(['Ember Rules!'], 'file.txt')]
});
await renderComponent({ changeset });
await triggerEvent('.tpk-file-list-placeholder-container', 'drop', {
dataTransfer: {
files: [new File(['Ember Rules!'], 'file.txt')],
}
});
assert.dom('.tpk-file-list-list-item').exists({ count: 2 });
assert.strictEqual(changeset.get('files').length, 2);
});

test('Delete button should remove the file from the changeset', async function (assert) {
const changeset = setupChangeset({
files: [new File(['Ember Rules!'], 'file.txt')]
});
await renderComponent({ changeset });
await click('.tpk-file-list-list-item:first-child .tpk-file-list-list-item-action-delete');
assert.dom('.tpk-file-list-list-item').doesNotExist();
assert.strictEqual(changeset.get('files').length, 0);
});

test('It changes data-has-error attribue on error', async function (assert) {
const changeset = setupChangeset({
files: []
});
await renderComponent({changeset});

changeset.addError({
message: 'required',
value: '',
originalValue: '',
key: 'files',
});

await settled();
assert.dom(`[data-test-tpk-file-list-input]`).hasNoText();

assert
.dom(`[data-test-tpk-prefab-file-list-container]`)
.hasAttribute('data-has-error', 'true');
});

test('CSS classes exist and have been attached to the correct element', async function (assert) {
const changeset = setupChangeset({
files: []
});
await renderComponent({changeset});
await assertTpkCssClassesExist(assert,'file-list');
});

test('Accessibility', async function (assert) {
assert.expect(0);
const changeset = setupChangeset({
files: []
});
await renderComponent({changeset});
await a11yAudit();
});
},
);
7 changes: 6 additions & 1 deletion packages/ember-input-validation/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,11 @@
"./static/DE.svg": "/DE.svg",
"./static/LU.svg": "/LU.svg",
"./static/NL.svg": "/NL.svg",
"./static/FR.svg": "/FR.svg"
"./static/FR.svg": "/FR.svg",
"./static/upload.svg": "/upload.svg",
"./static/download.svg": "/download.svg",
"./static/delete.svg": "/delete.svg",
"./static/document.svg": "/document.svg"
},
"app-js": {
"./components/base.js": "./dist/_app_/components/base.js",
Expand All @@ -131,6 +135,7 @@
"./components/prefabs/tpk-validation-datepicker.js": "./dist/_app_/components/prefabs/tpk-validation-datepicker.js",
"./components/prefabs/tpk-validation-email.js": "./dist/_app_/components/prefabs/tpk-validation-email.js",
"./components/prefabs/tpk-validation-errors.js": "./dist/_app_/components/prefabs/tpk-validation-errors.js",
"./components/prefabs/tpk-validation-file-list.js": "./dist/_app_/components/prefabs/tpk-validation-file-list.js",
"./components/prefabs/tpk-validation-file.js": "./dist/_app_/components/prefabs/tpk-validation-file.js",
"./components/prefabs/tpk-validation-iban.js": "./dist/_app_/components/prefabs/tpk-validation-iban.js",
"./components/prefabs/tpk-validation-input.js": "./dist/_app_/components/prefabs/tpk-validation-input.js",
Expand Down
1 change: 1 addition & 0 deletions packages/ember-input-validation/src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
@import url('./components/prefabs/styles/tpk-datepicker-range.css');
@import url('./components/prefabs/styles/tpk-email.css');
@import url('./components/prefabs/styles/tpk-file.css');
@import url('./components/prefabs/styles/tpk-file-list.css');
@import url('./components/prefabs/styles/tpk-timepicker.css');
@import url('./components/prefabs/styles/tpk-iban.css');
@import url('./components/prefabs/styles/tpk-mobile.css');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
.tpk-file-list-container {
@apply relative flex flex-col w-full pb-4;
}

.tpk-file-list-container .tpk-label {
@apply label;
}

.tpk-file-list-input {
@apply hidden;
}

.tpk-file-list-placeholder-container {
@apply min-h-[180px] w-full border-2 border-dashed border-base-300 rounded-lg flex flex-col items-center justify-center cursor-pointer transition-colors duration-200 hover:border-neutral-content hover:bg-base-200/10;
}

.tpk-file-list-placeholder-icon {
@apply w-8 h-8 mb-2 opacity-60;
}

.tpk-file-list-placeholder-container.disabled {
@apply hidden;
}

.tpk-file-list-placeholder {
@apply text-base-content/60 text-sm text-center;
}

.tpk-file-list-container[data-has-error~='true']
.tpk-file-list-placeholder-container {
@apply border-error bg-error/5;
}

.tpk-file-list-container[data-has-error~='true'] .tpk-label {
@apply text-error;
}

.tpk-file-list-container[data-has-error~='true'] .tpk-file-list-placeholder {
@apply text-error/60;
}

.tpk-file-list-container[data-has-error~='true'] .tpk-validation-errors {
@apply text-error text-sm absolute right-0 -bottom-1;
}

/* File list */

.tpk-file-list-list {
@apply space-y-4 mt-4;
}

.tpk-file-list-list.disabled {
@apply mt-0;
}

.tpk-file-list-list-item {
@apply flex items-center space-x-6 px-4 py-2 border border-base-300 rounded;
}

.tpk-file-list-list-item-preview {
@apply w-7 h-7 flex-shrink-0;
}

.tpk-file-list-list-item-preview img {
@apply w-7 h-7 object-cover rounded object-center;
}

.tpk-file-list-list-item-content {
@apply flex flex-col flex-grow truncate text-ellipsis;
}

.tpk-file-list-list-item-content-name {
@apply text-sm font-semibold truncate text-ellipsis;
}

.tpk-file-list-list-item-content-size {
@apply text-xs text-base-content/60;
}

.tpk-file-list-list-item-action {
@apply flex gap-2 flex-shrink-0;
}

.tpk-file-list-list-item-action button, .tpk-file-list-list-item-action img {
@apply w-6 h-6;
}
Loading

0 comments on commit d9b682c

Please sign in to comment.