Skip to content

Commit

Permalink
fix: sd input a11y issues (#1934)
Browse files Browse the repository at this point in the history
<!-- ## Title: Please consider adding the [skip chromatic] flag to the
PR title in case you dont need chromatic testing your changes. -->
## Description:
Closes #1877

<!-- *If this PR includes a bug fix, an E2E test is necessary to verify
the change. If the fix is purely visual, ensuring it is captured within
our chromatic screenshot tests is sufficient.* -->
- [x] Stories (features, a11y) are created/updated
- [x] relevant tickets are linked

---------

Co-authored-by: Sérgio Fonseca <42741644+smfonseca@users.noreply.github.com>
  • Loading branch information
auroraVasconcelos and smfonseca authored Mar 11, 2025
1 parent 4a96fb9 commit b8f1d87
Show file tree
Hide file tree
Showing 9 changed files with 83 additions and 71 deletions.
8 changes: 8 additions & 0 deletions .changeset/forty-experts-tie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@solid-design-system/docs': patch
---

Improved `sd-input` accessibility and consistency with other components.

- Add labels to sd-input.

9 changes: 9 additions & 0 deletions .changeset/popular-boats-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@solid-design-system/components': minor
---

Added new functionality to the `sd-input` for type="search" and improved the component accessibility.

- Search icon button is interactive.
- Trigger `sd-search` event when search button on `sd-input type="search"` is clicked.
- Added a translatable label to the search icon for the `sd-input type="search"`.
24 changes: 24 additions & 0 deletions packages/components/src/components/input/input.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,30 @@ describe('<sd-input>', () => {
});
});

describe('when type="search"', () => {
it('should emit sd-search when the user clicks the search button', async () => {
const el = await fixture<SdInput>(html` <sd-input type="search"></sd-input> `);
const searchHandler = sinon.spy();
const searchButton = el.shadowRoot!.querySelector('button')!;

el.addEventListener('sd-search', searchHandler);

searchButton.click();
await waitUntil(() => searchHandler.calledOnce);

expect(searchHandler).to.have.been.calledOnce;
});

it('should translate the search icon aria-label when the lang attribute is set', async () => {
const el = await fixture<SdInput>(html` <sd-input lang="de" type="search"></sd-input> `);
const searchIcon = el.shadowRoot!.querySelector('button')!.querySelector('sd-icon')!;

await el.updateComplete;

expect(searchIcon.getAttribute('aria-label')).to.equal('Suchen');
});
});

