Skip to content

Commit

Permalink
feat(sbb-header): introduce active state (#3154)
Browse files Browse the repository at this point in the history
  • Loading branch information
jeripeierSBB authored Oct 17, 2024
1 parent bb80ad4 commit ffdeec4
Show file tree
Hide file tree
Showing 9 changed files with 196 additions and 48 deletions.
33 changes: 32 additions & 1 deletion src/elements/header/common/header-action.scss
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
--sbb-header-action-translate-y: 0;
--sbb-header-action-gap: var(--sbb-spacing-fixed-2x);
--sbb-header-action-icon-dimension: var(--sbb-size-icon-ui-small);
--sbb-header-action-active-border-width: var(--sbb-border-width-2x);
--sbb-header-action-active-border-color: var(--sbb-color-black);
--sbb-header-action-active-border-margin-inline: var(--sbb-header-action-padding-inline);
--sbb-header-action-active-border-scale: 0;

// If expanded, move it left by left padding.
// As result, the icon should be aligned with the left side of the page wrapper.
Expand All @@ -37,6 +41,9 @@
@include sbb.if-forced-colors {
--sbb-header-action-border-color: CanvasText;
--sbb-header-action-color: LinkText;

// Hide border with forced colors, as state is displayed on border
--sbb-header-action-active-border-width: 0;
}
}

Expand All @@ -60,13 +67,21 @@
}
}

:host(.sbb-active) {
--sbb-header-action-active-border-scale: 1;

@include sbb.if-forced-colors {
--sbb-header-action-border-color: Highlight;
}
}

:host([role='button']) {
@include sbb.if-forced-colors {
--sbb-header-action-color: ButtonText;
}
}

:is(.sbb-header-button, .sbb-header-link) {
.sbb-action-base {
@include sbb.text-inherit;

display: flex;
Expand Down Expand Up @@ -110,6 +125,19 @@
@include sbb.focus-outline;
}
}

// Active state border
&::after {
content: '';
position: absolute;
border-bottom: var(--sbb-header-action-active-border-width) solid
var(--sbb-header-action-active-border-color);
inset: auto 0 calc(-1 * var(--sbb-header-action-active-border-width));
margin-inline: var(--sbb-header-action-active-border-margin-inline);
scale: var(--sbb-header-action-active-border-scale);
transition: scale var(--sbb-header-action-transition-easing)
var(--sbb-header-action-transition-duration);
}
}

.sbb-header-action__wrapper {
Expand Down Expand Up @@ -156,6 +184,9 @@
-0.5 * (var(--sbb-header-action-min-width) - var(--sbb-header-action-icon-dimension))
);
--sbb-header-action-padding-inline-zero: 0;
--sbb-header-action-active-border-margin-inline: calc(
0.5 * (100% - var(--sbb-size-icon-ui-small))
);

