Skip to content

Commit

Permalink
feat(button, split-button): show active state when expanded (VIV-1562) (
Browse files Browse the repository at this point in the history
  • Loading branch information
RichardHelm authored Mar 4, 2024
1 parent 83a685b commit b3e9982
Show file tree
Hide file tree
Showing 17 changed files with 107 additions and 34 deletions.
13 changes: 13 additions & 0 deletions libs/components/src/lib/button/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,19 @@ Semantically, buttons are usually used for triggering actions, while links are u
<vwc-button label="Button with a link" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a" target="_blank"></vwc-button>
```

### Active

Set the `active` attribute to make the button appear pressed.

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

```html preview
<vwc-button appearance='ghost' label='ghost' active></vwc-button>
<vwc-button appearance='filled' label='filled' active></vwc-button>
<vwc-button appearance='outlined' label='outlined' active></vwc-button>
```

## Slots

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

describe('active', () => {
it('should set active class when active is true', async () => {
element.active = true;
await elementUpdated(element);

expect(getControlElement(element).classList).toContain('active');
});
});

describe('label', () => {
it('set label property to node', async () => {
const label = 'lorem';
Expand Down
3 changes: 2 additions & 1 deletion libs/components/src/lib/button/button.template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const getAppearanceClassName = (appearance: ButtonAppearance, disabled: boolean)
};

const getClasses = ({
connotation, appearance, shape, iconTrailing, icon, label, disabled, stacked, size, iconSlottedContent
connotation, appearance, shape, iconTrailing, icon, label, disabled, stacked, size, iconSlottedContent, ariaExpanded, active
}: Button) => classNames(
'control',
[`connotation-${connotation}`, Boolean(connotation)],
Expand All @@ -25,6 +25,7 @@ const getClasses = ({
['icon-only', !label && !!(icon || iconSlottedContent?.length)],
['icon-trailing', iconTrailing],
['stacked', Boolean(stacked)],
['active', ariaExpanded === 'true' || active]
);

function renderIconOrPending(
Expand Down
12 changes: 12 additions & 0 deletions libs/components/src/lib/button/button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,18 @@ export class Button extends FoundationButton {
attribute: 'pending',
}) pending = false;

/**
* Displays the button in active state.
*
* @public
* @remarks
* HTML Attribute: active
*/
@attr({
mode: 'boolean',
attribute: 'active',
}) active = false;

/**
* Indicates the button's label.
*
Expand Down
4 changes: 4 additions & 0 deletions libs/components/src/lib/button/ui.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ test('should show the component', async ({ page }: { page: Page }) => {
<vwc-icon slot="icon" name="check-circle-solid" connotation="success"></vwc-icon>
</vwc-button>
</div>
<div style="margin: 5px;">
<vwc-button label="Expanded" aria-expanded="true"></vwc-button>
<vwc-button label="Active" active></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.
9 changes: 8 additions & 1 deletion libs/components/src/lib/nav-disclosure/nav-disclosure.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@use "sass:map";
@use "../../../../../dist/libs/tokens/scss/tokens.constants" as constants;
@use "../../../../shared/src/lib/sass/mixins/focus" as focus;
@use "../../../../shared/src/lib/sass/mixins/focus" as focus-variables;
Expand All @@ -18,8 +19,14 @@


.control {
@include appearance.appearance;
@include connotation.connotation (nav-disclosure);
@include appearance.appearance(
appearance.state-selectors(
map.merge(appearance.$state-aspect-selectors, (
selected: "[aria-current]",
))
)
);

$min-block-size: #{size.$vvd-size-normal};
$gap: 12px;
Expand Down
9 changes: 8 additions & 1 deletion libs/components/src/lib/nav-item/nav-item.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@use "sass:map";
@use "../../../../../dist/libs/tokens/scss/tokens.constants" as constants;
@use "../../../../shared/src/lib/sass/mixins/focus" as focus;
@use "../../../../shared/src/lib/sass/mixins/focus" as focus-variables;
Expand Down Expand Up @@ -37,8 +38,14 @@
text-decoration: none;
vertical-align: middle;

@include appearance.appearance;
@include connotation.connotation (nav-item);
@include appearance.appearance(
appearance.state-selectors(
map.merge(appearance.$state-aspect-selectors, (
selected: "[aria-current]",
))
)
);

&.icon-only {
display: flex;
Expand Down
9 changes: 8 additions & 1 deletion libs/components/src/lib/split-button/split-button.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@use "sass:map";
@use "../../../../../dist/libs/tokens/scss/tokens.constants" as constants;
@use "partials/variables" as variables;
@use "partials/mixins" as mixins;
Expand Down Expand Up @@ -33,7 +34,13 @@
.control,
.indicator {
@include connotation.connotation(split-button);
@include appearance.appearance;
@include appearance.appearance(
appearance.state-selectors(
map.merge(appearance.$state-aspect-selectors, (
active: ':active, [aria-expanded="true"]',
))
)
);

display: inline-flex;
box-sizing: border-box;
Expand Down
7 changes: 5 additions & 2 deletions libs/components/src/lib/split-button/ui.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { expect, test } from '@playwright/test';
import type { Page } from '@playwright/test';
import { expect, test } from '@playwright/test';
import {
loadComponents,
loadTemplate
loadTemplate,
} from '../../visual-tests/visual-tests-utils.js';

const components = ['split-button'];
Expand Down Expand Up @@ -89,6 +89,9 @@ test('should show the component', async ({ page }: { page: Page }) => {
<vwc-icon slot="icon" name="check-circle-solid" connotation="success"></vwc-icon>
</vwc-split-button>
</div>
<div style="margin: 5px;">
<vwc-split-button label="Expanded" aria-expanded="true"></vwc-split-button>
</div>
`;

page.setViewportSize({ width: 600, height: 720 });
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.
2 changes: 1 addition & 1 deletion libs/shared/src/lib/sass/mixins/appearance/_index.scss
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
@forward 'mixins' show appearance;
@forward 'mixins' show appearance, state-selectors, $state-aspect-selectors;
@forward 'functions' show get-appearance-token;
64 changes: 37 additions & 27 deletions libs/shared/src/lib/sass/mixins/appearance/_mixins.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,47 @@
@use 'variables' as variables;
@use 'config' as config;


////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
$hover: ':hover, .hover';
$disabled: ':disabled, .disabled';
$readonly: '.readonly';
$checked: ':checked, .checked';
$selected: '.selected, [aria-current]';
$active: ':active, .active';
$error: '.error';
$success: '.success';

$selectors: (
idle: '&',
hover: '&:where(#{$hover}):where(:not(#{$disabled}, #{$readonly}))',
disabled: '&:where(#{$disabled})',
readonly: '&:where(#{$readonly}):where(:not(#{$disabled}))',
selected: '&:where(#{$selected}):where(:not(#{$disabled}))',
selectedAndHover: '&:where(#{$selected}):where(#{$hover}):where(:not(#{$disabled}, #{$readonly}))',
selectedAndDisabled: '&:where(#{$selected}):where(#{$disabled})',
checked: '&:where(#{$checked}):where(:not(#{$disabled}))',
checkedAndHover: '&:where(#{$checked}):where(#{$hover}):where(:not(#{$disabled}, #{$readonly}))',
active: '&:where(#{$active}):where(:not(#{$disabled}))',
error: '&:where(#{$error}):where(:not(#{$disabled}))',
success: '&:where(#{$success}):where(:not(#{$disabled}))',
$state-aspect-selectors: (
hover: '.hover, :hover',
disabled: '.disabled, :disabled',
readonly: '.readonly',
checked: '.checked, :checked',
selected: '.selected',
active: '.active, :active',
error: '.error',
success: '.success',
);

@mixin appearance {
@function state-selectors($aspect-selectors) {
$hover: map.get($aspect-selectors, hover);
$disabled: map.get($aspect-selectors, disabled);
$readonly: map.get($aspect-selectors, readonly);
$checked: map.get($aspect-selectors, checked);
$selected: map.get($aspect-selectors, selected);
$active: map.get($aspect-selectors, active);
$error: map.get($aspect-selectors, error);
$success: map.get($aspect-selectors, success);

@return (
idle: '&',
hover: '&:where(#{$hover}):where(:not(#{$disabled}, #{$readonly}))',
disabled: '&:where(#{$disabled})',
readonly: '&:where(#{$readonly}):where(:not(#{$disabled}))',
selected: '&:where(#{$selected}):where(:not(#{$disabled}))',
selectedAndHover: '&:where(#{$selected}):where(#{$hover}):where(:not(#{$disabled}, #{$readonly}))',
selectedAndDisabled: '&:where(#{$selected}):where(#{$disabled})',
checked: '&:where(#{$checked}):where(:not(#{$disabled}))',
checkedAndHover: '&:where(#{$checked}):where(#{$hover}):where(:not(#{$disabled}, #{$readonly}))',
active: '&:where(#{$active}):where(:not(#{$disabled}))',
error: '&:where(#{$error}):where(:not(#{$disabled}))',
success: '&:where(#{$success}):where(:not(#{$disabled}))',
);
}

@mixin appearance($state-selectors: state-selectors($state-aspect-selectors)) {

@each $state in config.$states {
#{map.get($selectors, $state)} {
#{map.get($state-selectors, $state)} {

$state-mapping: map.get(variables.$states-mapping, $state);

Expand Down

0 comments on commit b3e9982

Please sign in to comment.