Skip to content

Commit

Permalink
feat(file): allow custom button label (#295)
Browse files Browse the repository at this point in the history
## What is the current behavior?

The `buttonLabel` property on `cds-file` can be set even though it is
private. When the file selection is cleared, the button label is reset
to "Browse" instead of the previous `buttonLabel` value.

Issue Number: CDE-1055

## What is the new behavior?

A custom button label is fully supported.

## Does this PR introduce a breaking change?

No.

## Other Information

- The `buttonLabel` property is now `public`.
- The is a new property named `buttonLabelForSelection`. This property
is `private` and should not be set externally.
  • Loading branch information
kevinbuhmann committed Mar 20, 2024
1 parent 1682911 commit 62d7b9f
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 19 deletions.
28 changes: 21 additions & 7 deletions projects/core/custom-elements.json
Original file line number Diff line number Diff line change
Expand Up @@ -7350,6 +7350,15 @@
{
"kind": "field",
"name": "buttonLabel",
"description": "Set the label of the browse button.",
"attribute": "buttonLabel"
},
{
"kind": "field",
"name": "buttonLabelForSelection",
"type": {
"text": "string"
},
"privacy": "private"
},
{
Expand Down Expand Up @@ -7901,13 +7910,12 @@
}
}
],
"superclass": {
"name": "CdsControl",
"package": "@cds/core/forms"
},
"tagName": "cds-file",
"customElement": true,
"attributes": [
{
"name": "buttonLabel",
"description": "Set the label of the browse button.",
"fieldName": "buttonLabel"
},
{
"name": "status",
"type": {
Expand Down Expand Up @@ -7975,7 +7983,13 @@
"module": "forms/control/control.element.js"
}
}
]
],
"superclass": {
"name": "CdsControl",
"package": "@cds/core/forms"
},
"tagName": "cds-file",
"customElement": true
}
],
"exports": [
Expand Down
37 changes: 30 additions & 7 deletions projects/core/src/file/file.element.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@ describe('cds-file', () => {
expect(component).toBeTruthy();
});

it('should have a default button label', async () => {
expect(button.innerText.trim().toLocaleLowerCase()).toBe('browse');
});

it('should have a customizable button label', async () => {
component.buttonLabel = 'custom browse';
await componentIsStable(component);
expect(button.innerText.trim().toLocaleLowerCase()).toBe('custom browse');
});

it('should set the file input as active when button is clicked', done => {
let clicked = false;
element.querySelector('label').addEventListener('click', () => {
Expand Down Expand Up @@ -97,18 +107,20 @@ describe('cds-file', () => {
});

it('should clear file input', async () => {
(component.inputControl as HTMLInputElement).dispatchEvent(new Event('change'));
await selectFileAndThenClearFile();

Object.defineProperty(component, 'inputControl', { get: () => ({ files: [{ name: 'test.png' }] }) });
component.requestUpdate();
await componentIsStable(component);

component.shadowRoot.querySelector('cds-button-action').click();
await componentIsStable(component);
expect(document.activeElement).toBe(component);
expect(button.innerText.trim().toLocaleLowerCase()).toBe('browse');
});

it('should clear file input with button custom label', async () => {
component.buttonLabel = 'custom browse';
await selectFileAndThenClearFile();

expect(document.activeElement).toBe(component);
expect(button.innerText.trim().toLocaleLowerCase()).toBe('custom browse');
});

it('should not run an update on a programmatic change event', async () => {
spyOn(component, 'updateLabelAndFocus');
(component.inputControl as HTMLInputElement).dispatchEvent(new Event('change'));
Expand All @@ -127,4 +139,15 @@ describe('cds-file', () => {
component.clearFiles(); // defaults to firing the event
expect(component.inputControl.dispatchEvent).toHaveBeenCalledTimes(2);
});

async function selectFileAndThenClearFile() {
(component.inputControl as HTMLInputElement).dispatchEvent(new Event('change'));

Object.defineProperty(component, 'inputControl', { get: () => ({ files: [{ name: 'test.png' }] }) });
component.requestUpdate();
await componentIsStable(component);

component.shadowRoot.querySelector('cds-button-action').click();
await componentIsStable(component);
}
});
15 changes: 10 additions & 5 deletions projects/core/src/file/file.element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import { html, PropertyValues } from 'lit';
import { state, i18n, I18nService } from '@cds/core/internal';
import { state, i18n, I18nService, property } from '@cds/core/internal';
import { CdsControl } from '@cds/core/forms';
import styles from './file.element.scss';

Expand All @@ -29,7 +29,12 @@ import styles from './file.element.scss';
export class CdsFile extends CdsControl {
@i18n() i18n = I18nService.keys.file;

@state() private buttonLabel = this.i18n.browse;
/**
* Set the label of the browse button.
*/
@property() buttonLabel = this.i18n.browse;

@state() private buttonLabelForSelection: string;

@state() protected fixedControlWidth = true;

Expand All @@ -44,7 +49,7 @@ export class CdsFile extends CdsControl {
<div cds-layout="horizontal gap:sm align:vertical-center">
<cds-button size="sm" action="outline" @click="${() => this.label.click()}" ?disabled=${this.disabled}>
<cds-icon shape="folder" aria-hidden="true"></cds-icon>
<span>${this.buttonLabel}</span>
<span>${this.buttonLabelForSelection || this.buttonLabel}</span>
</cds-button>
${this.clearFilesControlTemplate}
</div>
Expand Down Expand Up @@ -74,7 +79,7 @@ export class CdsFile extends CdsControl {

/** @private */
clearFiles(fireEvent = true) {
this.buttonLabel = this.i18n.browse;
this.buttonLabelForSelection = '';
this.inputControl.value = '';

// when input is reset like this it isn't registering an onchange event
Expand All @@ -92,7 +97,7 @@ export class CdsFile extends CdsControl {
/** @private */
updateLabelAndFocus(files?: FileList) {
if (files && files.length) {
this.buttonLabel = files.length > 1 ? `${files.length} ${this.i18n.files}` : files[0].name;
this.buttonLabelForSelection = files.length > 1 ? `${files.length} ${this.i18n.files}` : files[0].name;
} else {
this.clearFiles(false);
}
Expand Down

0 comments on commit 62d7b9f

Please sign in to comment.