.sbb-header-action__text {
@include sbb.screen-reader-only;
Expand Down
41 changes: 27 additions & 14 deletions src/elements/header/header-button/header-button.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,10 @@ import { sbbSpread } from '../../../storybook/helpers/spread.js';
import readme from './readme.md?raw';
import './header-button.js';

const TemplateSingle = (args: Args): TemplateResult => html`
<sbb-header-button ${sbbSpread(args)}>${args.text}</sbb-header-button>
`;

const TemplateMultiple = (args: Args): TemplateResult => html`
<div style="display: flex; gap: 2rem;">
<sbb-header-button ${sbbSpread(args)}>${args.text} 1</sbb-header-button>
<sbb-header-button ${sbbSpread(args)}>${args.text} 2</sbb-header-button>
<sbb-header-button ${sbbSpread(args)}>${args.text} 3</sbb-header-button>
</div>
const TemplateSingle = ({ active, text, ...args }: Args): TemplateResult => html`
<sbb-header-button ${sbbSpread(args)} class=${active ? 'sbb-active' : ''}>
${text}
</sbb-header-button>
`;

const text: InputType = {
Expand All @@ -39,6 +33,12 @@ const iconName: InputType = {
},
};

const active: InputType = {
control: {
type: 'boolean',
},
};

const type: InputType = {
control: {
type: 'select',
Expand Down Expand Up @@ -84,6 +84,7 @@ const basicArgTypes: ArgTypes = {
text,
'expand-from': expandFrom,
'icon-name': iconName,
active,
type,
name,
value,
Expand All @@ -95,23 +96,35 @@ const basicArgs: Args = {
text: 'Menu',
'expand-from': expandFrom.options![0],
'icon-name': 'hamburger-menu-small',
active: false,
type: type.options![0],
name: 'header-button',
value: 'value',
form: 'form',
'aria-label': undefined,
};

export const sbbHeaderActionButton: StoryObj = {
export const Default: StoryObj = {
render: TemplateSingle,
argTypes: basicArgTypes,
args: { ...basicArgs },
};

export const sbbHeaderActionButtonMultiple: StoryObj = {
render: TemplateMultiple,
export const Active: StoryObj = {
render: TemplateSingle,
argTypes: basicArgTypes,
args: { ...basicArgs },
args: { ...basicArgs, active: true, 'icon-name': 'magnifying-glass-small', text: 'Label' },
};

export const ExpandFromMedium: StoryObj = {
render: TemplateSingle,
argTypes: basicArgTypes,
args: {
...basicArgs,
'icon-name': 'magnifying-glass-small',
text: 'Label',
'expand-from': 'medium',
},
};

const meta: Meta = {
Expand Down
34 changes: 25 additions & 9 deletions src/elements/header/header-button/header-button.visual.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,31 @@ import './header-button.js';

describe(`sbb-header-button`, () => {
describeViewports({ viewports: ['zero', 'medium'] }, () => {
for (const state of visualDiffStandardStates) {
it(
state.name,
state.with(async (setup) => {
await setup.withFixture(html`
<sbb-header-button icon-name="hamburger-menu-small">Menu</sbb-header-button>
`);
}),
);
for (const forcedColors of [false, true]) {
describe(`forcedColors=${forcedColors}`, () => {
for (const active of [false, true]) {
describe(`active=${active}`, () => {
for (const state of visualDiffStandardStates) {
it(
state.name,
state.with(async (setup) => {
await setup.withFixture(
html`
<sbb-header-button
icon-name="hamburger-menu-small"
class=${active ? 'sbb-active' : ''}
>
Menu
</sbb-header-button>
`,
{ forcedColors },
);
}),
);
}
});
}
});
}
});
});
17 changes: 17 additions & 0 deletions src/elements/header/header-button/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,23 @@ from which the label is displayed; below that, only the icon is visible.
<sbb-header-button expand-from="medium">Text</sbb-header-button>
```

### Style

To indicate an active state, the CSS class `sbb-active` should be set.

From accessibility perspective `aria-current="page"` should be set whenever the CSS class `sbb-active` is set.

```html
<sbb-header-button
icon-name="magnifying-glass-small"
href="#"
class="sbb-active"
aria-current="page"
>
Overview
</sbb-header-button>
```

## Button properties

The component is internally rendered as a button,
Expand Down
41 changes: 27 additions & 14 deletions src/elements/header/header-link/header-link.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,10 @@ import { sbbSpread } from '../../../storybook/helpers/spread.js';
import readme from './readme.md?raw';
import './header-link.js';

const TemplateSingle = (args: Args): TemplateResult => html`
<sbb-header-link ${sbbSpread(args)}>${args.text}</sbb-header-link>
`;

const TemplateMultiple = (args: Args): TemplateResult => html`
<div style="display: flex; gap: 2rem;">
<sbb-header-link ${sbbSpread(args)}>${args.text} 1</sbb-header-link>
<sbb-header-link ${sbbSpread(args)}>${args.text} 2</sbb-header-link>
<sbb-header-link ${sbbSpread(args)}>${args.text} 3</sbb-header-link>
</div>
const TemplateSingle = ({ active, text, ...args }: Args): TemplateResult => html`
<sbb-header-link ${sbbSpread(args)} class=${active ? 'sbb-active' : ''}>
${text}
</sbb-header-link>
`;

const text: InputType = {
Expand All @@ -40,6 +34,12 @@ const iconName: InputType = {
},
};

const active: InputType = {
control: {
type: 'boolean',
},
};

const hrefs = ['https://www.sbb.ch', 'https://github.com/sbb-design-systems/lyne-components'];
const href: InputType = {
options: Object.keys(hrefs),
Expand Down Expand Up @@ -91,6 +91,7 @@ const basicArgTypes: ArgTypes = {
text,
'expand-from': expandFrom,
'icon-name': iconName,
active,
href,
target,
rel,
Expand All @@ -102,23 +103,35 @@ const basicArgs: Args = {
text: 'Menu',
'expand-from': expandFrom.options![0],
'icon-name': 'hamburger-menu-small',
active: false,
href: href.options![1],
target: '_blank',
rel: undefined,
download: false,
'accessibility-label': undefined,
};

export const sbbHeaderActionLink: StoryObj = {
export const Default: StoryObj = {
render: TemplateSingle,
argTypes: basicArgTypes,
args: { ...basicArgs },
};

export const sbbHeaderActionLinkMultiple: StoryObj = {
render: TemplateMultiple,
export const Active: StoryObj = {
render: TemplateSingle,
argTypes: basicArgTypes,
args: { ...basicArgs },
args: { ...basicArgs, active: true, 'icon-name': 'magnifying-glass-small', text: 'Label' },
};

export const ExpandFromMedium: StoryObj = {
render: TemplateSingle,
argTypes: basicArgTypes,
args: {
...basicArgs,
'icon-name': 'magnifying-glass-small',
text: 'Label',
'expand-from': 'medium',
},
};

const meta: Meta = {
Expand Down
35 changes: 26 additions & 9 deletions src/elements/header/header-link/header-link.visual.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,32 @@ import './header-link.js';

describe(`sbb-header-link`, () => {
describeViewports({ viewports: ['zero', 'medium'] }, () => {
for (const state of visualDiffStandardStates) {
it(
state.name,
state.with(async (setup) => {
await setup.withFixture(html`
<sbb-header-link icon-name="hamburger-menu-small" href="#">Menu</sbb-header-link>
`);
}),
);
for (const forcedColors of [false, true]) {
describe(`forcedColors=${forcedColors}`, () => {
for (const active of [false, true]) {
describe(`active=${active}`, () => {
for (const state of visualDiffStandardStates) {
it(
state.name,
state.with(async (setup) => {
await setup.withFixture(
html`
<sbb-header-link
icon-name="hamburger-menu-small"
class=${active ? 'sbb-active' : ''}
href="#"
>
Menu
</sbb-header-link>
`,
{ forcedColors },
);
}),
);
}
});
}
});
}
});
});
12 changes: 12 additions & 0 deletions src/elements/header/header-link/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@ from which the label is displayed; below that, only the icon is visible.
<sbb-header-link href="#" expand-from="medium">Text</sbb-header-link>
```

### Style

To indicate an active state, the CSS class `sbb-active` should be set.

From accessibility perspective `aria-current="page"` should be set whenever the CSS class `sbb-active` is set.

```html
<sbb-header-link icon-name="magnifying-glass-small" href="#" class="sbb-active" aria-current="page">
Overview
</sbb-header-link>
```

## Link properties

The component is internally rendered as a link,
Expand Down
8 changes: 7 additions & 1 deletion src/elements/header/header/header.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,13 @@ const HeaderBasicTemplate = (
</sbb-header-button>
${args.size === 's' ? appName() : nothing}
<div class="sbb-header-spacer"></div>
<sbb-header-link href="https://www.sbb.ch" target="_blank" icon-name="magnifying-glass-small">
<sbb-header-link
href="https://www.sbb.ch"
target="_blank"
icon-name="magnifying-glass-small"
class="sbb-active"
aria-current="page"
>
Search
</sbb-header-link>
${template}
Expand Down
Loading

0 comments on commit ffdeec4

Please sign in to comment.