diff --git a/__snapshots__/icon button.md b/__snapshots__/icon button.md index a26cde0aba..faa877048e 100644 --- a/__snapshots__/icon button.md +++ b/__snapshots__/icon button.md @@ -4,13 +4,13 @@ ```html <button - aria-label="" + aria-label="bin" class="mdc-icon-button" > <vwc-icon - class="icon" + class="vwc-icon" size="small" - type="" + type="bin" > </vwc-icon> <span class="default-slot-container"> diff --git a/common/foundation/scss/mixins/_layout-mixins.scss b/common/foundation/scss/mixins/_layout-mixins.scss index f1bdabeb1c..613ee5b6db 100644 --- a/common/foundation/scss/mixins/_layout-mixins.scss +++ b/common/foundation/scss/mixins/_layout-mixins.scss @@ -3,7 +3,7 @@ @use '../functions'; // override to apply relevance -$layouts: filled outlined soft text !default; +$layouts: filled outlined soft text ghost !default; //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// $allLayouts: ( @@ -11,6 +11,7 @@ $allLayouts: ( outlined: 'outlined', soft: 'soft', text: 'text', + ghost: 'ghost', ); $bg: var(#{color-semantic.$vvd-color-connotation}); @@ -32,6 +33,12 @@ $bg: var(#{color-semantic.$vvd-color-connotation}); --opaque: 20; } +%text, +%ghost { + --background-color: transparent; + --color: var(#{scheme-variables.$vvd-color-on-base}); +} + @mixin layout() { @each $layout in functions.pick($layouts, $allLayouts) { #{':host([layout="#{$layout}"])'} { diff --git a/common/foundation/scss/mixins/_shape-mixins.scss b/common/foundation/scss/mixins/_shape-mixins.scss index eedcb93537..4cf9a307a0 100644 --- a/common/foundation/scss/mixins/_shape-mixins.scss +++ b/common/foundation/scss/mixins/_shape-mixins.scss @@ -18,6 +18,7 @@ $shapes: ( rounded: var(#{shape-var-names.$shape-border-radius-md-css-variable-name}), pill: var(#{shape-var-names.$shape-border-radius-lg-css-variable-name}), + circled: 50%, ), $default: rounded ) { diff --git a/common/foundation/src/constants.ts b/common/foundation/src/constants.ts index e4fa40f613..561027e9db 100644 --- a/common/foundation/src/constants.ts +++ b/common/foundation/src/constants.ts @@ -13,6 +13,7 @@ export enum Connotation { export enum Shape { Rounded = 'rounded', Pill = 'pill', + Circled = 'circled', } export enum Size { @@ -27,4 +28,5 @@ export enum Layout { Filled = 'filled', Outlined = 'outlined', Soft = 'soft', + Ghost = 'ghost', } diff --git a/components/badge/src/vwc-badge-base.ts b/components/badge/src/vwc-badge-base.ts index 1b85eb8211..9c8257b37f 100644 --- a/components/badge/src/vwc-badge-base.ts +++ b/components/badge/src/vwc-badge-base.ts @@ -17,12 +17,14 @@ type BadgeLayout = Extract< Layout.Filled | Layout.Outlined | Layout.Soft >; +type BadgeShape = Extract<Shape, Shape.Rounded | Shape.Pill>; + export class BadgeBase extends LitElement { @property({ type: String, reflect: true }) connotation: BadgeConnotation = Connotation.Primary; @property({ type: String, reflect: true }) - shape?: Shape; + shape?: BadgeShape; @property({ type: String, reflect: true }) layout: BadgeLayout = Layout.Filled; diff --git a/components/badge/stories/arg-types.js b/components/badge/stories/arg-types.js index 0432f85219..e736b0696c 100644 --- a/components/badge/stories/arg-types.js +++ b/components/badge/stories/arg-types.js @@ -13,7 +13,9 @@ export const argTypes = { shape: { control: { type: 'select', - options: Object.values(Shape) + options: Object.values(Shape).filter(s => [ + Shape.Rounded, Shape.Pill + ].includes(s)), } }, layout: { diff --git a/components/button/src/vwc-button.ts b/components/button/src/vwc-button.ts index 3042c2ef3c..72afd871a4 100644 --- a/components/button/src/vwc-button.ts +++ b/components/button/src/vwc-button.ts @@ -35,6 +35,8 @@ type ButtonConnotation = Extract< | Connotation.Announcement >; +type ButtonShape = Extract<Shape, Shape.Rounded | Shape.Pill>; + /** * This component is an extension of [<mwc-button>](https://github.com/material-components/material-components-web-components/tree/master/packages/button) * Our button supports native features like the 'form' and 'type' attributes @@ -51,7 +53,7 @@ export class VWCButton extends MWCButton { connotation: ButtonConnotation = Connotation.Primary; @property({ type: String, reflect: true }) - shape?: Shape; + shape?: ButtonShape; @property({ type: String, reflect: true }) type: ButtonType[number] = 'submit'; diff --git a/components/button/stories/arg-types.js b/components/button/stories/arg-types.js index 89c3dc9331..b980342897 100644 --- a/components/button/stories/arg-types.js +++ b/components/button/stories/arg-types.js @@ -19,7 +19,9 @@ export const argTypes = { shape: { control: { type: 'select', - options: Object.values(Shape), + options: Object.values(Shape).filter(s => [ + Shape.Rounded, Shape.Pill + ].includes(s)), } }, dense: { diff --git a/components/button/test/button.connotation.test.js b/components/button/test/button.connotation.test.js index 33fcc13aa9..6cb584a3ee 100644 --- a/components/button/test/button.connotation.test.js +++ b/components/button/test/button.connotation.test.js @@ -1,4 +1,3 @@ -import '../vwc-button.js'; import { textToDomToParent, isolatedElementsCreation, @@ -7,16 +6,19 @@ import { assertConnotationAttribute, assertConnotationProperty, } from '@vonage/vvd-foundation/test/connotation.test.js'; +import { Connotation } from '@vonage/vvd-foundation/constants'; + +const CONNOTATIONS_SUPPORTED = Object.values(Connotation).filter((c) => + [ + Connotation.Primary, + Connotation.CTA, + Connotation.Success, + Connotation.Alert, + Connotation.Info, + Connotation.Announcement, + ].includes(c) +); -const VWC_BUTTON = 'vwc-button'; -const CONNOTATIONS_SUPPORTED = [ - 'primary', - 'cta', - 'success', - 'alert', - 'info', - 'announcement', -]; const LAYOUTS_AFFECTED = [ { layout: 'filled', @@ -36,7 +38,7 @@ const LAYOUTS_AFFECTED = [ }, ]; -describe('button connotation', () => { +export async function connotationTestCases(COMPONENT_NAME) { const addElement = isolatedElementsCreation(); for (const { layout, childrenAffected, stylesAffected } of LAYOUTS_AFFECTED) { @@ -44,7 +46,9 @@ describe('button connotation', () => { it(`should reflect '${connotation}' connotation (attribute) visually, ${layout}`, async () => { const [button] = addElement( textToDomToParent( - `<${VWC_BUTTON} layout="${layout}">Button</${VWC_BUTTON}>` + `<${COMPONENT_NAME} layout="${layout}" icon="bin"> + ${COMPONENT_NAME === 'vwc-button' ? 'Button' : ''} + </${COMPONENT_NAME}>` ) ); await assertConnotationAttribute({ @@ -58,7 +62,9 @@ describe('button connotation', () => { it(`should reflect '${connotation}' connotation (property) visually, ${layout}`, async () => { const [button] = addElement( textToDomToParent( - `<${VWC_BUTTON} layout="${layout}">Button</${VWC_BUTTON}>` + `<${COMPONENT_NAME} layout="${layout}" icon="bin"> + ${COMPONENT_NAME === 'vwc-button' ? 'Button' : ''} + </${COMPONENT_NAME}>` ) ); await assertConnotationProperty({ @@ -70,4 +76,4 @@ describe('button connotation', () => { }); } } -}); +} diff --git a/components/button/test/button.test.js b/components/button/test/button.test.js index ac9da7742e..af68ecb479 100644 --- a/components/button/test/button.test.js +++ b/components/button/test/button.test.js @@ -4,7 +4,12 @@ import { textToDomToParent, assertComputedStyle, } from '../../../test/test-helpers.js'; -import { shapeStyles } from '../../../test/style-utils.js'; +import { + sizingTestCases, + shapeRoundedTestCases, + shapePillTestCases, +} from '../../../test/shared'; +import { connotationTestCases } from './button.connotation.test.js'; import { chaiDomDiff } from '@open-wc/semantic-dom-diff'; import { isolatedElementsCreation, @@ -362,65 +367,15 @@ describe('button', () => { }); describe('sizing', () => { - it('should have normal size by default', async () => { - const addedElements = addElement( - textToDomToParent(`<${COMPONENT_NAME}>Button Text</${COMPONENT_NAME}>`) - ); - const actualElement = addedElements[0]; - await waitNextTask(); - assertComputedStyle(actualElement, { height: '40px' }); - }); - - it('should have dense size when dense', async () => { - const addedElements = addElement( - textToDomToParent( - `<${COMPONENT_NAME} dense>Button Text</${COMPONENT_NAME}>` - ) - ); - const actualElement = addedElements[0]; - await waitNextTask(); - assertComputedStyle(actualElement, { height: '32px' }); - }); - - it('should have enlarged size when enlarged', async () => { - const addedElements = addElement( - textToDomToParent( - `<${COMPONENT_NAME} enlarged>Button Text</${COMPONENT_NAME}>` - ) - ); - const actualElement = addedElements[0]; - await waitNextTask(); - assertComputedStyle(actualElement, { height: '48px' }); - }); + sizingTestCases(COMPONENT_NAME); }); describe('shape', () => { - let formElement, actualElement; - beforeEach(async () => { - const addedElements = addElement( - textToDomToParent( - `<${COMPONENT_NAME} layout="filled">Button Text</${COMPONENT_NAME}>` - ) - ); - await waitNextTask(); - formElement = addedElements[0]; - actualElement = formElement.shadowRoot.querySelector('#button'); - }); - - it('should have rounded shape by default', async () => { - assertComputedStyle(actualElement, shapeStyles('rounded')); - }); - - it('should have rounded shape when shape set to rounded', async () => { - formElement.shape = 'rounded'; - await waitNextTask(); - assertComputedStyle(actualElement, shapeStyles('rounded')); - }); + shapeRoundedTestCases(COMPONENT_NAME); + shapePillTestCases(COMPONENT_NAME); + }); - it('should have pill shape when shape set to pill', async () => { - formElement.shape = 'pill'; - await waitNextTask(); - assertComputedStyle(actualElement, shapeStyles('pill')); - }); + describe('button connotation', () => { + connotationTestCases(COMPONENT_NAME); }); }); diff --git a/components/icon-button/src/vwc-icon-button.scss b/components/icon-button/src/vwc-icon-button.scss index a3c4a2991b..87aeca2548 100644 --- a/components/icon-button/src/vwc-icon-button.scss +++ b/components/icon-button/src/vwc-icon-button.scss @@ -1,22 +1,52 @@ @use '@vonage/vvd-design-tokens/build/scss/semantic-variables/scheme-variables'; @use '@vonage/vvd-foundation/scss/variable-names/color-semantic-variable-names' as color-semantic; +@use '@vonage/vvd-foundation/scss/mixins/shape-mixins'; +@use '@vonage/vvd-foundation/scss/mixins/color-connotation-mixins' with ( + $connotations: primary cta success alert info announcement +); +@use '@vonage/vvd-foundation/scss/mixins/layout-mixins' with ( + $layouts: filled outlined ghost +); :host { .mdc-icon-button { + background-color: var(--background-color); + border: var(--border); + border-radius: var(--border-radius); + color: var(--color); display: inline-flex; align-items: center; justify-content: center; + overflow: hidden; + } + + .vwc-icon { + --size: 20px; + height: var(--size); + width: var(--size); } } // theming -:host { - .mdc-icon-button { - color: var(#{scheme-variables.$vvd-color-on-base}); +@include color-connotation-mixins.connotations-context( + ':host([layout="filled"]#{color-connotation-mixins.$connotation-placeholder}),:host([layout="outlined"]#{color-connotation-mixins.$connotation-placeholder})' +); + +:host([layout='filled']) { + .mdc-icon-button:disabled { + background-color: var(#{scheme-variables.$vvd-color-contrast-soft}); + } +} + +:host([layout='outlined']) { + .mdc-icon-button:disabled { + border-color: var(#{color-semantic.$formfield-disabled-ink}); } +} +:host { .mdc-icon-button:disabled { - color: var( #{color-semantic.$formfield-disabled-ink}); + color: var(#{color-semantic.$formfield-disabled-ink}); } } @@ -27,8 +57,25 @@ :host([dense]) { --mdc-icon-button-size: 32px; + + .vwc-icon { + --size: 16px; + } } :host([enlarged]) { --mdc-icon-button-size: 48px; + + .vwc-icon { + --size: 24px; + } } + +@include shape-mixins.shape( + $shapes: ( + rounded: 6px, + circled: 50%, + ) +); + +@include layout-mixins.layout(); diff --git a/components/icon-button/src/vwc-icon-button.ts b/components/icon-button/src/vwc-icon-button.ts index d649fba11d..c8447d4763 100644 --- a/components/icon-button/src/vwc-icon-button.ts +++ b/components/icon-button/src/vwc-icon-button.ts @@ -5,6 +5,8 @@ import { IconButton as MWCIconButton } from '@material/mwc-icon-button'; import { style as vwcButtonStyle } from './vwc-icon-button.css'; import { style as mwcIconButtonStyle } from '@material/mwc-icon-button/mwc-icon-button-css.js'; import { style as styleCoupling } from '@vonage/vvd-style-coupling/vvd-style-coupling.css.js'; +import { Connotation, Shape, Layout } from '@vonage/vvd-foundation/constants'; +import { handleMultipleDenseProps } from '@vonage/vvd-foundation/general-utils'; import { html, TemplateResult } from 'lit-element'; declare global { @@ -17,11 +19,37 @@ declare global { // @ts-ignore MWCIconButton.styles = [styleCoupling, mwcIconButtonStyle, vwcButtonStyle]; +type IconButtonLayout = Extract< + Layout, + Layout.Filled | Layout.Outlined | Layout.Ghost +>; + +type IconButtonShape = Extract<Shape, Shape.Rounded | Shape.Circled>; + +type IconButtonConnotation = Extract< + Connotation, + | Connotation.Primary + | Connotation.CTA + | Connotation.Success + | Connotation.Alert + | Connotation.Info + | Connotation.Announcement +>; + /** * This component is an extension of [<mwc-icon-button>](https://github.com/material-components/material-components-web-components/tree/master/packages/icon-button) */ @customElement('vwc-icon-button') export class VWCIconButton extends MWCIconButton { + @property({ type: String, reflect: true }) + layout: IconButtonLayout = Layout.Ghost; + + @property({ type: String, reflect: true }) + connotation: IconButtonConnotation = Connotation.Primary; + + @property({ type: String, reflect: true }) + shape?: IconButtonShape; + @property({ type: Boolean, reflect: true }) dense = false; @@ -29,18 +57,7 @@ export class VWCIconButton extends MWCIconButton { enlarged = false; protected updated(changes: Map<string, boolean>): void { - if (changes.has('dense')) { - if (this.dense && this.enlarged) { - this.enlarged = false; - } - } - - if (changes.has('enlarged')) { - if (this.enlarged && this.dense) { - this.removeAttribute('dense'); - this.dense = false; - } - } + handleMultipleDenseProps(this, changes); } protected render(): TemplateResult { @@ -66,9 +83,15 @@ export class VWCIconButton extends MWCIconButton { protected renderIcon(): TemplateResult { return html`<vwc-icon - class="icon" + class="vwc-icon" size="small" type="${this.icon}" ></vwc-icon>`; } + + renderRipple(): TemplateResult | '' { + return this.shouldRenderRipple + ? html` <mwc-ripple .disabled="${this.disabled}"></mwc-ripple>` + : ''; + } } diff --git a/components/icon-button/stories/arg-types.js b/components/icon-button/stories/arg-types.js index 5c54336a88..032e7cf979 100644 --- a/components/icon-button/stories/arg-types.js +++ b/components/icon-button/stories/arg-types.js @@ -1,4 +1,30 @@ +import { Connotation, Shape, Layout } from '@vonage/vvd-foundation/constants'; + export const argTypes = { + connotation: { + control: { + type: 'select', + options: Object.values(Connotation).filter(c => [ + Connotation.Primary, Connotation.CTA, Connotation.Success, Connotation.Alert, Connotation.Info, Connotation.Announcement + ].includes(c)), + } + }, + layout: { + control: { + type: 'select', + options: Object.values(Layout).filter(l => [ + Layout.Filled, Layout.Outlined, Layout.Ghost + ].includes(l)), + } + }, + shape: { + control: { + type: 'select', + options: Object.values(Shape).filter(s => [ + Shape.Rounded, Shape.Circled + ].includes(s)), + } + }, dense: { control: { type: 'inline-radio', diff --git a/components/icon-button/stories/icon-button.stories.js b/components/icon-button/stories/icon-button.stories.js index d1f24574c5..8496151fff 100644 --- a/components/icon-button/stories/icon-button.stories.js +++ b/components/icon-button/stories/icon-button.stories.js @@ -14,11 +14,20 @@ const Template = args => html`<vwc-icon-button ...=${spread(args)}></vwc-icon-bu export const Basic = Template.bind({}); Basic.args = { icon: 'bin' }; +export const Filled = Template.bind({}); +Filled.args = { icon: 'bin', layout: 'filled' }; + +export const Outlined = Template.bind({}); +Outlined.args = { icon: 'bin', layout: 'outlined' }; + +export const CircledShape = Template.bind({}); +CircledShape.args = { icon: 'bin', shape: 'circled', layout: 'filled' }; + export const Dense = Template.bind({}); -Dense.args = { icon: 'home', dense: true }; +Dense.args = { icon: 'home', dense: true, layout: 'filled' }; export const Enlarged = Template.bind({}); -Enlarged.args = { icon: 'home', enlarged: true }; +Enlarged.args = { icon: 'home', enlarged: true, layout: 'filled' }; export const Disabled = Template.bind({}); -Disabled.args = { icon: 'code', disabled: true }; \ No newline at end of file +Disabled.args = { icon: 'code', disabled: true, layout: 'filled' }; \ No newline at end of file diff --git a/components/icon-button/test/icon-button.test.js b/components/icon-button/test/icon-button.test.js index fb7e3cbdbc..14d280e168 100644 --- a/components/icon-button/test/icon-button.test.js +++ b/components/icon-button/test/icon-button.test.js @@ -1,5 +1,16 @@ import '../vwc-icon-button.js'; -import { waitNextTask, textToDomToParent } from '../../../test/test-helpers.js'; +import { + waitNextTask, + textToDomToParent, + assertComputedStyle, +} from '../../../test/test-helpers.js'; +import { layoutStyles, topLevelSelectors } from '../../../test/style-utils.js'; +import { + sizingTestCases, + shapeRoundedTestCases, + shapeCircledTestCases, +} from '../../../test/shared'; +import { connotationTestCases } from '../../button/test/button.connotation.test.js'; import { chaiDomDiff } from '@open-wc/semantic-dom-diff'; import { isolatedElementsCreation } from '../../../test/test-helpers'; @@ -18,9 +29,58 @@ describe('icon button', () => { it('should internal contents', async () => { const [e] = addElement( - textToDomToParent(`<${COMPONENT_NAME}></${COMPONENT_NAME}>`) + textToDomToParent(`<${COMPONENT_NAME} icon="bin"></${COMPONENT_NAME}>`) ); await waitNextTask(); expect(e.shadowRoot.innerHTML).to.equalSnapshot(); }); + + describe('sizing', () => { + sizingTestCases(COMPONENT_NAME); + }); + + describe('shape', () => { + shapeRoundedTestCases(COMPONENT_NAME); + shapeCircledTestCases(COMPONENT_NAME); + }); + + describe('icon button connotation', () => { + connotationTestCases(COMPONENT_NAME); + }); + + describe('icon button layout', () => { + let formElement, actualElement; + beforeEach(async () => { + const addedElements = addElement( + textToDomToParent(`<${COMPONENT_NAME} icon="bin"></${COMPONENT_NAME}>`) + ); + await waitNextTask(); + formElement = addedElements[0]; + actualElement = formElement.shadowRoot.querySelector( + topLevelSelectors[COMPONENT_NAME] + ); + }); + + it('should have ghost layout by default', async () => { + assertComputedStyle(actualElement, layoutStyles('ghost')); + }); + + it('should have ghost layout when layout set to ghost', async () => { + formElement.layout = 'ghost'; + await waitNextTask(); + assertComputedStyle(actualElement, layoutStyles('ghost')); + }); + + it('should have filled layout when layout set to filled', async () => { + formElement.layout = 'filled'; + await waitNextTask(); + assertComputedStyle(actualElement, layoutStyles('filled')); + }); + + it('should have outlined layout when layout set to outlined', async () => { + formElement.layout = 'outlined'; + await waitNextTask(); + assertComputedStyle(actualElement, layoutStyles('outlined')); + }); + }); }); diff --git a/components/select/src/vwc-select.ts b/components/select/src/vwc-select.ts index 9fe067c6aa..bd064a194d 100644 --- a/components/select/src/vwc-select.ts +++ b/components/select/src/vwc-select.ts @@ -27,6 +27,8 @@ declare global { // @ts-ignore MWCSelect.styles = [styleCoupling, mwcSelectStyle, vwcSelectStyle]; +type SelectShape = Extract<Shape, Shape.Rounded | Shape.Pill>; + /** * This component is an extension of [<mwc-select>](https://github.com/material-components/material-components-web-components/tree/master/packages/select) */ @@ -36,7 +38,7 @@ export class VWCSelect extends MWCSelect { dense = false; @property({ type: String, reflect: true }) - shape?: Shape; + shape?: SelectShape; @property({ type: String, reflect: true }) form: string | undefined; diff --git a/components/select/stories/arg-types.js b/components/select/stories/arg-types.js index 09149537bb..13817021b3 100644 --- a/components/select/stories/arg-types.js +++ b/components/select/stories/arg-types.js @@ -21,11 +21,13 @@ export const argTypes = { }, }, shape: { - control: { - type: 'select', - options: Object.values(Shape), - } - }, + control: { + type: 'select', + options: Object.values(Shape).filter(s => [ + Shape.Rounded, Shape.Pill + ].includes(s)), + } + }, required: { control: { type: 'inline-radio', diff --git a/components/select/test/select.test.js b/components/select/test/select.test.js index ab5b16b7f9..9aad2b9b82 100644 --- a/components/select/test/select.test.js +++ b/components/select/test/select.test.js @@ -10,7 +10,10 @@ import { isolatedElementsCreation, getTypographyStyle, } from '../../../test/test-helpers.js'; -import { shapeStyles } from '../../../test/style-utils.js'; +import { + shapeRoundedTestCases, + shapePillTestCases, +} from '../../../test/shared'; import { assertDenseStyles, hasNotchedOutline, @@ -325,36 +328,8 @@ describe('select', () => { }); describe('shape', () => { - let formElement, actualElement; - beforeEach(async () => { - const addedElements = addElement( - textToDomToParent(` - <${COMPONENT_NAME} outlined> - <vwc-list-item>Item 1</vwc-list-item> - <vwc-list-item>Item 2</vwc-list-item> - </${COMPONENT_NAME}> - `) - ); - await waitNextTask(); - formElement = addedElements[0]; - actualElement = formElement.shadowRoot.querySelector('.mdc-select'); - }); - - it('should have rounded shape by default', async () => { - assertComputedStyle(actualElement, shapeStyles('rounded')); - }); - - it('should have rounded shape when shape set to rounded', async () => { - formElement.shape = 'rounded'; - await waitNextTask(); - assertComputedStyle(actualElement, shapeStyles('rounded')); - }); - - it('should have pill shape when shape set to pill', async () => { - formElement.shape = 'pill'; - await waitNextTask(); - assertComputedStyle(actualElement, shapeStyles('pill')); - }); + shapeRoundedTestCases(COMPONENT_NAME); + shapePillTestCases(COMPONENT_NAME); }); describe(`performance acceptability`, function () { diff --git a/components/textfield/src/vwc-textfield.ts b/components/textfield/src/vwc-textfield.ts index 5aed438aba..480f49da37 100644 --- a/components/textfield/src/vwc-textfield.ts +++ b/components/textfield/src/vwc-textfield.ts @@ -27,6 +27,8 @@ declare global { } } +type TextfieldShape = Extract<Shape, Shape.Rounded | Shape.Pill>; + /* eslint-disable @typescript-eslint/ban-ts-comment */ // @ts-ignore MWCTextField.styles = [styleCoupling, mwcTextFieldStyle, vwcTextFieldStyle]; @@ -37,7 +39,7 @@ export class VWCTextField extends MWCTextField { dense = false; @property({ type: String, reflect: true }) - shape?: Shape; + shape?: TextfieldShape; @property({ type: String, reflect: true }) form: string | undefined; diff --git a/components/textfield/stories/arg-types.js b/components/textfield/stories/arg-types.js index f4b1105475..6f69bc30e7 100644 --- a/components/textfield/stories/arg-types.js +++ b/components/textfield/stories/arg-types.js @@ -21,11 +21,13 @@ export const argTypes = { }, }, shape: { - control: { - type: 'select', - options: Object.values(Shape), - } - }, + control: { + type: 'select', + options: Object.values(Shape).filter(s => [ + Shape.Rounded, Shape.Pill + ].includes(s)), + } + }, required: { control: { type: 'inline-radio', diff --git a/components/textfield/test/textfield.test.js b/components/textfield/test/textfield.test.js index 2a6f92a8db..acd1c92365 100644 --- a/components/textfield/test/textfield.test.js +++ b/components/textfield/test/textfield.test.js @@ -9,7 +9,10 @@ import { randomAlpha, listenToSubmission, } from '../../../test/test-helpers.js'; -import { shapeStyles } from '../../../test/style-utils.js'; +import { + shapeRoundedTestCases, + shapePillTestCases, +} from '../../../test/shared'; import { typographyTestCases, assertDenseStyles, @@ -288,30 +291,7 @@ describe('textfield', () => { }); describe('shape', () => { - let formElement, actualElement; - beforeEach(async () => { - const addedElements = addElement( - textToDomToParent(`<${COMPONENT_NAME} outlined></${COMPONENT_NAME}>`) - ); - await waitNextTask(); - formElement = addedElements[0]; - actualElement = formElement.shadowRoot.querySelector('.mdc-text-field'); - }); - - it('should have rounded shape by default', async () => { - assertComputedStyle(actualElement, shapeStyles('rounded')); - }); - - it('should have rounded shape when shape set to rounded', async () => { - formElement.shape = 'rounded'; - await waitNextTask(); - assertComputedStyle(actualElement, shapeStyles('rounded')); - }); - - it('should have pill shape when shape set to pill', async () => { - formElement.shape = 'pill'; - await waitNextTask(); - assertComputedStyle(actualElement, shapeStyles('pill')); - }); + shapeRoundedTestCases(COMPONENT_NAME); + shapePillTestCases(COMPONENT_NAME); }); }); diff --git a/test/shared/index.js b/test/shared/index.js new file mode 100644 index 0000000000..b8ddffe0b4 --- /dev/null +++ b/test/shared/index.js @@ -0,0 +1,6 @@ +export { + shapeRoundedTestCases, + shapePillTestCases, + shapeCircledTestCases +} from './shape.test.js'; +export { sizingTestCases } from './sizing.test.js'; diff --git a/test/shared/shape.test.js b/test/shared/shape.test.js new file mode 100644 index 0000000000..2d64e73fcc --- /dev/null +++ b/test/shared/shape.test.js @@ -0,0 +1,61 @@ +import { + waitNextTask, + textToDomToParent, + assertComputedStyle, + isolatedElementsCreation, +} from '../test-helpers.js'; +import { + shapeStyles, + topLevelSelectors, +} from '../style-utils.js'; + +export async function shapeRoundedTestCases(COMPONENT_NAME) { + const addElement = isolatedElementsCreation(); + + const addedElements = addElement( + textToDomToParent(`<${COMPONENT_NAME}></${COMPONENT_NAME}>`) + ); + await waitNextTask(); + const formElement = addedElements[0]; + const actualElement = formElement.shadowRoot.querySelector(topLevelSelectors[COMPONENT_NAME]); + + it('should have rounded shape by default', async () => { + assertComputedStyle(actualElement, shapeStyles('rounded')); + }); + + it('should have rounded shape when shape set to rounded', async () => { + formElement.shape = 'rounded'; + await waitNextTask(); + assertComputedStyle(actualElement, shapeStyles('rounded')); + }); +} + +export async function shapePillTestCases(COMPONENT_NAME) { + const addElement = isolatedElementsCreation(); + + const addedElements = addElement( + textToDomToParent(`<${COMPONENT_NAME} shape="pill"></${COMPONENT_NAME}>`) + ); + await waitNextTask(); + const formElement = addedElements[0]; + const actualElement = formElement.shadowRoot.querySelector(topLevelSelectors[COMPONENT_NAME]); + + it('should have pill shape when shape set to pill', async () => { + assertComputedStyle(actualElement, shapeStyles('pill')); + }); +} + +export async function shapeCircledTestCases(COMPONENT_NAME) { + const addElement = isolatedElementsCreation(); + + const addedElements = addElement( + textToDomToParent(`<${COMPONENT_NAME} shape="circled"></${COMPONENT_NAME}>`) + ); + await waitNextTask(); + const formElement = addedElements[0]; + const actualElement = formElement.shadowRoot.querySelector(topLevelSelectors[COMPONENT_NAME]); + + it('should have circled shape when shape set to circled', async () => { + assertComputedStyle(actualElement, shapeStyles('circled')); + }); +} diff --git a/test/shared/sizing.test.js b/test/shared/sizing.test.js new file mode 100644 index 0000000000..53860712b0 --- /dev/null +++ b/test/shared/sizing.test.js @@ -0,0 +1,40 @@ +import { + waitNextTask, + textToDomToParent, + assertComputedStyle, + isolatedElementsCreation, +} from '../test-helpers.js'; +import { + sizeStyles, + topLevelSelectors, +} from '../style-utils.js'; + +let addElement = isolatedElementsCreation(); + +export async function sizingTestCases(COMPONENT_NAME) { + let formElement, actualElement; + beforeEach(async () => { + const addedElements = addElement( + textToDomToParent(`<${COMPONENT_NAME}></${COMPONENT_NAME}>`) + ); + await waitNextTask(); + formElement = addedElements[0]; + actualElement = formElement.shadowRoot.querySelector(topLevelSelectors[COMPONENT_NAME]); + }); + + it('should have normal size by default', async () => { + assertComputedStyle(actualElement, sizeStyles('default')); + }); + + it('should have dense size when dense', async () => { + formElement.setAttribute('dense', 'true'); + await waitNextTask(); + assertComputedStyle(actualElement, sizeStyles('dense')); + }); + + it('should have enlarged size when enlarged', async () => { + formElement.enlarged = true; + await waitNextTask(); + assertComputedStyle(actualElement, sizeStyles('enlarged')); + }); +} diff --git a/test/style-utils.js b/test/style-utils.js index 1e802dc53d..1b87eddcbe 100644 --- a/test/style-utils.js +++ b/test/style-utils.js @@ -1,21 +1,78 @@ +export const topLevelSelectors = { + 'vwc-button': '.mdc-button', + 'vwc-icon-button': '.mdc-icon-button', + 'vwc-select': '.mdc-select', + 'vwc-textfield': '.mdc-text-field', +} + function borderRadiusStyles(expectedRadius) { return { - borderTopLeftRadius: `${expectedRadius}px`, - borderTopRightRadius: `${expectedRadius}px`, - borderBottomLeftRadius: `${expectedRadius}px`, - borderBottomRightRadius: `${expectedRadius}px`, + borderTopLeftRadius: expectedRadius, + borderTopRightRadius: expectedRadius, + borderBottomLeftRadius: expectedRadius, + borderBottomRightRadius: expectedRadius, }; } export function shapeStyles(shape, element) { const shapeRadius = { - rounded: element === 'badge' ? 4 : 6, - pill: element === 'badge' ? 14 : 24, + rounded: element === 'badge' ? '4px' : '6px', + pill: element === 'badge' ? '14px' : '24px', + circled: '50%', } return borderRadiusStyles(shapeRadius[shape]); } +export function sizeStyles(size) { + const sizes = { + dense: 32, + enlarged: 48, + default: 40, + } + + return { height: `${sizes[size]}px` }; +} + +export function layoutStyles(layout) { + const layouts = { + filled: { + backgroundColor: 'rgb(0, 0, 0)', + borderTopWidth: '0px', + borderRightWidth: '0px', + borderBottomWidth: '0px', + borderLeftWidth: '0px', + color: 'rgb(255, 255, 255)', + }, + outlined: { + backgroundColor: 'rgba(0, 0, 0, 0)', + borderTopWidth: '1px', + borderRightWidth: '1px', + borderBottomWidth: '1px', + borderLeftWidth: '1px', + borderTopStyle: 'solid', + borderRightStyle: 'solid', + borderBottomStyle: 'solid', + borderLeftStyle: 'solid', + borderTopColor: 'rgb(0, 0, 0)', + borderRightColor: 'rgb(0, 0, 0)', + borderBottomColor: 'rgb(0, 0, 0)', + borderLeftColor: 'rgb(0, 0, 0)', + color: 'rgb(0, 0, 0)', + }, + ghost: { + backgroundColor: 'rgba(0, 0, 0, 0)', + borderTopWidth: '0px', + borderRightWidth: '0px', + borderBottomWidth: '0px', + borderLeftWidth: '0px', + color: 'rgb(0, 0, 0)', + }, + } + + return layouts[layout]; +} + export const PRINCIPAL_SCHEME_VARIABLES_FILTER = /-(on-|)(base|surface|primary)$/; export function getSchemeFiles() { @@ -99,4 +156,4 @@ export function assertBaseVarsMatch(scheme, variablesFilter, element) { ); } }); -} \ No newline at end of file +}