-
Notifications
You must be signed in to change notification settings - Fork 23
/
Copy pathrh-subnav.ts
161 lines (130 loc) · 5.11 KB
/
rh-subnav.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
import { LitElement, html, isServer } from 'lit';
import { classMap } from 'lit/directives/class-map.js';
import { customElement } from 'lit/decorators/custom-element.js';
import { query } from 'lit/decorators/query.js';
import { queryAssignedElements } from 'lit/decorators/query-assigned-elements.js';
import { property } from 'lit/decorators/property.js';
import { OverflowController } from '@patternfly/pfe-core/controllers/overflow-controller.js';
import { colorContextConsumer, type ColorTheme } from '../../lib/context/color/consumer.js';
import { colorContextProvider, type ColorPalette } from '../../lib/context/color/provider.js';
import '@rhds/elements/rh-icon/rh-icon.js';
import styles from './rh-subnav.css';
/**
* A subnavigation allows users to navigate between a small number of page links.
* @summary Organizes content into sections using tabbed pages
* @slot - Navigation links, expects collection of `<a>` elements
* @csspart container - container, `<div>` element
* @csspart links - `<slot>` element
*/
@customElement('rh-subnav')
export class RhSubnav extends LitElement {
static readonly styles = [styles];
/** Icon name to use for the scroll left button */
protected static readonly scrollIconLeft = 'caret-left';
/** Icon name to use for the scroll right button */
protected static readonly scrollIconRight = 'caret-right';
/** Icon set to use for the scroll buttons */
protected static readonly scrollIconSet = 'ui';
private static instances = new Set<RhSubnav>();
static {
// on resize check for overflows to add or remove scroll buttons
if (!isServer) {
globalThis.addEventListener('resize', () => {
// this appears to be an eslint bug.
// `this` should refer to the class, but in the minified bundle, it is void
const { instances } = RhSubnav;
for (const instance of instances) {
instance.#overflow.onScroll();
}
}, { capture: false });
}
}
/**
* Sets color theme based on parent context
*/
@colorContextConsumer() private on?: ColorTheme;
/**
* Sets color palette, which affects the element's styles as well as descendants' color theme.
* Overrides parent color context.
* Your theme will influence these colors so check there first if you are seeing inconsistencies.
* See [CSS Custom Properties](#css-custom-properties) for default values
*/
@colorContextProvider()
@property({ reflect: true, attribute: 'color-palette' }) colorPalette?: ColorPalette;
/**
* Customize the default `aria-label` on the `<nav>` container.
* Defaults to "subnavigation" if no attribute/property is set.
*/
@property({ attribute: 'accessible-label' }) accessibleLabel = 'subnavigation';
@queryAssignedElements() private links!: HTMLAnchorElement[];
@query('[part="links"]') private linkList!: HTMLElement;
#allLinkElements: HTMLAnchorElement[] = [];
#overflow = new OverflowController(this);
get #allLinks() {
return this.#allLinkElements;
}
set #allLinks(links: HTMLAnchorElement[]) {
this.#allLinkElements = links.filter(link => link instanceof HTMLAnchorElement);
}
get #firstLink(): HTMLAnchorElement {
const [link] = this.#allLinks;
return link;
}
get #lastLink(): HTMLAnchorElement {
return this.#allLinks.at(-1) as HTMLAnchorElement;
}
connectedCallback() {
super.connectedCallback();
RhSubnav.instances.add(this);
}
disconnectedCallback() {
super.disconnectedCallback();
RhSubnav.instances.delete(this);
}
render() {
const { scrollIconSet, scrollIconLeft, scrollIconRight } = this.constructor as typeof RhSubnav;
const { showScrollButtons } = this.#overflow;
const { on = 'light' } = this;
return html`
<nav part="container" aria-label="${this.accessibleLabel}" class="${classMap({ on: true, [on]: !!on })}">${!showScrollButtons ? '' : html`
<button id="previous" tabindex="-1" aria-hidden="true"
?disabled="${!this.#overflow.overflowLeft}"
@click="${this.#scrollLeft}">
<rh-icon icon="${scrollIconLeft}"
set="${scrollIconSet}"
loading="eager"></rh-icon>
</button>`}
<slot part="links"
@slotchange="${this.#onSlotchange}"></slot>${!showScrollButtons ? '' : html`
<button id="next" tabindex="-1" aria-hidden="true"
?disabled="${!this.#overflow.overflowRight}"
@click="${this.#scrollRight}">
<rh-icon icon="${scrollIconRight}" set="${scrollIconSet}" loading="eager"></rh-icon>
</button>`}
</nav>
`;
}
firstUpdated() {
this.linkList.addEventListener('scroll', this.#overflow.onScroll.bind(this));
}
#onSlotchange() {
this.#allLinks = this.links;
this.#overflow.init(this.linkList, this.#allLinks);
this.#firstLastClasses();
}
#firstLastClasses() {
this.#firstLink.classList.add('first');
this.#lastLink.classList.add('last');
}
#scrollLeft() {
this.#overflow.scrollLeft();
}
#scrollRight() {
this.#overflow.scrollRight();
}
}
declare global {
interface HTMLElementTagNameMap {
'rh-subnav': RhSubnav;
}
}