Skip to content

Commit

Permalink
Merge branch 'main' into VIV-2160-menu-item-focus
Browse files Browse the repository at this point in the history
  • Loading branch information
rachelbt authored Oct 22, 2024
2 parents f29f34b + fbc115e commit adfd8a7
Show file tree
Hide file tree
Showing 26 changed files with 568 additions and 208 deletions.
5 changes: 5 additions & 0 deletions apps/docs/.eleventy.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ module.exports = function (eleventyConfig) {
components.filter((c) => c.page !== 'legacy')
);

eleventyConfig.addGlobalData(
'componentUseCases',
components.filter((c) => c.useCases)
);

eleventyConfig.addGlobalData(
'componentsLegacy',
components.filter((c) => c.page === 'legacy')
Expand Down
8 changes: 6 additions & 2 deletions apps/docs/content/_data/components.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,12 @@
},
{
"title": "Icon",
"page": "legacy",
"markdown": "./libs/components/src/lib/icon/README.md"
"description": "Displays icons from the Vivid Icons Library using predefined colors and sizes.",
"variations": "./libs/components/src/lib/icon/VARIATIONS.md",
"guidelines": "./libs/components/src/lib/icon/GUIDELINES.md",
"hideGuidelines": "true",
"code": "./libs/components/src/lib/icon/README.md",
"accessibility": "./libs/components/src/lib/icon/ACCESSIBILITY.md"
},
{
"title": "Layout",
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/_layouts/component.njk
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
<li><a href="/components/{{ component | componentSlug }}/guidelines/" {% if ("/components/" + (component | componentSlug) + "/guidelines/") == page.url %} aria-current="page" {% endif %}>Design Guidelines</a></li>
{% endif %}
<li><a href="/components/{{ component | componentSlug }}/code/" {% if ("/components/" + (component | componentSlug) + "/code/") == page.url %} aria-current="page" {% endif %}>Code</a></li>
{% if component.hideUseCases != "true" %}
{% if component.useCases and component.hideUseCases != "true" %}
<li><a href="/components/{{ component | componentSlug }}/use-cases/" {% if ("/components/" + (component | componentSlug) + "/use-cases/") == page.url %} aria-current="page" {% endif %}>Use Cases</a></li>
{% endif %}
{% if component.hideAccessibility != "true" %}
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/component-use-cases.njk
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
tags: components
pagination:
data: componentsNew
data: componentUseCases
size: 1
alias: component
permalink: 'components/{{ component | componentSlug }}/use-cases/'
Expand Down
13 changes: 0 additions & 13 deletions apps/docs/pages/component-code.njk

This file was deleted.

29 changes: 26 additions & 3 deletions libs/components/src/lib/checkbox/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Use the `label` member to set the checkbox's label.

### Checked

Toggle the `checked` member to set the checkbox's on/off state.
Toggle the `checked` member to set the checkbox's on/off state. Also changes `aria-checked` value.

- Type: `boolean`
- Default: `false`
Expand All @@ -34,14 +34,13 @@ Toggle the `checked` member to set the checkbox's on/off state.

### Indeterminate

Use the `indeterminate` member to indicate that the checkbox's is neither on nor off.
Use the `indeterminate` member to indicate that the checkbox's is neither on nor off. It also sets `aria-checked` to "mixed". Setting `aria-checked` to "mixed" will also set `indeterminate` to true. Changing `aria-checked` from "mixed" will set indeterminate to false.

- Type: `boolean`
- Default: `false`

> The indeterminate property sets or returns whether the state of a checkbox has changed.
> Checkboxes actually has three states: true, false and indeterminate which indicates that a checkbox is neither "on" or "off".
> A checkbox cannot be set to indeterminate state by an HTML attribute - it must be set by a JavaScript.
> This state can be used to force the user to check or uncheck the checkbox.
> -- <cite>[w3schools][1]</cite>
Expand All @@ -54,6 +53,30 @@ Use the `indeterminate` member to indicate that the checkbox's is neither on nor
</script>
```

### Aria Checked

Use the `aria-checked` attribute to set the checkbox checked state. It reflects the `checked` state as well as the `indeterminate` state with the value "mixed". "undefined" means the element does not support being checked.

- Type: `'true'` | `'false'` | `'mixed'` | `'undefined'`
- Default: `'false'`

> The ariaChecked property of the Element interface reflects the value of the aria-checked attribute, which indicates the current "checked" state of checkboxes.
> A string with one of the following values:
> "true" The element is checked.
> "mixed" Indicates a mixed mode value for a tri-state checkbox.
> "false" The element supports being checked but is not currently checked.
> "undefined" The element does not support being checked.
> -- <cite>[mdn][2]</cite>
[2]: https://developer.mozilla.org/en-US/docs/Web/API/Element/ariaChecked#value

```html preview
<vwc-checkbox aria-checked="true"></vwc-checkbox>
<vwc-checkbox aria-checked="false"></vwc-checkbox>
<vwc-checkbox aria-checked="mixed"></vwc-checkbox>
<vwc-checkbox aria-checked="undefined" disabled></vwc-checkbox>
```

### Connotation

Use the `connotation` attribute to set the checkbox color.
Expand Down
57 changes: 57 additions & 0 deletions libs/components/src/lib/checkbox/checkbox.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,63 @@ describe('vwc-checkbox', () => {

expect(element.indeterminate).toBeFalsy();
});

it('should set aria-checked to mixed when true', async () => {
element.indeterminate = true;
await elementUpdated(element);

expect(getBaseElement(element).getAttribute('aria-checked')).toBe(
'mixed'
);
});

it('should set checked to false when true', async () => {
element.indeterminate = true;
await elementUpdated(element);

expect(element.checked).toBe(false);
});
});

describe('aria-checked', () => {
it('should set indeterminate to true when set to "mixed"', async () => {
element.ariaChecked = 'mixed';
await elementUpdated(element);

expect(element.indeterminate).toBe(true);
});

it('should be true when checkbox is checked', async () => {
element.checked = true;
await elementUpdated(element);

expect(getBaseElement(element).getAttribute('aria-checked')).toBe('true');
});

it('should be false when checkbox is unchecked', async () => {
element.checked = false;
await elementUpdated(element);

expect(getBaseElement(element).getAttribute('aria-checked')).toBe(
'false'
);
});

it('should set checked to false when set to false', async () => {
element.checked = true;
element.ariaChecked = 'false';
await elementUpdated(element);

expect(element.checked).toBe(false);
});

it('should set checked to true when set to true', async () => {
element.checked = false;
element.ariaChecked = 'true';
await elementUpdated(element);

expect(element.checked).toBe(true);
});
});

describe('connotation', function () {
Expand Down
2 changes: 1 addition & 1 deletion libs/components/src/lib/checkbox/checkbox.template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const CheckboxTemplate: FoundationElementTemplate<
class="${getClasses}"
role="checkbox"
aria-label="${(x) => x.ariaLabel}"
aria-checked="${(x) => x.checked}"
aria-checked="${(x) => (x.indeterminate ? 'mixed' : x.checked)}"
aria-required="${(x) => x.required}"
aria-disabled="${(x) => x.disabled}"
aria-readonly="${(x) => x.readOnly}"
Expand Down
25 changes: 25 additions & 0 deletions libs/components/src/lib/checkbox/checkbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export type CheckboxConnotation = Extract<
Connotation.Accent | Connotation.CTA
>;

export type AriaCheckedStates = 'false' | 'true' | 'mixed' | 'undefined';

/**
* @public
* @component checkbox
Expand All @@ -46,12 +48,35 @@ export class Checkbox extends FoundationCheckbox {
*/
@attr connotation?: CheckboxConnotation;

/**
* The current checkbox state
*
* @public
* @remarks
* HTML Attribute: aria-checked
*/
@attr({ attribute: 'aria-checked' })
override ariaChecked: AriaCheckedStates | null = null;

indeterminateChanged(_: boolean, next: boolean) {
this.checked = !next;
}

ariaCheckedChanged() {
if (this.ariaChecked === 'mixed') {
this.indeterminate = true;
} else {
this.indeterminate = false;
this.checked = this.ariaChecked === 'true' ? true : false;
}
}
/**
* @internal
*/
override checkedChanged(prev: boolean | undefined, next: boolean): void {
super.checkedChanged(prev, next);

this.ariaChecked = next == true ? 'true' : 'false';
if (prev !== undefined) {
this.$emit('input');
}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
75 changes: 53 additions & 22 deletions libs/components/src/lib/dial-pad/dial-pad.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,7 @@ describe('vwc-dial-pad', () => {

it('should set value in text field when has value attribute', async function () {
const value = '123';
element.value = value;
await elementUpdated(element);
await setValue(value);
expect(getTextField().value).toEqual(value);
});

Expand Down Expand Up @@ -145,30 +144,67 @@ describe('vwc-dial-pad', () => {
});
});

async function setValue(value: string) {
element.value = value;
await elementUpdated(element);
}

describe('delete', function () {
it('should show delete button when text field has value', async function () {
element.value = '123';
async function clickDeleteButton() {
getDeleteButton().click();
await elementUpdated(element);
}

it('should show delete button when text field has value', async function () {
await setValue('123');
expect(getDeleteButton()).not.toBeNull();
});

it('should remove last character from text field when clicked on delete button', async function () {
element.value = '123';
await elementUpdated(element);
getDeleteButton().click();
await elementUpdated(element);
await setValue('123');

await clickDeleteButton();

expect(getTextField().value).toEqual('12');
});

it('should emit a change event', async () => {
const spy = jest.fn();
element.addEventListener('change', spy);
element.value = '123';
await elementUpdated(element);
getDeleteButton().click();
await elementUpdated(element);
await setValue('123');

await clickDeleteButton();

expect(spy).toHaveBeenCalledTimes(1);
});

it('should emit an input event', async () => {
const spy = jest.fn();
element.addEventListener('input', spy);
await setValue('123');

await clickDeleteButton();

expect(spy).toHaveBeenCalledTimes(1);
});

it('should prevent blur event after deleting the last value', async () => {
const spy = jest.fn();
element.addEventListener('blur', spy);
await setValue('1');

await clickDeleteButton();

expect(spy).toHaveBeenCalledTimes(0);
});

it('should focus on the dialpad after deleting the last element', async () => {
await setValue('1');

await clickDeleteButton();

expect(document.activeElement === element).toBe(true);
});
});

describe('dial', function () {
Expand Down Expand Up @@ -237,8 +273,7 @@ describe('vwc-dial-pad', () => {
it('should fire dial event when enter is pressed on input', async function () {
const spy = jest.fn();
element.addEventListener('dial', spy);
element.value = '123';
await elementUpdated(element);
await setValue('123');
const input: HTMLInputElement = getTextField().querySelector(
'input'
) as HTMLInputElement;
Expand All @@ -255,17 +290,15 @@ describe('vwc-dial-pad', () => {
it('should fire dial event with value when clicked on call button', async function () {
const spy = jest.fn();
element.addEventListener('dial', spy);
element.value = '123';
await elementUpdated(element);
await setValue('123');
getCallButton().click();
expect(spy).toHaveBeenCalledTimes(1);
});

it('should prevent dial event when enter is pressed on delete button', async function () {
const spy = jest.fn();
element.value = '123';
await setValue('123');
element.addEventListener('dial', spy);
await elementUpdated(element);
getDeleteButton().dispatchEvent(
new KeyboardEvent('keydown', { key: 'Enter' })
);
Expand Down Expand Up @@ -440,8 +473,7 @@ describe('vwc-dial-pad', () => {

it('should set delete button disabled when has disabled attribute', async function () {
element.disabled = true;
element.value = '123';
await elementUpdated(element);
await setValue('123');
expect(getDeleteButton().disabled).toEqual(true);
});
});
Expand All @@ -463,8 +495,7 @@ describe('vwc-dial-pad', () => {

it('should set the delete button to be disabled', async function () {
element.callActive = true;
element.value = '123';
await elementUpdated(element);
await setValue('123');
expect(getDeleteButton().disabled).toBe(true);
});
});
Expand Down
Loading

0 comments on commit adfd8a7

Please sign in to comment.