@ds-gn/ui 0.8.0 → 0.10.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/custom-elements.json +101 -0
- package/dist/common/controllers/SectionEntranceController.d.ts +37 -0
- package/dist/common/props/motion/motion.props.d.ts +10 -0
- package/dist/components/Container/src/Container.d.ts +10 -0
- package/dist/components/Container/src/Container.js +148 -23
- package/dist/components/FeatureTabs/src/FeatureTabs.d.ts +19 -0
- package/dist/components/FeatureTabs/src/FeatureTabs.js +83 -3
- package/dist/components/FeatureTabs/src/FeatureTabs.props.d.ts +1 -1
- package/dist/components/Hero/src/Hero.d.ts +11 -0
- package/dist/components/Hero/src/Hero.js +39 -0
- package/dist/styles/hero-fallback.css +5 -0
- package/package.json +2 -2
package/custom-elements.json
CHANGED
|
@@ -1219,6 +1219,22 @@
|
|
|
1219
1219
|
"description": "Space below the content.",
|
|
1220
1220
|
"attribute": "padding-bottom",
|
|
1221
1221
|
"reflects": true
|
|
1222
|
+
},
|
|
1223
|
+
{
|
|
1224
|
+
"kind": "field",
|
|
1225
|
+
"name": "motion",
|
|
1226
|
+
"type": {
|
|
1227
|
+
"text": "MotionType"
|
|
1228
|
+
},
|
|
1229
|
+
"description": "Animate the section in when it scrolls into view. Each heading block\ninside fades in its eyebrow, writes its heading letter by letter, then\nfades in its description and actions; each form block then fades in its\ninput and button together, then its note. A section already in view when\nit loads, and reduced motion, stay static.",
|
|
1230
|
+
"attribute": "motion",
|
|
1231
|
+
"reflects": true
|
|
1232
|
+
},
|
|
1233
|
+
{
|
|
1234
|
+
"kind": "field",
|
|
1235
|
+
"name": "_entrance",
|
|
1236
|
+
"privacy": "private",
|
|
1237
|
+
"default": "new SectionEntranceController( this, () => this.motion === motionValues.ON, )"
|
|
1222
1238
|
}
|
|
1223
1239
|
],
|
|
1224
1240
|
"superclass": {
|
|
@@ -1268,6 +1284,14 @@
|
|
|
1268
1284
|
"text": "SectionSpaceType | undefined"
|
|
1269
1285
|
},
|
|
1270
1286
|
"description": "Space below the content."
|
|
1287
|
+
},
|
|
1288
|
+
{
|
|
1289
|
+
"name": "motion",
|
|
1290
|
+
"fieldName": "motion",
|
|
1291
|
+
"type": {
|
|
1292
|
+
"text": "MotionType"
|
|
1293
|
+
},
|
|
1294
|
+
"description": "Animate the section in when it scrolls into view. Each heading block\ninside fades in its eyebrow, writes its heading letter by letter, then\nfades in its description and actions; each form block then fades in its\ninput and button together, then its note. A section already in view when\nit loads, and reduced motion, stay static."
|
|
1271
1295
|
}
|
|
1272
1296
|
]
|
|
1273
1297
|
}
|
|
@@ -1520,6 +1544,15 @@
|
|
|
1520
1544
|
"privacy": "private",
|
|
1521
1545
|
"description": "The feature whose detail last faded in; the fade replays when it changes."
|
|
1522
1546
|
},
|
|
1547
|
+
{
|
|
1548
|
+
"kind": "field",
|
|
1549
|
+
"name": "_motionState",
|
|
1550
|
+
"type": {
|
|
1551
|
+
"text": "'static' | 'held' | 'played' | undefined"
|
|
1552
|
+
},
|
|
1553
|
+
"privacy": "private",
|
|
1554
|
+
"description": "Motion progress, decided by the first visibility report."
|
|
1555
|
+
},
|
|
1523
1556
|
{
|
|
1524
1557
|
"kind": "field",
|
|
1525
1558
|
"name": "surface",
|
|
@@ -1606,6 +1639,32 @@
|
|
|
1606
1639
|
"description": "Accessible label for the tab list.",
|
|
1607
1640
|
"attribute": "tabs-aria-label"
|
|
1608
1641
|
},
|
|
1642
|
+
{
|
|
1643
|
+
"kind": "field",
|
|
1644
|
+
"name": "motion",
|
|
1645
|
+
"type": {
|
|
1646
|
+
"text": "MotionType"
|
|
1647
|
+
},
|
|
1648
|
+
"description": "Animate the section in when it scrolls into view: the header's heading\nletter by letter, then its description, then the tabs as a whole. The\neyebrow stays static. A section already in view when it loads, and\nreduced motion, stay static.",
|
|
1649
|
+
"attribute": "motion",
|
|
1650
|
+
"reflects": true
|
|
1651
|
+
},
|
|
1652
|
+
{
|
|
1653
|
+
"kind": "method",
|
|
1654
|
+
"name": "_updateMotion",
|
|
1655
|
+
"privacy": "private",
|
|
1656
|
+
"description": "Decide the motion from the first visibility report: a section already\non screen stays static; one off screen holds its parts hidden (from\nscript, so nothing is hidden when scripts do not run) and plays when it\ncomes into view, so even a jump to it shows no unanimated frame."
|
|
1657
|
+
},
|
|
1658
|
+
{
|
|
1659
|
+
"kind": "method",
|
|
1660
|
+
"name": "_motionParts",
|
|
1661
|
+
"privacy": "private"
|
|
1662
|
+
},
|
|
1663
|
+
{
|
|
1664
|
+
"kind": "method",
|
|
1665
|
+
"name": "_playMotion",
|
|
1666
|
+
"privacy": "private"
|
|
1667
|
+
},
|
|
1609
1668
|
{
|
|
1610
1669
|
"kind": "method",
|
|
1611
1670
|
"name": "_fadeInDetail",
|
|
@@ -1862,6 +1921,14 @@
|
|
|
1862
1921
|
},
|
|
1863
1922
|
"default": "DEFAULT_TABS_ARIA_LABEL",
|
|
1864
1923
|
"description": "Accessible label for the tab list."
|
|
1924
|
+
},
|
|
1925
|
+
{
|
|
1926
|
+
"name": "motion",
|
|
1927
|
+
"fieldName": "motion",
|
|
1928
|
+
"type": {
|
|
1929
|
+
"text": "MotionType"
|
|
1930
|
+
},
|
|
1931
|
+
"description": "Animate the section in when it scrolls into view: the header's heading\nletter by letter, then its description, then the tabs as a whole. The\neyebrow stays static. A section already in view when it loads, and\nreduced motion, stay static."
|
|
1865
1932
|
}
|
|
1866
1933
|
]
|
|
1867
1934
|
}
|
|
@@ -2642,6 +2709,32 @@
|
|
|
2642
2709
|
"description": "Space below the section. Unset follows the inherited section space.",
|
|
2643
2710
|
"attribute": "padding-bottom",
|
|
2644
2711
|
"reflects": true
|
|
2712
|
+
},
|
|
2713
|
+
{
|
|
2714
|
+
"kind": "field",
|
|
2715
|
+
"name": "motion",
|
|
2716
|
+
"type": {
|
|
2717
|
+
"text": "MotionType"
|
|
2718
|
+
},
|
|
2719
|
+
"description": "Animate the content's eyebrow in letter by letter once the hero loads,\neach letter fading in with a small rise.\nEverything else stays static, so the hero's largest text paints at once.\nUnder reduced motion the eyebrow is static too.",
|
|
2720
|
+
"attribute": "motion",
|
|
2721
|
+
"reflects": true
|
|
2722
|
+
},
|
|
2723
|
+
{
|
|
2724
|
+
"kind": "field",
|
|
2725
|
+
"name": "_motion",
|
|
2726
|
+
"privacy": "private",
|
|
2727
|
+
"default": "new MotionController(this)"
|
|
2728
|
+
},
|
|
2729
|
+
{
|
|
2730
|
+
"kind": "method",
|
|
2731
|
+
"name": "_eyebrows",
|
|
2732
|
+
"privacy": "private",
|
|
2733
|
+
"return": {
|
|
2734
|
+
"type": {
|
|
2735
|
+
"text": "Element[]"
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2645
2738
|
}
|
|
2646
2739
|
],
|
|
2647
2740
|
"superclass": {
|
|
@@ -2698,6 +2791,14 @@
|
|
|
2698
2791
|
"text": "SectionSpaceType | undefined"
|
|
2699
2792
|
},
|
|
2700
2793
|
"description": "Space below the section. Unset follows the inherited section space."
|
|
2794
|
+
},
|
|
2795
|
+
{
|
|
2796
|
+
"name": "motion",
|
|
2797
|
+
"fieldName": "motion",
|
|
2798
|
+
"type": {
|
|
2799
|
+
"text": "MotionType"
|
|
2800
|
+
},
|
|
2801
|
+
"description": "Animate the content's eyebrow in letter by letter once the hero loads,\neach letter fading in with a small rise.\nEverything else stays static, so the hero's largest text paints at once.\nUnder reduced motion the eyebrow is static too."
|
|
2701
2802
|
}
|
|
2702
2803
|
]
|
|
2703
2804
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ReactiveController, ReactiveControllerHost } from 'lit';
|
|
2
|
+
type EntrancePart = {
|
|
3
|
+
kind: 'fade';
|
|
4
|
+
els: HTMLElement[];
|
|
5
|
+
} | {
|
|
6
|
+
kind: 'letters';
|
|
7
|
+
el: HTMLElement;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* The entrance parts inside `root`, in document order. Each heading block
|
|
11
|
+
* contributes its eyebrow (fade), heading (letter by letter), description,
|
|
12
|
+
* then actions; each form block its input and button together, then its note.
|
|
13
|
+
*/
|
|
14
|
+
export declare function sectionEntranceParts(root: Element): EntrancePart[];
|
|
15
|
+
/**
|
|
16
|
+
* Plays a section's entrance when it scrolls into view: the heading blocks
|
|
17
|
+
* and form blocks it contains animate in one timeline (see
|
|
18
|
+
* `sectionEntranceParts`). Decided from the first visibility report: a
|
|
19
|
+
* section already on screen, and reduced motion, stay static; one off screen
|
|
20
|
+
* holds its parts hidden (from script, so nothing is hidden when scripts do
|
|
21
|
+
* not run) and plays once when it comes into view.
|
|
22
|
+
*/
|
|
23
|
+
export declare class SectionEntranceController implements ReactiveController {
|
|
24
|
+
private readonly host;
|
|
25
|
+
private readonly enabled;
|
|
26
|
+
private readonly motion;
|
|
27
|
+
private observer?;
|
|
28
|
+
private state?;
|
|
29
|
+
private parts;
|
|
30
|
+
constructor(host: ReactiveControllerHost & HTMLElement, enabled: () => boolean);
|
|
31
|
+
hostUpdated(): void;
|
|
32
|
+
hostDisconnected(): void;
|
|
33
|
+
private report;
|
|
34
|
+
private play;
|
|
35
|
+
private stopObserving;
|
|
36
|
+
}
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a section animates in. Components with their own motion
|
|
3
|
+
* vocabulary (Modal's direction values) declare their own `motion` values.
|
|
4
|
+
*/
|
|
5
|
+
export declare const motionValues: {
|
|
6
|
+
readonly ON: "on";
|
|
7
|
+
readonly OFF: "off";
|
|
8
|
+
};
|
|
9
|
+
export declare const motionProps: import("lit").PropertyDeclaration<unknown, unknown>;
|
|
10
|
+
export type MotionType = (typeof motionValues)[keyof typeof motionValues];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import '../../../types/global.d.ts';
|
|
2
2
|
import { type CSSResultGroup } from 'lit';
|
|
3
3
|
import { Base } from '../../../common/Base/Base.js';
|
|
4
|
+
import { type MotionType } from '../../../common/props/motion/motion.props.js';
|
|
4
5
|
import { type ContainerPaddingType, type ContainerSizeType, type SectionSpaceType } from './Container.props.js';
|
|
5
6
|
/** @slot - Constrained page content. */
|
|
6
7
|
export declare class Container extends Base {
|
|
@@ -22,6 +23,15 @@ export declare class Container extends Base {
|
|
|
22
23
|
paddingTop?: SectionSpaceType;
|
|
23
24
|
/** Space below the content. */
|
|
24
25
|
paddingBottom?: SectionSpaceType;
|
|
26
|
+
/**
|
|
27
|
+
* Animate the section in when it scrolls into view. Each heading block
|
|
28
|
+
* inside fades in its eyebrow, writes its heading letter by letter, then
|
|
29
|
+
* fades in its description and actions; each form block then fades in its
|
|
30
|
+
* input and button together, then its note. A section already in view when
|
|
31
|
+
* it loads, and reduced motion, stay static.
|
|
32
|
+
*/
|
|
33
|
+
motion: MotionType;
|
|
34
|
+
private _entrance;
|
|
25
35
|
render(): import("lit").TemplateResult<1>;
|
|
26
36
|
}
|
|
27
37
|
declare global {
|
|
@@ -91,6 +91,146 @@ class Base extends LitElement {
|
|
|
91
91
|
}
|
|
92
92
|
defineElement("dsgn-base", Base);
|
|
93
93
|
|
|
94
|
+
// common/controllers/SectionEntranceController.ts
|
|
95
|
+
import {
|
|
96
|
+
prefersReducedMotion,
|
|
97
|
+
splitText
|
|
98
|
+
} from "@ds-gn/motion";
|
|
99
|
+
import { MotionController } from "@ds-gn/motion/lit";
|
|
100
|
+
var HEADING_STAGGER = 20;
|
|
101
|
+
var HEADING_MAX_SPREAD = 500;
|
|
102
|
+
var HEADING_RISE = 4;
|
|
103
|
+
var MOTION_OVERLAP = 160;
|
|
104
|
+
var holdHidden = (el) => el.style.setProperty("opacity", "0");
|
|
105
|
+
var releaseHidden = (el) => el.style.removeProperty("opacity");
|
|
106
|
+
var ENTRANCE_BLOCKS = new Set(["dsgn-heading-block", "dsgn-form-block"]);
|
|
107
|
+
var hasContent = (el) => !["div", "span"].includes(el.localName) || el.children.length > 0 || (el.textContent ?? "").trim() !== "";
|
|
108
|
+
var childrenIn = (parent, ...slots) => Array.from(parent.children).filter((child) => child instanceof HTMLElement && slots.includes(child.slot) && hasContent(child));
|
|
109
|
+
function sectionEntranceParts(root) {
|
|
110
|
+
const parts = [];
|
|
111
|
+
const fade = (els) => {
|
|
112
|
+
if (els.length > 0)
|
|
113
|
+
parts.push({ kind: "fade", els });
|
|
114
|
+
};
|
|
115
|
+
Array.from(root.querySelectorAll("*")).filter((el) => ENTRANCE_BLOCKS.has(el.localName)).forEach((block) => {
|
|
116
|
+
if (block.localName === "dsgn-form-block") {
|
|
117
|
+
fade(childrenIn(block, "input", "button"));
|
|
118
|
+
fade(childrenIn(block, "note"));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
fade(childrenIn(block, "eyebrow"));
|
|
122
|
+
const [heading] = childrenIn(block, "heading");
|
|
123
|
+
if (heading)
|
|
124
|
+
parts.push({ kind: "letters", el: heading });
|
|
125
|
+
fade(childrenIn(block, "description"));
|
|
126
|
+
fade(childrenIn(block, "action-primary", "action-secondary"));
|
|
127
|
+
});
|
|
128
|
+
return parts;
|
|
129
|
+
}
|
|
130
|
+
var elementsOf = (part) => part.kind === "fade" ? part.els : [part.el];
|
|
131
|
+
|
|
132
|
+
class SectionEntranceController {
|
|
133
|
+
constructor(host, enabled) {
|
|
134
|
+
this.parts = [];
|
|
135
|
+
this.host = host;
|
|
136
|
+
this.enabled = enabled;
|
|
137
|
+
this.motion = new MotionController(host);
|
|
138
|
+
host.addController(this);
|
|
139
|
+
}
|
|
140
|
+
hostUpdated() {
|
|
141
|
+
if (this.observer || this.state !== undefined || !this.enabled())
|
|
142
|
+
return;
|
|
143
|
+
if (typeof IntersectionObserver === "undefined") {
|
|
144
|
+
this.state = "static";
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
this.observer = new IntersectionObserver((entries) => {
|
|
148
|
+
const entry = entries[entries.length - 1];
|
|
149
|
+
if (entry)
|
|
150
|
+
this.report(entry.isIntersecting);
|
|
151
|
+
});
|
|
152
|
+
this.observer.observe(this.host);
|
|
153
|
+
}
|
|
154
|
+
hostDisconnected() {
|
|
155
|
+
this.stopObserving();
|
|
156
|
+
if (this.state === "held") {
|
|
157
|
+
this.parts.flatMap(elementsOf).forEach(releaseHidden);
|
|
158
|
+
this.state = undefined;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
report(inView) {
|
|
162
|
+
if (this.state === undefined) {
|
|
163
|
+
const play = !inView && !prefersReducedMotion();
|
|
164
|
+
this.state = play ? "held" : "static";
|
|
165
|
+
if (!play)
|
|
166
|
+
return this.stopObserving();
|
|
167
|
+
this.parts = sectionEntranceParts(this.host);
|
|
168
|
+
this.parts.flatMap(elementsOf).forEach(holdHidden);
|
|
169
|
+
}
|
|
170
|
+
if (this.state === "held" && inView) {
|
|
171
|
+
this.state = "played";
|
|
172
|
+
this.stopObserving();
|
|
173
|
+
this.play();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
play() {
|
|
177
|
+
this.parts.flatMap(elementsOf).forEach(releaseHidden);
|
|
178
|
+
const splits = [];
|
|
179
|
+
const steps = this.parts.map((part, index) => {
|
|
180
|
+
const overlap = index === 0 ? undefined : MOTION_OVERLAP;
|
|
181
|
+
if (part.kind === "fade") {
|
|
182
|
+
return { targets: part.els, preset: "fade", stagger: 0, overlap };
|
|
183
|
+
}
|
|
184
|
+
const split = splitText(part.el);
|
|
185
|
+
splits.push(split);
|
|
186
|
+
return {
|
|
187
|
+
targets: split.chars,
|
|
188
|
+
preset: "fade",
|
|
189
|
+
y: HEADING_RISE,
|
|
190
|
+
stagger: HEADING_STAGGER,
|
|
191
|
+
maxDuration: HEADING_MAX_SPREAD
|
|
192
|
+
};
|
|
193
|
+
});
|
|
194
|
+
if (steps.length === 0)
|
|
195
|
+
return;
|
|
196
|
+
this.motion.timeline(steps).finished.then(() => splits.forEach((split) => split.revert()));
|
|
197
|
+
}
|
|
198
|
+
stopObserving() {
|
|
199
|
+
this.observer?.disconnect();
|
|
200
|
+
this.observer = undefined;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// common/helpers/enum-validator.ts
|
|
205
|
+
var enumValidator = (propName, enumSet, candidateValue) => {
|
|
206
|
+
if (!enumSet.has(candidateValue)) {
|
|
207
|
+
throw new Error(`Invalid prop ${propName} of value ${candidateValue}. Expected one of: ${Array.from(enumSet).join(", ")}`);
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// common/helpers/enum-creator.ts
|
|
212
|
+
var enumCreator = (acceptableValues, propName, type, other = {}) => {
|
|
213
|
+
const acceptableValuesSet = new Set(acceptableValues);
|
|
214
|
+
return {
|
|
215
|
+
type,
|
|
216
|
+
hasChanged(newValue, oldValue) {
|
|
217
|
+
if (newValue !== oldValue) {
|
|
218
|
+
enumValidator(propName, acceptableValuesSet, newValue);
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
return false;
|
|
222
|
+
},
|
|
223
|
+
...other
|
|
224
|
+
};
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// common/props/motion/motion.props.ts
|
|
228
|
+
var motionValues = {
|
|
229
|
+
ON: "on",
|
|
230
|
+
OFF: "off"
|
|
231
|
+
};
|
|
232
|
+
var motionProps = enumCreator(Object.values(motionValues), "motion", String, { reflect: true });
|
|
233
|
+
|
|
94
234
|
// components/Container/src/Container.css
|
|
95
235
|
import { css as css2 } from "lit";
|
|
96
236
|
var styles2 = css2`/*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.com */
|
|
@@ -138,6 +278,9 @@ var styles2 = css2`/*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.co
|
|
|
138
278
|
.mx-auto {
|
|
139
279
|
margin-inline: auto;
|
|
140
280
|
}
|
|
281
|
+
.block {
|
|
282
|
+
display: block;
|
|
283
|
+
}
|
|
141
284
|
.w-full {
|
|
142
285
|
width: 100%;
|
|
143
286
|
}
|
|
@@ -229,29 +372,6 @@ var styles2 = css2`/*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.co
|
|
|
229
372
|
`;
|
|
230
373
|
var Container_default = styles2;
|
|
231
374
|
|
|
232
|
-
// common/helpers/enum-validator.ts
|
|
233
|
-
var enumValidator = (propName, enumSet, candidateValue) => {
|
|
234
|
-
if (!enumSet.has(candidateValue)) {
|
|
235
|
-
throw new Error(`Invalid prop ${propName} of value ${candidateValue}. Expected one of: ${Array.from(enumSet).join(", ")}`);
|
|
236
|
-
}
|
|
237
|
-
};
|
|
238
|
-
|
|
239
|
-
// common/helpers/enum-creator.ts
|
|
240
|
-
var enumCreator = (acceptableValues, propName, type, other = {}) => {
|
|
241
|
-
const acceptableValuesSet = new Set(acceptableValues);
|
|
242
|
-
return {
|
|
243
|
-
type,
|
|
244
|
-
hasChanged(newValue, oldValue) {
|
|
245
|
-
if (newValue !== oldValue) {
|
|
246
|
-
enumValidator(propName, acceptableValuesSet, newValue);
|
|
247
|
-
return true;
|
|
248
|
-
}
|
|
249
|
-
return false;
|
|
250
|
-
},
|
|
251
|
-
...other
|
|
252
|
-
};
|
|
253
|
-
};
|
|
254
|
-
|
|
255
375
|
// components/Container/src/Container.props.ts
|
|
256
376
|
var containerSizeValues = {
|
|
257
377
|
SM: "sm",
|
|
@@ -341,6 +461,8 @@ class Container extends Base {
|
|
|
341
461
|
super(...arguments);
|
|
342
462
|
this.size = containerSizeValues.LG;
|
|
343
463
|
this.section = false;
|
|
464
|
+
this.motion = motionValues.OFF;
|
|
465
|
+
this._entrance = new SectionEntranceController(this, () => this.motion === motionValues.ON);
|
|
344
466
|
}
|
|
345
467
|
static styles = [Base.styles, Container_default];
|
|
346
468
|
render() {
|
|
@@ -366,6 +488,9 @@ __legacyDecorateClassTS([
|
|
|
366
488
|
__legacyDecorateClassTS([
|
|
367
489
|
property({ ...paddingBottomProps })
|
|
368
490
|
], Container.prototype, "paddingBottom", undefined);
|
|
491
|
+
__legacyDecorateClassTS([
|
|
492
|
+
property({ ...motionProps })
|
|
493
|
+
], Container.prototype, "motion", undefined);
|
|
369
494
|
defineElement("dsgn-container", Container);
|
|
370
495
|
export {
|
|
371
496
|
Container
|
|
@@ -8,6 +8,7 @@ import '../../Icon/src/Icon.js';
|
|
|
8
8
|
import { Base } from '../../../common/Base/Base.js';
|
|
9
9
|
import { type SurfaceType } from '../../../common/props/surface/surface.props.js';
|
|
10
10
|
import { type CSSResultGroup } from 'lit';
|
|
11
|
+
import { type MotionType } from '../../../common/props/motion/motion.props.js';
|
|
11
12
|
import { type SectionSpaceType } from '../../Container/src/Container.props.js';
|
|
12
13
|
import { type FeatureTabData } from './FeatureTabs.props.js';
|
|
13
14
|
/** @slot header - Optional content above the tab interface. */
|
|
@@ -22,6 +23,8 @@ export declare class FeatureTabs extends Base {
|
|
|
22
23
|
private _motion;
|
|
23
24
|
/** The feature whose detail last faded in; the fade replays when it changes. */
|
|
24
25
|
private _shownIndex?;
|
|
26
|
+
/** Motion progress, decided by the first visibility report. */
|
|
27
|
+
private _motionState?;
|
|
25
28
|
surface: SurfaceType;
|
|
26
29
|
features: FeatureTabData[];
|
|
27
30
|
duration: number;
|
|
@@ -37,8 +40,24 @@ export declare class FeatureTabs extends Base {
|
|
|
37
40
|
resumeAriaLabel: string;
|
|
38
41
|
/** Accessible label for the tab list. */
|
|
39
42
|
tabsAriaLabel: string;
|
|
43
|
+
/**
|
|
44
|
+
* Animate the section in when it scrolls into view: the header's heading
|
|
45
|
+
* letter by letter, then its description, then the tabs as a whole. The
|
|
46
|
+
* eyebrow stays static. A section already in view when it loads, and
|
|
47
|
+
* reduced motion, stay static.
|
|
48
|
+
*/
|
|
49
|
+
motion: MotionType;
|
|
40
50
|
constructor();
|
|
41
51
|
firstUpdated(): void;
|
|
52
|
+
/**
|
|
53
|
+
* Decide the motion from the first visibility report: a section already
|
|
54
|
+
* on screen stays static; one off screen holds its parts hidden (from
|
|
55
|
+
* script, so nothing is hidden when scripts do not run) and plays when it
|
|
56
|
+
* comes into view, so even a jump to it shows no unanimated frame.
|
|
57
|
+
*/
|
|
58
|
+
private _updateMotion;
|
|
59
|
+
private _motionParts;
|
|
60
|
+
private _playMotion;
|
|
42
61
|
updated(changedProperties: Map<string | number | symbol, unknown>): void;
|
|
43
62
|
/**
|
|
44
63
|
* Fade the detail in whenever the active feature changes. `keyed` renders a
|
|
@@ -218,6 +218,10 @@ var surfaceValues = {
|
|
|
218
218
|
var surfaceProps = enumCreator(Object.values(surfaceValues), "surface", String, { reflect: true });
|
|
219
219
|
|
|
220
220
|
// components/FeatureTabs/src/FeatureTabs.ts
|
|
221
|
+
import {
|
|
222
|
+
prefersReducedMotion as prefersReducedMotion2,
|
|
223
|
+
splitText
|
|
224
|
+
} from "@ds-gn/motion";
|
|
221
225
|
import { MotionController } from "@ds-gn/motion/lit";
|
|
222
226
|
|
|
223
227
|
// ../../node_modules/.bun/lit-html@3.3.3/node_modules/lit-html/development/is-server.js
|
|
@@ -294,6 +298,15 @@ import {
|
|
|
294
298
|
import { property } from "lit/decorators.js";
|
|
295
299
|
import { keyed } from "lit/directives/keyed.js";
|
|
296
300
|
import { styleMap } from "lit/directives/style-map.js";
|
|
301
|
+
|
|
302
|
+
// common/props/motion/motion.props.ts
|
|
303
|
+
var motionValues = {
|
|
304
|
+
ON: "on",
|
|
305
|
+
OFF: "off"
|
|
306
|
+
};
|
|
307
|
+
var motionProps = enumCreator(Object.values(motionValues), "motion", String, { reflect: true });
|
|
308
|
+
|
|
309
|
+
// components/FeatureTabs/src/FeatureTabs.ts
|
|
297
310
|
import {
|
|
298
311
|
containerSizeValues,
|
|
299
312
|
paddingBottomProps,
|
|
@@ -1003,13 +1016,20 @@ var dotPipInnerClassNames = (active) => clsx_default("block", "rounded-full", "h
|
|
|
1003
1016
|
var playPauseClassNames = () => clsx_default("appearance-none", "border-0", "outline-none", "bg-transparent", "cursor-pointer", "text-accent", "py-[10px]", "px-[8px]", "flex", "items-center", "justify-center", "rounded-full", "transition-colors", "duration-150");
|
|
1004
1017
|
|
|
1005
1018
|
// components/FeatureTabs/src/FeatureTabs.ts
|
|
1019
|
+
var holdHidden = (el) => el.style.setProperty("opacity", "0");
|
|
1020
|
+
var releaseHidden = (el) => el.style.removeProperty("opacity");
|
|
1021
|
+
var HEADING_STAGGER = 20;
|
|
1022
|
+
var HEADING_MAX_SPREAD = 500;
|
|
1023
|
+
var HEADING_RISE = 4;
|
|
1024
|
+
var MOTION_OVERLAP = 160;
|
|
1006
1025
|
var FEATURE_TAB_ICONS = {
|
|
1007
1026
|
terminal: svg`<polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/>`,
|
|
1008
1027
|
brain: svg`<path d="M9 4.5a2.5 2.5 0 0 0-2.5 2.5v.5a2.5 2.5 0 0 0-2 4 2.5 2.5 0 0 0 2 4v.5A2.5 2.5 0 0 0 9 18.5a2.5 2.5 0 0 0 3-2.5V6a2.5 2.5 0 0 0-3-1.5z"/><path d="M15 4.5A2.5 2.5 0 0 1 17.5 7v.5a2.5 2.5 0 0 1 2 4 2.5 2.5 0 0 1-2 4v.5A2.5 2.5 0 0 1 15 18.5a2.5 2.5 0 0 1-3-2.5V6a2.5 2.5 0 0 1 3-1.5z"/>`,
|
|
1009
1028
|
cpu: svg`<rect x="5" y="5" width="14" height="14" rx="2"/><rect x="9" y="9" width="6" height="6"/><line x1="9" y1="2" x2="9" y2="5"/><line x1="15" y1="2" x2="15" y2="5"/><line x1="9" y1="19" x2="9" y2="22"/><line x1="15" y1="19" x2="15" y2="22"/><line x1="2" y1="9" x2="5" y2="9"/><line x1="2" y1="15" x2="5" y2="15"/><line x1="19" y1="9" x2="22" y2="9"/><line x1="19" y1="15" x2="22" y2="15"/>`,
|
|
1010
1029
|
palette: svg`<circle cx="12" cy="12" r="9"/><circle cx="7.5" cy="10.5" r="1" fill="currentColor"/><circle cx="12" cy="7.5" r="1" fill="currentColor"/><circle cx="16.5" cy="10.5" r="1" fill="currentColor"/><path d="M12 21a3 3 0 0 1-3-3c0-1.5 1-2 1-3a2 2 0 0 0-2-2"/>`,
|
|
1011
1030
|
plug: svg`<path d="M9 2v6"/><path d="M15 2v6"/><path d="M7 8h10v3a5 5 0 0 1-5 5 5 5 0 0 1-5-5z"/><path d="M12 16v6"/>`,
|
|
1012
|
-
shield: svg`<path d="M12 3l8 3v5c0 5-3.5 8.5-8 10-4.5-1.5-8-5-8-10V6z"/><path d="M9 12l2 2 4-4"
|
|
1031
|
+
shield: svg`<path d="M12 3l8 3v5c0 5-3.5 8.5-8 10-4.5-1.5-8-5-8-10V6z"/><path d="M9 12l2 2 4-4"/>`,
|
|
1032
|
+
zap: svg`<path d="M13 2 4 14h7l-1 8 9-12h-7z"/>`
|
|
1013
1033
|
};
|
|
1014
1034
|
var nextTabIndex = (key, index, lastIndex) => {
|
|
1015
1035
|
switch (key) {
|
|
@@ -1046,11 +1066,67 @@ class FeatureTabs extends Base {
|
|
|
1046
1066
|
this.pauseAriaLabel = DEFAULT_PAUSE_ARIA_LABEL;
|
|
1047
1067
|
this.resumeAriaLabel = DEFAULT_RESUME_ARIA_LABEL;
|
|
1048
1068
|
this.tabsAriaLabel = DEFAULT_TABS_ARIA_LABEL;
|
|
1069
|
+
this.motion = motionValues.OFF;
|
|
1049
1070
|
this._rotate = new AutoRotateController(this, DEFAULT_DURATION, () => this._inView.value === true);
|
|
1050
1071
|
}
|
|
1051
1072
|
firstUpdated() {
|
|
1052
1073
|
this._rotate.resume(this.features.length);
|
|
1053
1074
|
}
|
|
1075
|
+
_updateMotion() {
|
|
1076
|
+
const inView = this._inView.value;
|
|
1077
|
+
if (inView === undefined)
|
|
1078
|
+
return;
|
|
1079
|
+
if (this._motionState === undefined) {
|
|
1080
|
+
const play = this.motion === motionValues.ON && !inView && !prefersReducedMotion2();
|
|
1081
|
+
this._motionState = play ? "held" : "static";
|
|
1082
|
+
if (play)
|
|
1083
|
+
this._motionParts().forEach(holdHidden);
|
|
1084
|
+
}
|
|
1085
|
+
if (this._motionState === "held" && inView) {
|
|
1086
|
+
this._motionState = "played";
|
|
1087
|
+
this._playMotion();
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
_motionParts() {
|
|
1091
|
+
const header = Array.from(this.children).filter((el) => el.slot === "header");
|
|
1092
|
+
const find = (slot) => header.map((el) => el.querySelector(`[slot="${slot}"]`)).find((el) => el !== null);
|
|
1093
|
+
return [
|
|
1094
|
+
find("heading"),
|
|
1095
|
+
find("description"),
|
|
1096
|
+
...this.renderRoot.querySelectorAll("[data-motion-body]")
|
|
1097
|
+
].filter((el) => el !== undefined);
|
|
1098
|
+
}
|
|
1099
|
+
_playMotion() {
|
|
1100
|
+
const parts = this._motionParts();
|
|
1101
|
+
parts.forEach(releaseHidden);
|
|
1102
|
+
const heading = parts.find((el) => el.slot === "heading");
|
|
1103
|
+
const description = parts.find((el) => el.slot === "description");
|
|
1104
|
+
const split = heading ? splitText(heading) : undefined;
|
|
1105
|
+
const steps = [
|
|
1106
|
+
...split ? [
|
|
1107
|
+
{
|
|
1108
|
+
targets: split.chars,
|
|
1109
|
+
preset: "fade",
|
|
1110
|
+
y: HEADING_RISE,
|
|
1111
|
+
stagger: HEADING_STAGGER,
|
|
1112
|
+
maxDuration: HEADING_MAX_SPREAD
|
|
1113
|
+
}
|
|
1114
|
+
] : [],
|
|
1115
|
+
...description ? [
|
|
1116
|
+
{
|
|
1117
|
+
el: description,
|
|
1118
|
+
preset: "fade",
|
|
1119
|
+
overlap: MOTION_OVERLAP
|
|
1120
|
+
}
|
|
1121
|
+
] : [],
|
|
1122
|
+
{
|
|
1123
|
+
targets: parts.filter((el) => el.hasAttribute("data-motion-body")),
|
|
1124
|
+
preset: "fade",
|
|
1125
|
+
overlap: MOTION_OVERLAP
|
|
1126
|
+
}
|
|
1127
|
+
];
|
|
1128
|
+
this._motion.timeline(steps).finished.then(() => split?.revert());
|
|
1129
|
+
}
|
|
1054
1130
|
updated(changedProperties) {
|
|
1055
1131
|
super.updated(changedProperties);
|
|
1056
1132
|
this.updateTheme(changedProperties, this.surface);
|
|
@@ -1058,6 +1134,7 @@ class FeatureTabs extends Base {
|
|
|
1058
1134
|
this._rotate.setDuration(this.duration);
|
|
1059
1135
|
}
|
|
1060
1136
|
this._fadeInDetail();
|
|
1137
|
+
this._updateMotion();
|
|
1061
1138
|
}
|
|
1062
1139
|
_fadeInDetail() {
|
|
1063
1140
|
const index = this._rotate.activeIndex;
|
|
@@ -1262,12 +1339,12 @@ class FeatureTabs extends Base {
|
|
|
1262
1339
|
</div>
|
|
1263
1340
|
|
|
1264
1341
|
<!-- Mobile: detail panel only, no grid -->
|
|
1265
|
-
<div class="lg:hidden">
|
|
1342
|
+
<div class="lg:hidden" data-motion-body>
|
|
1266
1343
|
${this._renderDetailPanel(idx, activeFeature, progress, total, paused, features)}
|
|
1267
1344
|
</div>
|
|
1268
1345
|
|
|
1269
1346
|
<!-- Desktop: full grid with tabs + detail -->
|
|
1270
|
-
<div class="hidden lg:block">
|
|
1347
|
+
<div class="hidden lg:block" data-motion-body>
|
|
1271
1348
|
<dsgn-grid cols="7-5" gap="xl" ?reverse=${true}>
|
|
1272
1349
|
${this._renderTabList(features, idx, progress, paused, "col-2")}
|
|
1273
1350
|
${this._renderDetailPanel(idx, activeFeature, progress, total, paused, features, "col-1")}
|
|
@@ -1311,6 +1388,9 @@ __legacyDecorateClassTS([
|
|
|
1311
1388
|
__legacyDecorateClassTS([
|
|
1312
1389
|
property({ type: String, attribute: "tabs-aria-label" })
|
|
1313
1390
|
], FeatureTabs.prototype, "tabsAriaLabel", undefined);
|
|
1391
|
+
__legacyDecorateClassTS([
|
|
1392
|
+
property({ ...motionProps })
|
|
1393
|
+
], FeatureTabs.prototype, "motion", undefined);
|
|
1314
1394
|
defineElement("dsgn-feature-tabs", FeatureTabs);
|
|
1315
1395
|
export {
|
|
1316
1396
|
FeatureTabs
|
|
@@ -5,6 +5,7 @@ import '../../Grid/src/Grid.js';
|
|
|
5
5
|
import '../../HeadingBlock/src/HeadingBlock.js';
|
|
6
6
|
import { type CSSResultGroup } from 'lit';
|
|
7
7
|
import { Base } from '../../../common/Base/Base.js';
|
|
8
|
+
import { type MotionType } from '../../../common/props/motion/motion.props.js';
|
|
8
9
|
import { type SurfaceType } from '../../../common/props/surface/surface.props.js';
|
|
9
10
|
import { type SectionSpaceType } from '../../Container/src/Container.props.js';
|
|
10
11
|
import { type HeroContentAlignType, type HeroLayoutType, type HeroMinHeightType } from './Hero.props.js';
|
|
@@ -37,6 +38,16 @@ export declare class Hero extends Base {
|
|
|
37
38
|
paddingTop?: SectionSpaceType;
|
|
38
39
|
/** Space below the section. Unset follows the inherited section space. */
|
|
39
40
|
paddingBottom?: SectionSpaceType;
|
|
41
|
+
/**
|
|
42
|
+
* Animate the content's eyebrow in letter by letter once the hero loads,
|
|
43
|
+
* each letter fading in with a small rise.
|
|
44
|
+
* Everything else stays static, so the hero's largest text paints at once.
|
|
45
|
+
* Under reduced motion the eyebrow is static too.
|
|
46
|
+
*/
|
|
47
|
+
motion: MotionType;
|
|
48
|
+
private _motion;
|
|
49
|
+
firstUpdated(): void;
|
|
50
|
+
private _eyebrows;
|
|
40
51
|
updated(changedProperties: Map<string | number | symbol, unknown>): void;
|
|
41
52
|
render(): import("lit").TemplateResult<1>;
|
|
42
53
|
}
|
|
@@ -14,6 +14,8 @@ import"../../Container/src/Container.js";
|
|
|
14
14
|
import"../../FormBlock/src/FormBlock.js";
|
|
15
15
|
import"../../Grid/src/Grid.js";
|
|
16
16
|
import"../../HeadingBlock/src/HeadingBlock.js";
|
|
17
|
+
import { prefersReducedMotion, splitText } from "@ds-gn/motion";
|
|
18
|
+
import { MotionController } from "@ds-gn/motion/lit";
|
|
17
19
|
import { html as html2 } from "lit";
|
|
18
20
|
import { property } from "lit/decorators.js";
|
|
19
21
|
|
|
@@ -118,6 +120,13 @@ var enumCreator = (acceptableValues, propName, type, other = {}) => {
|
|
|
118
120
|
};
|
|
119
121
|
};
|
|
120
122
|
|
|
123
|
+
// common/props/motion/motion.props.ts
|
|
124
|
+
var motionValues = {
|
|
125
|
+
ON: "on",
|
|
126
|
+
OFF: "off"
|
|
127
|
+
};
|
|
128
|
+
var motionProps = enumCreator(Object.values(motionValues), "motion", String, { reflect: true });
|
|
129
|
+
|
|
121
130
|
// common/props/surface/surface.props.ts
|
|
122
131
|
var surfaceValues = {
|
|
123
132
|
LIGHT: "light",
|
|
@@ -189,6 +198,9 @@ var styles2 = css2`/*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.co
|
|
|
189
198
|
.grid {
|
|
190
199
|
display: grid;
|
|
191
200
|
}
|
|
201
|
+
.hidden {
|
|
202
|
+
display: none;
|
|
203
|
+
}
|
|
192
204
|
.h-64 {
|
|
193
205
|
height: calc(var(--spacing) * 64);
|
|
194
206
|
}
|
|
@@ -412,6 +424,11 @@ var centeredFormClassNames = (contentAlign) => {
|
|
|
412
424
|
};
|
|
413
425
|
|
|
414
426
|
// components/Hero/src/Hero.ts
|
|
427
|
+
var EYEBROW_DELAY = 500;
|
|
428
|
+
var EYEBROW_STAGGER = 30;
|
|
429
|
+
var EYEBROW_MAX_SPREAD = 600;
|
|
430
|
+
var EYEBROW_RISE = 4;
|
|
431
|
+
|
|
415
432
|
class Hero extends Base {
|
|
416
433
|
constructor() {
|
|
417
434
|
super(...arguments);
|
|
@@ -419,8 +436,27 @@ class Hero extends Base {
|
|
|
419
436
|
this.layout = heroLayoutValues.ASSET_RIGHT;
|
|
420
437
|
this.minHeight = heroMinHeightValues.MD;
|
|
421
438
|
this.contentAlign = heroContentAlignValues.START;
|
|
439
|
+
this.motion = motionValues.OFF;
|
|
440
|
+
this._motion = new MotionController(this);
|
|
422
441
|
}
|
|
423
442
|
static styles = [Base.styles, Hero_default];
|
|
443
|
+
firstUpdated() {
|
|
444
|
+
if (this.motion !== motionValues.ON || prefersReducedMotion())
|
|
445
|
+
return;
|
|
446
|
+
this._eyebrows().forEach((eyebrow) => {
|
|
447
|
+
const split = splitText(eyebrow);
|
|
448
|
+
this._motion.sequence(split.chars, {
|
|
449
|
+
preset: "fade",
|
|
450
|
+
y: EYEBROW_RISE,
|
|
451
|
+
delay: EYEBROW_DELAY,
|
|
452
|
+
stagger: EYEBROW_STAGGER,
|
|
453
|
+
maxDuration: EYEBROW_MAX_SPREAD
|
|
454
|
+
}).finished.then(split.revert);
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
_eyebrows() {
|
|
458
|
+
return Array.from(this.children).filter((el) => el.slot === "content").flatMap((el) => Array.from(el.querySelectorAll('[slot="eyebrow"]')));
|
|
459
|
+
}
|
|
424
460
|
updated(changedProperties) {
|
|
425
461
|
super.updated(changedProperties);
|
|
426
462
|
this.updateTheme(changedProperties, this.surface);
|
|
@@ -526,6 +562,9 @@ __legacyDecorateClassTS([
|
|
|
526
562
|
__legacyDecorateClassTS([
|
|
527
563
|
property({ ...paddingBottomProps })
|
|
528
564
|
], Hero.prototype, "paddingBottom", undefined);
|
|
565
|
+
__legacyDecorateClassTS([
|
|
566
|
+
property({ ...motionProps })
|
|
567
|
+
], Hero.prototype, "motion", undefined);
|
|
529
568
|
defineElement("dsgn-hero", Hero);
|
|
530
569
|
export {
|
|
531
570
|
Hero
|
|
@@ -489,6 +489,11 @@ dsgn-hero:not(:defined)[layout='asset-background']:not(:has(> [slot='features'])
|
|
|
489
489
|
padding-bottom: calc(var(--spacing) * 6);
|
|
490
490
|
grid-row: 3;
|
|
491
491
|
}
|
|
492
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
493
|
+
dsgn-hero:not(:defined)[motion='on'] > [slot='content'] [slot='eyebrow'] {
|
|
494
|
+
opacity: 0;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
492
497
|
@property --tw-tracking {
|
|
493
498
|
syntax: "*";
|
|
494
499
|
inherits: false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ds-gn/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "UI components for the DS-GN Design System",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -346,7 +346,7 @@
|
|
|
346
346
|
"react-dom": "19.2.4"
|
|
347
347
|
},
|
|
348
348
|
"dependencies": {
|
|
349
|
-
"@ds-gn/motion": "0.
|
|
349
|
+
"@ds-gn/motion": "0.9.0",
|
|
350
350
|
"@ds-gn/tokens": "0.8.0",
|
|
351
351
|
"@lit-labs/observers": "^2.0.5",
|
|
352
352
|
"@lit/react": "^1.0.7",
|