Skip to content

Commit 141782f

Browse files
committed
feat(wcp-navigation): add togglability to navigation
1 parent 534ccac commit 141782f

File tree

11 files changed

+256
-44
lines changed

11 files changed

+256
-44
lines changed

src/components/features/navigation/navigation-item/navigation-item.component.scss

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,33 @@
4545
}
4646
}
4747

48-
a,
4948
span {
5049
display: block;
5150
padding: var(---wcp-navigation-item-spacing);
5251

52+
overflow: hidden;
5353
text-decoration: none;
5454
text-overflow: ellipsis;
5555
white-space: nowrap;
5656

5757
background-color: var(---wcp-navigation-item-background);
5858
color: var(---wcp-navigation-item-color);
5959
}
60+
61+
// as the surrounding navigation element might be inset, we add the background
62+
// in front to fill the whole navigation width to increase the clickable area
63+
a {
64+
position: relative;
65+
color: inherit;
66+
text-decoration: none;
67+
68+
&::before {
69+
content: '';
70+
71+
position: absolute;
72+
inset: 0 100% 0 auto;
73+
width: calc(var(---wcp-aside-max-width, 100vw) - 100%);
74+
75+
background-color: var(---wcp-navigation-item-background);
76+
}
77+
}

src/components/features/navigation/navigation-item/navigation-item.component.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ export class NavigationItem extends ColorSchemable(LitElement) {
4242
return html`
4343
${when(
4444
this.href !== undefined,
45-
() => html`<a href="${ifDefined(this.href)}"><slot></slot></a>`,
45+
() => html`
46+
<a href="${ifDefined(this.href)}">
47+
<span><slot></slot></span>
48+
</a>
49+
`,
4650
() => html`<span><slot></slot></span>`,
4751
)}
4852
`;

src/components/features/navigation/navigation/EXAMPLES.md

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,38 @@
1212
```html
1313
<wcp-navigation headline="Navigation">
1414
<wcp-navigation-item href="/home">Home</wcp-navigation-item>
15-
<wcp-navigation nested headline="Nested">
15+
<wcp-navigation headline="Nested">
16+
<wcp-navigation-item href="/about">About</wcp-navigation-item>
17+
<wcp-navigation-item href="/imprint">Imprint</wcp-navigation-item>
18+
</wcp-navigation>
19+
</wcp-navigation>
20+
```
21+
22+
### Togglable navigation
23+
24+
```html
25+
<wcp-navigation headline="Navigation" togglable>
26+
<wcp-navigation-item href="/home">Home</wcp-navigation-item>
27+
<wcp-navigation-item href="/about">About</wcp-navigation-item>
28+
</wcp-navigation>
29+
```
30+
31+
### Initially opened togglable navigation
32+
33+
```html
34+
<wcp-navigation headline="Navigation" togglable open>
35+
<wcp-navigation-item href="/home">Home</wcp-navigation-item>
36+
<wcp-navigation-item href="/about">About</wcp-navigation-item>
37+
</wcp-navigation>
38+
```
39+
40+
### Togglable nested navigation
41+
42+
```html
43+
<wcp-navigation headline="Navigation">
44+
<wcp-navigation-item href="/home">Home</wcp-navigation-item>
45+
<wcp-navigation-item href="/about">About</wcp-navigation-item>
46+
<wcp-navigation togglable headline="Nested">
1647
<wcp-navigation-item href="/about">About</wcp-navigation-item>
1748
<wcp-navigation-item href="/imprint">Imprint</wcp-navigation-item>
1849
</wcp-navigation>

src/components/features/navigation/navigation/README.md

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,52 @@
66

77
## Properties
88

9-
| Property | Attribute | Type | Default |
10-
|------------|------------|-----------------------|---------|
11-
| `headline` | `headline` | `string \| undefined` | |
12-
| `nested` | `nested` | `boolean` | false |
9+
| Property | Attribute | Type | Default | Description |
10+
|-------------|-------------|-----------------------|---------|--------------------------------------------------|
11+
| `headline` | `headline` | `string \| undefined` | | An optional headline to be shown for categorization |
12+
| `open` | `open` | `boolean` | false | If togglable, this flag indicates if the nested items are currently visible |
13+
| `togglable` | `togglable` | `boolean` | false | Allows the nested items to be toggled |
14+
15+
## Methods
16+
17+
| Method | Type |
18+
|------------------|--------------------------------|
19+
| `toggleClick` | `(): void` |
20+
| `toggleKeyboard` | `(event: KeyboardEvent): void` |
21+
22+
## Events
23+
24+
| Event | Description |
25+
|-------------------------|-----------------------------------------------|
26+
| `wcp-navigation-toggle` | Emitted when the togglable open state changes |
1327

1428
## Slots
1529

16-
| Name | Description |
17-
|------|--------------------------------------------------|
18-
| | Default slot for navigation items or nested navigation |
30+
| Name | Description |
31+
|----------|--------------------------------------------------|
32+
| | Default slot for navigation items or nested navigation |
33+
| `action` | Slot for an action to be shown next to the headline |
34+
35+
## CSS Shadow Parts
36+
37+
| Part | Description |
38+
|------------|--------------------------------|
39+
| `headline` | The headline of the navigation |
40+
| `nav` | The nested navigation |
1941

2042
## CSS Custom Properties
2143

22-
| Property | Description |
23-
|----------------------------------------------|--------------------------------------------------|
24-
| `--wcp-navigation-dark-border-color` | Border color of the navigation headline in dark mode |
25-
| `--wcp-navigation-headline-dark-background` | Background color of the navigation headline in dark mode |
26-
| `--wcp-navigation-headline-light-background` | Background color of the navigation headline in light mode |
27-
| `--wcp-navigation-headline-size` | Font size of the navigation headline |
28-
| `--wcp-navigation-headline-spacing` | Letter spacing of the navigation headline |
29-
| `--wcp-navigation-headline-weight` | Font weight of the navigation headline |
30-
| `--wcp-navigation-inset` | Inset of the navigation if nested (is applied on each level) |
31-
| `--wcp-navigation-light-border-color` | Border color of the navigation headline in light mode |
32-
| `--wcp-navigation-spacing` | Spacing between navigation and headline |
33-
| `--wcp-navigation-spacing-headline` | Inner padding of the navigation headline |
34-
| `--wcp-navigation-spacing-items` | Spacing between navigation items |
44+
| Property | Description |
45+
|-----------------------------------------------|--------------------------------------------------|
46+
| `--wcp-navigation-dark-border-color` | Border color of the navigation headline in dark mode |
47+
| `--wcp-navigation-headline-dark-background` | Background color of the navigation headline in dark mode |
48+
| `--wcp-navigation-headline-light-background` | Background color of the navigation headline in light mode |
49+
| `--wcp-navigation-headline-size` | Font size of the navigation headline |
50+
| `--wcp-navigation-headline-spacing` | Letter spacing of the navigation headline |
51+
| `--wcp-navigation-headline-weight` | Font weight of the navigation headline |
52+
| `--wcp-navigation-inset` | Inset of the navigation if nested (is applied on each level) |
53+
| `--wcp-navigation-light-border-color` | Border color of the navigation headline in light mode |
54+
| `--wcp-navigation-spacing` | Spacing between navigation and headline |
55+
| `--wcp-navigation-spacing-headline` | Inner padding of the navigation headline |
56+
| `--wcp-navigation-spacing-headline-togglable` | Inner padding of the navigation headline if togglable |
57+
| `--wcp-navigation-spacing-items` | Spacing between navigation items |

src/components/features/navigation/navigation/navigation.component.scss

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
---wcp-navigation-spacing: var(--wcp-navigation-spacing, #{0 0 utils.size(1)});
66
---wcp-navigation-spacing-items: var(--wcp-navigation-spacing-items, 0);
77
---wcp-navigation-spacing-headline: var(--wcp-navigation-spacing-headline, #{utils.size(1.5) 0 utils.size(1) utils.size(2)});
8+
---wcp-navigation-spacing-headline-togglable: var(--wcp-navigation-spacing-headline-togglable, #{utils.size(1) utils.size(1) utils.size(1) utils.size(2)});
89

910
---wcp-navigation-inset: var(--wcp-navigation-inset, #{utils.size(1.5)});
1011

@@ -36,9 +37,10 @@
3637
}
3738

3839
h3 {
39-
position: sticky;
40-
top: 0;
41-
z-index: 1;
40+
display: flex;
41+
flex-direction: row;
42+
gap: 1em;
43+
align-items: center;
4244

4345
margin: 0;
4446
padding: var(---wcp-navigation-spacing-headline);
@@ -51,32 +53,55 @@ h3 {
5153
line-height: 1;
5254

5355
span {
56+
flex: 1 1 auto;
5457
display: block;
58+
5559
overflow: hidden;
5660
text-overflow: ellipsis;
5761
text-transform: uppercase;
62+
white-space: nowrap;
63+
}
64+
65+
:host([togglable]) & {
66+
cursor: pointer;
67+
padding: var(---wcp-navigation-spacing-headline-togglable);
68+
69+
wcp-icon {
70+
flex: 0 1 auto;
71+
pointer-events: none;
72+
color: var(---wcp-navigation-border-color);
73+
--wcp-icon-size: 20;
74+
}
5875
}
5976

60-
:host(:not([nested])) &::before {
61-
content: '';
77+
// static first level headlines are treated as sticky categories with a separator
78+
:host(:not([togglable])) & {
79+
position: sticky;
80+
top: 0;
81+
z-index: 1;
6282

63-
position: absolute;
64-
inset: -1px 0 auto;
65-
height: 1px;
83+
&::before {
84+
content: '';
6685

67-
background-color: var(---wcp-navigation-border-color);
86+
position: absolute;
87+
inset: -1px 0 auto;
88+
height: 1px;
89+
90+
background-color: var(---wcp-navigation-border-color);
91+
}
6892
}
6993
}
7094

7195
nav {
7296
position: relative;
97+
margin-left: var(---wcp-navigation-inset);
7398
padding: var(---wcp-navigation-spacing);
7499

75100
display: flex;
76101
flex-direction: column;
77102
gap: var(---wcp-navigation-spacing-items);
78-
}
79103

80-
:host([nested]) {
81-
margin-left: var(---wcp-navigation-inset);
104+
:host([togglable]:not([open])) & {
105+
display: none;
106+
}
82107
}

src/components/features/navigation/navigation/navigation.component.ts

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { html, LitElement, type TemplateResult, unsafeCSS } from 'lit';
2-
import { customElement, property } from 'lit/decorators.js';
2+
import { customElement, eventOptions, property } from 'lit/decorators.js';
3+
import { ifDefined } from 'lit/directives/if-defined.js';
34
import { when } from 'lit/directives/when.js';
45

56
import { ColorSchemable } from '@/mixins/color-schemable.mixin.js';
@@ -8,10 +9,15 @@ import styles from './navigation.component.scss';
89

910
/**
1011
* @slot - Default slot for navigation items or nested navigation
12+
* @slot action - Slot for an action to be shown next to the headline
13+
*
14+
* @csspart headline - The headline of the navigation
15+
* @csspart nav - The nested navigation
1116
*
1217
* @cssprop --wcp-navigation-spacing - Spacing between navigation and headline
1318
* @cssprop --wcp-navigation-spacing-items - Spacing between navigation items
1419
* @cssprop --wcp-navigation-spacing-headline - Inner padding of the navigation headline
20+
* @cssprop --wcp-navigation-spacing-headline-togglable - Inner padding of the navigation headline if togglable
1521
* @cssprop --wcp-navigation-inset - Inset of the navigation if nested (is applied on each level)
1622
* @cssprop --wcp-navigation-dark-border-color - Border color of the navigation headline in dark mode
1723
* @cssprop --wcp-navigation-light-border-color - Border color of the navigation headline in light mode
@@ -20,28 +26,76 @@ import styles from './navigation.component.scss';
2026
* @cssprop --wcp-navigation-headline-spacing - Letter spacing of the navigation headline
2127
* @cssprop --wcp-navigation-headline-dark-background - Background color of the navigation headline in dark mode
2228
* @cssprop --wcp-navigation-headline-light-background - Background color of the navigation headline in light mode
29+
*
30+
* @emits wcp-navigation-toggle - Emitted when the togglable open state changes
2331
*/
2432
@customElement('wcp-navigation')
2533
export class Navigation extends ColorSchemable(LitElement) {
2634
static override readonly styles = unsafeCSS(styles);
2735

36+
/**
37+
* An optional headline to be shown for categorization
38+
*/
2839
@property({ type: String, reflect: true })
2940
headline?: string;
3041

42+
/**
43+
* Allows the nested items to be toggled
44+
*/
45+
@property({ type: Boolean, reflect: true })
46+
togglable = false;
47+
48+
/**
49+
* If togglable, this flag indicates if the nested items are currently visible
50+
*/
3151
@property({ type: Boolean, reflect: true })
32-
nested = false;
52+
open = false;
53+
54+
@eventOptions({ passive: true })
55+
toggleClick(): void {
56+
if (!this.togglable) return;
57+
this.open = !this.open;
58+
this.dispatchEvent(new CustomEvent('wcp-navigation-toggle', { detail: this.open }));
59+
}
60+
61+
@eventOptions({ capture: true })
62+
toggleKeyboard(event: KeyboardEvent): void {
63+
if (!this.togglable) return;
64+
if (![' ', 'Enter'].includes(event.key)) return;
65+
66+
event.preventDefault();
67+
this.open = !this.open;
68+
this.dispatchEvent(new CustomEvent('wcp-navigation-toggle', { detail: this.open }));
69+
}
3370

3471
protected override render(): TemplateResult {
3572
return html`
36-
${when(this.headline !== undefined, () => html`<h3 part="headline"><span>${this.headline}</span></h3>`)}
37-
<nav>
38-
<slot></slot>
39-
</nav>
73+
${when(
74+
this.headline !== undefined,
75+
() => html`
76+
<h3
77+
part="headline"
78+
tabindex="${ifDefined(this.togglable ? '0' : undefined)}"
79+
@click="${this.toggleClick}"
80+
@keydown="${this.toggleKeyboard}"
81+
>
82+
<span>${this.headline}</span>
83+
<slot name="action">
84+
${when(this.togglable, () => html`<wcp-icon name="chevron-${this.open ? 'up' : 'down'}"></wcp-icon>`)}
85+
</slot>
86+
</h3>
87+
`,
88+
)}
89+
<nav part="nav"><slot></slot></nav>
4090
`;
4191
}
4292
}
4393

4494
declare global {
95+
interface HTMLElementEventMap {
96+
'wcp-navigation-toggle': CustomEvent<boolean>;
97+
}
98+
4599
interface HTMLElementTagNameMap {
46100
'wcp-navigation': Navigation;
47101
}

src/components/layout/aside/aside.component.scss

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// prettier-ignore
44
:host {
55
---wcp-aside-max-width: var(--wcp-aside-max-width, #{utils.size(24)});
6+
---wcp-aside-min-width: var(--wcp-aside-min-width, var(---wcp-aside-max-width));
67

78
---wcp-aside-spacing: var(--wcp-aside-spacing, 0);
89

@@ -43,7 +44,7 @@
4344
@include utils.breakpoint(sm) {
4445
position: relative;
4546
max-width: var(---wcp-aside-max-width);
46-
min-width: var(---wcp-aside-max-width);
47+
min-width: var(---wcp-aside-min-width);
4748

4849
transition: margin-left ease-in-out var(--wcp-fx-layout-duration);
4950
will-change: margin-left;

src/components/root/root-navigation/root-navigation.component.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
---wcp-root-navigation-empty-message-font-size: var(--wcp-root-navigation-empty-message-font-size, 0.8em);
77
}
88

9+
:host {
10+
user-select: none;
11+
}
12+
913
p {
1014
margin: var(---wcp-root-navigation-empty-message-spacing);
1115
font-size: var(---wcp-root-navigation-empty-message-font-size);

0 commit comments

Comments
 (0)