diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template_component.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template_component.md index 2586010a9..c3b3e639e 100644 --- a/.github/PULL_REQUEST_TEMPLATE/pull_request_template_component.md +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template_component.md @@ -16,7 +16,7 @@ * [ ] A11y tests -* [ ] Ui tests +* [ ] Ui tests * [ ] Unit and coverage * [ ] Test for Aria [matching roles](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles) * [ ] [Check the markup](https://validator.w3.org/) diff --git a/__snapshots__/badge.md b/__snapshots__/badge.md index dd65d6de5..72543e5ff 100644 --- a/__snapshots__/badge.md +++ b/__snapshots__/badge.md @@ -1,10 +1,100 @@ # `badge` -#### `should internal contents` +#### `should match internal contents` ```html - - + +``` + +#### `should match internal contents (legacy)` + +```html + + + + + +``` + +## `icons` + +#### `should have icons when icons are set` + +```html + + + + + + + + + +``` + +#### `should have icons added when icons are set dynamically (property)` + +```html + + + + + + + + + +``` + +#### `should have icons added when icons are set dynamically (attribute)` + +```html + + + + + + + + + +``` + +#### `should have icons removed when icons are unset (property)` + +```html + + + + + +``` + +#### `should have icon removed when icon is unset (attribute)` + +```html + + + + ``` diff --git a/components/badge/package.json b/components/badge/package.json index b569a9667..aeb29a60d 100644 --- a/components/badge/package.json +++ b/components/badge/package.json @@ -29,8 +29,10 @@ }, "dependencies": { "@vonage/vvd-core": "2.15.0", + "@vonage/vwc-icon": "2.15.0", "@vonage/vvd-foundation": "2.15.0", "lit-element": "^2.4.0", + "lit-html": "^1.3.0", "tslib": "^2.3.0" }, "devDependencies": { @@ -39,4 +41,4 @@ "@vonage/vvd-umbrella": "2.15.0", "typescript": "^4.3.2" } -} +} \ No newline at end of file diff --git a/components/badge/readme.md b/components/badge/readme.md index 7217586d9..981c7a77b 100644 --- a/components/badge/readme.md +++ b/components/badge/readme.md @@ -1,51 +1,23 @@ -# vwc-select +# vwc-badge -This component is an extension of [](https://github.com/material-components/material-components-web-components/tree/master/packages/select) +Represents a badge custom element. +badge is a label that holds small amounts of information. A badge can be used to display unread notifications, or to label a block of text. Badges don’t work for navigation because they can't include a hyperlink. ## Properties -| Property | Modifiers | Type | Description | -|---------------------------|-----------|--------------------------------------------------|--------------------------------------------------| -| `disabled` | | `boolean` | | -| `floatingLabelFoundation` | | `MDCFloatingLabelFoundation \| undefined` | | -| `helper` | | `string` | | -| `icon` | | `string` | | -| `index` | readonly | `number` | | -| `items` | readonly | `ListItemBase[]` | | -| `label` | | `string` | | -| `lineRippleFoundation` | | `MDCLineRippleFoundation \| undefined` | | -| `naturalMenuWidth` | | `boolean` | | -| `outlined` | | `boolean` | | -| `required` | | `boolean` | | -| `ripple` | readonly | `RippleInterface \| Promise \| undefined` | Implement ripple getter for Ripple integration with mwc-formfield | -| `selected` | readonly | `ListItemBase \| null` | | -| `validateOnInitialRender` | | `boolean` | | -| `validationMessage` | | `string` | | -| `validity` | readonly | `ValidityState` | | -| `validityTransform` | | `((value: string, nativeValidity: ValidityState) => Partial) \| null` | | -| `value` | | `string` | | +| Property | Attribute | Type | Default | +| -------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| `connotation` | `connotation` | `Connotation.Primary \| Connotation.CTA \| Connotation.Success \| Connotation.Alert \| Connotation.Warning \| Connotation.Info \| undefined` | | +| `dense` | `dense` | `boolean` | false | +| `enlarged` | `enlarged` | `boolean` | false | +| `icon` | `icon` | `string \| undefined` | | +| `iconTrailing` | `iconTrailing` | `string \| undefined` | | +| `layout` | `layout` | `Layout.Filled \| Layout.Outlined \| Layout.Soft` | "filled" | +| `shape` | `shape` | `Shape.Rounded \| Shape.Pill \| undefined` | | +| `text` | `text` | `string \| undefined` | | -## Methods +## Slots -| Method | Type | -|---------------------|--------------------------------------------------| -| `blur` | `(): void` | -| `checkValidity` | `(): boolean` | -| `click` | `(): void` | -| `focus` | `(): void` | -| `layout` | `(updateItems?: boolean \| undefined): Promise` | -| `reportValidity` | `(): boolean` | -| `select` | `(index: number): void` | -| `setAriaLabel` | `(label: string): void` | -| `setCustomValidity` | `(message: string): void` | - -## Events - -| Event | Description | -|------------|------------------| -| `action` | {ActionDetail} | -| `change` | | -| `closed` | | -| `invalid` | | -| `opened` | | -| `selected` | {SelectedDetail} | +| Name | Description | +| --------- | ------------------------------------------------------------------------------------------------------ | +| _default_ | This is a default/unnamed slot to assign text content. *deprecated* please use _text_ property instead | diff --git a/components/badge/src/vwc-badge-base.ts b/components/badge/src/vwc-badge-base.ts index a7aa973f0..4d46e509e 100644 --- a/components/badge/src/vwc-badge-base.ts +++ b/components/badge/src/vwc-badge-base.ts @@ -1,6 +1,9 @@ import { html, LitElement, property, TemplateResult } from 'lit-element'; +import { nothing } from 'lit-html'; +import { classMap } from 'lit-html/directives/class-map'; + import { Connotation, Shape, Layout } from '@vonage/vvd-foundation/constants'; import { handleMultipleDenseProps } from '@vonage/vvd-foundation/general-utils'; @@ -21,6 +24,9 @@ type BadgeLayout = Extract< type BadgeShape = Extract; +/** + * @slot - This is a default/unnamed slot to assign text content. *deprecated* please use _text_ property instead + */ export class VWCBadgeBase extends LitElement { @property({ type: String, reflect: true }) connotation?: BadgeConnotation; @@ -37,11 +43,38 @@ export class VWCBadgeBase extends LitElement { @property({ type: Boolean, reflect: true }) enlarged = false; + @property({ type: String, reflect: true }) + text?: string; + + @property({ type: String, reflect: true }) + icon?: string; + + @property({ type: String, reflect: true }) + iconTrailing?: string; + + protected renderIcon(type?: string, isTrailingIcon = false): TemplateResult | typeof nothing { + const classes = { + 'icon--leading': !isTrailingIcon, + 'icon--trailing': isTrailingIcon + }; + + return type ? + html`` + : nothing; + } + protected updated(changes: Map): void { - handleMultipleDenseProps(this, changes); + handleMultipleDenseProps(this, changes); } protected render(): TemplateResult { - return html``; + return html` + + ${this.renderIcon(this.icon)} + + ${this.text || nothing} + + ${this.renderIcon(this.iconTrailing, true)} + `; } } diff --git a/components/badge/src/vwc-badge.scss b/components/badge/src/vwc-badge.scss index e5270fd9a..9389dbd31 100644 --- a/components/badge/src/vwc-badge.scss +++ b/components/badge/src/vwc-badge.scss @@ -31,6 +31,17 @@ $vvd-badge-block-size: --#{$namespace}-block-size; :host { #{$vvd-badge-block-size}: 24px; +} + +:host([dense]) { + #{$vvd-badge-block-size}: 20px; +} + +:host([enlarged]) { + #{$vvd-badge-block-size}: 28px; +} + +.vwc-badge { @include typography.typography-cat-shorthand('caption-bold'); display: inline-block; padding: 0 10px; @@ -39,24 +50,24 @@ $vvd-badge-block-size: --#{$namespace}-block-size; border-radius: var(--#{$namespace}-shape); color: var(--#{$namespace}-color); line-height: var(#{$vvd-badge-block-size}); -} -:host([layout="outlined"i]) { - --color: var(#{scheme-variables.$vvd-color-on-base}); -} + :host([dense]) & { + padding: 0 8px; + } -:host([disabled]) { - background-color: var(#{scheme-variables.$vvd-color-neutral-30}); - color: var(#{scheme-variables.$vvd-color-neutral-50}); + :host([enlarged]) & { + padding: 0 12px; + } } -:host([dense]) { - #{$vvd-badge-block-size}: 20px; - padding: 0 8px; -} +.icon { + --icon-size: 12px; + $margin: 8px; -:host([enlarged]) { - #{$vvd-badge-block-size}: 28px; - padding: 0 12px; + &.icon--leading { + margin-inline-end: $margin; + } + &.icon--trailing { + margin-inline-start: $margin; + } } - diff --git a/components/badge/src/vwc-badge.ts b/components/badge/src/vwc-badge.ts index cbbc8a5bd..0bd23db5c 100644 --- a/components/badge/src/vwc-badge.ts +++ b/components/badge/src/vwc-badge.ts @@ -1,9 +1,14 @@ import '@vonage/vvd-core'; +import '@vonage/vwc-icon'; import { customElement } from 'lit-element'; import { VWCBadgeBase } from './vwc-badge-base.js'; import { style } from './vwc-badge.css.js'; +/** + * Represents a badge custom element. + * badge is a label that holds small amounts of information. A badge can be used to display unread notifications, or to label a block of text. Badges don’t work for navigation because they can't include a hyperlink. + */ @customElement('vwc-badge') export class VWCBadge extends VWCBadgeBase { static styles = style; diff --git a/components/badge/stories/badge-introduction.config.mjs b/components/badge/stories/badge-introduction.config.mjs new file mode 100644 index 000000000..0673ebdec --- /dev/null +++ b/components/badge/stories/badge-introduction.config.mjs @@ -0,0 +1,14 @@ +export default { + sourcePath: '../readme.md', + outputName: 'badge-introduction', + story: { + title: 'Alpha/Components/Badge/Introduction', + name: 'Introduction', + parameters: { + options: { + showPanel: false, + isToolshown: false + } + } + } +}; diff --git a/components/badge/stories/badge.stories.js b/components/badge/stories/badge.stories.js index 4eaefbfa7..4902fa144 100644 --- a/components/badge/stories/badge.stories.js +++ b/components/badge/stories/badge.stories.js @@ -7,24 +7,29 @@ export default { title: 'Components/Badge', component: 'vwc-badge', argTypes -} +}; -const Template = args => html`I'm a badge`; +const Template = args => html``; export const Basic = Template.bind({}); -Basic.args = { connotation: 'cta', layout: 'filled' }; +Basic.args = { connotation: 'cta', layout: 'filled', text: 'badge' }; + +export const WithIcons = Template.bind({}); +WithIcons.args = { + connotation: 'cta', layout: 'filled', text: 'badge', icon: 'check-line', iconTrailing: 'check-line' +}; export const Soft = Template.bind({}); -Soft.args = { connotation: 'cta', layout: 'soft' }; +Soft.args = { connotation: 'cta', layout: 'soft', text: 'badge' }; export const Outlined = Template.bind({}); -Outlined.args = { layout: 'outlined' }; +Outlined.args = { layout: 'outlined', text: 'badge' }; export const PillShape = Template.bind({}); -PillShape.args = { layout: 'filled', shape: 'pill' }; +PillShape.args = { layout: 'filled', shape: 'pill', text: 'badge' }; export const Dense = Template.bind({}); -Dense.args = { layout: 'filled', dense: '' }; +Dense.args = { layout: 'filled', dense: '', text: 'badge' }; export const Enlarged = Template.bind({}); -Enlarged.args = { layout: 'filled', enlarged: '' }; +Enlarged.args = { layout: 'filled', enlarged: '', text: 'badge' }; diff --git a/components/badge/test/badge.connotation.test.js b/components/badge/test/badge.connotation.test.js deleted file mode 100644 index fbd37f25a..000000000 --- a/components/badge/test/badge.connotation.test.js +++ /dev/null @@ -1,76 +0,0 @@ -import '../vwc-badge.js'; -import { - waitNextTask, - textToDomToParent, - isolatedElementsCreation, -} from '../../../test/test-helpers.js'; -import { - assertConnotationAttribute, - assertConnotationProperty, -} from '@vonage/vvd-foundation/test/connotation.test.js'; - -const VWC_BADGE = 'vwc-badge'; -const CONNOTATIONS_SUPPORTED = [ - 'primary', - 'cta', - 'success', - 'alert', - 'info', -]; - -describe('badge connotation', () => { - const addElement = isolatedElementsCreation(); - - it(`should sync badge class member 'connotation' and html attribute 'connotation'`, async function () { - const [badge] = addElement( - textToDomToParent(`<${VWC_BADGE}>Badge Text`) - ); - await waitNextTask(); - - const syncMatchFn = connotation => badge.connotation == connotation && - badge.getAttribute('connotation') == connotation; - - let connotationValue = 'cta'; - badge.connotation = connotationValue; - await waitNextTask(); - const propertyChangesAffectsAttribute = syncMatchFn(connotationValue); - - connotationValue = 'primary'; - badge.setAttribute('connotation', connotationValue); - await waitNextTask(); - const attributeChangesAffectsProperty = syncMatchFn(connotationValue); - - expect( - propertyChangesAffectsAttribute, - 'Property change did not apply to attribute' - ).to.equal(true); - expect( - attributeChangesAffectsProperty, - 'Attribute change did not apply to property' - ).to.equal(true); - }); - - for (const connotation of CONNOTATIONS_SUPPORTED) { - it(`should reflect '${connotation}' connotation (attribute) visually`, async () => { - const [badge] = addElement( - textToDomToParent(`<${VWC_BADGE}>Badge Text`) - ); - await assertConnotationAttribute({ - element: badge, - connotation: connotation, - stylesAffected: ['backgroundColor'], - }); - }); - - it(`should reflect '${connotation}' connotation (property) visually`, async () => { - const [badge] = addElement( - textToDomToParent(`<${VWC_BADGE}>Badge Text`) - ); - await assertConnotationProperty({ - element: badge, - connotation: connotation, - stylesAffected: ['backgroundColor'], - }); - }); - } -}); diff --git a/components/badge/test/badge.test.js b/components/badge/test/badge.test.js index e3b790191..161526953 100644 --- a/components/badge/test/badge.test.js +++ b/components/badge/test/badge.test.js @@ -1,6 +1,5 @@ import '../vwc-badge.js'; import { - waitNextTask, textToDomToParent, assertComputedStyle, isolatedElementsCreation, @@ -22,86 +21,167 @@ describe('badge', () => { ); }); - it('should internal contents', async () => { - const [actualElement] = addElement( - textToDomToParent(`<${VWC_BADGE}>Badge Text`) + it('should match internal contents', async () => { + const [badge] = addElement( + textToDomToParent(`<${VWC_BADGE} text="badge">`) ); - await waitNextTask(); - expect(actualElement.shadowRoot.innerHTML).to.equalSnapshot(); + expect(badge.shadowRoot.innerHTML).to.equalSnapshot(); + }); + + it('should match internal contents (legacy)', async () => { + const [badge] = addElement( + textToDomToParent(`<${VWC_BADGE}>badge`) + ); + await badge.updateComplete; + expect(badge.shadowRoot.innerHTML).to.equalSnapshot(); }); describe('typography', function () { it(`should have set badge (text, default) typography correct`, async function () { const [badge] = addElement( - textToDomToParent(`<${VWC_BADGE}>Badge Text`) + textToDomToParent(`<${VWC_BADGE} text="badge">`) ); - await waitNextTask(); - expect(badge).to.exist; - assertComputedStyle(badge, { ...(await getTypographyStyle('caption-bold')), lineHeight: '24px' }); + await badge.updateComplete; + const container = badge.shadowRoot.querySelector('.vwc-badge'); + assertComputedStyle(container, { ...(await getTypographyStyle('caption-bold')), lineHeight: '24px' }); }); }); describe('sizing', () => { it('should have normal size by default', async () => { - const addedElements = addElement( - textToDomToParent(`<${VWC_BADGE}>I'm a badge`) + const [badge] = addElement( + textToDomToParent(`<${VWC_BADGE} text="badge">`) ); - const actualElement = addedElements[0]; - await waitNextTask(); - assertComputedStyle(actualElement, { height: '24px' }); + await badge.updateComplete; + const container = badge.shadowRoot.querySelector('.vwc-badge'); + assertComputedStyle(container, { height: '24px' }); }); it('should have dense size when dense', async () => { - const addedElements = addElement( - textToDomToParent(`<${VWC_BADGE} dense>I'm a badge`) + const [badge] = addElement( + textToDomToParent(`<${VWC_BADGE} dense text="badge">`) ); - const actualElement = addedElements[0]; - await waitNextTask(); - assertComputedStyle(actualElement, { height: '20px' }); + await badge.updateComplete; + const container = badge.shadowRoot.querySelector('.vwc-badge'); + assertComputedStyle(container, { height: '20px' }); }); it('should have enlarged size when enlarged', async () => { - const addedElements = addElement( - textToDomToParent(`<${VWC_BADGE} enlarged>I'm a badge`) + const [badge] = addElement( + textToDomToParent(`<${VWC_BADGE} enlarged text="badge">`) ); - const actualElement = addedElements[0]; - await waitNextTask(); - assertComputedStyle(actualElement, { height: '28px' }); + await badge.updateComplete; + const container = badge.shadowRoot.querySelector('.vwc-badge'); + assertComputedStyle(container, { height: '28px' }); }); }); describe('shape', () => { - let actualElement; + let badge; beforeEach(async () => { - const addedElements = addElement( + const [el] = addElement( textToDomToParent( - `<${VWC_BADGE} layout="filled">I'm a badge` + `<${VWC_BADGE} layout="filled" text="badge">` ) ); - await waitNextTask(); - actualElement = addedElements[0]; + await el.updateComplete; + badge = el; }); it('should have rounded shape by default', async () => { + const container = badge.shadowRoot.querySelector('.vwc-badge'); assertComputedStyle( - actualElement, + container, shapeStyles('rounded', 'badge') ); }); it('should have rounded shape when shape set to rounded', async () => { - actualElement.shape = 'rounded'; - await waitNextTask(); + badge.shape = 'rounded'; + await badge.updateComplete; + + const container = badge.shadowRoot.querySelector('.vwc-badge'); assertComputedStyle( - actualElement, + container, shapeStyles('rounded', 'badge') ); }); it('should have pill shape when shape set to pill', async () => { - actualElement.shape = 'pill'; - await waitNextTask(); - assertComputedStyle(actualElement, shapeStyles('pill', 'badge')); + badge.shape = 'pill'; + await badge.updateComplete; + + const container = badge.shadowRoot.querySelector('.vwc-badge'); + assertComputedStyle(container, + shapeStyles('pill', 'badge')); + }); + }); + + describe('icons', () => { + it('should have icons when icons are set', async () => { + const [badge] = addElement( + textToDomToParent( + `<${VWC_BADGE} icon="thumbs-down-line" iconTrailing="thumbs-up-line">` + ) + ); + + await badge.updateComplete; + await badge.updateComplete; + expect(badge.shadowRoot.innerHTML).to.equalSnapshot(); + }); + + it('should have icons added when icons are set dynamically (property)', async () => { + const [badge] = addElement( + textToDomToParent( + `<${VWC_BADGE}>` + ) + ); + + badge.icon = 'thumbs-down-line'; + badge.iconTrailing = 'thumbs-down-line'; + await badge.updateComplete; + await badge.updateComplete; + expect(badge.shadowRoot.innerHTML).to.equalSnapshot(); + }); + + it('should have icons added when icons are set dynamically (attribute)', async () => { + const [badge] = addElement( + textToDomToParent( + `<${VWC_BADGE}>` + ) + ); + + badge.setAttribute('icon', 'thumbs-down-line'); + badge.setAttribute('iconTrailing', 'thumbs-up-line'); + await badge.updateComplete; + await badge.updateComplete; + expect(badge.shadowRoot.innerHTML).to.equalSnapshot(); + }); + + it('should have icons removed when icons are unset (property)', async () => { + const [badge] = addElement( + textToDomToParent( + `<${VWC_BADGE} icon="thumbs-down-line" iconTrailing="thumbs-up-line">` + ) + ); + + badge.icon = undefined; + badge.iconTrailing = undefined; + await badge.updateComplete; + expect(badge.shadowRoot.innerHTML).to.equalSnapshot(); + }); + + it('should have icon removed when icon is unset (attribute)', async () => { + const [badge] = addElement( + textToDomToParent( + `<${VWC_BADGE} icon="thumbs-down-line" iconTrailing="thumbs-up-line">` + ) + ); + + badge.removeAttribute('icon'); + badge.removeAttribute('iconTrailing'); + await badge.updateComplete; + expect(badge.shadowRoot.innerHTML).to.equalSnapshot(); }); }); }); diff --git a/ui-tests/snapshots/vwc-badge.png b/ui-tests/snapshots/vwc-badge.png index 184d5972c..ddcbe238a 100644 Binary files a/ui-tests/snapshots/vwc-badge.png and b/ui-tests/snapshots/vwc-badge.png differ diff --git a/ui-tests/tests/vwc-badge/index.js b/ui-tests/tests/vwc-badge/index.js index cbc9dc623..9bb9c7d9d 100644 --- a/ui-tests/tests/vwc-badge/index.js +++ b/ui-tests/tests/vwc-badge/index.js @@ -4,15 +4,20 @@ import '@vonage/vwc-badge'; export async function createElementVariations(wrapper) { const badgeElementWrapper = document.createElement('div'); - badgeElementWrapper.innerHTML = - ` -I'm a badge -I'm a badge -I'm a badge -I'm a badge -I'm a badge -I'm a badge -`; + badgeElementWrapper.innerHTML = ` + + + + + + + + + + + + + `; wrapper.appendChild(badgeElementWrapper); await vvdCore.settled;