@ds-mo/ui 1.7.3 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.build-stamp +1 -1
- package/dist/components/ds-app-shell.js +1 -1
- package/dist/components/ds-app-shell.js.map +1 -1
- package/dist/components/ds-badge.js +1 -1
- package/dist/components/ds-bar-nav-action.js +1 -1
- package/dist/components/ds-bar-nav.js +1 -1
- package/dist/components/ds-bar-nav.js.map +1 -1
- package/dist/components/ds-checkbox.js +1 -1
- package/dist/components/ds-fade.js +1 -1
- package/dist/components/ds-fade.js.map +1 -1
- package/dist/components/ds-field.js +1 -1
- package/dist/components/ds-panel-nav.js +1 -1
- package/dist/components/ds-panel-nav.js.map +1 -1
- package/dist/components/ds-panel-tools.d.ts +11 -0
- package/dist/components/ds-panel-tools.js +2 -0
- package/dist/components/ds-panel-tools.js.map +1 -0
- package/dist/components/ds-slider.js +1 -1
- package/dist/components/ds-slider.js.map +1 -1
- package/dist/components/ds-tab-group-nav.js +1 -1
- package/dist/components/ds-tab-group.js +1 -1
- package/dist/components/ds-toggle.js +1 -1
- package/dist/components/p-B8VZmH43.js +2 -0
- package/dist/components/p-B8VZmH43.js.map +1 -0
- package/dist/components/p-BucV1U6A.js +2 -0
- package/dist/components/p-BucV1U6A.js.map +1 -0
- package/dist/components/p-CX7-_qOW.js +2 -0
- package/dist/components/p-CX7-_qOW.js.map +1 -0
- package/dist/components/{p-rzEgrLPJ.js → p-DZDXwEMg.js} +2 -2
- package/dist/components/{p-Cz8OKcrG.js → p-DrvpsLK6.js} +2 -2
- package/dist/components/p-TtnqbcnR.js +2 -0
- package/dist/components/p-TtnqbcnR.js.map +1 -0
- package/dist/types/components/Badge/Badge.d.ts +5 -2
- package/dist/types/components/BarNav/BarNav.d.ts +9 -2
- package/dist/types/components/Fade/Fade.d.ts +5 -0
- package/dist/types/components/PanelTools/PanelTools.d.ts +27 -0
- package/dist/types/components/PanelTools/index.d.ts +3 -0
- package/dist/types/components/PanelTools/panel-tools-types.d.ts +5 -0
- package/dist/types/components.d.ts +39 -0
- package/dist/types/nav/index.d.ts +1 -1
- package/dist/types/nav/shell-view-transition.d.ts +3 -0
- package/dist/types/utils/index.d.ts +2 -0
- package/dist/types/utils/scroll-edge-fade.d.ts +13 -0
- package/package.json +2 -2
- package/src/angular/index.ts +1 -0
- package/src/angular/proxies.ts +23 -0
- package/src/react/components.ts +1 -0
- package/src/react/ds-panel-tools.ts +26 -0
- package/src/wc/components/AppShell/AppShell.tsx +7 -2
- package/src/wc/components/Badge/Badge.tsx +39 -15
- package/src/wc/components/BarNav/BarNav.tsx +75 -19
- package/src/wc/components/Fade/Fade.tsx +22 -1
- package/src/wc/components/PanelNav/PanelNav.tsx +10 -9
- package/src/wc/components/PanelTools/PanelTools.tsx +163 -0
- package/src/wc/components/PanelTools/index.ts +2 -0
- package/src/wc/components/PanelTools/panel-tools-types.ts +18 -0
- package/src/wc/components.d.ts +39 -0
- package/src/wc/nav/index.ts +2 -0
- package/src/wc/nav/shell-gradient.ts +2 -2
- package/src/wc/nav/shell-view-transition.ts +24 -5
- package/src/wc/utils/index.ts +7 -0
- package/src/wc/utils/scroll-edge-fade.css +73 -0
- package/src/wc/utils/scroll-edge-fade.ts +38 -0
- package/dist/components/p-B6NnMfZA.js +0 -2
- package/dist/components/p-B6NnMfZA.js.map +0 -1
- package/dist/components/p-CXkDP-ct.js +0 -2
- package/dist/components/p-CXkDP-ct.js.map +0 -1
- package/dist/components/p-DfL2MOso.js +0 -2
- package/dist/components/p-DfL2MOso.js.map +0 -1
- package/dist/components/p-vW9EMP8s.js +0 -2
- package/dist/components/p-vW9EMP8s.js.map +0 -1
- /package/dist/components/{p-rzEgrLPJ.js.map → p-DZDXwEMg.js.map} +0 -0
- /package/dist/components/{p-Cz8OKcrG.js.map → p-DrvpsLK6.js.map} +0 -0
package/src/angular/proxies.ts
CHANGED
|
@@ -688,6 +688,29 @@ export declare interface DsPanelNav extends Components.DsPanelNav {
|
|
|
688
688
|
}
|
|
689
689
|
|
|
690
690
|
|
|
691
|
+
@ProxyCmp({
|
|
692
|
+
inputs: ['activeTool', 'open']
|
|
693
|
+
})
|
|
694
|
+
@Component({
|
|
695
|
+
selector: 'ds-panel-tools',
|
|
696
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
697
|
+
template: '<ng-content></ng-content>',
|
|
698
|
+
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
|
699
|
+
inputs: ['activeTool', 'open'],
|
|
700
|
+
standalone: false
|
|
701
|
+
})
|
|
702
|
+
export class DsPanelTools {
|
|
703
|
+
protected el: HTMLDsPanelToolsElement;
|
|
704
|
+
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
|
|
705
|
+
c.detach();
|
|
706
|
+
this.el = r.nativeElement;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
export declare interface DsPanelTools extends Components.DsPanelTools {}
|
|
712
|
+
|
|
713
|
+
|
|
691
714
|
@ProxyCmp({
|
|
692
715
|
inputs: ['ariaLabel', 'ariaLabelledby', 'direction', 'inactive', 'options', 'value']
|
|
693
716
|
})
|
package/src/react/components.ts
CHANGED
|
@@ -28,6 +28,7 @@ export { DsMenu } from "./ds-menu.js";
|
|
|
28
28
|
export { DsModal } from "./ds-modal.js";
|
|
29
29
|
export { DsPagination } from "./ds-pagination.js";
|
|
30
30
|
export { DsPanelNav } from "./ds-panel-nav.js";
|
|
31
|
+
export { DsPanelTools } from "./ds-panel-tools.js";
|
|
31
32
|
export { DsRadioGroup } from "./ds-radio-group.js";
|
|
32
33
|
export { DsScrollbar } from "./ds-scrollbar.js";
|
|
33
34
|
export { DsSelect } from "./ds-select.js";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This file was automatically generated by the Stencil React Output Target.
|
|
5
|
+
* Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/* eslint-disable */
|
|
9
|
+
|
|
10
|
+
import type { StencilReactComponent } from '@stencil/react-output-target/runtime';
|
|
11
|
+
import { createComponent } from '@stencil/react-output-target/runtime';
|
|
12
|
+
import React from 'react';
|
|
13
|
+
|
|
14
|
+
import type { Components } from "@ds-mo/ui/dist/components";
|
|
15
|
+
import { DsPanelTools as DsPanelToolsElement, defineCustomElement as defineDsPanelTools } from "@ds-mo/ui/dist/components/ds-panel-tools.js";
|
|
16
|
+
|
|
17
|
+
export type DsPanelToolsEvents = NonNullable<unknown>;
|
|
18
|
+
|
|
19
|
+
export const DsPanelTools: StencilReactComponent<DsPanelToolsElement, DsPanelToolsEvents, Components.DsPanelTools> = /*@__PURE__*/ createComponent<DsPanelToolsElement, DsPanelToolsEvents, Components.DsPanelTools>({
|
|
20
|
+
tagName: 'ds-panel-tools',
|
|
21
|
+
elementClass: DsPanelToolsElement,
|
|
22
|
+
// @ts-ignore - ignore potential React type mismatches between the Stencil Output Target and your project.
|
|
23
|
+
react: React,
|
|
24
|
+
events: {} as DsPanelToolsEvents,
|
|
25
|
+
defineCustomElement: defineDsPanelTools
|
|
26
|
+
});
|
|
@@ -169,8 +169,13 @@ export class AppShell {
|
|
|
169
169
|
<div class="app-shell__bar">
|
|
170
170
|
<slot name="bar" />
|
|
171
171
|
</div>
|
|
172
|
-
<div class="app-
|
|
173
|
-
<
|
|
172
|
+
<div class="app-shell__workspace">
|
|
173
|
+
<div class="app-shell__content">
|
|
174
|
+
<slot />
|
|
175
|
+
</div>
|
|
176
|
+
<div class="app-shell__tools">
|
|
177
|
+
<slot name="tools" />
|
|
178
|
+
</div>
|
|
174
179
|
</div>
|
|
175
180
|
</div>
|
|
176
181
|
</div>
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { Component, Prop, Element, Watch, h, Host } from '@stencil/core';
|
|
1
|
+
import { Component, Prop, Element, State, Watch, h, Host } from '@stencil/core';
|
|
2
2
|
import {
|
|
3
3
|
BADGE_GRADIENT_POSITION_VAR,
|
|
4
|
+
badgeGradientPosition,
|
|
5
|
+
findGradientSurface,
|
|
4
6
|
isShellGradientActive,
|
|
5
|
-
|
|
7
|
+
readShellGradientPosition,
|
|
6
8
|
} from '../../nav/badge-gradient-ring';
|
|
7
9
|
|
|
8
10
|
export type BadgeVariant = 'counter' | 'dot';
|
|
@@ -63,6 +65,9 @@ export class Badge {
|
|
|
63
65
|
/** Accessible label. Defaults to the count as a string. */
|
|
64
66
|
@Prop() label: string | undefined;
|
|
65
67
|
|
|
68
|
+
/** Bumps on resize/layout so render recomputes gradient ring position. */
|
|
69
|
+
@State() private gradientLayoutVersion = 0;
|
|
70
|
+
|
|
66
71
|
private gradientObserver: ResizeObserver | null = null;
|
|
67
72
|
private gradientWindowListener: (() => void) | null = null;
|
|
68
73
|
|
|
@@ -70,19 +75,8 @@ export class Badge {
|
|
|
70
75
|
this.enableShellGradientRingIfNeeded();
|
|
71
76
|
}
|
|
72
77
|
|
|
73
|
-
/** Imperative opt-in avoids Stencil aborting parent render for nested badges. */
|
|
74
|
-
private enableShellGradientRingIfNeeded() {
|
|
75
|
-
if (!this.gradientBackground && isShellGradientActive(this.el)) {
|
|
76
|
-
this.gradientBackground = true;
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
this.bindGradientRingSync();
|
|
81
|
-
}
|
|
82
|
-
|
|
83
78
|
disconnectedCallback() {
|
|
84
79
|
this.unbindGradientRingSync();
|
|
85
|
-
this.el.style.removeProperty(BADGE_GRADIENT_POSITION_VAR);
|
|
86
80
|
}
|
|
87
81
|
|
|
88
82
|
@Watch('gradientBackground')
|
|
@@ -90,12 +84,25 @@ export class Badge {
|
|
|
90
84
|
this.bindGradientRingSync();
|
|
91
85
|
}
|
|
92
86
|
|
|
87
|
+
/** Imperative opt-in avoids Stencil aborting parent render for nested badges. */
|
|
88
|
+
private enableShellGradientRingIfNeeded() {
|
|
89
|
+
if (!this.gradientBackground && isShellGradientActive(this.el)) {
|
|
90
|
+
this.gradientBackground = true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (this.gradientBackground) {
|
|
94
|
+
this.bindGradientRingSync();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
93
98
|
private bindGradientRingSync() {
|
|
94
99
|
this.unbindGradientRingSync();
|
|
95
100
|
|
|
96
101
|
if (!this.gradientBackground) return;
|
|
97
102
|
|
|
98
|
-
const update = () =>
|
|
103
|
+
const update = () => {
|
|
104
|
+
this.gradientLayoutVersion += 1;
|
|
105
|
+
};
|
|
99
106
|
update();
|
|
100
107
|
|
|
101
108
|
this.gradientWindowListener = update;
|
|
@@ -126,6 +133,23 @@ export class Badge {
|
|
|
126
133
|
this.gradientObserver = null;
|
|
127
134
|
}
|
|
128
135
|
|
|
136
|
+
private ringHostStyle(ring: string): Record<string, string> {
|
|
137
|
+
void this.gradientLayoutVersion;
|
|
138
|
+
|
|
139
|
+
const style: Record<string, string> = { '--_badge-ring': ring };
|
|
140
|
+
if (!this.gradientBackground) return style;
|
|
141
|
+
|
|
142
|
+
const surface = findGradientSurface(this.el);
|
|
143
|
+
if (!surface) return style;
|
|
144
|
+
|
|
145
|
+
style[BADGE_GRADIENT_POSITION_VAR] = badgeGradientPosition(
|
|
146
|
+
this.el,
|
|
147
|
+
surface,
|
|
148
|
+
readShellGradientPosition(surface),
|
|
149
|
+
);
|
|
150
|
+
return style;
|
|
151
|
+
}
|
|
152
|
+
|
|
129
153
|
render() {
|
|
130
154
|
const isDot = this.variant === 'dot';
|
|
131
155
|
|
|
@@ -145,7 +169,7 @@ export class Badge {
|
|
|
145
169
|
'badge--on-gradient-background': this.gradientBackground,
|
|
146
170
|
}}
|
|
147
171
|
aria-label={ariaLabel}
|
|
148
|
-
style={
|
|
172
|
+
style={this.ringHostStyle(ring)}
|
|
149
173
|
>
|
|
150
174
|
<span
|
|
151
175
|
class={{
|
|
@@ -105,6 +105,10 @@ export class BarNav {
|
|
|
105
105
|
private resizeObserver: ResizeObserver | null = null;
|
|
106
106
|
private overflowCheckScheduled = false;
|
|
107
107
|
private intrinsicWidthRetryCount = 0;
|
|
108
|
+
/** Matches `basePath` once tabs + URL are reconciled for the active section. */
|
|
109
|
+
@State() private committedSection = '';
|
|
110
|
+
/** Section waiting for URL + tabs to catch up after a cross-area navigation. */
|
|
111
|
+
@State() private pendingSection = '';
|
|
108
112
|
|
|
109
113
|
private get effectiveValue(): string {
|
|
110
114
|
if (this.currentUrl && this.basePath) {
|
|
@@ -125,23 +129,10 @@ export class BarNav {
|
|
|
125
129
|
@Watch('tabsJson')
|
|
126
130
|
@Watch('actions')
|
|
127
131
|
@Watch('actionsJson')
|
|
128
|
-
onPropsChange() {
|
|
129
|
-
this.resolvedTabs = this.tabsJson
|
|
130
|
-
? parseJsonArrayProp(this.tabsJson, [])
|
|
131
|
-
: (this.tabs ?? []);
|
|
132
|
-
this.resolvedActions = this.actionsJson
|
|
133
|
-
? parseJsonArrayProp(this.actionsJson, [])
|
|
134
|
-
: (this.actions ?? []);
|
|
135
|
-
this.syncValueFromUrl();
|
|
136
|
-
this.intrinsicWidthRetryCount = 0;
|
|
137
|
-
this.scheduleOverflowCheck();
|
|
138
|
-
}
|
|
139
|
-
|
|
140
132
|
@Watch('currentUrl')
|
|
141
133
|
@Watch('basePath')
|
|
142
|
-
|
|
143
|
-
this.
|
|
144
|
-
this.scheduleOverflowCheck();
|
|
134
|
+
onHostPropsChange() {
|
|
135
|
+
this.applyHostProps();
|
|
145
136
|
}
|
|
146
137
|
|
|
147
138
|
@Watch('value')
|
|
@@ -179,7 +170,7 @@ export class BarNav {
|
|
|
179
170
|
}
|
|
180
171
|
|
|
181
172
|
componentWillLoad() {
|
|
182
|
-
this.
|
|
173
|
+
this.applyHostProps();
|
|
183
174
|
}
|
|
184
175
|
|
|
185
176
|
componentDidLoad() {
|
|
@@ -215,7 +206,7 @@ export class BarNav {
|
|
|
215
206
|
this.actionsJson,
|
|
216
207
|
)
|
|
217
208
|
) {
|
|
218
|
-
this.
|
|
209
|
+
this.applyHostProps();
|
|
219
210
|
} else if (this.currentUrl && this.basePath) {
|
|
220
211
|
this.syncValueFromUrl();
|
|
221
212
|
}
|
|
@@ -367,22 +358,81 @@ export class BarNav {
|
|
|
367
358
|
this.menuOpen = false;
|
|
368
359
|
}
|
|
369
360
|
|
|
361
|
+
/** Batch tabs/basePath/currentUrl updates so URL derivation never runs with a mixed section. */
|
|
362
|
+
private incomingTabs(): BarNavTab[] {
|
|
363
|
+
return this.tabsJson
|
|
364
|
+
? parseJsonArrayProp(this.tabsJson, [])
|
|
365
|
+
: (this.tabs ?? []);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
private incomingActions(): BarNavActionItem[] {
|
|
369
|
+
return this.actionsJson
|
|
370
|
+
? parseJsonArrayProp(this.actionsJson, [])
|
|
371
|
+
: (this.actions ?? []);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
private applyHostProps() {
|
|
375
|
+
const nextBasePath = this.basePath;
|
|
376
|
+
const sectionChanged =
|
|
377
|
+
nextBasePath !== '' &&
|
|
378
|
+
this.committedSection !== '' &&
|
|
379
|
+
nextBasePath !== this.committedSection;
|
|
380
|
+
|
|
381
|
+
if (sectionChanged) {
|
|
382
|
+
this.urlDerivedValue = '';
|
|
383
|
+
this.committedSection = '';
|
|
384
|
+
this.pendingSection = nextBasePath;
|
|
385
|
+
this.resolvedTabs = [];
|
|
386
|
+
this.resolvedActions = this.incomingActions();
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (this.pendingSection) {
|
|
391
|
+
if (!nextBasePath || nextBasePath !== this.pendingSection) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
if (!this.currentUrl.startsWith(this.pendingSection)) {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
const tabs = this.incomingTabs();
|
|
398
|
+
if (tabs.length === 0) {
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
this.pendingSection = '';
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
this.resolvedTabs = this.incomingTabs();
|
|
405
|
+
this.resolvedActions = this.incomingActions();
|
|
406
|
+
this.syncValueFromUrl();
|
|
407
|
+
this.committedSection = nextBasePath;
|
|
408
|
+
this.intrinsicWidthRetryCount = 0;
|
|
409
|
+
this.scheduleOverflowCheck();
|
|
410
|
+
}
|
|
411
|
+
|
|
370
412
|
private syncMenuSections() {
|
|
371
413
|
if (!this.menuEl) return;
|
|
372
414
|
this.menuEl.sections = tabsToMenuSections(this.resolvedTabs, this.effectiveValue);
|
|
373
415
|
}
|
|
374
416
|
|
|
375
417
|
private syncValueFromUrl() {
|
|
418
|
+
const tabs = this.incomingTabs();
|
|
419
|
+
|
|
376
420
|
if (!this.currentUrl || !this.basePath) {
|
|
377
421
|
this.urlDerivedValue = '';
|
|
378
422
|
this.hideTabsForDetailRoute = false;
|
|
379
423
|
return;
|
|
380
424
|
}
|
|
381
425
|
|
|
426
|
+
if (!this.currentUrl.startsWith(this.basePath)) {
|
|
427
|
+
this.urlDerivedValue = '';
|
|
428
|
+
this.hideTabsForDetailRoute = false;
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
382
432
|
const { value, hideTabs } = deriveBarNavValueFromUrl(
|
|
383
433
|
this.currentUrl,
|
|
384
434
|
this.basePath,
|
|
385
|
-
|
|
435
|
+
tabs,
|
|
386
436
|
);
|
|
387
437
|
this.urlDerivedValue = value;
|
|
388
438
|
this.hideTabsForDetailRoute = hideTabs;
|
|
@@ -449,8 +499,12 @@ export class BarNav {
|
|
|
449
499
|
}
|
|
450
500
|
|
|
451
501
|
render() {
|
|
452
|
-
const
|
|
502
|
+
const sectionReady =
|
|
503
|
+
this.basePath !== '' && this.committedSection === this.basePath;
|
|
504
|
+
const hasTabs =
|
|
505
|
+
sectionReady && this.resolvedTabs.length > 0 && !this.hideTabsForDetailRoute;
|
|
453
506
|
const tabSurface = this.tabSurface;
|
|
507
|
+
const tabGroupKey = this.basePath || 'no-section';
|
|
454
508
|
|
|
455
509
|
return (
|
|
456
510
|
<Host>
|
|
@@ -472,6 +526,7 @@ export class BarNav {
|
|
|
472
526
|
{hasTabs && (
|
|
473
527
|
<div class="bar-nav__tabs-probe" aria-hidden="true" inert>
|
|
474
528
|
<ds-tab-group-nav
|
|
529
|
+
key={`probe-${tabGroupKey}`}
|
|
475
530
|
ref={el => {
|
|
476
531
|
this.probeTabGroupEl = el ?? null;
|
|
477
532
|
if (el) {
|
|
@@ -489,6 +544,7 @@ export class BarNav {
|
|
|
489
544
|
{hasTabs && !this.tabsCollapsed && (
|
|
490
545
|
<div class="bar-nav__left">
|
|
491
546
|
<ds-tab-group-nav
|
|
547
|
+
key={`visible-${tabGroupKey}`}
|
|
492
548
|
class="bar-nav__tabs-visible"
|
|
493
549
|
ref={el => {
|
|
494
550
|
this.visibleTabGroupEl = el ?? null;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { Component, Prop, h, Host } from '@stencil/core';
|
|
1
|
+
import { Component, Prop, Element, State, h, Host } from '@stencil/core';
|
|
2
|
+
import { isShellGradientActive } from '../../nav/badge-gradient-ring';
|
|
2
3
|
|
|
3
4
|
export type FadeSide = 'top' | 'bottom' | 'left' | 'right';
|
|
4
5
|
export type FadeSurface =
|
|
@@ -55,6 +56,11 @@ const SURFACE_BACKGROUND: Record<FadeSurface, string> = {
|
|
|
55
56
|
scoped: true,
|
|
56
57
|
})
|
|
57
58
|
export class Fade {
|
|
59
|
+
@Element() el!: HTMLElement;
|
|
60
|
+
|
|
61
|
+
/** When under `ds-app-shell[gradient]`, composites the shell wash over the base fade. */
|
|
62
|
+
@State() private shellGradientChrome: 'panel' | 'bar' | null = null;
|
|
63
|
+
|
|
58
64
|
/** Edge where the fade is anchored. */
|
|
59
65
|
@Prop() side: FadeSide = 'bottom';
|
|
60
66
|
|
|
@@ -76,6 +82,18 @@ export class Fade {
|
|
|
76
82
|
/** Controls visibility without removing the element from layout/positioning. */
|
|
77
83
|
@Prop() visible: boolean = true;
|
|
78
84
|
|
|
85
|
+
componentDidLoad() {
|
|
86
|
+
this.syncShellGradient();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private syncShellGradient() {
|
|
90
|
+
if (!isShellGradientActive(this.el)) {
|
|
91
|
+
this.shellGradientChrome = null;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
this.shellGradientChrome = this.el.closest('ds-bar-nav') ? 'bar' : 'panel';
|
|
95
|
+
}
|
|
96
|
+
|
|
79
97
|
private resolveSize(): string {
|
|
80
98
|
if (this.height) return this.height;
|
|
81
99
|
|
|
@@ -100,6 +118,9 @@ export class Fade {
|
|
|
100
118
|
'fade--hidden': !this.visible,
|
|
101
119
|
'fade--vertical-edge': isVerticalEdge,
|
|
102
120
|
'fade--horizontal-edge': !isVerticalEdge,
|
|
121
|
+
'fade--shell-gradient': this.shellGradientChrome !== null,
|
|
122
|
+
'fade--shell-gradient-panel': this.shellGradientChrome === 'panel',
|
|
123
|
+
'fade--shell-gradient-bar': this.shellGradientChrome === 'bar',
|
|
103
124
|
[`fade--${this.side}`]: true,
|
|
104
125
|
}}
|
|
105
126
|
style={{
|
|
@@ -37,6 +37,7 @@ const vtResolvers = new WeakMap<object, () => void>();
|
|
|
37
37
|
@Component({
|
|
38
38
|
tag: 'ds-panel-nav',
|
|
39
39
|
styleUrl: 'PanelNav.css',
|
|
40
|
+
styleUrls: ['../../utils/scroll-edge-fade.css'],
|
|
40
41
|
scoped: true,
|
|
41
42
|
})
|
|
42
43
|
export class PanelNav {
|
|
@@ -550,7 +551,15 @@ export class PanelNav {
|
|
|
550
551
|
</div>
|
|
551
552
|
|
|
552
553
|
{/* ── Scrollable body ── */}
|
|
553
|
-
<div
|
|
554
|
+
<div
|
|
555
|
+
class={{
|
|
556
|
+
'panel-nav__body': true,
|
|
557
|
+
'scroll-edge-fade': true,
|
|
558
|
+
'scroll-edge-fade--bottom': true,
|
|
559
|
+
'scroll-edge-fade--at-end': this.atBottom,
|
|
560
|
+
}}
|
|
561
|
+
onScroll={e => this.handleBodyScroll(e)}
|
|
562
|
+
>
|
|
554
563
|
{(() => {
|
|
555
564
|
let flatIdx = 0;
|
|
556
565
|
return this.parsedGroups.map(group => (
|
|
@@ -572,14 +581,6 @@ export class PanelNav {
|
|
|
572
581
|
|
|
573
582
|
{/* ── Footer ── */}
|
|
574
583
|
<div class="panel-nav__footer">
|
|
575
|
-
<ds-fade
|
|
576
|
-
class="panel-nav__footer-fade"
|
|
577
|
-
side="bottom"
|
|
578
|
-
size="size-600"
|
|
579
|
-
background="var(--_nav-bg)"
|
|
580
|
-
visible={!this.atBottom}
|
|
581
|
-
/>
|
|
582
|
-
|
|
583
584
|
{/* Left icon button — Gear in navigation style, Dashboard in default style */}
|
|
584
585
|
<button
|
|
585
586
|
type="button"
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { Component, Prop, Element, State, Watch, h, Host } from '@stencil/core';
|
|
2
|
+
import {
|
|
3
|
+
PANEL_TOOLS_LABELS,
|
|
4
|
+
type PanelToolsToolId,
|
|
5
|
+
} from './panel-tools-types';
|
|
6
|
+
|
|
7
|
+
@Component({
|
|
8
|
+
tag: 'ds-panel-tools',
|
|
9
|
+
styleUrl: 'PanelTools.css',
|
|
10
|
+
scoped: true,
|
|
11
|
+
})
|
|
12
|
+
export class PanelTools {
|
|
13
|
+
@Element() el!: HTMLElement;
|
|
14
|
+
|
|
15
|
+
/** When false, width animates to 0. */
|
|
16
|
+
@Prop({ reflect: true }) open: boolean = false;
|
|
17
|
+
|
|
18
|
+
/** Active tool view — `search`, `messages`, `stacks`, `activity`, or `agents`. */
|
|
19
|
+
@Prop({ attribute: 'active-tool', reflect: true }) activeTool: PanelToolsToolId | '' = '';
|
|
20
|
+
|
|
21
|
+
/** Stays true until the close width transition finishes. */
|
|
22
|
+
@State() private expanded = false;
|
|
23
|
+
|
|
24
|
+
/** Suppresses width transition until the host has painted its initial open state. */
|
|
25
|
+
@State() private readyForMotion = false;
|
|
26
|
+
|
|
27
|
+
private motionEnableGeneration = 0;
|
|
28
|
+
|
|
29
|
+
componentWillLoad() {
|
|
30
|
+
if (this.open) this.expanded = true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
componentDidLoad() {
|
|
34
|
+
this.deferMotionEnable();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
disconnectedCallback() {
|
|
38
|
+
this.el.removeEventListener('transitionend', this.handleTransitionEnd);
|
|
39
|
+
this.motionEnableGeneration += 1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Two rAFs after the last pre-ready prop change — host bindings may land late on refresh. */
|
|
43
|
+
private deferMotionEnable() {
|
|
44
|
+
if (this.readyForMotion) return;
|
|
45
|
+
const generation = ++this.motionEnableGeneration;
|
|
46
|
+
requestAnimationFrame(() => {
|
|
47
|
+
if (generation !== this.motionEnableGeneration) return;
|
|
48
|
+
requestAnimationFrame(() => {
|
|
49
|
+
if (generation !== this.motionEnableGeneration) return;
|
|
50
|
+
this.readyForMotion = true;
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
connectedCallback() {
|
|
56
|
+
this.el.addEventListener('transitionend', this.handleTransitionEnd);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@Watch('open')
|
|
60
|
+
openChanged(isOpen: boolean) {
|
|
61
|
+
if (isOpen) this.expanded = true;
|
|
62
|
+
this.deferMotionEnable();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@Watch('activeTool')
|
|
66
|
+
activeToolChanged() {
|
|
67
|
+
this.deferMotionEnable();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private handleTransitionEnd = (event: TransitionEvent) => {
|
|
71
|
+
if (event.target !== this.el) return;
|
|
72
|
+
if (event.propertyName !== 'max-width') return;
|
|
73
|
+
if (!this.open) this.expanded = false;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
private isShowingContent(): boolean {
|
|
77
|
+
return this.open || this.expanded;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private isActiveTool(id: PanelToolsToolId): boolean {
|
|
81
|
+
return this.isShowingContent() && this.activeTool === id;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private headerLabel(): string {
|
|
85
|
+
if (!this.activeTool) return '';
|
|
86
|
+
return PANEL_TOOLS_LABELS[this.activeTool as PanelToolsToolId] ?? '';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
render() {
|
|
90
|
+
const showingContent = this.isShowingContent();
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<Host
|
|
94
|
+
class={{
|
|
95
|
+
'panel-tools': true,
|
|
96
|
+
'panel-tools--open': this.open,
|
|
97
|
+
'panel-tools--ready': this.readyForMotion,
|
|
98
|
+
}}
|
|
99
|
+
role="complementary"
|
|
100
|
+
aria-label={this.headerLabel() || 'Tools'}
|
|
101
|
+
aria-hidden={showingContent ? null : 'true'}
|
|
102
|
+
>
|
|
103
|
+
<div class="panel-tools__inner">
|
|
104
|
+
<header class="panel-tools__header">
|
|
105
|
+
<span class="text-body-medium-emphasis">{this.headerLabel()}</span>
|
|
106
|
+
</header>
|
|
107
|
+
<div class="panel-tools__body">
|
|
108
|
+
<div
|
|
109
|
+
class={{
|
|
110
|
+
'panel-tools__view': true,
|
|
111
|
+
'panel-tools__view--active': this.isActiveTool('search'),
|
|
112
|
+
'text-body-medium': true,
|
|
113
|
+
}}
|
|
114
|
+
hidden={!this.isActiveTool('search')}
|
|
115
|
+
>
|
|
116
|
+
<slot name="search" />
|
|
117
|
+
</div>
|
|
118
|
+
<div
|
|
119
|
+
class={{
|
|
120
|
+
'panel-tools__view': true,
|
|
121
|
+
'panel-tools__view--active': this.isActiveTool('messages'),
|
|
122
|
+
'text-body-medium': true,
|
|
123
|
+
}}
|
|
124
|
+
hidden={!this.isActiveTool('messages')}
|
|
125
|
+
>
|
|
126
|
+
<slot name="messages" />
|
|
127
|
+
</div>
|
|
128
|
+
<div
|
|
129
|
+
class={{
|
|
130
|
+
'panel-tools__view': true,
|
|
131
|
+
'panel-tools__view--active': this.isActiveTool('stacks'),
|
|
132
|
+
'text-body-medium': true,
|
|
133
|
+
}}
|
|
134
|
+
hidden={!this.isActiveTool('stacks')}
|
|
135
|
+
>
|
|
136
|
+
<slot name="stacks" />
|
|
137
|
+
</div>
|
|
138
|
+
<div
|
|
139
|
+
class={{
|
|
140
|
+
'panel-tools__view': true,
|
|
141
|
+
'panel-tools__view--active': this.isActiveTool('activity'),
|
|
142
|
+
'text-body-medium': true,
|
|
143
|
+
}}
|
|
144
|
+
hidden={!this.isActiveTool('activity')}
|
|
145
|
+
>
|
|
146
|
+
<slot name="activity" />
|
|
147
|
+
</div>
|
|
148
|
+
<div
|
|
149
|
+
class={{
|
|
150
|
+
'panel-tools__view': true,
|
|
151
|
+
'panel-tools__view--active': this.isActiveTool('agents'),
|
|
152
|
+
'text-body-medium': true,
|
|
153
|
+
}}
|
|
154
|
+
hidden={!this.isActiveTool('agents')}
|
|
155
|
+
>
|
|
156
|
+
<slot name="agents" />
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
</Host>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Bar-nav tool ids that map to `ds-panel-tools` views. */
|
|
2
|
+
export type PanelToolsToolId = 'search' | 'messages' | 'stacks' | 'activity' | 'agents';
|
|
3
|
+
|
|
4
|
+
export const PANEL_TOOLS_TOOL_IDS: PanelToolsToolId[] = [
|
|
5
|
+
'search',
|
|
6
|
+
'messages',
|
|
7
|
+
'stacks',
|
|
8
|
+
'activity',
|
|
9
|
+
'agents',
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
export const PANEL_TOOLS_LABELS: Record<PanelToolsToolId, string> = {
|
|
13
|
+
search: 'Search',
|
|
14
|
+
messages: 'Messages',
|
|
15
|
+
stacks: 'Stacks',
|
|
16
|
+
activity: 'Activity',
|
|
17
|
+
agents: 'Agents',
|
|
18
|
+
};
|