Skip to content

Commit

Permalink
feat(button): add dropdown-indicator option (VIV-1741) (#1702)
Browse files Browse the repository at this point in the history
* Factor out chevron into pattern

* feat(button): add dropdown-indicator (VIV-1741)

* Update libs/components/src/lib/button/button.scss

Co-authored-by: Rachel Bratt Tannenbaum <rachelbt@users.noreply.github.com>

* Fix formatting

* Scale chevron size

* Update chevron sizes

---------

Co-authored-by: Rachel Bratt Tannenbaum <rachelbt@users.noreply.github.com>
  • Loading branch information
RichardHelm and rachelbt authored Jul 18, 2024
1 parent f3d7222 commit 5caaf84
Show file tree
Hide file tree
Showing 15 changed files with 176 additions and 50 deletions.
21 changes: 21 additions & 0 deletions libs/components/src/lib/button/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,27 @@ Semantically, buttons are usually used for triggering actions, while links are u
></vwc-button>
```

### Dropdown Indicator

When the button is used to trigger a menu or other dropdown, you can set `dropdown-indicator` to add a chevron to the button.

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

```html preview 200px
<vwc-menu trigger="auto" auto-dismiss placement="bottom-start">
<vwc-button
slot="anchor"
appearance="filled"
label="Menu"
dropdown-indicator
></vwc-button>
<vwc-menu-item icon="copy-line" text="Copy"></vwc-menu-item>
<vwc-menu-item icon="inbox-line" text="Share"></vwc-menu-item>
<vwc-menu-item icon="delete-line" text="Archive"></vwc-menu-item>
</vwc-menu>
```

### Active

Set the `active` attribute to make the button appear pressed.
Expand Down
57 changes: 33 additions & 24 deletions libs/components/src/lib/button/button.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
);
@use '../../../../shared/src/lib/sass/mixins/appearance' as appearance;
@use '../../../../../dist/libs/tokens/scss/size.variables' as size;
@import '../../shared/patterns/chevron';
@include connotation.variables-backwards-compatibility(
'button',
'.control:not(.appearance-ghost-light, .appearance-filled, .appearance-outlined)'
Expand All @@ -36,7 +37,7 @@
display: inline-flex;
box-sizing: border-box;
align-items: center;
justify-content: center;
justify-content: space-between;
border: 0 none;
border-radius: var(#{variables.$button-border-radius});
margin: 0;
Expand Down Expand Up @@ -82,28 +83,15 @@
cursor: not-allowed;
}

&.icon-only {
contain: size;
padding-inline: 0;
place-content: center;

@supports (aspect-ratio: 1) {
aspect-ratio: 1;
}

@supports not (aspect-ratio: 1) {
inline-size: var(#{variables.$block-size});
}
}

/* Size */
&:not(.stacked) {
&.size-super-condensed {
@include mixins.get-size-properties(
#{size.$vvd-size-super-condensed},
#{constants.$vvd-typography-base-condensed-bold},
4px,
8px
8px,
10px
);
}

Expand All @@ -112,6 +100,7 @@
#{size.$vvd-size-condensed},
#{constants.$vvd-typography-base-condensed-bold},
8px,
12px,
12px
);
}
Expand All @@ -121,7 +110,8 @@
#{size.$vvd-size-expanded},
#{constants.$vvd-typography-base-extended-bold},
10px,
20px
20px,
16px
);
}

Expand All @@ -130,6 +120,7 @@
#{size.$vvd-size-normal},
#{constants.$vvd-typography-base-bold},
8px,
16px,
16px
);
}
Expand Down Expand Up @@ -170,17 +161,15 @@
}

&.stacked {
flex-direction: column;
justify-content: center;

&.size-super-condensed {
--stacked-size: #{size.$vvd-size-super-condensed};

@include mixins.get-size-properties(
calc(var(--stacked-size) + 20 * 1px),
#{constants.$vvd-typography-base-condensed-bold},
4px,
16px
16px,
10px
);
}

Expand All @@ -191,6 +180,7 @@
calc(var(--stacked-size) + 24 * 1px),
#{constants.$vvd-typography-base-condensed-bold},
6px,
12px,
12px
);
}
Expand All @@ -202,7 +192,8 @@
calc(var(--stacked-size) + 32 * 1px),
#{constants.$vvd-typography-base-extended-bold},
10px,
20px
20px,
16px
);
}

Expand All @@ -213,6 +204,7 @@
calc(var(--stacked-size) + 28 * 1px),
#{constants.$vvd-typography-base-bold},
8px,
16px,
16px
);
}
Expand All @@ -228,15 +220,32 @@ slot[name='icon'] {
order: 1;
}

.control.stacked > & {
.control.stacked & {
font-size: calc(var(--stacked-size) / 2);
}

.control:not(.stacked) > & {
.control:not(.stacked) & {
font-size: calc(var(#{variables.$block-size}) / 2);
}
}

.chevron {
font-size: var(#{variables.$chevron-size});
}

.content {
display: flex;
overflow: hidden;
flex: 1;
align-items: center;
justify-content: center;
gap: var(#{variables.$icon-gap});

.control.stacked & {
flex-direction: column;
}
}

:host(:not([icon])) .pending {
position: absolute;

Expand Down
15 changes: 15 additions & 0 deletions libs/components/src/lib/button/button.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,21 @@ describe('vwc-button', () => {
});
});

describe('dropdown-indicator', () => {
const getChevron = () => element.shadowRoot?.querySelector('.chevron');

it('should not display a chevron if not set', async () => {
expect(getChevron()).toBe(null);
});

it('should display a chevron if set', async () => {
element.dropdownIndicator = true;
await elementUpdated(element);

expect(getChevron()).toBeInstanceOf(Element);
});
});

describe('active', () => {
it('should set active class when active is true', async () => {
element.active = true;
Expand Down
25 changes: 15 additions & 10 deletions libs/components/src/lib/button/button.template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
affixIconTemplateFactory,
IconWrapper,
} from '../../shared/patterns/affix';
import { chevronTemplateFactory } from '../../shared/patterns/chevron';
import type { Button, ButtonAppearance, ButtonSize } from './button';

const getAppearanceClassName = (
Expand Down Expand Up @@ -71,6 +72,18 @@ function renderIconOrPending(
}
}

const buttonContent = (context: ElementDefinitionContext) => {
const chevronTemplate = chevronTemplateFactory(context);
return html<Button>`<span class="content">
${(x) => renderIconOrPending(context, x.icon, x.pending, x.size)}
${when(
(x) => x.label,
html`<span class="text" role="presentation">${(x) => x.label}</span>`
)}
</span>
${when((x) => x.dropdownIndicator, chevronTemplate)}`;
};

function renderButtonContent(context: ElementDefinitionContext) {
return html` <button
class="${getClasses}"
Expand Down Expand Up @@ -103,11 +116,7 @@ function renderButtonContent(context: ElementDefinitionContext) {
title="${(x) => x.title}"
${ref('control')}
>
${(x) => renderIconOrPending(context, x.icon, x.pending, x.size)}
${when(
(x) => x.label,
html`<span class="text" role="presentation">${(x) => x.label}</span>`
)}
${buttonContent(context)}
</button>`;
}

Expand Down Expand Up @@ -139,11 +148,7 @@ function renderAnchorContent(context: ElementDefinitionContext) {
aria-roledescription="${(x) => x.ariaRoledescription}"
${ref('control')}
>
${(x) => renderIconOrPending(context, x.icon, x.pending, x.size)}
${when(
(x) => x.label,
html`<span class="text" role="presentation">${(x) => x.label}</span>`
)}
${buttonContent(context)}
</a>`;
}

Expand Down
13 changes: 13 additions & 0 deletions libs/components/src/lib/button/button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,19 @@ export class Button extends FoundationButton {
})
pending = false;