describe('when type="number"', () => {
it('should be valid when the value is within the boundary of a step', async () => {
const el = await fixture<SdInput>(html` <sd-input type="number" step=".5" value="1.5"></sd-input> `);
Expand Down
17 changes: 14 additions & 3 deletions packages/components/src/components/input/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const isFirefox = isChromium ? false : navigator.userAgent.includes('Firefox');
* @event sd-clear - Emitted when the clear button is activated.
* @event sd-focus - Emitted when the control gains focus.
* @event sd-input - Emitted when the control receives input.
* @event sd-search - Emitted when the search button is activated.
*
* @csspart form-control - The form control that wraps the label, input, and help text.
* @csspart form-control-label - The label's wrapper.
Expand Down Expand Up @@ -279,6 +280,11 @@ export default class SdInput extends SolidElement implements SolidFormControl {
event.stopPropagation();
}

private handleSearchClick(event: MouseEvent) {
this.emit('sd-search');
event.stopPropagation();
}

private handleFocus() {
this.hasFocus = true;
this.emit('sd-focus');
Expand Down Expand Up @@ -615,7 +621,7 @@ export default class SdInput extends SolidElement implements SolidFormControl {
? html`
<button
part="clear-button"
class=${cx('flex justify-center ', iconMarginLeft)}
class=${cx('flex justify-center', iconMarginLeft)}
type="button"
aria-label=${this.localize.term('clearEntry')}
@click=${this.handleClearClick}
Expand Down Expand Up @@ -675,11 +681,16 @@ export default class SdInput extends SolidElement implements SolidFormControl {
: ''}
${this.type === 'search'
? html`
<button class="flex items-center" type="button" tabindex="-1">
<button
class=${cx('flex items-center sd-interactive', iconMarginLeft)}
type="button"
@click=${this.handleSearchClick}
>
<sd-icon
class=${cx(iconColor, iconMarginLeft, iconSize)}
class=${cx(iconColor, iconSize)}
library="system"
name="magnifying-glass"
label=${this.localize.term('search')}
></sd-icon>
</button>
`
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/translations/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const translation: Translation = {
removed: name => `${name} entfernt`,
resize: 'Größe ändern',
scrollToEnd: 'Zum Ende scrollen',
search: 'Suchen',
seekBar: 'Bar suchen',
scrollToStart: 'Zum Anfang scrollen',
selectAColorFromTheScreen: 'Farbe vom Bildschirm auswählen',
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/translations/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const translation: Translation = {
resize: 'Resize',
scrollToEnd: 'Scroll to end',
scrollToStart: 'Scroll to start',
search: 'Search',
seekBar: 'Seek bar',
selectAColorFromTheScreen: 'Select a color from the screen',
selectDefaultPlaceholder: 'Please select',
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/utilities/localize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export interface Translation extends DefaultTranslation {
resize: string;
scrollToEnd: string;
scrollToStart: string;
search: string;
seekBar: string;
selectAColorFromTheScreen: string;
selectDefaultPlaceholder: string;
Expand Down
8 changes: 4 additions & 4 deletions packages/docs/src/stories/components/input.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ const { overrideArgs } = storybookHelpers('sd-input');
* **Related templates**:
* - [Input](?path=/docs/templates-input--docs)
* - [Autocomplete](?path=/docs/templates-autocomplete--docs)
* - [Tooltip](?path=/docs/templates-tooltip--docs)
* - [Input with Tooltip](?path=/docs/templates-tooltip--docs#input%20with%20tooltip)
*
*/
export default {
tags: ['!dev', 'skip-a11y'],
tags: ['!dev'],
title: 'Components/sd-input',
component: 'sd-input',
args: overrideArgs({
Expand Down Expand Up @@ -79,7 +79,7 @@ export const Label = {
export const Placeholder = {
render: () => html`
<div class="w-[250px]">
<sd-input placeholder="Placeholder example" spellcheck></sd-input>
<sd-input placeholder="Placeholder example" label="Label" spellcheck></sd-input>
</div>
`
};
Expand Down Expand Up @@ -343,7 +343,7 @@ export const Invalid = {
<script type="module">
await Promise.all([customElements.whenDefined('sd-input')]).then(() => {
const input = document.getElementById('invalid-input');
input.setCustomValidity('Error message');
input.setCustomValidity('Error text');
input.reportValidity();
});
</script>`
Expand Down
85 changes: 21 additions & 64 deletions packages/docs/src/stories/components/input.test.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,17 @@ import {
import { userEvent } from '@storybook/test';
import { waitUntil } from '@open-wc/testing-helpers';
import { withActions } from '@storybook/addon-actions/decorator';
import type SdInput from '../../../../components/src/components/input/input';

const { argTypes, args, parameters } = storybookDefaults('sd-input');
const { argTypes, parameters } = storybookDefaults('sd-input');
const { generateTemplate } = storybookTemplate('sd-input');
const { overrideArgs } = storybookHelpers('sd-input');
const { generateScreenshotStory } = storybookUtilities;

export default {
title: 'Components/sd-input/Screenshots: sd-input',
component: 'sd-input',
tags: ['!autodocs', 'skip-a11y'],
args,
tags: ['!autodocs'],
args: overrideArgs([{ type: 'attribute', name: 'label', value: 'Label' }]),
argTypes: {
...argTypes,
'type-attr': {
Expand Down Expand Up @@ -256,7 +255,7 @@ export const Sizes = {
export const StyleOnValid = {
name: 'Style on Valid',
args: overrideArgs([
{ type: 'attribute', name: 'value', value: 'valu' },
{ type: 'attribute', name: 'value', value: 'value' },
{ type: 'attribute', name: 'label', value: 'Label' },
{ type: 'attribute', name: 'help-text', value: 'help-text' },
{ type: 'attribute', name: 'clearable', value: true },
Expand Down Expand Up @@ -409,6 +408,18 @@ export const Types = {
args
})}
</div>
<div class="mb-2">
${generateTemplate({
constants: [
{ type: 'attribute', name: 'type', value: 'number' },
{ type: 'attribute', name: 'label', value: 'Spin Buttons' },
{ type: 'attribute', name: 'min', value: 0 },
{ type: 'attribute', name: 'max', value: 100 },
{ type: 'attribute', name: 'spin-buttons', value: 'true' }
],
args
})}
</div>
</div>
`;
}
Expand Down Expand Up @@ -689,9 +700,10 @@ export const Slots = {
title: 'slot=...',
values: [
{
value: `<div slot='${slot}' class="slot slot--border slot--background h-6 ${
slot === 'label' || slot === 'help-text' ? 'w-20' : 'w-6'
}"></div>`,
value: `<div slot='${slot}'
class="slot slot--border slot--background h-6 ${
slot === 'label' || slot === 'help-text' ? 'w-20' : 'w-6'
}">${slot === 'label' ? 'Label' : ''}</div>`,
title: slot
}
]
Expand Down Expand Up @@ -851,60 +863,6 @@ export const Mouseless = {
}
};

/**
* Sample implementation of a currency stepper.
*/

export const Samples = {
name: 'Sample: Currency Stepper',
render: () => {
return html`
<div class="w-[250px]">
<sd-input label="Currency Stepper" id="stepperSampleInput" type="number" min="0"
><span slot="right" class="text-sm inline-flex items-center"
><span class="text-neutral-700">EUR</span>
<button
disabled
id="stepDownButton"
@click=${() => {
const inputEl: SdInput = document.querySelector('#stepperSampleInput')!;
const stepDownButton: HTMLButtonElement = document.querySelector('#stepDownButton')!;
const numericValue = parseInt(inputEl.value, 10);
const stepDownValue = numericValue - 1;
if (stepDownValue <= 0) {
stepDownButton.disabled = true;
inputEl.value = '0.00';
} else {
inputEl.stepDown();
// Adjust input value to 2 decimals (currency)
inputEl.value = String(parseInt(inputEl.value, 10).toFixed(2));
}
}}
class="ml-4 scale-[1.714] inline-flex items-center sd-interactive"
>
<sd-icon name="system/minus-circle"></sd-icon>
</button>
<button
id="stepUpButton"
@click=${() => {
const inputEl: SdInput = document.querySelector('#stepperSampleInput')!;
const stepDownButton: HTMLButtonElement = document.querySelector('#stepDownButton')!;
stepDownButton.disabled = false;
inputEl.stepUp();
// Adjust input value to 2 decimals (currency)
inputEl.value = String(parseInt(inputEl.value, 10).toFixed(2));
}}
class="ml-4 scale-[1.714] inline-flex items-center sd-interactive"
>
<sd-icon name="system/plus-circle"></sd-icon></button
></span>
</sd-input>
</div>
`;
}
};

export const Combination = generateScreenshotStory([
Default,
Labels,
Expand All @@ -921,6 +879,5 @@ export const Combination = generateScreenshotStory([
Slots,
Parts,
setCustomValidity,
Mouseless,
Samples
Mouseless
]);

0 comments on commit b8f1d87

Please sign in to comment.