/**
* Display a chevron to indicate that the button opens a dropdown.
*
* @public
* @remarks
* HTML Attribute: dropdown-indicator
*/
@attr({
mode: 'boolean',
attribute: 'dropdown-indicator',
})
dropdownIndicator = false;

/**
* Displays the button in active state.
*
Expand Down
13 changes: 10 additions & 3 deletions libs/components/src/lib/button/partials/_mixins.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,21 @@
$block-size,
$font-face,
$icon-gap,
$padding-inline
$padding-inline,
$chevron-size
) {
#{variables.$block-size}: $block-size;

font: var(#{$font-face});
&:not(.icon-only) {
#{variables.$icon-gap}: $icon-gap;

#{variables.$icon-gap}: $icon-gap;

#{variables.$chevron-size}: $chevron-size;

&.icon-only {
padding-inline: calc(var(#{variables.$block-size}) / 4);
}
&:not(.icon-only) {
padding-inline: $padding-inline;
}
}
1 change: 1 addition & 0 deletions libs/components/src/lib/button/partials/variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ $stacked-icon-gap: --_stacked-button-icon-gap;
$block-size: --_button-block-size;
$button-line-clamp: --button-line-clamp;
$button-border-radius: --_button-border-radius;
$chevron-size: --_button-chevron-size;
43 changes: 43 additions & 0 deletions libs/components/src/lib/button/ui.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,49 @@ test('should show the component', async ({ page }: { page: Page }) => {
<vwc-button active appearance="outlined-light" label='alert' connotation='alert'></vwc-button>
<vwc-button active appearance="outlined-light" label='announcement' connotation='announcement'></vwc-button>
</div>
<div style="margin: 5px;">
<vwc-button dropdown-indicator appearance="ghost" label="ghost"></vwc-button>
<vwc-button dropdown-indicator appearance="outlined" label="outlined"></vwc-button>
<vwc-button dropdown-indicator appearance="filled" label="filled"></vwc-button>
</div>
<div style="margin: 5px;">
<vwc-button dropdown-indicator appearance="ghost-light" label="ghost-light"></vwc-button>
<vwc-button dropdown-indicator appearance="outlined-light" label="outlined-light"></vwc-button>
</div>
<div style="margin: 5px;">
<vwc-button dropdown-indicator connotation="cta" appearance="ghost" label="ghost"></vwc-button>
<vwc-button dropdown-indicator connotation="cta" appearance="outlined" label="outlined"></vwc-button>
<vwc-button dropdown-indicator connotation="cta" appearance="filled" label="filled"></vwc-button>
</div>
<div style="margin: 5px;">
<vwc-button dropdown-indicator connotation="cta" appearance="ghost-light" label="ghost-light"></vwc-button>
<vwc-button dropdown-indicator connotation="cta" appearance="outlined-light" label="outlined-light"></vwc-button>
</div>
<div style="margin: 5px;">
<vwc-button style="width: 200px" appearance="filled" dropdown-indicator label="wide"></vwc-button>
<vwc-button style="width: 100px" appearance="filled" dropdown-indicator label="too small for label"></vwc-button>
<vwc-button appearance="filled" dropdown-indicator icon="user-line"></vwc-button>
</div>
<div style="margin: 5px;">
<vwc-button appearance="filled" dropdown-indicator label="expanded" aria-expanded="true"></vwc-button>
</div>
<div style="margin: 5px;">
<vwc-button appearance="filled" dropdown-indicator icon="user-line" label="stacked" stacked></vwc-button>
<vwc-button style="width: 200px" appearance="filled" dropdown-indicator icon="user-line" label="wide" stacked></vwc-button>
<vwc-button style="width: 100px" appearance="filled" dropdown-indicator icon="user-line" label="too small for label" stacked></vwc-button>
</div>
<div style="margin: 5px;">
<vwc-button dropdown-indicator appearance="filled" label="super-condensed" size="super-condensed"></vwc-button>
<vwc-button dropdown-indicator appearance="filled" label="condensed" size="condensed"></vwc-button>
<vwc-button dropdown-indicator appearance="filled" label="normal" size="normal"></vwc-button>
<vwc-button dropdown-indicator appearance="filled" label="expanded" size="expanded"></vwc-button>
</div>
<div style="margin: 5px;">
<vwc-button dropdown-indicator icon="user-line" stacked appearance="filled" label="super-condensed" size="super-condensed"></vwc-button>
<vwc-button dropdown-indicator icon="user-line" stacked appearance="filled" label="condensed" size="condensed"></vwc-button>
<vwc-button dropdown-indicator icon="user-line" stacked appearance="filled" label="normal" size="normal"></vwc-button>
<vwc-button dropdown-indicator icon="user-line" stacked appearance="filled" label="expanded" size="expanded"></vwc-button>
</div>
</div>
`;

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.
13 changes: 1 addition & 12 deletions libs/components/src/lib/select/select.scss
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
@use 'partials/variables' as variables;
@use '../../../../shared/src/lib/sass/mixins/border-radius' as
border-radius-variables;
@import '../../shared/patterns/chevron';

$low-ink-color: --_low-ink-color;

Expand Down Expand Up @@ -101,18 +102,6 @@ $low-ink-color: --_low-ink-color;
}
}

.chevron {
display: flex;
flex-shrink: 0;
font: var(#{constants.$vvd-typography-base-extended});
transform: rotate(0);
transition: transform 0.2s;

:host([aria-expanded='true']) & {
transform: rotate(180deg);
}
}

.selected-value {
display: flex;
overflow: hidden;
Expand Down
Loading

0 comments on commit 5caaf84

Please sign in to comment.