@pure-ds/storybook 0.1.3 → 0.1.5
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/default-pds.config.js +1 -0
- package/dist/pds-reference.json +1 -1
- package/package.json +3 -2
- package/public/assets/js/app.js +486 -9855
- package/public/assets/js/lit.js +3 -1048
- package/public/assets/js/pds.js +309 -6687
- package/scripts/package-build.js +7 -0
- package/src/js/pds-configurator/figma-export.js +153 -0
- package/src/js/pds-configurator/pds-config-form.js +1058 -0
- package/src/js/pds-configurator/pds-configurator.js +22 -0
- package/src/js/pds-configurator/pds-demo.js +3621 -0
- package/stories/components/PdsDrawer.stories.js +19 -602
- package/stories/components/PdsIcon.stories.js +6 -22
- package/stories/components/PdsTabstrip.stories.js +26 -434
- package/stories/foundations/Colors.stories.js +75 -240
- package/stories/foundations/Icons.stories.js +177 -287
- package/stories/foundations/Spacing.stories.js +57 -161
- package/stories/foundations/Typography.stories.js +68 -945
- package/stories/primitives/Alerts.stories.js +31 -25
- package/stories/primitives/Badges.stories.js +35 -146
- package/stories/primitives/Buttons.stories.js +85 -213
- package/stories/primitives/Cards.stories.js +53 -330
- package/stories/primitives/Forms.stories.js +161 -92
- package/public/assets/js/app.js.map +0 -7
- package/public/assets/js/lit.js.map +0 -7
- package/public/assets/js/pds.js.map +0 -7
|
@@ -0,0 +1,3621 @@
|
|
|
1
|
+
import { LitElement, html, nothing, render, unsafeHTML } from "../../../src/js/lit.js";
|
|
2
|
+
import { PDS } from "../../../src/js/pds.js";
|
|
3
|
+
|
|
4
|
+
import { AutoComplete } from "pure-web/ac";
|
|
5
|
+
|
|
6
|
+
const toast = (message, options) => {
|
|
7
|
+
const toaster = document.getElementById("global-toaster");
|
|
8
|
+
toaster.toast(message, options);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
customElements.define(
|
|
12
|
+
"pds-demo",
|
|
13
|
+
class extends LitElement {
|
|
14
|
+
#shiki = null;
|
|
15
|
+
#shikiLoading = false;
|
|
16
|
+
|
|
17
|
+
static properties = {
|
|
18
|
+
config: { type: Object },
|
|
19
|
+
designer: { type: Object },
|
|
20
|
+
sections: { type: Array, state: true },
|
|
21
|
+
inspectorActive: { type: Boolean, state: true },
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
constructor() {
|
|
25
|
+
super();
|
|
26
|
+
this.config = null;
|
|
27
|
+
this.designer = null;
|
|
28
|
+
this.sections = [];
|
|
29
|
+
this.inspectorActive = false;
|
|
30
|
+
this._docsBase = "/pds";
|
|
31
|
+
// Showdown (Markdown) converter will be loaded from CDN on demand
|
|
32
|
+
this._showdown = null;
|
|
33
|
+
this._showdownLoading = false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Disable shadow DOM to use global styles
|
|
37
|
+
createRenderRoot() {
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
connectedCallback() {
|
|
42
|
+
super.connectedCallback();
|
|
43
|
+
|
|
44
|
+
// Listen for design updates from unified PDS bus
|
|
45
|
+
PDS.addEventListener("pds:design:updated", (e) => {
|
|
46
|
+
this.config = e.detail.config;
|
|
47
|
+
this.designer = e.detail.designer;
|
|
48
|
+
// Update docs base if staticBase changes
|
|
49
|
+
if (this.config && this.config.staticBase) {
|
|
50
|
+
this._docsBase =
|
|
51
|
+
"/" + String(this.config.staticBase).replace(/^\/+|\/+$/g, "");
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Listen for field changes to scroll to relevant section
|
|
56
|
+
PDS.addEventListener("pds:design:field:changed", (e) => {
|
|
57
|
+
setTimeout(() => {
|
|
58
|
+
this.scrollToRelevantSection(e.detail.field);
|
|
59
|
+
}, 1000);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Listen for inspector mode changes
|
|
63
|
+
PDS.addEventListener("pds:inspector:mode:changed", (e) => {
|
|
64
|
+
this.inspectorActive = e.detail.active;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Extract sections after initial render
|
|
68
|
+
setTimeout(() => {
|
|
69
|
+
this.extractSections();
|
|
70
|
+
this.handleInitialHash();
|
|
71
|
+
}, 100);
|
|
72
|
+
|
|
73
|
+
// Capture-phase handler to prevent interactive actions when inspector is active
|
|
74
|
+
this._inspectorCaptureHandler = (e) => {
|
|
75
|
+
if (!this.inspectorActive) return;
|
|
76
|
+
const target = e.target;
|
|
77
|
+
|
|
78
|
+
// Prevent link navigation
|
|
79
|
+
const anchor = target.closest && target.closest("a[href]");
|
|
80
|
+
if (anchor) {
|
|
81
|
+
e.preventDefault();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Prevent button activation
|
|
86
|
+
const button = target.closest && target.closest("button");
|
|
87
|
+
if (button) {
|
|
88
|
+
e.preventDefault();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
this.addEventListener("click", this._inspectorCaptureHandler, true);
|
|
94
|
+
// Determine docs base from global override or config
|
|
95
|
+
try {
|
|
96
|
+
const globalBase = window.PDS_DOCS_BASE;
|
|
97
|
+
if (typeof globalBase === "string" && globalBase.trim()) {
|
|
98
|
+
this._docsBase = globalBase.replace(/\/+$/, "");
|
|
99
|
+
}
|
|
100
|
+
} catch {}
|
|
101
|
+
// Defer to config.staticBase if provided
|
|
102
|
+
if (this.config && this.config.staticBase) {
|
|
103
|
+
this._docsBase =
|
|
104
|
+
"/" + String(this.config.staticBase).replace(/^\/+|\/+$/g, "");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Listen for external requests to view docs via PDS bus
|
|
108
|
+
PDS.addEventListener("pds:docs:view", async (e) => {
|
|
109
|
+
const file = (e.detail && e.detail.file) || "README.md";
|
|
110
|
+
await this._renderDocToDialog(file);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
disconnectedCallback() {
|
|
115
|
+
super.disconnectedCallback();
|
|
116
|
+
if (this._inspectorCaptureHandler) {
|
|
117
|
+
this.removeEventListener("click", this._inspectorCaptureHandler, true);
|
|
118
|
+
this._inspectorCaptureHandler = null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Fetch a markdown file from the docs base and return HTML */
|
|
123
|
+
async fetchDocHTML(file = "README.md") {
|
|
124
|
+
const base = this._docsBase || "/pds";
|
|
125
|
+
const url = `${base.replace(/\/+$/, "")}/${file}`;
|
|
126
|
+
try {
|
|
127
|
+
const res = await fetch(url, { cache: "no-cache" });
|
|
128
|
+
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
|
|
129
|
+
const md = await res.text();
|
|
130
|
+
const conv = await this.getShowdownConverter();
|
|
131
|
+
return conv ? conv.makeHtml(md) : `<pre>${this.escapeHTML(md)}</pre>`;
|
|
132
|
+
} catch (err) {
|
|
133
|
+
return `<p>Failed to load ${file} from ${base}: ${String(
|
|
134
|
+
err.message || err
|
|
135
|
+
)}</p>`;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Render markdown into a simple dialog overlay */
|
|
140
|
+
async _renderDocToDialog(file) {
|
|
141
|
+
const htmlContent = await this.fetchDocHTML(file);
|
|
142
|
+
let dlg = document.getElementById("pds-docs-dialog");
|
|
143
|
+
if (!dlg) {
|
|
144
|
+
dlg = document.createElement("dialog");
|
|
145
|
+
dlg.id = "pds-docs-dialog";
|
|
146
|
+
dlg.style.width = "min(900px, 90vw)";
|
|
147
|
+
dlg.style.maxHeight = "85vh";
|
|
148
|
+
dlg.style.padding = "0";
|
|
149
|
+
dlg.innerHTML = `<div style="padding:16px 20px; overflow:auto; max-height:85vh">
|
|
150
|
+
<div class="markdown-body"></div>
|
|
151
|
+
</div>`;
|
|
152
|
+
document.body.appendChild(dlg);
|
|
153
|
+
}
|
|
154
|
+
const body = dlg.querySelector(".markdown-body");
|
|
155
|
+
if (body) body.innerHTML = htmlContent;
|
|
156
|
+
if (!dlg.open) dlg.showModal();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
deactivateInspector() {
|
|
160
|
+
// Dispatch request on PDS bus to toggle inspector mode off
|
|
161
|
+
PDS.dispatchEvent(
|
|
162
|
+
new CustomEvent("pds:inspector:deactivate", {
|
|
163
|
+
detail: {},
|
|
164
|
+
})
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
extractSections() {
|
|
169
|
+
const sectionElements = this.querySelectorAll("[data-section]");
|
|
170
|
+
this.sections = Array.from(sectionElements).map((el) => {
|
|
171
|
+
const id = el.getAttribute("data-section");
|
|
172
|
+
const heading = el.querySelector("h2");
|
|
173
|
+
const title = heading?.textContent?.trim() || id;
|
|
174
|
+
return { id, title };
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
handleInitialHash() {
|
|
179
|
+
const hash = window.location.hash.slice(1);
|
|
180
|
+
if (hash) {
|
|
181
|
+
const section = this.querySelector(`[data-section="${hash}"]`);
|
|
182
|
+
if (section) {
|
|
183
|
+
setTimeout(() => {
|
|
184
|
+
section.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
185
|
+
}, 300);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Smart element detection for code inspector
|
|
192
|
+
* Returns the appropriate element and metadata to display
|
|
193
|
+
*/
|
|
194
|
+
detectComponentElement(clickedElement) {
|
|
195
|
+
let element = clickedElement;
|
|
196
|
+
let componentType = "element";
|
|
197
|
+
let displayName = element.tagName.toLowerCase();
|
|
198
|
+
|
|
199
|
+
// Skip if clicked on TOC
|
|
200
|
+
if (element.closest(".showcase-toc")) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Never select pds-demo itself
|
|
205
|
+
if (element.tagName === "DS-SHOWCASE") {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Never select showcase-section - find component within it
|
|
210
|
+
if (
|
|
211
|
+
element.classList.contains("showcase-section") ||
|
|
212
|
+
element.closest(".showcase-section") === element
|
|
213
|
+
) {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Check for progressive enhancements (nav[data-dropdown], label[data-toggle], etc.)
|
|
218
|
+
const enhancedElement = this.findEnhancedElement(element);
|
|
219
|
+
if (enhancedElement) {
|
|
220
|
+
return {
|
|
221
|
+
element: enhancedElement,
|
|
222
|
+
componentType: "enhanced-component",
|
|
223
|
+
displayName: this.getEnhancedElementName(enhancedElement),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Prioritize semantic HTML primitives (figure, table, details, etc.)
|
|
228
|
+
const semanticElements = [
|
|
229
|
+
"FIGURE",
|
|
230
|
+
"TABLE",
|
|
231
|
+
"DETAILS",
|
|
232
|
+
"VIDEO",
|
|
233
|
+
"AUDIO",
|
|
234
|
+
"PICTURE",
|
|
235
|
+
"BLOCKQUOTE",
|
|
236
|
+
"PRE",
|
|
237
|
+
"CODE",
|
|
238
|
+
];
|
|
239
|
+
if (semanticElements.includes(element.tagName)) {
|
|
240
|
+
return {
|
|
241
|
+
element: element,
|
|
242
|
+
componentType: "html-primitive",
|
|
243
|
+
displayName: element.tagName.toLowerCase(),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Check if inside a semantic HTML element
|
|
248
|
+
for (const tag of semanticElements) {
|
|
249
|
+
const semanticParent = element.closest(tag.toLowerCase());
|
|
250
|
+
if (semanticParent) {
|
|
251
|
+
return {
|
|
252
|
+
element: semanticParent,
|
|
253
|
+
componentType: "html-primitive",
|
|
254
|
+
displayName: tag.toLowerCase(),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Check for PDS-styled primitives (elements styled by PDS classes)
|
|
260
|
+
const pdsStyledElement = this.findPDSStyledElement(element);
|
|
261
|
+
if (pdsStyledElement) {
|
|
262
|
+
return pdsStyledElement;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Fieldset with role="group" or role="radiogroup" is a component
|
|
266
|
+
if (element.tagName === "FIELDSET") {
|
|
267
|
+
const role = element.getAttribute("role");
|
|
268
|
+
if (role === "group" || role === "radiogroup") {
|
|
269
|
+
componentType = "form-group";
|
|
270
|
+
displayName = role === "radiogroup" ? "radio group" : "form group";
|
|
271
|
+
return { element, componentType, displayName };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Check if clicked element is inside a fieldset with role
|
|
276
|
+
const fieldsetParent = element.closest(
|
|
277
|
+
'fieldset[role="group"], fieldset[role="radiogroup"]'
|
|
278
|
+
);
|
|
279
|
+
if (fieldsetParent) {
|
|
280
|
+
const role = fieldsetParent.getAttribute("role");
|
|
281
|
+
return {
|
|
282
|
+
element: fieldsetParent,
|
|
283
|
+
componentType: "form-group",
|
|
284
|
+
displayName: role === "radiogroup" ? "radio group" : "form group",
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Label with ANY input is always a component
|
|
289
|
+
if (element.tagName === "LABEL" || element.closest("label")) {
|
|
290
|
+
const label =
|
|
291
|
+
element.tagName === "LABEL" ? element : element.closest("label");
|
|
292
|
+
const input = label.querySelector("input, select, textarea");
|
|
293
|
+
if (input) {
|
|
294
|
+
componentType = "form-control";
|
|
295
|
+
displayName = `${input.tagName.toLowerCase()} field`;
|
|
296
|
+
return { element: label, componentType, displayName };
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Form elements - get the label container (if exists)
|
|
301
|
+
if (["INPUT", "SELECT", "TEXTAREA"].includes(element.tagName)) {
|
|
302
|
+
const label = element.closest("label");
|
|
303
|
+
if (label) {
|
|
304
|
+
element = label;
|
|
305
|
+
componentType = "form-control";
|
|
306
|
+
displayName = `${clickedElement.tagName.toLowerCase()} field`;
|
|
307
|
+
return { element, componentType, displayName };
|
|
308
|
+
}
|
|
309
|
+
// If no label, return the form element itself
|
|
310
|
+
componentType = "form-control";
|
|
311
|
+
displayName = `${element.tagName.toLowerCase()}`;
|
|
312
|
+
return { element, componentType, displayName };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Custom web components (pds-* or other custom elements with hyphen)
|
|
316
|
+
if (element.tagName.includes("-")) {
|
|
317
|
+
componentType = "web-component";
|
|
318
|
+
displayName = element.tagName.toLowerCase();
|
|
319
|
+
return { element, componentType, displayName };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Check if inside a custom web component
|
|
323
|
+
const customParent = element.closest("[tagName*='-']");
|
|
324
|
+
if (customParent && customParent.tagName.includes("-")) {
|
|
325
|
+
return {
|
|
326
|
+
element: customParent,
|
|
327
|
+
componentType: "web-component",
|
|
328
|
+
displayName: customParent.tagName.toLowerCase(),
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Buttons with icons
|
|
333
|
+
if (element.tagName === "BUTTON" || element.closest("button")) {
|
|
334
|
+
element =
|
|
335
|
+
element.tagName === "BUTTON" ? element : element.closest("button");
|
|
336
|
+
componentType = "button";
|
|
337
|
+
const hasIcon = element.querySelector("pds-icon");
|
|
338
|
+
displayName = hasIcon ? "button with icon" : "button";
|
|
339
|
+
return { element, componentType, displayName };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// SVG icons
|
|
343
|
+
if (element.tagName === "pds-icon" || element.closest("pds-icon")) {
|
|
344
|
+
element =
|
|
345
|
+
element.tagName === "pds-icon"
|
|
346
|
+
? element
|
|
347
|
+
: element.closest("pds-icon");
|
|
348
|
+
componentType = "icon";
|
|
349
|
+
displayName = `pds-icon (${element.getAttribute("icon") || "unknown"})`;
|
|
350
|
+
return { element, componentType, displayName };
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Navigation elements
|
|
354
|
+
if (element.tagName === "NAV" || element.closest("nav[data-dropdown]")) {
|
|
355
|
+
element = element.closest("nav[data-dropdown]") || element;
|
|
356
|
+
componentType = "navigation";
|
|
357
|
+
displayName = "dropdown menu";
|
|
358
|
+
return { element, componentType, displayName };
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Generic container with interesting classes (but not showcase-section)
|
|
362
|
+
const interestingClasses = [
|
|
363
|
+
"color-card",
|
|
364
|
+
"color-scale",
|
|
365
|
+
"grid",
|
|
366
|
+
"flex-wrap",
|
|
367
|
+
"btn-group",
|
|
368
|
+
];
|
|
369
|
+
|
|
370
|
+
for (const cls of interestingClasses) {
|
|
371
|
+
if (element.classList.contains(cls)) {
|
|
372
|
+
componentType = "container";
|
|
373
|
+
displayName = cls.replace(/-/g, " ");
|
|
374
|
+
return { element, componentType, displayName };
|
|
375
|
+
}
|
|
376
|
+
const container = element.closest(`.${cls}`);
|
|
377
|
+
if (container) {
|
|
378
|
+
element = container;
|
|
379
|
+
componentType = "container";
|
|
380
|
+
displayName = cls.replace(/-/g, " ");
|
|
381
|
+
return { element, componentType, displayName };
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Find nearest meaningful parent (not showcase-section)
|
|
386
|
+
const meaningfulParent = this.findNearestComponent(element);
|
|
387
|
+
if (meaningfulParent && meaningfulParent !== element) {
|
|
388
|
+
return this.detectComponentElement(meaningfulParent);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return { element, componentType, displayName };
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Find PDS-styled elements (primitives styled by PDS classes or semantic HTML)
|
|
396
|
+
*/
|
|
397
|
+
findPDSStyledElement(element) {
|
|
398
|
+
// Delegate to ontology-driven lookup
|
|
399
|
+
const res = PDS.findComponentForElement(element, { maxDepth: 5 });
|
|
400
|
+
if (!res) return null;
|
|
401
|
+
if (res.element && res.element.tagName === "DS-SHOWCASE") return null;
|
|
402
|
+
return {
|
|
403
|
+
element: res.element,
|
|
404
|
+
componentType: res.componentType || "pds-primitive",
|
|
405
|
+
displayName:
|
|
406
|
+
res.displayName || (res.element?.tagName || "element").toLowerCase(),
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Find enhanced elements (progressive enhancements from config)
|
|
412
|
+
*/
|
|
413
|
+
findEnhancedElement(element) {
|
|
414
|
+
// Check common enhancement patterns
|
|
415
|
+
const enhancementSelectors = [
|
|
416
|
+
"nav[data-dropdown]",
|
|
417
|
+
"label[data-toggle]",
|
|
418
|
+
"[data-tabs]",
|
|
419
|
+
"[data-modal]",
|
|
420
|
+
"[data-tooltip]",
|
|
421
|
+
];
|
|
422
|
+
|
|
423
|
+
for (const selector of enhancementSelectors) {
|
|
424
|
+
if (element.matches && element.matches(selector)) {
|
|
425
|
+
return element;
|
|
426
|
+
}
|
|
427
|
+
const enhanced = element.closest(selector);
|
|
428
|
+
if (enhanced) {
|
|
429
|
+
return enhanced;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Get descriptive name for enhanced element
|
|
438
|
+
*/
|
|
439
|
+
getEnhancedElementName(element) {
|
|
440
|
+
if (element.matches("nav[data-dropdown]")) return "dropdown menu";
|
|
441
|
+
if (element.matches("label[data-toggle]")) return "toggle switch";
|
|
442
|
+
if (element.matches("[data-tabs]")) return "tab component";
|
|
443
|
+
if (element.matches("[data-modal]")) return "modal dialog";
|
|
444
|
+
if (element.matches("[data-tooltip]")) return "tooltip";
|
|
445
|
+
|
|
446
|
+
// Fallback
|
|
447
|
+
const dataAttrs = Array.from(element.attributes)
|
|
448
|
+
.filter((attr) => attr.name.startsWith("data-"))
|
|
449
|
+
.map((attr) => attr.name.replace("data-", ""));
|
|
450
|
+
|
|
451
|
+
return dataAttrs[0]
|
|
452
|
+
? `${dataAttrs[0]} component`
|
|
453
|
+
: element.tagName.toLowerCase();
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Find nearest meaningful component (not showcase-section, pds-demo, or generic divs)
|
|
458
|
+
* Maximum 5 levels up to prevent going too high
|
|
459
|
+
*/
|
|
460
|
+
findNearestComponent(element) {
|
|
461
|
+
let current = element.parentElement;
|
|
462
|
+
let level = 0;
|
|
463
|
+
const maxLevels = 5;
|
|
464
|
+
|
|
465
|
+
while (current && level < maxLevels) {
|
|
466
|
+
level++;
|
|
467
|
+
|
|
468
|
+
// Never traverse beyond pds-demo - it's too high, return null
|
|
469
|
+
if (current.tagName === "DS-SHOWCASE") {
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Skip showcase-section but continue traversing
|
|
474
|
+
if (current.classList.contains("showcase-section")) {
|
|
475
|
+
current = current.parentElement;
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Check if this is a meaningful component
|
|
480
|
+
if (
|
|
481
|
+
current.tagName.includes("-") || // Custom element
|
|
482
|
+
current.tagName === "BUTTON" ||
|
|
483
|
+
current.tagName === "NAV" ||
|
|
484
|
+
current.tagName === "FIELDSET" ||
|
|
485
|
+
current.tagName === "LABEL" ||
|
|
486
|
+
current.tagName === "TABLE" ||
|
|
487
|
+
current.tagName === "FIGURE" ||
|
|
488
|
+
current.tagName === "BLOCKQUOTE" ||
|
|
489
|
+
current.tagName === "ARTICLE" ||
|
|
490
|
+
current.hasAttribute("role") ||
|
|
491
|
+
current.hasAttribute("data-dropdown") ||
|
|
492
|
+
current.hasAttribute("data-toggle") ||
|
|
493
|
+
this.hasPDSClass(current)
|
|
494
|
+
) {
|
|
495
|
+
return current;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
current = current.parentElement;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return null;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Check if element has any PDS component/primitive class
|
|
506
|
+
*/
|
|
507
|
+
hasPDSClass(element) {
|
|
508
|
+
const pdsClassPrefixes = [
|
|
509
|
+
"card",
|
|
510
|
+
"surface",
|
|
511
|
+
"tag",
|
|
512
|
+
"badge",
|
|
513
|
+
"pill",
|
|
514
|
+
"chip",
|
|
515
|
+
"alert",
|
|
516
|
+
"toast",
|
|
517
|
+
"notification",
|
|
518
|
+
"message",
|
|
519
|
+
"accordion",
|
|
520
|
+
"collapse",
|
|
521
|
+
"expandable",
|
|
522
|
+
"list-group",
|
|
523
|
+
"menu-list",
|
|
524
|
+
"nav-list",
|
|
525
|
+
"breadcrumb",
|
|
526
|
+
"pagination",
|
|
527
|
+
"tabs",
|
|
528
|
+
"tab",
|
|
529
|
+
"progress",
|
|
530
|
+
"spinner",
|
|
531
|
+
"loader",
|
|
532
|
+
"skeleton",
|
|
533
|
+
"divider",
|
|
534
|
+
"separator",
|
|
535
|
+
"avatar",
|
|
536
|
+
"thumbnail",
|
|
537
|
+
"form-group",
|
|
538
|
+
"input-group",
|
|
539
|
+
"checkbox-group",
|
|
540
|
+
"radio-group",
|
|
541
|
+
"btn-",
|
|
542
|
+
"icon-only",
|
|
543
|
+
"dropdown",
|
|
544
|
+
"popover",
|
|
545
|
+
"tooltip",
|
|
546
|
+
"modal",
|
|
547
|
+
"dialog",
|
|
548
|
+
"table-responsive",
|
|
549
|
+
"data-table",
|
|
550
|
+
"card-grid",
|
|
551
|
+
"media-object",
|
|
552
|
+
"status",
|
|
553
|
+
"indicator",
|
|
554
|
+
"dot-indicator",
|
|
555
|
+
"pulse",
|
|
556
|
+
];
|
|
557
|
+
|
|
558
|
+
return Array.from(element.classList).some((cls) =>
|
|
559
|
+
pdsClassPrefixes.some((prefix) => cls.startsWith(prefix))
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Extract HTML with smart formatting
|
|
565
|
+
*/
|
|
566
|
+
extractHTML(element) {
|
|
567
|
+
const clone = element.cloneNode(true);
|
|
568
|
+
|
|
569
|
+
// Remove event listeners and internal state
|
|
570
|
+
const allElements = [clone, ...clone.querySelectorAll("*")];
|
|
571
|
+
allElements.forEach((el) => {
|
|
572
|
+
// Remove Lit internal attributes
|
|
573
|
+
if (el.removeAttribute) {
|
|
574
|
+
el.removeAttribute("_$litPart$");
|
|
575
|
+
el.removeAttribute("_$litElement$");
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
// Pretty print HTML using DOM-aware formatter
|
|
580
|
+
let html = this.formatHTMLElement(clone);
|
|
581
|
+
|
|
582
|
+
// Extract Lit properties (attributes starting with .)
|
|
583
|
+
const litProps = this.extractLitProperties(element);
|
|
584
|
+
|
|
585
|
+
return { html, litProps };
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Extract Lit property bindings from element
|
|
590
|
+
*/
|
|
591
|
+
extractLitProperties(element) {
|
|
592
|
+
const props = [];
|
|
593
|
+
|
|
594
|
+
// Check for common Lit property patterns
|
|
595
|
+
if (element.checked !== undefined && element.type === "checkbox") {
|
|
596
|
+
props.push({ name: ".checked", value: element.checked });
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
if (
|
|
600
|
+
element.value !== undefined &&
|
|
601
|
+
["INPUT", "SELECT", "TEXTAREA"].includes(element.tagName)
|
|
602
|
+
) {
|
|
603
|
+
props.push({ name: ".value", value: element.value });
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Check for custom element properties
|
|
607
|
+
if (element.tagName.includes("-")) {
|
|
608
|
+
const constructor = customElements.get(element.tagName.toLowerCase());
|
|
609
|
+
if (constructor && constructor.properties) {
|
|
610
|
+
Object.keys(constructor.properties).forEach((prop) => {
|
|
611
|
+
if (
|
|
612
|
+
element[prop] !== undefined &&
|
|
613
|
+
typeof element[prop] !== "function"
|
|
614
|
+
) {
|
|
615
|
+
props.push({ name: `.${prop}`, value: element[prop] });
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return props;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Format HTML with indentation
|
|
626
|
+
*/
|
|
627
|
+
formatHTMLElement(node, level = 0) {
|
|
628
|
+
const indent = (n) => " ".repeat(n);
|
|
629
|
+
let out = "";
|
|
630
|
+
|
|
631
|
+
const escapeText = (s) =>
|
|
632
|
+
s
|
|
633
|
+
.replace(/&/g, "&")
|
|
634
|
+
.replace(/</g, "<")
|
|
635
|
+
.replace(/>/g, ">")
|
|
636
|
+
.replace(/"/g, """)
|
|
637
|
+
.replace(/'/g, "'");
|
|
638
|
+
|
|
639
|
+
const isVoid = (tag) =>
|
|
640
|
+
[
|
|
641
|
+
"area",
|
|
642
|
+
"base",
|
|
643
|
+
"br",
|
|
644
|
+
"col",
|
|
645
|
+
"embed",
|
|
646
|
+
"hr",
|
|
647
|
+
"img",
|
|
648
|
+
"input",
|
|
649
|
+
"link",
|
|
650
|
+
"meta",
|
|
651
|
+
"param",
|
|
652
|
+
"source",
|
|
653
|
+
"track",
|
|
654
|
+
"wbr",
|
|
655
|
+
].includes(tag);
|
|
656
|
+
|
|
657
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
658
|
+
const t = node.textContent.replace(/\s+/g, " ").trim();
|
|
659
|
+
if (t.length === 0) return "";
|
|
660
|
+
return indent(level) + escapeText(t) + "\n";
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if (node.nodeType === Node.COMMENT_NODE) {
|
|
664
|
+
return indent(level) + `<!-- ${escapeText(node.textContent)} -->\n`;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
668
|
+
const tag = node.tagName.toLowerCase();
|
|
669
|
+
const attrs = [];
|
|
670
|
+
const booleanDataAttrs = new Set(["data-label", "data-toggle"]);
|
|
671
|
+
for (const a of node.attributes) {
|
|
672
|
+
const name = a.name;
|
|
673
|
+
const val = a.value;
|
|
674
|
+
|
|
675
|
+
// Special-case certain data-* attributes as boolean markers
|
|
676
|
+
if (booleanDataAttrs.has(name)) {
|
|
677
|
+
// Render as bare boolean attribute (e.g., data-toggle)
|
|
678
|
+
attrs.push(name);
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if (val === "") {
|
|
683
|
+
// Render empty data-* attributes explicitly as data-xxx=""
|
|
684
|
+
if (name.startsWith("data-")) {
|
|
685
|
+
attrs.push(`${name}=""`);
|
|
686
|
+
} else {
|
|
687
|
+
// For other boolean-ish attributes, render bare attribute
|
|
688
|
+
attrs.push(name);
|
|
689
|
+
}
|
|
690
|
+
} else {
|
|
691
|
+
attrs.push(`${name}="${escapeText(val)}"`);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
const attrString = attrs.length ? " " + attrs.join(" ") : "";
|
|
695
|
+
|
|
696
|
+
// Start tag
|
|
697
|
+
out += indent(level) + `<${tag}${attrString}>` + "\n";
|
|
698
|
+
|
|
699
|
+
// Children
|
|
700
|
+
for (const child of Array.from(node.childNodes)) {
|
|
701
|
+
out += this.formatHTMLElement(child, level + 1);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// End tag (void elements don't need explicit closing)
|
|
705
|
+
if (!isVoid(tag)) {
|
|
706
|
+
out += indent(level) + `</${tag}>` + "\n";
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
return out;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
return "";
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Handle click in inspector mode
|
|
717
|
+
*/
|
|
718
|
+
handleInspectorClick(e) {
|
|
719
|
+
if (!this.inspectorActive) return;
|
|
720
|
+
|
|
721
|
+
e.preventDefault();
|
|
722
|
+
e.stopPropagation();
|
|
723
|
+
|
|
724
|
+
const detected = this.detectComponentElement(e.target);
|
|
725
|
+
if (!detected) return;
|
|
726
|
+
|
|
727
|
+
const { element, componentType, displayName } = detected;
|
|
728
|
+
|
|
729
|
+
// Turn off inspector mode after selecting an element (like a color picker)
|
|
730
|
+
this.deactivateInspector();
|
|
731
|
+
|
|
732
|
+
// Check if an enhancer provides a demo HTML to display (clean template)
|
|
733
|
+
let demoHtml = null;
|
|
734
|
+
let enhancer = null;
|
|
735
|
+
try {
|
|
736
|
+
const enhancers =
|
|
737
|
+
this.config?.autoDefine?.enhancers ||
|
|
738
|
+
(typeof appConfig !== "undefined"
|
|
739
|
+
? appConfig?.autoDefine?.enhancers
|
|
740
|
+
: null) ||
|
|
741
|
+
(typeof window !== "undefined"
|
|
742
|
+
? window?.appConfig?.autoDefine?.enhancers
|
|
743
|
+
: null) ||
|
|
744
|
+
(document.querySelector &&
|
|
745
|
+
document.querySelector("pure-app")?.config?.autoDefine
|
|
746
|
+
?.enhancers) ||
|
|
747
|
+
[];
|
|
748
|
+
enhancer = enhancers.find((en) => {
|
|
749
|
+
try {
|
|
750
|
+
return (
|
|
751
|
+
(element.matches && element.matches(en.selector)) ||
|
|
752
|
+
(element.closest && element.closest(en.selector))
|
|
753
|
+
);
|
|
754
|
+
} catch (ex) {
|
|
755
|
+
return false;
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
} catch (ex) {
|
|
759
|
+
enhancer = null;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
let litProps = [];
|
|
763
|
+
let html = null;
|
|
764
|
+
if (enhancer && enhancer.demoHtml) {
|
|
765
|
+
demoHtml =
|
|
766
|
+
typeof enhancer.demoHtml === "function"
|
|
767
|
+
? enhancer.demoHtml(element)
|
|
768
|
+
: enhancer.demoHtml;
|
|
769
|
+
// If demoHtml is a string, parse it into a DOM node and pretty-print it
|
|
770
|
+
if (typeof demoHtml === "string") {
|
|
771
|
+
try {
|
|
772
|
+
const wrapper = document.createElement("div");
|
|
773
|
+
wrapper.innerHTML = demoHtml.trim();
|
|
774
|
+
// If there are multiple top-level nodes, format them all
|
|
775
|
+
let combined = "";
|
|
776
|
+
for (const child of Array.from(wrapper.childNodes)) {
|
|
777
|
+
combined += this.formatHTMLElement(child);
|
|
778
|
+
}
|
|
779
|
+
html = combined;
|
|
780
|
+
} catch (ex) {
|
|
781
|
+
// Fallback to raw string if parsing fails
|
|
782
|
+
html = demoHtml;
|
|
783
|
+
}
|
|
784
|
+
} else {
|
|
785
|
+
// If the enhancer returned a DOM node or other structure, try to format it
|
|
786
|
+
if (demoHtml instanceof Node) {
|
|
787
|
+
html = this.formatHTMLElement(demoHtml);
|
|
788
|
+
} else {
|
|
789
|
+
html = String(demoHtml);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
// still extract lit props from real element if useful
|
|
793
|
+
litProps = this.extractLitProperties(element);
|
|
794
|
+
} else {
|
|
795
|
+
const res = this.extractHTML(element);
|
|
796
|
+
html = res.html;
|
|
797
|
+
litProps = res.litProps;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// Show code in drawer
|
|
801
|
+
this.showCodeDrawer(html, litProps, displayName, componentType);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* Show code in pds-drawer
|
|
806
|
+
*/
|
|
807
|
+
async showCodeDrawer(htmlCode, litProps, displayName, componentType) {
|
|
808
|
+
const drawer = document.querySelector("#global-drawer");
|
|
809
|
+
|
|
810
|
+
// Create header template
|
|
811
|
+
const headerTemplate = html`
|
|
812
|
+
<div class="code-drawer-header">
|
|
813
|
+
<div class="code-drawer-title">
|
|
814
|
+
<pds-icon icon="code" size="sm"></pds-icon>
|
|
815
|
+
<span>${displayName}</span>
|
|
816
|
+
<span class="component-type-badge">${componentType}</span>
|
|
817
|
+
</div>
|
|
818
|
+
<button class="copy-code-btn" id="copyCodeBtn">
|
|
819
|
+
<pds-icon icon="clipboard" size="sm"></pds-icon>
|
|
820
|
+
Copy HTML
|
|
821
|
+
</button>
|
|
822
|
+
</div>
|
|
823
|
+
`;
|
|
824
|
+
|
|
825
|
+
// Create content template with loading state
|
|
826
|
+
const litPropsTemplate =
|
|
827
|
+
litProps.length > 0
|
|
828
|
+
? html`
|
|
829
|
+
<div class="lit-properties">
|
|
830
|
+
<h4>Lit Properties</h4>
|
|
831
|
+
<div class="lit-props-list">
|
|
832
|
+
${litProps.map(
|
|
833
|
+
(prop) => html`
|
|
834
|
+
<div class="lit-prop">
|
|
835
|
+
<code class="prop-name">${prop.name}</code>
|
|
836
|
+
<code class="prop-value"
|
|
837
|
+
>${JSON.stringify(prop.value)}</code
|
|
838
|
+
>
|
|
839
|
+
</div>
|
|
840
|
+
`
|
|
841
|
+
)}
|
|
842
|
+
</div>
|
|
843
|
+
</div>
|
|
844
|
+
`
|
|
845
|
+
: nothing;
|
|
846
|
+
|
|
847
|
+
const loadingTemplate = html`
|
|
848
|
+
${litPropsTemplate}
|
|
849
|
+
<div class="code-block-wrapper">
|
|
850
|
+
<pre
|
|
851
|
+
class="code-block"
|
|
852
|
+
><code class="language-html">Loading syntax highlighting...</code></pre>
|
|
853
|
+
</div>
|
|
854
|
+
`;
|
|
855
|
+
|
|
856
|
+
// Show drawer with loading content
|
|
857
|
+
await drawer.show(loadingTemplate, { header: headerTemplate });
|
|
858
|
+
|
|
859
|
+
// Highlight code asynchronously
|
|
860
|
+
const highlightedCode = await this.highlightWithShiki(htmlCode);
|
|
861
|
+
|
|
862
|
+
// Update with highlighted code
|
|
863
|
+
const finalTemplate = html`
|
|
864
|
+
${litPropsTemplate}
|
|
865
|
+
<div class="code-block-wrapper">
|
|
866
|
+
<pre class="code-block"><code class="language-html">${unsafeHTML(
|
|
867
|
+
highlightedCode
|
|
868
|
+
)}</code></pre>
|
|
869
|
+
</div>
|
|
870
|
+
`;
|
|
871
|
+
|
|
872
|
+
// Re-render with highlighted code
|
|
873
|
+
await document
|
|
874
|
+
.getElementById("global-drawer")
|
|
875
|
+
.show(finalTemplate, { header: headerTemplate });
|
|
876
|
+
|
|
877
|
+
// Add copy functionality
|
|
878
|
+
setTimeout(() => {
|
|
879
|
+
const drawer = document.getElementById("global-drawer");
|
|
880
|
+
const copyBtn = drawer?.querySelector("#copyCodeBtn");
|
|
881
|
+
if (copyBtn) {
|
|
882
|
+
copyBtn.onclick = () => {
|
|
883
|
+
let textToCopy = htmlCode;
|
|
884
|
+
if (litProps.length > 0) {
|
|
885
|
+
textToCopy =
|
|
886
|
+
"<!-- Lit Properties:\n" +
|
|
887
|
+
litProps
|
|
888
|
+
.map((p) => ` ${p.name}=${JSON.stringify(p.value)}`)
|
|
889
|
+
.join("\n") +
|
|
890
|
+
"\n-->\n\n" +
|
|
891
|
+
htmlCode;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
navigator.clipboard.writeText(textToCopy).then(() => {
|
|
895
|
+
copyBtn.innerHTML = `
|
|
896
|
+
<pds-icon icon="check" size="sm"></pds-icon>
|
|
897
|
+
Copied!
|
|
898
|
+
`;
|
|
899
|
+
setTimeout(() => {
|
|
900
|
+
copyBtn.innerHTML = `
|
|
901
|
+
<pds-icon icon="clipboard" size="sm"></pds-icon>
|
|
902
|
+
Copy HTML
|
|
903
|
+
`;
|
|
904
|
+
}, 2000);
|
|
905
|
+
});
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
}, 100);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* Load Shiki syntax highlighter dynamically
|
|
913
|
+
*/
|
|
914
|
+
async loadShiki() {
|
|
915
|
+
if (this.#shiki) return this.#shiki;
|
|
916
|
+
if (this.#shikiLoading) {
|
|
917
|
+
// Wait for the loading to complete
|
|
918
|
+
while (this.#shikiLoading) {
|
|
919
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
920
|
+
}
|
|
921
|
+
return this.#shiki;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
this.#shikiLoading = true;
|
|
925
|
+
try {
|
|
926
|
+
const shiki = await import("https://esm.sh/shiki@1.0.0");
|
|
927
|
+
this.#shiki = await shiki.getHighlighter({
|
|
928
|
+
themes: ["dark-plus"],
|
|
929
|
+
langs: ["html"],
|
|
930
|
+
});
|
|
931
|
+
return this.#shiki;
|
|
932
|
+
} catch (error) {
|
|
933
|
+
console.error("Failed to load Shiki:", error);
|
|
934
|
+
return null;
|
|
935
|
+
} finally {
|
|
936
|
+
this.#shikiLoading = false;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* Highlight code with Shiki
|
|
942
|
+
*/
|
|
943
|
+
async highlightWithShiki(code) {
|
|
944
|
+
const highlighter = await this.loadShiki();
|
|
945
|
+
if (!highlighter) {
|
|
946
|
+
// Fallback to escaped HTML without highlighting
|
|
947
|
+
return this.escapeHTML(code);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
try {
|
|
951
|
+
const html = highlighter.codeToHtml(code, {
|
|
952
|
+
lang: "html",
|
|
953
|
+
theme: "dark-plus",
|
|
954
|
+
});
|
|
955
|
+
// Extract just the code content from the generated HTML
|
|
956
|
+
const match = html.match(/<code[^>]*>([\s\S]*)<\/code>/);
|
|
957
|
+
return match ? match[1] : this.escapeHTML(code);
|
|
958
|
+
} catch (error) {
|
|
959
|
+
console.error("Shiki highlighting failed:", error);
|
|
960
|
+
return this.escapeHTML(code);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
escapeHTML(html) {
|
|
964
|
+
return html
|
|
965
|
+
.replace(/&/g, "&")
|
|
966
|
+
.replace(/</g, "<")
|
|
967
|
+
.replace(/>/g, ">")
|
|
968
|
+
.replace(/"/g, """)
|
|
969
|
+
.replace(/'/g, "'");
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* Ensure Showdown is available (loaded from CDN) and return a Converter
|
|
974
|
+
*/
|
|
975
|
+
async getShowdownConverter() {
|
|
976
|
+
if (this._showdown) return this._showdown;
|
|
977
|
+
const showdownNS = await this.loadShowdownFromCDN();
|
|
978
|
+
if (!showdownNS || !showdownNS.Converter) return null;
|
|
979
|
+
this._showdown = new showdownNS.Converter({
|
|
980
|
+
ghCompatibleHeaderId: true,
|
|
981
|
+
tables: true,
|
|
982
|
+
strikethrough: true,
|
|
983
|
+
tasklists: true,
|
|
984
|
+
});
|
|
985
|
+
return this._showdown;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
/**
|
|
989
|
+
* Load Showdown from a reliable CDN (jsDelivr with unpkg fallback)
|
|
990
|
+
*/
|
|
991
|
+
async loadShowdownFromCDN() {
|
|
992
|
+
if (typeof window !== "undefined" && window.showdown)
|
|
993
|
+
return window.showdown;
|
|
994
|
+
if (this._showdownLoading) {
|
|
995
|
+
// wait while another load is in progress
|
|
996
|
+
while (this._showdownLoading) {
|
|
997
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
998
|
+
}
|
|
999
|
+
return window.showdown || null;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
this._showdownLoading = true;
|
|
1003
|
+
const urls = [
|
|
1004
|
+
"https://cdn.jsdelivr.net/npm/showdown@2.1.0/dist/showdown.min.js",
|
|
1005
|
+
"https://unpkg.com/showdown@2.1.0/dist/showdown.min.js",
|
|
1006
|
+
];
|
|
1007
|
+
|
|
1008
|
+
for (const src of urls) {
|
|
1009
|
+
try {
|
|
1010
|
+
await this._injectScript(src, "showdown");
|
|
1011
|
+
if (window.showdown) {
|
|
1012
|
+
this._showdownLoading = false;
|
|
1013
|
+
return window.showdown;
|
|
1014
|
+
}
|
|
1015
|
+
} catch (e) {
|
|
1016
|
+
// try next
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
this._showdownLoading = false;
|
|
1020
|
+
return null;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
_injectScript(src, libName) {
|
|
1024
|
+
return new Promise((resolve, reject) => {
|
|
1025
|
+
// Avoid duplicates
|
|
1026
|
+
if (
|
|
1027
|
+
document.querySelector(`script[data-lib="${libName}"][src="${src}"]`)
|
|
1028
|
+
) {
|
|
1029
|
+
// Resolve on next tick
|
|
1030
|
+
setTimeout(resolve, 0);
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
const s = document.createElement("script");
|
|
1034
|
+
s.src = src;
|
|
1035
|
+
s.async = true;
|
|
1036
|
+
s.defer = true;
|
|
1037
|
+
s.dataset.lib = libName || "lib";
|
|
1038
|
+
s.onload = () => resolve();
|
|
1039
|
+
s.onerror = () => {
|
|
1040
|
+
s.remove();
|
|
1041
|
+
reject(new Error(`Failed to load script: ${src}`));
|
|
1042
|
+
};
|
|
1043
|
+
document.head.appendChild(s);
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
scrollToRelevantSection(fieldPath) {
|
|
1048
|
+
console.log("🎯 Scrolling to section for field:", fieldPath);
|
|
1049
|
+
|
|
1050
|
+
// Remove leading slash if present (pds-jsonform sends "/behavior.transitionSpeed")
|
|
1051
|
+
const normalizedPath = fieldPath.startsWith("/")
|
|
1052
|
+
? fieldPath.slice(1)
|
|
1053
|
+
: fieldPath;
|
|
1054
|
+
console.log(" Normalized path:", normalizedPath);
|
|
1055
|
+
|
|
1056
|
+
// Map field paths to section IDs
|
|
1057
|
+
const sectionMap = {
|
|
1058
|
+
// Colors
|
|
1059
|
+
"colors/primary": "color-system",
|
|
1060
|
+
"colors/secondary": "color-system",
|
|
1061
|
+
"colors/accent": "color-system",
|
|
1062
|
+
"colors/background": "color-system",
|
|
1063
|
+
"colors/success": "color-system",
|
|
1064
|
+
"colors/warning": "color-system",
|
|
1065
|
+
"colors/danger": "color-system",
|
|
1066
|
+
"colors/info": "color-system",
|
|
1067
|
+
|
|
1068
|
+
// Typography
|
|
1069
|
+
"typography/": "typography",
|
|
1070
|
+
|
|
1071
|
+
// Spacing
|
|
1072
|
+
"spatialRhythm/": "spacing",
|
|
1073
|
+
|
|
1074
|
+
// Shadows & Layers
|
|
1075
|
+
"layers/": "surfaces-shadows",
|
|
1076
|
+
|
|
1077
|
+
// Shape (radius, borders)
|
|
1078
|
+
"shape/": "buttons",
|
|
1079
|
+
|
|
1080
|
+
// Behavior (transitions - goes to interactive states where transition demo lives)
|
|
1081
|
+
"behavior/transitionSpeed": "interactive-states",
|
|
1082
|
+
"behavior/": "interactive-states",
|
|
1083
|
+
|
|
1084
|
+
// Components
|
|
1085
|
+
"components/forms": "forms",
|
|
1086
|
+
"components/alerts": "alerts",
|
|
1087
|
+
"components/badges": "badges",
|
|
1088
|
+
"components/tables": "tables",
|
|
1089
|
+
"components/toasts": "toasts",
|
|
1090
|
+
"components/modals": "modals",
|
|
1091
|
+
"components/tabStrip": "tabs",
|
|
1092
|
+
|
|
1093
|
+
// Icons
|
|
1094
|
+
"icons/": "icons",
|
|
1095
|
+
};
|
|
1096
|
+
|
|
1097
|
+
// Find matching section
|
|
1098
|
+
let sectionId = null;
|
|
1099
|
+
for (const [pattern, id] of Object.entries(sectionMap)) {
|
|
1100
|
+
if (normalizedPath.startsWith(pattern)) {
|
|
1101
|
+
sectionId = id;
|
|
1102
|
+
console.log(` ✓ Matched pattern "${pattern}" → section "${id}"`);
|
|
1103
|
+
break;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
if (sectionId) {
|
|
1108
|
+
// Find the section element
|
|
1109
|
+
const section = this.querySelector(`[data-section="${sectionId}"]`);
|
|
1110
|
+
console.log(
|
|
1111
|
+
` Searching for section: [data-section="${sectionId}"]`,
|
|
1112
|
+
section ? "✓ Found" : "✗ Not found"
|
|
1113
|
+
);
|
|
1114
|
+
|
|
1115
|
+
if (section) {
|
|
1116
|
+
section.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
1117
|
+
|
|
1118
|
+
// Add a brief highlight effect
|
|
1119
|
+
section.style.transition = "background-color 0.3s ease";
|
|
1120
|
+
section.style.backgroundColor = "var(--color-primary-50)";
|
|
1121
|
+
setTimeout(() => {
|
|
1122
|
+
section.style.backgroundColor = "";
|
|
1123
|
+
}, 1500);
|
|
1124
|
+
|
|
1125
|
+
console.log(" ✓ Scrolled and highlighted section");
|
|
1126
|
+
} else {
|
|
1127
|
+
console.warn(
|
|
1128
|
+
` ✗ Section [data-section="${sectionId}"] not found in DOM`
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
} else {
|
|
1132
|
+
console.warn(` ✗ No section mapping found for field: ${fieldPath}`);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
renderDisabledSection(title, message) {
|
|
1137
|
+
return html`
|
|
1138
|
+
<section class="showcase-section disabled">
|
|
1139
|
+
<h2>${title}</h2>
|
|
1140
|
+
<p class="disabled-message">${message}</p>
|
|
1141
|
+
</section>
|
|
1142
|
+
`;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
renderTOC() {
|
|
1146
|
+
if (!this.sections || this.sections.length === 0) return nothing;
|
|
1147
|
+
|
|
1148
|
+
return html`
|
|
1149
|
+
<nav class="showcase-toc" aria-label="Table of Contents">
|
|
1150
|
+
<div class="toc-wrapper">
|
|
1151
|
+
<div class="input-icon">
|
|
1152
|
+
<pds-icon icon="magnifying-glass"></pds-icon>
|
|
1153
|
+
<input
|
|
1154
|
+
id="pds-search"
|
|
1155
|
+
@focus=${(e) =>
|
|
1156
|
+
AutoComplete.connect(e, this.autoCompleteSettings)}
|
|
1157
|
+
type="search"
|
|
1158
|
+
placeholder="Search design system..."
|
|
1159
|
+
/>
|
|
1160
|
+
</div>
|
|
1161
|
+
</div>
|
|
1162
|
+
</nav>
|
|
1163
|
+
`;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
get autoCompleteSettings() {
|
|
1167
|
+
return {
|
|
1168
|
+
//debug: true,
|
|
1169
|
+
iconHandler: (item) => {
|
|
1170
|
+
return item.icon ? `<pds-icon icon="${item.icon}"></pds-icon>` : null;
|
|
1171
|
+
},
|
|
1172
|
+
categories: {
|
|
1173
|
+
Sections: {
|
|
1174
|
+
action: (options) => {
|
|
1175
|
+
document
|
|
1176
|
+
.querySelector(`[data-section="${options.id}"]`)
|
|
1177
|
+
.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
1178
|
+
},
|
|
1179
|
+
trigger: (options) => options.search.length === 0,
|
|
1180
|
+
getItems: (options) => {
|
|
1181
|
+
return this.sections.map((section) => {
|
|
1182
|
+
return {
|
|
1183
|
+
text: section.title,
|
|
1184
|
+
id: section.id,
|
|
1185
|
+
icon: "folder-simple",
|
|
1186
|
+
};
|
|
1187
|
+
});
|
|
1188
|
+
},
|
|
1189
|
+
},
|
|
1190
|
+
Query: {
|
|
1191
|
+
action: (options) => {
|
|
1192
|
+
// For query results, display the code/value or scroll to relevant section
|
|
1193
|
+
if (options.code) {
|
|
1194
|
+
// If there's code, copy to clipboard
|
|
1195
|
+
if (navigator.clipboard) {
|
|
1196
|
+
navigator.clipboard.writeText(options.code).then(() => {
|
|
1197
|
+
PDS.dispatchEvent(
|
|
1198
|
+
new CustomEvent("pds:toast", {
|
|
1199
|
+
detail: {
|
|
1200
|
+
message: "Code copied to clipboard",
|
|
1201
|
+
type: "success",
|
|
1202
|
+
duration: 2000,
|
|
1203
|
+
},
|
|
1204
|
+
})
|
|
1205
|
+
);
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
// Also try to navigate to relevant section if available
|
|
1210
|
+
const category = options.category?.toLowerCase() || "";
|
|
1211
|
+
let sectionId = null;
|
|
1212
|
+
|
|
1213
|
+
if (category.includes("color") || category.includes("surface")) {
|
|
1214
|
+
sectionId = "color-system";
|
|
1215
|
+
} else if (
|
|
1216
|
+
category.includes("utility") ||
|
|
1217
|
+
category.includes("layout")
|
|
1218
|
+
) {
|
|
1219
|
+
sectionId = "utilities";
|
|
1220
|
+
} else if (category.includes("component")) {
|
|
1221
|
+
sectionId = "components";
|
|
1222
|
+
} else if (category.includes("typography")) {
|
|
1223
|
+
sectionId = "typography";
|
|
1224
|
+
} else if (category.includes("spacing")) {
|
|
1225
|
+
sectionId = "spacing";
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
if (sectionId) {
|
|
1229
|
+
const el = document.querySelector(
|
|
1230
|
+
`[data-section="${sectionId}"]`
|
|
1231
|
+
);
|
|
1232
|
+
if (el) {
|
|
1233
|
+
el.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
},
|
|
1237
|
+
trigger: (options) => options.search.length >= 2,
|
|
1238
|
+
getItems: async (options) => {
|
|
1239
|
+
const q = (options.search || "").trim();
|
|
1240
|
+
if (!q) return [];
|
|
1241
|
+
|
|
1242
|
+
try {
|
|
1243
|
+
const results = await PDS.query(q);
|
|
1244
|
+
|
|
1245
|
+
return results.map((result) => {
|
|
1246
|
+
return {
|
|
1247
|
+
text: result.text,
|
|
1248
|
+
id: result.value,
|
|
1249
|
+
icon: result.icon || "magnifying-glass",
|
|
1250
|
+
category: result.category,
|
|
1251
|
+
code: result.code,
|
|
1252
|
+
cssVar: result.cssVar,
|
|
1253
|
+
description: result.description,
|
|
1254
|
+
};
|
|
1255
|
+
});
|
|
1256
|
+
} catch (err) {
|
|
1257
|
+
console.error("Query error:", err);
|
|
1258
|
+
return [];
|
|
1259
|
+
}
|
|
1260
|
+
},
|
|
1261
|
+
},
|
|
1262
|
+
Search: {
|
|
1263
|
+
action: (options) => {
|
|
1264
|
+
// When a user selects an item, try to resolve it to a showcase section
|
|
1265
|
+
const rawId = options.id || "";
|
|
1266
|
+
const query = (options.search || "").toLowerCase();
|
|
1267
|
+
|
|
1268
|
+
// id is encoded as `type|key` (see getItems)
|
|
1269
|
+
const [type, key] = rawId.split("|");
|
|
1270
|
+
|
|
1271
|
+
// 1) try to find a section with id exactly matching key
|
|
1272
|
+
let section = this.sections.find((s) => s.id === key);
|
|
1273
|
+
|
|
1274
|
+
// 2) try to match against title or id containing the key or query
|
|
1275
|
+
if (!section) {
|
|
1276
|
+
section = this.sections.find(
|
|
1277
|
+
(s) =>
|
|
1278
|
+
s.title
|
|
1279
|
+
?.toLowerCase()
|
|
1280
|
+
.includes(key?.toLowerCase?.() || "") ||
|
|
1281
|
+
s.id?.toLowerCase().includes(key?.toLowerCase?.() || "") ||
|
|
1282
|
+
s.title?.toLowerCase().includes(query) ||
|
|
1283
|
+
s.id?.toLowerCase().includes(query)
|
|
1284
|
+
);
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
// 3) fallback: search inside each section element for the query text
|
|
1288
|
+
if (!section && query) {
|
|
1289
|
+
for (const s of this.sections) {
|
|
1290
|
+
const el = this.querySelector(`[data-section="${s.id}"]`);
|
|
1291
|
+
if (!el) continue;
|
|
1292
|
+
const text = (el.innerText || "").toLowerCase();
|
|
1293
|
+
if (
|
|
1294
|
+
text.includes(query) ||
|
|
1295
|
+
s.title.toLowerCase().includes(query)
|
|
1296
|
+
) {
|
|
1297
|
+
section = s;
|
|
1298
|
+
break;
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
if (section) {
|
|
1304
|
+
const el = this.querySelector(`[data-section="${section.id}"]`);
|
|
1305
|
+
if (el)
|
|
1306
|
+
el.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
1307
|
+
} else {
|
|
1308
|
+
// no section found - try a global text search and show first match
|
|
1309
|
+
const allText = (this.innerText || "").toLowerCase();
|
|
1310
|
+
const idx = allText.indexOf(query);
|
|
1311
|
+
if (idx !== -1) {
|
|
1312
|
+
// find first section that contains the query
|
|
1313
|
+
for (const s of this.sections) {
|
|
1314
|
+
const el = this.querySelector(`[data-section="${s.id}"]`);
|
|
1315
|
+
if (!el) continue;
|
|
1316
|
+
if ((el.innerText || "").toLowerCase().includes(query)) {
|
|
1317
|
+
el.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
1318
|
+
break;
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
},
|
|
1324
|
+
trigger: (options) => options.search.length > 1,
|
|
1325
|
+
getItems: (options) => {
|
|
1326
|
+
const q = (options.search || "").trim().toLowerCase();
|
|
1327
|
+
if (!q) return [];
|
|
1328
|
+
|
|
1329
|
+
const candidates = [];
|
|
1330
|
+
|
|
1331
|
+
// primitives
|
|
1332
|
+
for (const p of PDS.ontology.primitives || []) {
|
|
1333
|
+
const name = (p.name || p.id || "").toString();
|
|
1334
|
+
const id = p.id || name.replace(/\s+/g, "-").toLowerCase();
|
|
1335
|
+
const score =
|
|
1336
|
+
(name.toLowerCase().includes(q) ? 30 : 0) +
|
|
1337
|
+
(id.includes(q) ? 20 : 0);
|
|
1338
|
+
// selectors
|
|
1339
|
+
const selMatch = (p.selectors || []).some((s) =>
|
|
1340
|
+
String(s).toLowerCase().includes(q)
|
|
1341
|
+
);
|
|
1342
|
+
const total = score + (selMatch ? 10 : 0);
|
|
1343
|
+
candidates.push({
|
|
1344
|
+
type: "primitive",
|
|
1345
|
+
key: id,
|
|
1346
|
+
name,
|
|
1347
|
+
score: total,
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
// components
|
|
1352
|
+
for (const c of PDS.ontology.components || []) {
|
|
1353
|
+
const name = (c.name || c.id || "").toString();
|
|
1354
|
+
const id = c.id || name.replace(/\s+/g, "-").toLowerCase();
|
|
1355
|
+
const score =
|
|
1356
|
+
(name.toLowerCase().includes(q) ? 40 : 0) +
|
|
1357
|
+
(id.includes(q) ? 25 : 0);
|
|
1358
|
+
const selMatch = (c.selectors || []).some((s) =>
|
|
1359
|
+
String(s).toLowerCase().includes(q)
|
|
1360
|
+
);
|
|
1361
|
+
const total = score + (selMatch ? 10 : 0);
|
|
1362
|
+
candidates.push({
|
|
1363
|
+
type: "component",
|
|
1364
|
+
key: id,
|
|
1365
|
+
name,
|
|
1366
|
+
score: total,
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
// tokens (flatten groups)
|
|
1371
|
+
if (PDS.ontology.tokens) {
|
|
1372
|
+
for (const [group, items] of Object.entries(
|
|
1373
|
+
PDS.ontology.tokens
|
|
1374
|
+
)) {
|
|
1375
|
+
if (Array.isArray(items)) {
|
|
1376
|
+
for (const t of items) {
|
|
1377
|
+
const name = `${group}/${t}`;
|
|
1378
|
+
const id = `${group}-${t}`;
|
|
1379
|
+
const score = name.toLowerCase().includes(q) ? 35 : 0;
|
|
1380
|
+
candidates.push({ type: "token", key: id, name, score });
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
// enhancements
|
|
1387
|
+
for (const enh of PDS.ontology.enhancements || []) {
|
|
1388
|
+
const name = String(enh);
|
|
1389
|
+
const key = `enh-${name.replace(
|
|
1390
|
+
/[^a-z0-9]+/gi,
|
|
1391
|
+
"-"
|
|
1392
|
+
)}`.toLowerCase();
|
|
1393
|
+
const score = name.toLowerCase().includes(q) ? 25 : 0;
|
|
1394
|
+
candidates.push({ type: "enhancement", key, name, score });
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
// utilities
|
|
1398
|
+
for (const util of PDS.ontology.utilities || []) {
|
|
1399
|
+
const name = String(util);
|
|
1400
|
+
const key = `util-${name.replace(
|
|
1401
|
+
/[^a-z0-9]+/gi,
|
|
1402
|
+
"-"
|
|
1403
|
+
)}`.toLowerCase();
|
|
1404
|
+
const score = name.toLowerCase().includes(q) ? 20 : 0;
|
|
1405
|
+
candidates.push({ type: "utility", key, name, score });
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
// styles (flat)
|
|
1409
|
+
if (PDS.ontology.styles) {
|
|
1410
|
+
for (const [k, v] of Object.entries(PDS.ontology.styles)) {
|
|
1411
|
+
const name = k;
|
|
1412
|
+
const key = `style-${k}`;
|
|
1413
|
+
const score = name.toLowerCase().includes(q) ? 10 : 0;
|
|
1414
|
+
candidates.push({ type: "style", key, name, score });
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
// Basic fuzzy/substring scoring boost for any candidate containing q in name/key
|
|
1419
|
+
for (const c of candidates) {
|
|
1420
|
+
const lname = (c.name || "").toLowerCase();
|
|
1421
|
+
const lkey = (c.key || "").toLowerCase();
|
|
1422
|
+
if (lname.includes(q)) c.score += 50;
|
|
1423
|
+
if (lkey.includes(q)) c.score += 20;
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
// Filter and sort
|
|
1427
|
+
const results = candidates
|
|
1428
|
+
.filter((c) => c.score > 0)
|
|
1429
|
+
.sort((a, b) => b.score - a.score)
|
|
1430
|
+
.slice(0, 30)
|
|
1431
|
+
.map((c) => ({
|
|
1432
|
+
text: c.name,
|
|
1433
|
+
id: `${c.type}|${c.key}`,
|
|
1434
|
+
icon:
|
|
1435
|
+
c.type === "component"
|
|
1436
|
+
? "brackets-curly"
|
|
1437
|
+
: c.type === "primitive"
|
|
1438
|
+
? "tag"
|
|
1439
|
+
: c.type === "token"
|
|
1440
|
+
? "palette"
|
|
1441
|
+
: "folder-simple",
|
|
1442
|
+
}));
|
|
1443
|
+
|
|
1444
|
+
return results;
|
|
1445
|
+
},
|
|
1446
|
+
},
|
|
1447
|
+
},
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
render() {
|
|
1452
|
+
const components = this.config?.components || {};
|
|
1453
|
+
// Determine current theme from DOM so section copy can adapt
|
|
1454
|
+
const theme =
|
|
1455
|
+
(typeof document !== "undefined" &&
|
|
1456
|
+
document.documentElement?.getAttribute("data-theme")) ||
|
|
1457
|
+
"light";
|
|
1458
|
+
const inversionTitle =
|
|
1459
|
+
theme === "dark"
|
|
1460
|
+
? "Light Surfaces in Dark Mode"
|
|
1461
|
+
: "Dark Surfaces in Light Mode";
|
|
1462
|
+
const inversePhrase =
|
|
1463
|
+
theme === "dark"
|
|
1464
|
+
? "light surface in dark mode"
|
|
1465
|
+
: "dark surface in light mode";
|
|
1466
|
+
|
|
1467
|
+
return html`
|
|
1468
|
+
<div
|
|
1469
|
+
class="showcase-container ${this.inspectorActive
|
|
1470
|
+
? "inspector-active"
|
|
1471
|
+
: ""}"
|
|
1472
|
+
@click=${this.handleInspectorClick}
|
|
1473
|
+
>
|
|
1474
|
+
<!-- Table of Contents Navigation -->
|
|
1475
|
+
${this.renderTOC()}
|
|
1476
|
+
|
|
1477
|
+
<!-- Hero Section -->
|
|
1478
|
+
<section class="showcase-hero">
|
|
1479
|
+
<h1>Pure Design System</h1>
|
|
1480
|
+
<p>Why build a design system if you can generate it?</p>
|
|
1481
|
+
<div class="btn-group">
|
|
1482
|
+
<button
|
|
1483
|
+
class="btn-primary btn-lg"
|
|
1484
|
+
@click=${() => {
|
|
1485
|
+
this.showDoc("getting-started.md");
|
|
1486
|
+
}}
|
|
1487
|
+
>
|
|
1488
|
+
<pds-icon icon="download"></pds-icon>
|
|
1489
|
+
Get Started
|
|
1490
|
+
</button>
|
|
1491
|
+
<button
|
|
1492
|
+
class="btn-secondary btn-lg"
|
|
1493
|
+
@click=${() => {
|
|
1494
|
+
this.showDoc("readme.md");
|
|
1495
|
+
}}
|
|
1496
|
+
>
|
|
1497
|
+
<pds-icon icon="book-open"></pds-icon>
|
|
1498
|
+
View Docs
|
|
1499
|
+
</button>
|
|
1500
|
+
</div>
|
|
1501
|
+
</section>
|
|
1502
|
+
|
|
1503
|
+
<!-- Colors Section -->
|
|
1504
|
+
<section class="showcase-section" data-section="color-system">
|
|
1505
|
+
<h2>
|
|
1506
|
+
<pds-icon
|
|
1507
|
+
icon="palette"
|
|
1508
|
+
size="lg"
|
|
1509
|
+
class="icon-primary"
|
|
1510
|
+
></pds-icon>
|
|
1511
|
+
Color System
|
|
1512
|
+
</h2>
|
|
1513
|
+
|
|
1514
|
+
|
|
1515
|
+
<div class="color-grid">
|
|
1516
|
+
${this.renderColorCard("Primary", "primary")}
|
|
1517
|
+
${this.renderColorCard("Secondary", "secondary")}
|
|
1518
|
+
${this.renderColorCard("Accent", "accent")}
|
|
1519
|
+
${this.renderColorCard("Success", "success")}
|
|
1520
|
+
${this.renderColorCard("Warning", "warning")}
|
|
1521
|
+
${this.renderColorCard("Danger", "danger")}
|
|
1522
|
+
${this.renderColorCard("Info", "info")}
|
|
1523
|
+
</div>
|
|
1524
|
+
|
|
1525
|
+
<h3>Semantic Color Usage</h3>
|
|
1526
|
+
<div class="semantic-usage">
|
|
1527
|
+
<div class="semantic-message success">
|
|
1528
|
+
<pds-icon
|
|
1529
|
+
icon="check-circle"
|
|
1530
|
+
class="icon-success"
|
|
1531
|
+
size="lg"
|
|
1532
|
+
></pds-icon>
|
|
1533
|
+
<div>
|
|
1534
|
+
<strong>Success</strong>
|
|
1535
|
+
<p>Operations completed successfully</p>
|
|
1536
|
+
</div>
|
|
1537
|
+
</div>
|
|
1538
|
+
|
|
1539
|
+
<div class="semantic-message warning">
|
|
1540
|
+
<pds-icon
|
|
1541
|
+
icon="warning"
|
|
1542
|
+
class="icon-warning"
|
|
1543
|
+
size="lg"
|
|
1544
|
+
></pds-icon>
|
|
1545
|
+
<div>
|
|
1546
|
+
<strong>Warning</strong>
|
|
1547
|
+
<p>Please review carefully</p>
|
|
1548
|
+
</div>
|
|
1549
|
+
</div>
|
|
1550
|
+
|
|
1551
|
+
<div class="semantic-message danger">
|
|
1552
|
+
<pds-icon
|
|
1553
|
+
icon="x-circle"
|
|
1554
|
+
class="icon-danger"
|
|
1555
|
+
size="lg"
|
|
1556
|
+
></pds-icon>
|
|
1557
|
+
<div>
|
|
1558
|
+
<strong>Danger</strong>
|
|
1559
|
+
<p>Critical error occurred</p>
|
|
1560
|
+
</div>
|
|
1561
|
+
</div>
|
|
1562
|
+
|
|
1563
|
+
<div class="semantic-message info">
|
|
1564
|
+
<pds-icon icon="info" class="icon-info" size="lg"></pds-icon>
|
|
1565
|
+
<div>
|
|
1566
|
+
<strong>Info</strong>
|
|
1567
|
+
<p>Helpful information</p>
|
|
1568
|
+
</div>
|
|
1569
|
+
</div>
|
|
1570
|
+
</div>
|
|
1571
|
+
|
|
1572
|
+
<h3>Gray Scale (from Secondary)</h3>
|
|
1573
|
+
<div class="gray-scale-grid">
|
|
1574
|
+
${[50, 100, 200, 300, 400, 500, 600, 700, 800].map(
|
|
1575
|
+
(shade) => html`
|
|
1576
|
+
<div class="gray-scale-item">
|
|
1577
|
+
<div
|
|
1578
|
+
class="gray-scale-swatch"
|
|
1579
|
+
style="background-color: var(--color-gray-${shade});"
|
|
1580
|
+
title="gray-${shade}"
|
|
1581
|
+
></div>
|
|
1582
|
+
<div class="gray-scale-label">${shade}</div>
|
|
1583
|
+
</div>
|
|
1584
|
+
`
|
|
1585
|
+
)}
|
|
1586
|
+
</div>
|
|
1587
|
+
</section>
|
|
1588
|
+
|
|
1589
|
+
<!-- Derived Color Scales Section -->
|
|
1590
|
+
<section class="showcase-section alt-bg">
|
|
1591
|
+
<h2>
|
|
1592
|
+
<pds-icon icon="sun" size="lg" class="icon-warning"></pds-icon>
|
|
1593
|
+
Derived Color Scales
|
|
1594
|
+
</h2>
|
|
1595
|
+
<p>
|
|
1596
|
+
Complete 9-step color scales (50-800) automatically generated from
|
|
1597
|
+
your base colors. Each scale maintains proper contrast and color
|
|
1598
|
+
relationships.
|
|
1599
|
+
</p>
|
|
1600
|
+
|
|
1601
|
+
<h3>Primary Color Scale</h3>
|
|
1602
|
+
${this.renderColorScale("primary")}
|
|
1603
|
+
|
|
1604
|
+
<h3>Secondary (Neutral) Scale</h3>
|
|
1605
|
+
${this.renderColorScale("secondary")}
|
|
1606
|
+
|
|
1607
|
+
<h3>Accent Color Scale</h3>
|
|
1608
|
+
${this.renderColorScale("accent")}
|
|
1609
|
+
|
|
1610
|
+
<h3>Semantic Color Scales (Auto-Derived)</h3>
|
|
1611
|
+
<p class="interactive-demo">
|
|
1612
|
+
These colors are automatically derived from your primary color
|
|
1613
|
+
with intelligent hue shifting for semantic meaning.
|
|
1614
|
+
</p>
|
|
1615
|
+
|
|
1616
|
+
${this.renderColorScale("success")}
|
|
1617
|
+
${this.renderColorScale("warning")}
|
|
1618
|
+
${this.renderColorScale("danger")} ${this.renderColorScale("info")}
|
|
1619
|
+
</section>
|
|
1620
|
+
|
|
1621
|
+
<!-- Typography Section -->
|
|
1622
|
+
<section class="showcase-section alt-bg" data-section="typography">
|
|
1623
|
+
<h2>
|
|
1624
|
+
<pds-icon
|
|
1625
|
+
icon="text-aa"
|
|
1626
|
+
size="lg"
|
|
1627
|
+
class="icon-primary"
|
|
1628
|
+
></pds-icon>
|
|
1629
|
+
Typography
|
|
1630
|
+
</h2>
|
|
1631
|
+
|
|
1632
|
+
<div class="grid grid-cols-1">
|
|
1633
|
+
<h1>Heading 1 - The quick brown fox</h1>
|
|
1634
|
+
<h2>Heading 2 - The quick brown fox</h2>
|
|
1635
|
+
<h3>Heading 3 - The quick brown fox</h3>
|
|
1636
|
+
<h4>Heading 4 - The quick brown fox</h4>
|
|
1637
|
+
<h5>Heading 5 - The quick brown fox</h5>
|
|
1638
|
+
<h6>Heading 6 - The quick brown fox</h6>
|
|
1639
|
+
<p>
|
|
1640
|
+
Regular paragraph text with <a href="#">a link</a> and
|
|
1641
|
+
<code>inline code</code>.
|
|
1642
|
+
</p>
|
|
1643
|
+
</div>
|
|
1644
|
+
</section>
|
|
1645
|
+
|
|
1646
|
+
<!-- Buttons Section -->
|
|
1647
|
+
<section class="showcase-section" data-section="buttons">
|
|
1648
|
+
<h2>
|
|
1649
|
+
<pds-icon
|
|
1650
|
+
icon="cursor-click"
|
|
1651
|
+
size="lg"
|
|
1652
|
+
class="icon-primary"
|
|
1653
|
+
></pds-icon>
|
|
1654
|
+
Buttons
|
|
1655
|
+
</h2>
|
|
1656
|
+
|
|
1657
|
+
<div class="flex-wrap">
|
|
1658
|
+
<button class="btn-primary">Primary</button>
|
|
1659
|
+
<button class="btn-secondary">Secondary</button>
|
|
1660
|
+
<button class="btn-outline">Outline</button>
|
|
1661
|
+
<button class="btn-primary" disabled>Disabled</button>
|
|
1662
|
+
</div>
|
|
1663
|
+
|
|
1664
|
+
<h3>Sizes</h3>
|
|
1665
|
+
<div class="flex-wrap">
|
|
1666
|
+
<button class="btn-primary btn-sm">Small</button>
|
|
1667
|
+
<button class="btn-primary">Default</button>
|
|
1668
|
+
<button class="btn-primary btn-lg">Large</button>
|
|
1669
|
+
</div>
|
|
1670
|
+
|
|
1671
|
+
<h3>Icon Buttons</h3>
|
|
1672
|
+
<div class="flex-wrap gap-sm">
|
|
1673
|
+
<button class="icon-only btn-primary">
|
|
1674
|
+
<pds-icon icon="gear" label="Settings"></pds-icon>
|
|
1675
|
+
</button>
|
|
1676
|
+
<button class="icon-only btn-secondary">
|
|
1677
|
+
<pds-icon icon="bell" label="Notifications"></pds-icon>
|
|
1678
|
+
</button>
|
|
1679
|
+
<button class="icon-only btn-outline">
|
|
1680
|
+
<pds-icon icon="heart" label="Favorite"></pds-icon>
|
|
1681
|
+
</button>
|
|
1682
|
+
<button class="btn-primary">
|
|
1683
|
+
<pds-icon icon="download"></pds-icon>
|
|
1684
|
+
<span>Download</span>
|
|
1685
|
+
</button>
|
|
1686
|
+
</div>
|
|
1687
|
+
</section>
|
|
1688
|
+
|
|
1689
|
+
<section class="showcase-section alt-bg" data-section="forms">
|
|
1690
|
+
<h2>
|
|
1691
|
+
<pds-icon
|
|
1692
|
+
icon="note-pencil"
|
|
1693
|
+
size="lg"
|
|
1694
|
+
class="icon-primary"
|
|
1695
|
+
></pds-icon>
|
|
1696
|
+
Form Controls
|
|
1697
|
+
</h2>
|
|
1698
|
+
|
|
1699
|
+
<form
|
|
1700
|
+
class="form-demo"
|
|
1701
|
+
onsubmit="event.preventDefault(); console.log('Form submitted (prevented)'); return false;"
|
|
1702
|
+
>
|
|
1703
|
+
<fieldset>
|
|
1704
|
+
<legend>Personal Information</legend>
|
|
1705
|
+
|
|
1706
|
+
<label>
|
|
1707
|
+
<span>Full Name</span>
|
|
1708
|
+
<input type="text" placeholder="Enter your name" required />
|
|
1709
|
+
</label>
|
|
1710
|
+
|
|
1711
|
+
<label>
|
|
1712
|
+
<span>Email</span>
|
|
1713
|
+
<input
|
|
1714
|
+
type="email"
|
|
1715
|
+
placeholder="you@example.com"
|
|
1716
|
+
autocomplete="username"
|
|
1717
|
+
required
|
|
1718
|
+
/>
|
|
1719
|
+
</label>
|
|
1720
|
+
|
|
1721
|
+
<label>
|
|
1722
|
+
<span>Phone</span>
|
|
1723
|
+
<input type="tel" placeholder="+1 (555) 000-0000" />
|
|
1724
|
+
</label>
|
|
1725
|
+
|
|
1726
|
+
<label>
|
|
1727
|
+
<span>Date of Birth</span>
|
|
1728
|
+
<input type="date" />
|
|
1729
|
+
</label>
|
|
1730
|
+
|
|
1731
|
+
<label>
|
|
1732
|
+
<span>Rich Text</span>
|
|
1733
|
+
<pds-richtext
|
|
1734
|
+
name="richtext"
|
|
1735
|
+
placeholder="Enter rich text"
|
|
1736
|
+
></pds-richtext>
|
|
1737
|
+
</label>
|
|
1738
|
+
|
|
1739
|
+
<label>
|
|
1740
|
+
<span>Password</span>
|
|
1741
|
+
<input
|
|
1742
|
+
type="password"
|
|
1743
|
+
placeholder="Enter password"
|
|
1744
|
+
autocomplete="current-password"
|
|
1745
|
+
/>
|
|
1746
|
+
</label>
|
|
1747
|
+
|
|
1748
|
+
<label>
|
|
1749
|
+
<span>Country</span>
|
|
1750
|
+
<select>
|
|
1751
|
+
<option>United States</option>
|
|
1752
|
+
<option>Canada</option>
|
|
1753
|
+
<option>United Kingdom</option>
|
|
1754
|
+
<option>Germany</option>
|
|
1755
|
+
<option>France</option>
|
|
1756
|
+
<option>Other</option>
|
|
1757
|
+
</select>
|
|
1758
|
+
</label>
|
|
1759
|
+
|
|
1760
|
+
<label>
|
|
1761
|
+
<span>Number</span>
|
|
1762
|
+
<input type="number" min="0" max="100" value="50" />
|
|
1763
|
+
</label>
|
|
1764
|
+
|
|
1765
|
+
<label>
|
|
1766
|
+
<span>Range</span>
|
|
1767
|
+
<input type="range" min="0" max="100" value="50" />
|
|
1768
|
+
</label>
|
|
1769
|
+
|
|
1770
|
+
<label>
|
|
1771
|
+
<span>URL</span>
|
|
1772
|
+
<input type="url" placeholder="https://example.com" />
|
|
1773
|
+
</label>
|
|
1774
|
+
|
|
1775
|
+
<label>
|
|
1776
|
+
<span>File Upload</span>
|
|
1777
|
+
<pds-upload name="file"></pds-upload>
|
|
1778
|
+
</label>
|
|
1779
|
+
|
|
1780
|
+
<label>
|
|
1781
|
+
<span>Message</span>
|
|
1782
|
+
<textarea
|
|
1783
|
+
rows="4"
|
|
1784
|
+
placeholder="Your message here..."
|
|
1785
|
+
></textarea>
|
|
1786
|
+
</label>
|
|
1787
|
+
</fieldset>
|
|
1788
|
+
|
|
1789
|
+
<fieldset>
|
|
1790
|
+
<legend>Preferences</legend>
|
|
1791
|
+
|
|
1792
|
+
<label data-toggle>
|
|
1793
|
+
<input type="checkbox" checked />
|
|
1794
|
+
Subscribe to newsletter
|
|
1795
|
+
</label>
|
|
1796
|
+
|
|
1797
|
+
<label data-toggle>
|
|
1798
|
+
<input type="checkbox" />
|
|
1799
|
+
Receive marketing emails
|
|
1800
|
+
</label>
|
|
1801
|
+
</fieldset>
|
|
1802
|
+
|
|
1803
|
+
<fieldset>
|
|
1804
|
+
<legend>Toggle Switches (Progressive Enhancement)</legend>
|
|
1805
|
+
|
|
1806
|
+
<label data-toggle>
|
|
1807
|
+
<input type="checkbox" checked />
|
|
1808
|
+
Enable notifications
|
|
1809
|
+
</label>
|
|
1810
|
+
|
|
1811
|
+
<label data-toggle>
|
|
1812
|
+
<input type="checkbox" />
|
|
1813
|
+
Dark mode
|
|
1814
|
+
</label>
|
|
1815
|
+
|
|
1816
|
+
<label data-toggle>
|
|
1817
|
+
<input type="checkbox" checked />
|
|
1818
|
+
Auto-save
|
|
1819
|
+
</label>
|
|
1820
|
+
</fieldset>
|
|
1821
|
+
|
|
1822
|
+
<div class="form-demo-actions">
|
|
1823
|
+
<button type="submit" class="btn-primary">Submit Form</button>
|
|
1824
|
+
<button type="reset" class="btn-secondary">Reset</button>
|
|
1825
|
+
<button type="button" class="btn-outline">Cancel</button>
|
|
1826
|
+
</div>
|
|
1827
|
+
</form>
|
|
1828
|
+
</section>
|
|
1829
|
+
|
|
1830
|
+
<section class="showcase-section" data-section="alerts">
|
|
1831
|
+
<h2>
|
|
1832
|
+
<pds-icon
|
|
1833
|
+
icon="bell-ringing"
|
|
1834
|
+
size="lg"
|
|
1835
|
+
class="icon-primary"
|
|
1836
|
+
></pds-icon>
|
|
1837
|
+
Alerts & Feedback
|
|
1838
|
+
</h2>
|
|
1839
|
+
|
|
1840
|
+
<div class="grid grid-cols-1">
|
|
1841
|
+
<div class="alert alert-success">
|
|
1842
|
+
<div class="alert-icon">
|
|
1843
|
+
<pds-icon
|
|
1844
|
+
icon="check-circle"
|
|
1845
|
+
class="icon-success"
|
|
1846
|
+
size="lg"
|
|
1847
|
+
></pds-icon>
|
|
1848
|
+
</div>
|
|
1849
|
+
<div>
|
|
1850
|
+
<div class="alert-title">Success!</div>
|
|
1851
|
+
<p>Your operation completed successfully.</p>
|
|
1852
|
+
</div>
|
|
1853
|
+
</div>
|
|
1854
|
+
|
|
1855
|
+
<div class="alert alert-info">
|
|
1856
|
+
<div class="alert-icon">
|
|
1857
|
+
<pds-icon icon="info" class="icon-info" size="lg"></pds-icon>
|
|
1858
|
+
</div>
|
|
1859
|
+
<div>
|
|
1860
|
+
<div class="alert-title">Information</div>
|
|
1861
|
+
<p>This is an informational message.</p>
|
|
1862
|
+
</div>
|
|
1863
|
+
</div>
|
|
1864
|
+
|
|
1865
|
+
<div class="alert alert-warning">
|
|
1866
|
+
<div class="alert-icon">
|
|
1867
|
+
<pds-icon
|
|
1868
|
+
icon="warning"
|
|
1869
|
+
class="icon-warning"
|
|
1870
|
+
size="lg"
|
|
1871
|
+
></pds-icon>
|
|
1872
|
+
</div>
|
|
1873
|
+
<div>
|
|
1874
|
+
<div class="alert-title">Warning</div>
|
|
1875
|
+
<p>Please review this warning.</p>
|
|
1876
|
+
</div>
|
|
1877
|
+
</div>
|
|
1878
|
+
|
|
1879
|
+
<div class="alert alert-danger">
|
|
1880
|
+
<div class="alert-icon">
|
|
1881
|
+
<pds-icon
|
|
1882
|
+
icon="x-circle"
|
|
1883
|
+
class="icon-danger"
|
|
1884
|
+
size="lg"
|
|
1885
|
+
></pds-icon>
|
|
1886
|
+
</div>
|
|
1887
|
+
<div>
|
|
1888
|
+
<div class="alert-title">Error</div>
|
|
1889
|
+
<p>An error occurred.</p>
|
|
1890
|
+
</div>
|
|
1891
|
+
</div>
|
|
1892
|
+
</div>
|
|
1893
|
+
</section>
|
|
1894
|
+
|
|
1895
|
+
<!-- Badges Section -->
|
|
1896
|
+
|
|
1897
|
+
<section class="showcase-section alt-bg">
|
|
1898
|
+
<h2>
|
|
1899
|
+
<pds-icon icon="tag" size="lg" class="icon-primary"></pds-icon>
|
|
1900
|
+
Badges & Pills
|
|
1901
|
+
</h2>
|
|
1902
|
+
|
|
1903
|
+
<h3>Default Badges</h3>
|
|
1904
|
+
<div class="badge-grid">
|
|
1905
|
+
<span class="badge">Default</span>
|
|
1906
|
+
<span class="badge badge-primary">Primary</span>
|
|
1907
|
+
<span class="badge badge-secondary">Secondary</span>
|
|
1908
|
+
<span class="badge badge-success">Success</span>
|
|
1909
|
+
<span class="badge badge-warning">Warning</span>
|
|
1910
|
+
<span class="badge badge-danger">Danger</span>
|
|
1911
|
+
<span class="badge badge-info">Info</span>
|
|
1912
|
+
</div>
|
|
1913
|
+
|
|
1914
|
+
<h3>Outlined Badges</h3>
|
|
1915
|
+
<div class="badge-grid">
|
|
1916
|
+
<span class="badge badge-outline badge-primary">Primary</span>
|
|
1917
|
+
<span class="badge badge-outline badge-secondary">Secondary</span>
|
|
1918
|
+
<span class="badge badge-outline badge-success">Success</span>
|
|
1919
|
+
<span class="badge badge-outline badge-info">Info</span>
|
|
1920
|
+
<span class="badge badge-outline badge-warning">Warning</span>
|
|
1921
|
+
<span class="badge badge-outline badge-danger">Danger</span>
|
|
1922
|
+
</div>
|
|
1923
|
+
|
|
1924
|
+
<h3>Badge Sizes</h3>
|
|
1925
|
+
<div class="size-demo">
|
|
1926
|
+
<span class="badge badge-primary badge-sm">Small</span>
|
|
1927
|
+
<span class="badge badge-primary">Default</span>
|
|
1928
|
+
<span class="badge badge-primary badge-lg">Large</span>
|
|
1929
|
+
</div>
|
|
1930
|
+
|
|
1931
|
+
<h3>Pills</h3>
|
|
1932
|
+
<div class="badge-grid">
|
|
1933
|
+
<span class="pill badge-primary">React</span>
|
|
1934
|
+
<span class="pill badge-secondary">Vue</span>
|
|
1935
|
+
<span class="pill badge-success">Node.js</span>
|
|
1936
|
+
<span class="pill badge-info">TypeScript</span>
|
|
1937
|
+
<span class="pill badge-warning">JavaScript</span>
|
|
1938
|
+
<span class="pill badge-danger">Critical</span>
|
|
1939
|
+
</div>
|
|
1940
|
+
</section>
|
|
1941
|
+
|
|
1942
|
+
<!-- Media Elements Section -->
|
|
1943
|
+
<section class="showcase-section">
|
|
1944
|
+
<h2>
|
|
1945
|
+
<pds-icon icon="image" size="lg" class="icon-primary"></pds-icon>
|
|
1946
|
+
Media Elements
|
|
1947
|
+
</h2>
|
|
1948
|
+
|
|
1949
|
+
<h3>Responsive Images</h3>
|
|
1950
|
+
<div class="media-grid">
|
|
1951
|
+
<figure class="media-figure">
|
|
1952
|
+
<img
|
|
1953
|
+
class="media-image"
|
|
1954
|
+
src="https://picsum.photos/800/600?random=1"
|
|
1955
|
+
alt="Random landscape"
|
|
1956
|
+
/>
|
|
1957
|
+
<figcaption class="media-caption">
|
|
1958
|
+
<strong>Figure 1:</strong> A beautiful landscape demonstrating
|
|
1959
|
+
image handling in the design system.
|
|
1960
|
+
</figcaption>
|
|
1961
|
+
</figure>
|
|
1962
|
+
|
|
1963
|
+
<figure class="media-figure">
|
|
1964
|
+
<img
|
|
1965
|
+
class="media-image"
|
|
1966
|
+
src="https://picsum.photos/800/600?random=2"
|
|
1967
|
+
alt="Random architecture"
|
|
1968
|
+
/>
|
|
1969
|
+
<figcaption class="media-caption">
|
|
1970
|
+
<strong>Figure 2:</strong> Architectural photography
|
|
1971
|
+
showcasing the responsive image behavior.
|
|
1972
|
+
</figcaption>
|
|
1973
|
+
</figure>
|
|
1974
|
+
</div>
|
|
1975
|
+
|
|
1976
|
+
<h3>Image Gallery</h3>
|
|
1977
|
+
<div class="gallery-grid">
|
|
1978
|
+
<img
|
|
1979
|
+
class="gallery-image"
|
|
1980
|
+
src="https://picsum.photos/400/400?random=3"
|
|
1981
|
+
alt="Gallery image 1"
|
|
1982
|
+
/>
|
|
1983
|
+
<img
|
|
1984
|
+
class="gallery-image"
|
|
1985
|
+
src="https://picsum.photos/400/400?random=4"
|
|
1986
|
+
alt="Gallery image 2"
|
|
1987
|
+
/>
|
|
1988
|
+
<img
|
|
1989
|
+
class="gallery-image"
|
|
1990
|
+
src="https://picsum.photos/400/400?random=5"
|
|
1991
|
+
alt="Gallery image 3"
|
|
1992
|
+
/>
|
|
1993
|
+
<img
|
|
1994
|
+
class="gallery-image"
|
|
1995
|
+
src="https://picsum.photos/400/400?random=6"
|
|
1996
|
+
alt="Gallery image 4"
|
|
1997
|
+
/>
|
|
1998
|
+
</div>
|
|
1999
|
+
|
|
2000
|
+
<h3>Netflix Row</h3>
|
|
2001
|
+
|
|
2002
|
+
<pds-scrollrow>
|
|
2003
|
+
${new Array(20)
|
|
2004
|
+
.fill(0)
|
|
2005
|
+
.map(
|
|
2006
|
+
(_, i) => html`<img
|
|
2007
|
+
loading="lazy"
|
|
2008
|
+
class="scroll-row-image"
|
|
2009
|
+
src="https://picsum.photos/200/200?random=${i + 1}"
|
|
2010
|
+
alt="Gallery image ${i + 1}"
|
|
2011
|
+
/>`
|
|
2012
|
+
)}
|
|
2013
|
+
</pds-scrollrow>
|
|
2014
|
+
|
|
2015
|
+
<h3>Video Element</h3>
|
|
2016
|
+
<figure class="video-container">
|
|
2017
|
+
<video
|
|
2018
|
+
class="video-element"
|
|
2019
|
+
controls
|
|
2020
|
+
poster="https://picsum.photos/1200/675?random=7"
|
|
2021
|
+
>
|
|
2022
|
+
<source
|
|
2023
|
+
src="https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"
|
|
2024
|
+
type="video/mp4"
|
|
2025
|
+
/>
|
|
2026
|
+
Your browser does not support the video tag.
|
|
2027
|
+
</video>
|
|
2028
|
+
<figcaption class="media-caption">
|
|
2029
|
+
<strong>Video Demo:</strong> Big Buck Bunny sample video
|
|
2030
|
+
demonstrating video element styling.
|
|
2031
|
+
</figcaption>
|
|
2032
|
+
</figure>
|
|
2033
|
+
</section>
|
|
2034
|
+
|
|
2035
|
+
<!-- Enhanced Components Section -->
|
|
2036
|
+
<section class="showcase-section alt-bg">
|
|
2037
|
+
<h2>
|
|
2038
|
+
<pds-icon
|
|
2039
|
+
icon="brackets-curly"
|
|
2040
|
+
size="lg"
|
|
2041
|
+
class="icon-primary"
|
|
2042
|
+
></pds-icon>
|
|
2043
|
+
Enhanced Components
|
|
2044
|
+
</h2>
|
|
2045
|
+
|
|
2046
|
+
<h3>Dropdown Menu (Progressive Enhancement)</h3>
|
|
2047
|
+
<p class="dropdown-demo">
|
|
2048
|
+
Click the button to toggle the dropdown menu:
|
|
2049
|
+
</p>
|
|
2050
|
+
<nav data-dropdown>
|
|
2051
|
+
<button class="btn-primary">
|
|
2052
|
+
<pds-icon icon="list"></pds-icon>
|
|
2053
|
+
Click for Menu
|
|
2054
|
+
</button>
|
|
2055
|
+
<menu class="dropdown-menu liquid-glass">
|
|
2056
|
+
<li>
|
|
2057
|
+
<a href="#">
|
|
2058
|
+
<pds-icon icon="user" size="sm"></pds-icon>
|
|
2059
|
+
Profile
|
|
2060
|
+
</a>
|
|
2061
|
+
</li>
|
|
2062
|
+
<li>
|
|
2063
|
+
<a href="#">
|
|
2064
|
+
<pds-icon icon="gear" size="sm"></pds-icon>
|
|
2065
|
+
Settings
|
|
2066
|
+
</a>
|
|
2067
|
+
</li>
|
|
2068
|
+
<li>
|
|
2069
|
+
<a href="#" class="danger">
|
|
2070
|
+
<pds-icon icon="x" size="sm"></pds-icon>
|
|
2071
|
+
Logout
|
|
2072
|
+
</a>
|
|
2073
|
+
</li>
|
|
2074
|
+
</menu>
|
|
2075
|
+
</nav>
|
|
2076
|
+
</section>
|
|
2077
|
+
|
|
2078
|
+
<!-- Icons Section -->
|
|
2079
|
+
<section class="showcase-section" data-section="icons">
|
|
2080
|
+
<h2>
|
|
2081
|
+
<pds-icon icon="star" size="lg" class="icon-accent"></pds-icon>
|
|
2082
|
+
Icon System
|
|
2083
|
+
</h2>
|
|
2084
|
+
|
|
2085
|
+
<h3>Sizes</h3>
|
|
2086
|
+
<div class="icon-sizes surface-translucent">
|
|
2087
|
+
<pds-icon icon="heart" size="xs"></pds-icon>
|
|
2088
|
+
<pds-icon icon="heart" size="sm"></pds-icon>
|
|
2089
|
+
<pds-icon icon="heart" size="md"></pds-icon>
|
|
2090
|
+
<pds-icon icon="heart" size="lg"></pds-icon>
|
|
2091
|
+
<pds-icon icon="heart" size="xl"></pds-icon>
|
|
2092
|
+
<pds-icon icon="heart" size="2xl"></pds-icon>
|
|
2093
|
+
</div>
|
|
2094
|
+
|
|
2095
|
+
<h3>Semantic Colors</h3>
|
|
2096
|
+
<div class="icon-colors surface-translucent">
|
|
2097
|
+
<pds-icon
|
|
2098
|
+
icon="check-circle"
|
|
2099
|
+
class="icon-primary"
|
|
2100
|
+
size="lg"
|
|
2101
|
+
></pds-icon>
|
|
2102
|
+
<pds-icon
|
|
2103
|
+
icon="check-circle"
|
|
2104
|
+
class="icon-secondary"
|
|
2105
|
+
size="lg"
|
|
2106
|
+
></pds-icon>
|
|
2107
|
+
<pds-icon
|
|
2108
|
+
icon="check-circle"
|
|
2109
|
+
class="icon-accent"
|
|
2110
|
+
size="lg"
|
|
2111
|
+
></pds-icon>
|
|
2112
|
+
<pds-icon
|
|
2113
|
+
icon="check-circle"
|
|
2114
|
+
class="icon-success"
|
|
2115
|
+
size="lg"
|
|
2116
|
+
></pds-icon>
|
|
2117
|
+
<pds-icon
|
|
2118
|
+
icon="warning"
|
|
2119
|
+
class="icon-warning"
|
|
2120
|
+
size="lg"
|
|
2121
|
+
></pds-icon>
|
|
2122
|
+
<pds-icon
|
|
2123
|
+
icon="x-circle"
|
|
2124
|
+
class="icon-danger"
|
|
2125
|
+
size="lg"
|
|
2126
|
+
></pds-icon>
|
|
2127
|
+
<pds-icon icon="info" class="icon-info" size="lg"></pds-icon>
|
|
2128
|
+
</div>
|
|
2129
|
+
|
|
2130
|
+
<h3>Icon with Text</h3>
|
|
2131
|
+
<div class="icon-text-demo">
|
|
2132
|
+
<div class="icon-text">
|
|
2133
|
+
<pds-icon icon="envelope"></pds-icon>
|
|
2134
|
+
<span>Email</span>
|
|
2135
|
+
</div>
|
|
2136
|
+
<div class="icon-text">
|
|
2137
|
+
<pds-icon icon="phone"></pds-icon>
|
|
2138
|
+
<span>Phone</span>
|
|
2139
|
+
</div>
|
|
2140
|
+
<div class="icon-text">
|
|
2141
|
+
<pds-icon icon="user"></pds-icon>
|
|
2142
|
+
<span>Profile</span>
|
|
2143
|
+
</div>
|
|
2144
|
+
<div class="icon-text">
|
|
2145
|
+
<pds-icon icon="calendar"></pds-icon>
|
|
2146
|
+
<span>Schedule</span>
|
|
2147
|
+
</div>
|
|
2148
|
+
</div>
|
|
2149
|
+
|
|
2150
|
+
<h3>Inputs with Icons</h3>
|
|
2151
|
+
<div class="input-icon-demo">
|
|
2152
|
+
<div class="input-icon">
|
|
2153
|
+
<pds-icon icon="magnifying-glass"></pds-icon>
|
|
2154
|
+
<input type="search" placeholder="Search..." />
|
|
2155
|
+
</div>
|
|
2156
|
+
<div class="input-icon input-icon-end">
|
|
2157
|
+
<input type="text" placeholder="Username" />
|
|
2158
|
+
<pds-icon icon="user"></pds-icon>
|
|
2159
|
+
</div>
|
|
2160
|
+
</div>
|
|
2161
|
+
|
|
2162
|
+
<h3>Common Icons</h3>
|
|
2163
|
+
<div class="icon-grid surface-translucent">
|
|
2164
|
+
<div class="icon-grid-item">
|
|
2165
|
+
<pds-icon icon="house" size="lg"></pds-icon>
|
|
2166
|
+
<span class="icon-grid-label">house</span>
|
|
2167
|
+
</div>
|
|
2168
|
+
<div class="icon-grid-item">
|
|
2169
|
+
<pds-icon icon="gear" size="lg"></pds-icon>
|
|
2170
|
+
<span class="icon-grid-label">gear</span>
|
|
2171
|
+
</div>
|
|
2172
|
+
<div class="icon-grid-item">
|
|
2173
|
+
<pds-icon icon="bell" size="lg"></pds-icon>
|
|
2174
|
+
<span class="icon-grid-label">bell</span>
|
|
2175
|
+
</div>
|
|
2176
|
+
<div class="icon-grid-item">
|
|
2177
|
+
<pds-icon icon="heart" size="lg"></pds-icon>
|
|
2178
|
+
<span class="icon-grid-label">heart</span>
|
|
2179
|
+
</div>
|
|
2180
|
+
<div class="icon-grid-item">
|
|
2181
|
+
<pds-icon icon="star" size="lg"></pds-icon>
|
|
2182
|
+
<span class="icon-grid-label">star</span>
|
|
2183
|
+
</div>
|
|
2184
|
+
<div class="icon-grid-item">
|
|
2185
|
+
<pds-icon icon="trash" size="lg"></pds-icon>
|
|
2186
|
+
<span class="icon-grid-label">trash</span>
|
|
2187
|
+
</div>
|
|
2188
|
+
<div class="icon-grid-item">
|
|
2189
|
+
<pds-icon icon="pencil" size="lg"></pds-icon>
|
|
2190
|
+
<span class="icon-grid-label">pencil</span>
|
|
2191
|
+
</div>
|
|
2192
|
+
<div class="icon-grid-item">
|
|
2193
|
+
<pds-icon icon="check" size="lg"></pds-icon>
|
|
2194
|
+
<span class="icon-grid-label">check</span>
|
|
2195
|
+
</div>
|
|
2196
|
+
<div class="icon-grid-item">
|
|
2197
|
+
<pds-icon icon="x" size="lg"></pds-icon>
|
|
2198
|
+
<span class="icon-grid-label">x</span>
|
|
2199
|
+
</div>
|
|
2200
|
+
<div class="icon-grid-item">
|
|
2201
|
+
<pds-icon icon="plus" size="lg"></pds-icon>
|
|
2202
|
+
<span class="icon-grid-label">plus</span>
|
|
2203
|
+
</div>
|
|
2204
|
+
<div class="icon-grid-item">
|
|
2205
|
+
<pds-icon icon="minus" size="lg"></pds-icon>
|
|
2206
|
+
<span class="icon-grid-label">minus</span>
|
|
2207
|
+
</div>
|
|
2208
|
+
<div class="icon-grid-item">
|
|
2209
|
+
<pds-icon icon="download" size="lg"></pds-icon>
|
|
2210
|
+
<span class="icon-grid-label">download</span>
|
|
2211
|
+
</div>
|
|
2212
|
+
</div>
|
|
2213
|
+
</section>
|
|
2214
|
+
|
|
2215
|
+
<!-- Layout & Cards Section -->
|
|
2216
|
+
<section class="showcase-section alt-bg">
|
|
2217
|
+
<h2>
|
|
2218
|
+
<pds-icon
|
|
2219
|
+
icon="desktop"
|
|
2220
|
+
size="lg"
|
|
2221
|
+
class="icon-primary"
|
|
2222
|
+
></pds-icon>
|
|
2223
|
+
Layout & Cards
|
|
2224
|
+
</h2>
|
|
2225
|
+
|
|
2226
|
+
<div class="grid grid-cols-1">
|
|
2227
|
+
<div class="card surface-elevated">
|
|
2228
|
+
<h3>Elevated Surface</h3>
|
|
2229
|
+
<p>This surface has a subtle shadow and elevated background.</p>
|
|
2230
|
+
</div>
|
|
2231
|
+
|
|
2232
|
+
<div class="grid grid-cols-3">
|
|
2233
|
+
<div class="card">
|
|
2234
|
+
<h4>Card 1</h4>
|
|
2235
|
+
<p>Cards provide a clean container for content.</p>
|
|
2236
|
+
</div>
|
|
2237
|
+
<div class="card">
|
|
2238
|
+
<h4>Card 2</h4>
|
|
2239
|
+
<p>They have consistent spacing and shadows.</p>
|
|
2240
|
+
</div>
|
|
2241
|
+
<div class="card">
|
|
2242
|
+
<h4>Card 3</h4>
|
|
2243
|
+
<p>And they respond beautifully to hover states.</p>
|
|
2244
|
+
</div>
|
|
2245
|
+
</div>
|
|
2246
|
+
</div>
|
|
2247
|
+
|
|
2248
|
+
<h3>Accordion Component</h3>
|
|
2249
|
+
<section class="accordion" aria-label="FAQ">
|
|
2250
|
+
<details>
|
|
2251
|
+
<summary id="q1">How billing works</summary>
|
|
2252
|
+
<div role="region" aria-labelledby="q1">
|
|
2253
|
+
<p>We charge monthly on the 1st…</p>
|
|
2254
|
+
</div>
|
|
2255
|
+
</details>
|
|
2256
|
+
|
|
2257
|
+
<details>
|
|
2258
|
+
<summary id="q2">Refund policy</summary>
|
|
2259
|
+
<div role="region" aria-labelledby="q2">
|
|
2260
|
+
<p>You can request a refund within 14 days…</p>
|
|
2261
|
+
</div>
|
|
2262
|
+
</details>
|
|
2263
|
+
|
|
2264
|
+
<details>
|
|
2265
|
+
<summary id="q3">Invoices</summary>
|
|
2266
|
+
<div role="region" aria-labelledby="q3">
|
|
2267
|
+
<p>Download invoices from Settings → Billing…</p>
|
|
2268
|
+
</div>
|
|
2269
|
+
</details>
|
|
2270
|
+
</section>
|
|
2271
|
+
</section>
|
|
2272
|
+
|
|
2273
|
+
<section class="showcase-section">
|
|
2274
|
+
<h2>
|
|
2275
|
+
<pds-icon icon="list" size="lg" class="icon-primary"></pds-icon>
|
|
2276
|
+
Tables
|
|
2277
|
+
</h2>
|
|
2278
|
+
|
|
2279
|
+
<h3>Default Table</h3>
|
|
2280
|
+
<table>
|
|
2281
|
+
<thead>
|
|
2282
|
+
<tr>
|
|
2283
|
+
<th>Name</th>
|
|
2284
|
+
<th>Role</th>
|
|
2285
|
+
<th>Department</th>
|
|
2286
|
+
<th>Status</th>
|
|
2287
|
+
</tr>
|
|
2288
|
+
</thead>
|
|
2289
|
+
<tbody>
|
|
2290
|
+
<tr>
|
|
2291
|
+
<td>Alice Johnson</td>
|
|
2292
|
+
<td>Senior Developer</td>
|
|
2293
|
+
<td>Engineering</td>
|
|
2294
|
+
<td><span class="badge badge-success">Active</span></td>
|
|
2295
|
+
</tr>
|
|
2296
|
+
<tr>
|
|
2297
|
+
<td>Bob Smith</td>
|
|
2298
|
+
<td>Product Manager</td>
|
|
2299
|
+
<td>Product</td>
|
|
2300
|
+
<td><span class="badge badge-success">Active</span></td>
|
|
2301
|
+
</tr>
|
|
2302
|
+
<tr>
|
|
2303
|
+
<td>Carol Williams</td>
|
|
2304
|
+
<td>UX Designer</td>
|
|
2305
|
+
<td>Design</td>
|
|
2306
|
+
<td><span class="badge badge-warning">Away</span></td>
|
|
2307
|
+
</tr>
|
|
2308
|
+
<tr>
|
|
2309
|
+
<td>David Brown</td>
|
|
2310
|
+
<td>DevOps Engineer</td>
|
|
2311
|
+
<td>Engineering</td>
|
|
2312
|
+
<td><span class="badge badge-danger">Offline</span></td>
|
|
2313
|
+
</tr>
|
|
2314
|
+
</tbody>
|
|
2315
|
+
</table>
|
|
2316
|
+
|
|
2317
|
+
<h3>Striped Table</h3>
|
|
2318
|
+
<table class="table-striped">
|
|
2319
|
+
<thead>
|
|
2320
|
+
<tr>
|
|
2321
|
+
<th>Product</th>
|
|
2322
|
+
<th>Price</th>
|
|
2323
|
+
<th>Stock</th>
|
|
2324
|
+
<th>Category</th>
|
|
2325
|
+
</tr>
|
|
2326
|
+
</thead>
|
|
2327
|
+
<tbody>
|
|
2328
|
+
<tr>
|
|
2329
|
+
<td>Laptop Pro</td>
|
|
2330
|
+
<td>$1,299</td>
|
|
2331
|
+
<td>45</td>
|
|
2332
|
+
<td>Electronics</td>
|
|
2333
|
+
</tr>
|
|
2334
|
+
<tr>
|
|
2335
|
+
<td>Wireless Mouse</td>
|
|
2336
|
+
<td>$29</td>
|
|
2337
|
+
<td>128</td>
|
|
2338
|
+
<td>Accessories</td>
|
|
2339
|
+
</tr>
|
|
2340
|
+
<tr>
|
|
2341
|
+
<td>USB-C Hub</td>
|
|
2342
|
+
<td>$59</td>
|
|
2343
|
+
<td>76</td>
|
|
2344
|
+
<td>Accessories</td>
|
|
2345
|
+
</tr>
|
|
2346
|
+
<tr>
|
|
2347
|
+
<td>Monitor 27"</td>
|
|
2348
|
+
<td>$449</td>
|
|
2349
|
+
<td>23</td>
|
|
2350
|
+
<td>Electronics</td>
|
|
2351
|
+
</tr>
|
|
2352
|
+
</tbody>
|
|
2353
|
+
</table>
|
|
2354
|
+
|
|
2355
|
+
<h3>Bordered Compact Table</h3>
|
|
2356
|
+
<table class="table-bordered table-compact">
|
|
2357
|
+
<thead>
|
|
2358
|
+
<tr>
|
|
2359
|
+
<th>ID</th>
|
|
2360
|
+
<th>Task</th>
|
|
2361
|
+
<th>Priority</th>
|
|
2362
|
+
<th>Due Date</th>
|
|
2363
|
+
</tr>
|
|
2364
|
+
</thead>
|
|
2365
|
+
<tbody>
|
|
2366
|
+
<tr>
|
|
2367
|
+
<td>#101</td>
|
|
2368
|
+
<td>Fix navigation bug</td>
|
|
2369
|
+
<td><span class="badge badge-danger">High</span></td>
|
|
2370
|
+
<td>Oct 15, 2025</td>
|
|
2371
|
+
</tr>
|
|
2372
|
+
<tr>
|
|
2373
|
+
<td>#102</td>
|
|
2374
|
+
<td>Update documentation</td>
|
|
2375
|
+
<td><span class="badge badge-warning">Medium</span></td>
|
|
2376
|
+
<td>Oct 18, 2025</td>
|
|
2377
|
+
</tr>
|
|
2378
|
+
<tr>
|
|
2379
|
+
<td>#103</td>
|
|
2380
|
+
<td>Refactor CSS</td>
|
|
2381
|
+
<td><span class="badge badge-info">Low</span></td>
|
|
2382
|
+
<td>Oct 25, 2025</td>
|
|
2383
|
+
</tr>
|
|
2384
|
+
</tbody>
|
|
2385
|
+
</table>
|
|
2386
|
+
</section>
|
|
2387
|
+
|
|
2388
|
+
<!-- Form Groups Section -->
|
|
2389
|
+
<section class="showcase-section alt-bg">
|
|
2390
|
+
<h2>
|
|
2391
|
+
<pds-icon
|
|
2392
|
+
icon="list-bullets"
|
|
2393
|
+
size="lg"
|
|
2394
|
+
class="icon-primary"
|
|
2395
|
+
></pds-icon>
|
|
2396
|
+
Form Groups
|
|
2397
|
+
</h2>
|
|
2398
|
+
|
|
2399
|
+
<div class="grid grid-cols-2">
|
|
2400
|
+
<div class="form-group">
|
|
2401
|
+
<h3>Radio Buttons</h3>
|
|
2402
|
+
<fieldset role="radiogroup">
|
|
2403
|
+
<legend>Select your plan</legend>
|
|
2404
|
+
<label>
|
|
2405
|
+
<input type="radio" name="plan" value="free" checked />
|
|
2406
|
+
<span>Free - $0/month</span>
|
|
2407
|
+
</label>
|
|
2408
|
+
<label>
|
|
2409
|
+
<input type="radio" name="plan" value="pro" />
|
|
2410
|
+
<span>Pro - $29/month</span>
|
|
2411
|
+
</label>
|
|
2412
|
+
<label>
|
|
2413
|
+
<input type="radio" name="plan" value="enterprise" />
|
|
2414
|
+
<span>Enterprise - $99/month</span>
|
|
2415
|
+
</label>
|
|
2416
|
+
</fieldset>
|
|
2417
|
+
</div>
|
|
2418
|
+
|
|
2419
|
+
<div class="form-group">
|
|
2420
|
+
<h3>Checkboxes</h3>
|
|
2421
|
+
<fieldset role="group">
|
|
2422
|
+
<legend>Select features</legend>
|
|
2423
|
+
<label>
|
|
2424
|
+
<input
|
|
2425
|
+
type="checkbox"
|
|
2426
|
+
name="features"
|
|
2427
|
+
value="api"
|
|
2428
|
+
checked
|
|
2429
|
+
/>
|
|
2430
|
+
<span>API Access</span>
|
|
2431
|
+
</label>
|
|
2432
|
+
<label>
|
|
2433
|
+
<input
|
|
2434
|
+
type="checkbox"
|
|
2435
|
+
name="features"
|
|
2436
|
+
value="analytics"
|
|
2437
|
+
checked
|
|
2438
|
+
/>
|
|
2439
|
+
<span>Advanced Analytics</span>
|
|
2440
|
+
</label>
|
|
2441
|
+
<label>
|
|
2442
|
+
<input type="checkbox" name="features" value="support" />
|
|
2443
|
+
<span>Priority Support</span>
|
|
2444
|
+
</label>
|
|
2445
|
+
<label>
|
|
2446
|
+
<input type="checkbox" name="features" value="sso" />
|
|
2447
|
+
<span>Single Sign-On</span>
|
|
2448
|
+
</label>
|
|
2449
|
+
</fieldset>
|
|
2450
|
+
</div>
|
|
2451
|
+
</div>
|
|
2452
|
+
</section>
|
|
2453
|
+
|
|
2454
|
+
<!-- Smart Surfaces Section -->
|
|
2455
|
+
<section class="showcase-section" data-section="smart-surfaces">
|
|
2456
|
+
<h2>
|
|
2457
|
+
<pds-icon
|
|
2458
|
+
icon="palette"
|
|
2459
|
+
size="lg"
|
|
2460
|
+
class="icon-primary"
|
|
2461
|
+
></pds-icon>
|
|
2462
|
+
Smart Surface System
|
|
2463
|
+
</h2>
|
|
2464
|
+
|
|
2465
|
+
<p>
|
|
2466
|
+
The smart surface system automatically adapts text, icon, shadow,
|
|
2467
|
+
and border colors based on surface backgrounds. All colors
|
|
2468
|
+
maintain WCAG AA contrast ratios automatically.
|
|
2469
|
+
</p>
|
|
2470
|
+
|
|
2471
|
+
<h3>Surface Variants</h3>
|
|
2472
|
+
<div class="surface-demo-grid">
|
|
2473
|
+
<div class="surface-base" style="padding: var(--spacing-6);">
|
|
2474
|
+
<strong class="surface-title">
|
|
2475
|
+
<pds-icon icon="square"></pds-icon>
|
|
2476
|
+
Base Surface
|
|
2477
|
+
</strong>
|
|
2478
|
+
<p class="surface-description">
|
|
2479
|
+
Default background with auto-adjusted text and icons
|
|
2480
|
+
</p>
|
|
2481
|
+
<button
|
|
2482
|
+
class="btn-primary"
|
|
2483
|
+
style="margin-top: var(--spacing-3);"
|
|
2484
|
+
>
|
|
2485
|
+
Button
|
|
2486
|
+
</button>
|
|
2487
|
+
</div>
|
|
2488
|
+
<div class="surface-subtle" style="padding: var(--spacing-6);">
|
|
2489
|
+
<strong class="surface-title">
|
|
2490
|
+
<pds-icon icon="square"></pds-icon>
|
|
2491
|
+
Subtle Surface
|
|
2492
|
+
</strong>
|
|
2493
|
+
<p class="surface-description">
|
|
2494
|
+
Slightly different tone for visual hierarchy
|
|
2495
|
+
</p>
|
|
2496
|
+
<button
|
|
2497
|
+
class="btn-secondary"
|
|
2498
|
+
style="margin-top: var(--spacing-3);"
|
|
2499
|
+
>
|
|
2500
|
+
Button
|
|
2501
|
+
</button>
|
|
2502
|
+
</div>
|
|
2503
|
+
<div class="surface-elevated" style="padding: var(--spacing-6);">
|
|
2504
|
+
<strong class="surface-title">
|
|
2505
|
+
<pds-icon icon="arrow-up"></pds-icon>
|
|
2506
|
+
Elevated Surface
|
|
2507
|
+
</strong>
|
|
2508
|
+
<p class="surface-description">
|
|
2509
|
+
Raised with smart shadows that adapt in dark mode
|
|
2510
|
+
</p>
|
|
2511
|
+
<button
|
|
2512
|
+
class="btn-primary"
|
|
2513
|
+
style="margin-top: var(--spacing-3);"
|
|
2514
|
+
>
|
|
2515
|
+
Button
|
|
2516
|
+
</button>
|
|
2517
|
+
</div>
|
|
2518
|
+
<div class="surface-overlay" style="padding: var(--spacing-6);">
|
|
2519
|
+
<strong class="surface-title">
|
|
2520
|
+
<pds-icon icon="desktop"></pds-icon>
|
|
2521
|
+
Overlay Surface
|
|
2522
|
+
</strong>
|
|
2523
|
+
<p class="surface-description">
|
|
2524
|
+
Modal/dropdown backgrounds with stronger shadows
|
|
2525
|
+
</p>
|
|
2526
|
+
<button
|
|
2527
|
+
class="btn-outline"
|
|
2528
|
+
style="margin-top: var(--spacing-3);"
|
|
2529
|
+
>
|
|
2530
|
+
Button
|
|
2531
|
+
</button>
|
|
2532
|
+
</div>
|
|
2533
|
+
</div>
|
|
2534
|
+
|
|
2535
|
+
<h3>Context-Aware Shadows</h3>
|
|
2536
|
+
<p>
|
|
2537
|
+
Shadows automatically invert in dark mode: dark shadows on light
|
|
2538
|
+
surfaces, light shadows on dark surfaces.
|
|
2539
|
+
</p>
|
|
2540
|
+
<div class="shadow-demo-grid">
|
|
2541
|
+
<div class="shadow-demo-item shadow-sm">
|
|
2542
|
+
<pds-icon icon="feather" size="lg"></pds-icon>
|
|
2543
|
+
<strong>Small</strong>
|
|
2544
|
+
<p>--shadow-sm</p>
|
|
2545
|
+
</div>
|
|
2546
|
+
<div class="shadow-demo-item shadow-md">
|
|
2547
|
+
<pds-icon icon="grid-four" size="lg"></pds-icon>
|
|
2548
|
+
<strong>Medium</strong>
|
|
2549
|
+
<p>--shadow-md</p>
|
|
2550
|
+
</div>
|
|
2551
|
+
<div class="shadow-demo-item shadow-lg">
|
|
2552
|
+
<pds-icon icon="rocket" size="lg"></pds-icon>
|
|
2553
|
+
<strong>Large</strong>
|
|
2554
|
+
<p>--shadow-lg</p>
|
|
2555
|
+
</div>
|
|
2556
|
+
</div>
|
|
2557
|
+
|
|
2558
|
+
<h3>Surface Borders</h3>
|
|
2559
|
+
<article class="card border-gradient">
|
|
2560
|
+
<p>A card with a border gradient</p>
|
|
2561
|
+
</article>
|
|
2562
|
+
|
|
2563
|
+
<article class="card border-gradient-glow">
|
|
2564
|
+
<p>A card with a glowing border gradient</p>
|
|
2565
|
+
</article>
|
|
2566
|
+
</section>
|
|
2567
|
+
|
|
2568
|
+
<!-- Nested Surfaces Section -->
|
|
2569
|
+
<section
|
|
2570
|
+
class="showcase-section alt-bg"
|
|
2571
|
+
data-section="nested-surfaces"
|
|
2572
|
+
>
|
|
2573
|
+
<h2>
|
|
2574
|
+
<pds-icon
|
|
2575
|
+
icon="grid-four"
|
|
2576
|
+
size="lg"
|
|
2577
|
+
class="icon-primary"
|
|
2578
|
+
></pds-icon>
|
|
2579
|
+
Nested Surfaces
|
|
2580
|
+
</h2>
|
|
2581
|
+
|
|
2582
|
+
<p>
|
|
2583
|
+
Surfaces can be nested infinitely. Each level maintains proper
|
|
2584
|
+
contrast and visual hierarchy automatically.
|
|
2585
|
+
</p>
|
|
2586
|
+
|
|
2587
|
+
<div class="surface-base" style="padding: var(--spacing-6);">
|
|
2588
|
+
<h4>
|
|
2589
|
+
<pds-icon icon="circle"></pds-icon>
|
|
2590
|
+
Level 1: Base Surface
|
|
2591
|
+
</h4>
|
|
2592
|
+
<p>
|
|
2593
|
+
Notice how icons and text adapt at each nesting level to
|
|
2594
|
+
maintain readability.
|
|
2595
|
+
</p>
|
|
2596
|
+
|
|
2597
|
+
<div
|
|
2598
|
+
class="surface-elevated"
|
|
2599
|
+
style="padding: var(--spacing-6); margin-top: var(--spacing-4);"
|
|
2600
|
+
>
|
|
2601
|
+
<h5>
|
|
2602
|
+
<pds-icon icon="arrow-right"></pds-icon>
|
|
2603
|
+
Level 2: Elevated Surface
|
|
2604
|
+
</h5>
|
|
2605
|
+
<p>Shadows and text colors automatically adjust</p>
|
|
2606
|
+
|
|
2607
|
+
<div
|
|
2608
|
+
class="grid grid-cols-2"
|
|
2609
|
+
style="margin-top: var(--spacing-4);"
|
|
2610
|
+
>
|
|
2611
|
+
<div class="card">
|
|
2612
|
+
<h6>
|
|
2613
|
+
<pds-icon icon="check"></pds-icon>
|
|
2614
|
+
Level 3: Card
|
|
2615
|
+
</h6>
|
|
2616
|
+
<p>Perfect contrast maintained</p>
|
|
2617
|
+
|
|
2618
|
+
<div
|
|
2619
|
+
class="surface-sunken"
|
|
2620
|
+
style="padding: var(--spacing-4); margin-top: var(--spacing-3);"
|
|
2621
|
+
>
|
|
2622
|
+
<small>
|
|
2623
|
+
<pds-icon icon="info" size="sm"></pds-icon>
|
|
2624
|
+
Level 4: Sunken surface inside card
|
|
2625
|
+
</small>
|
|
2626
|
+
</div>
|
|
2627
|
+
</div>
|
|
2628
|
+
|
|
2629
|
+
<div class="card">
|
|
2630
|
+
<h6>
|
|
2631
|
+
<pds-icon icon="star"></pds-icon>
|
|
2632
|
+
Another Card
|
|
2633
|
+
</h6>
|
|
2634
|
+
<p>All elements adapt automatically</p>
|
|
2635
|
+
<button
|
|
2636
|
+
class="btn-primary btn-sm"
|
|
2637
|
+
style="margin-top: var(--spacing-2);"
|
|
2638
|
+
>
|
|
2639
|
+
<pds-icon icon="heart" size="sm"></pds-icon>
|
|
2640
|
+
Action
|
|
2641
|
+
</button>
|
|
2642
|
+
</div>
|
|
2643
|
+
</div>
|
|
2644
|
+
</div>
|
|
2645
|
+
</div>
|
|
2646
|
+
|
|
2647
|
+
<h3>Card Grids</h3>
|
|
2648
|
+
<p>Cards automatically adapt when nested or grouped</p>
|
|
2649
|
+
<div class="grid grid-cols-3">
|
|
2650
|
+
<div class="card">
|
|
2651
|
+
<h5>
|
|
2652
|
+
<pds-icon icon="palette"></pds-icon>
|
|
2653
|
+
Design
|
|
2654
|
+
</h5>
|
|
2655
|
+
<p>Smart surfaces handle theming automatically</p>
|
|
2656
|
+
</div>
|
|
2657
|
+
<div class="card">
|
|
2658
|
+
<h5>
|
|
2659
|
+
<pds-icon icon="code"></pds-icon>
|
|
2660
|
+
Development
|
|
2661
|
+
</h5>
|
|
2662
|
+
<p>Zero manual color overrides needed</p>
|
|
2663
|
+
</div>
|
|
2664
|
+
<div class="card">
|
|
2665
|
+
<h5>
|
|
2666
|
+
<pds-icon icon="rocket"></pds-icon>
|
|
2667
|
+
Performance
|
|
2668
|
+
</h5>
|
|
2669
|
+
<p>CSS custom properties are fast</p>
|
|
2670
|
+
</div>
|
|
2671
|
+
</div>
|
|
2672
|
+
</section>
|
|
2673
|
+
|
|
2674
|
+
<!-- Surface Inversion Section -->
|
|
2675
|
+
<section class="showcase-section" data-section="surface-inversion">
|
|
2676
|
+
<h2>
|
|
2677
|
+
<pds-icon icon="moon" size="lg" class="icon-primary"></pds-icon>
|
|
2678
|
+
Surface Inversion
|
|
2679
|
+
</h2>
|
|
2680
|
+
|
|
2681
|
+
<p>
|
|
2682
|
+
The smart surface system automatically inverts text and icon
|
|
2683
|
+
colors when you use a ${inversePhrase} (or vice versa). Toggle
|
|
2684
|
+
dark mode to see the magic!
|
|
2685
|
+
</p>
|
|
2686
|
+
|
|
2687
|
+
<h3>${inversionTitle}</h3>
|
|
2688
|
+
<div class="grid grid-cols-2">
|
|
2689
|
+
<div class="demo-inversion-box surface-inverse surface-box">
|
|
2690
|
+
<h4>
|
|
2691
|
+
<pds-icon icon="moon"></pds-icon>
|
|
2692
|
+
Automatic Inversion
|
|
2693
|
+
</h4>
|
|
2694
|
+
<p>
|
|
2695
|
+
This dark surface automatically uses light text and icons for
|
|
2696
|
+
perfect readability
|
|
2697
|
+
</p>
|
|
2698
|
+
<button
|
|
2699
|
+
class="btn-primary"
|
|
2700
|
+
style="margin-top: var(--spacing-3);"
|
|
2701
|
+
>
|
|
2702
|
+
Primary Button
|
|
2703
|
+
</button>
|
|
2704
|
+
</div>
|
|
2705
|
+
|
|
2706
|
+
<div class="demo-inversion-box surface-overlay surface-box">
|
|
2707
|
+
<h4>
|
|
2708
|
+
<pds-icon icon="palette"></pds-icon>
|
|
2709
|
+
Overlay Surface
|
|
2710
|
+
</h4>
|
|
2711
|
+
<p>Text and icons auto-adapt to maintain WCAG AA contrast</p>
|
|
2712
|
+
<button
|
|
2713
|
+
class="btn-secondary"
|
|
2714
|
+
style="margin-top: var(--spacing-3);"
|
|
2715
|
+
>
|
|
2716
|
+
Secondary Button
|
|
2717
|
+
</button>
|
|
2718
|
+
</div>
|
|
2719
|
+
</div>
|
|
2720
|
+
|
|
2721
|
+
<h3>Semantic Surfaces with Auto-Contrast</h3>
|
|
2722
|
+
<div class="grid grid-cols-3">
|
|
2723
|
+
<div
|
|
2724
|
+
class="demo-inversion-box alert alert-success surface-center"
|
|
2725
|
+
>
|
|
2726
|
+
<pds-icon icon="check-circle" size="xl"></pds-icon>
|
|
2727
|
+
<h5 style="margin-top: var(--spacing-2);">Success</h5>
|
|
2728
|
+
<p>Icons remain visible</p>
|
|
2729
|
+
</div>
|
|
2730
|
+
|
|
2731
|
+
<div
|
|
2732
|
+
class="demo-inversion-box alert alert-warning surface-center"
|
|
2733
|
+
>
|
|
2734
|
+
<pds-icon icon="warning" size="xl"></pds-icon>
|
|
2735
|
+
<h5 style="margin-top: var(--spacing-2);">Warning</h5>
|
|
2736
|
+
<p>Perfect contrast maintained</p>
|
|
2737
|
+
</div>
|
|
2738
|
+
|
|
2739
|
+
<div class="demo-inversion-box alert alert-danger surface-center">
|
|
2740
|
+
<pds-icon icon="heart" size="xl"></pds-icon>
|
|
2741
|
+
<h5 style="margin-top: var(--spacing-2);">Danger</h5>
|
|
2742
|
+
<p>Automatic adjustment</p>
|
|
2743
|
+
</div>
|
|
2744
|
+
</div>
|
|
2745
|
+
</section>
|
|
2746
|
+
|
|
2747
|
+
<!-- Grid Utilities Section -->
|
|
2748
|
+
<section class="showcase-section" data-section="grid-utilities">
|
|
2749
|
+
<h2>
|
|
2750
|
+
<pds-icon
|
|
2751
|
+
icon="squares-four"
|
|
2752
|
+
size="lg"
|
|
2753
|
+
class="icon-primary"
|
|
2754
|
+
></pds-icon>
|
|
2755
|
+
Grid Utilities
|
|
2756
|
+
</h2>
|
|
2757
|
+
<p>
|
|
2758
|
+
Modern, config-driven grid system with auto-fit responsive
|
|
2759
|
+
layouts. All utilities are generated from
|
|
2760
|
+
<code>layout.gridSystem</code> configuration.
|
|
2761
|
+
</p>
|
|
2762
|
+
|
|
2763
|
+
<h3>Fixed Column Grids</h3>
|
|
2764
|
+
<p>
|
|
2765
|
+
Use <code>.grid-cols-{n}</code> classes for fixed column layouts:
|
|
2766
|
+
</p>
|
|
2767
|
+
|
|
2768
|
+
<div
|
|
2769
|
+
class="grid grid-cols-2 gap-md"
|
|
2770
|
+
style="margin-bottom: var(--spacing-4);"
|
|
2771
|
+
>
|
|
2772
|
+
<div class="card">
|
|
2773
|
+
<pds-icon
|
|
2774
|
+
icon="square"
|
|
2775
|
+
size="lg"
|
|
2776
|
+
class="icon-primary"
|
|
2777
|
+
></pds-icon>
|
|
2778
|
+
<h4>Grid Column 1</h4>
|
|
2779
|
+
<p>Two column layout</p>
|
|
2780
|
+
</div>
|
|
2781
|
+
<div class="card">
|
|
2782
|
+
<pds-icon
|
|
2783
|
+
icon="square"
|
|
2784
|
+
size="lg"
|
|
2785
|
+
class="icon-secondary"
|
|
2786
|
+
></pds-icon>
|
|
2787
|
+
<h4>Grid Column 2</h4>
|
|
2788
|
+
<p>Equal width columns</p>
|
|
2789
|
+
</div>
|
|
2790
|
+
</div>
|
|
2791
|
+
|
|
2792
|
+
<div
|
|
2793
|
+
class="grid grid-cols-3 gap-sm"
|
|
2794
|
+
style="margin-bottom: var(--spacing-4);"
|
|
2795
|
+
>
|
|
2796
|
+
<div class="card">
|
|
2797
|
+
<pds-icon
|
|
2798
|
+
icon="circle"
|
|
2799
|
+
size="md"
|
|
2800
|
+
class="icon-success"
|
|
2801
|
+
></pds-icon>
|
|
2802
|
+
<p>Column 1</p>
|
|
2803
|
+
</div>
|
|
2804
|
+
<div class="card">
|
|
2805
|
+
<pds-icon
|
|
2806
|
+
icon="circle"
|
|
2807
|
+
size="md"
|
|
2808
|
+
class="icon-warning"
|
|
2809
|
+
></pds-icon>
|
|
2810
|
+
<p>Column 2</p>
|
|
2811
|
+
</div>
|
|
2812
|
+
<div class="card">
|
|
2813
|
+
<pds-icon icon="circle" size="md" class="icon-error"></pds-icon>
|
|
2814
|
+
<p>Column 3</p>
|
|
2815
|
+
</div>
|
|
2816
|
+
</div>
|
|
2817
|
+
|
|
2818
|
+
<div class="grid grid-cols-4 gap-xs">
|
|
2819
|
+
<div class="card"><p>1</p></div>
|
|
2820
|
+
<div class="card"><p>2</p></div>
|
|
2821
|
+
<div class="card"><p>3</p></div>
|
|
2822
|
+
<div class="card"><p>4</p></div>
|
|
2823
|
+
</div>
|
|
2824
|
+
|
|
2825
|
+
<h3>Auto-Fit Responsive Grids</h3>
|
|
2826
|
+
<p>
|
|
2827
|
+
Use <code>.grid-auto-{size}</code> for responsive layouts that
|
|
2828
|
+
automatically adjust columns based on available space:
|
|
2829
|
+
</p>
|
|
2830
|
+
|
|
2831
|
+
<h4><code>.grid-auto-sm</code> (min 150px)</h4>
|
|
2832
|
+
<div
|
|
2833
|
+
class="grid grid-auto-sm gap-md"
|
|
2834
|
+
style="margin-bottom: var(--spacing-4);"
|
|
2835
|
+
>
|
|
2836
|
+
<div class="card">
|
|
2837
|
+
<pds-icon icon="desktop" size="lg" class="icon-info"></pds-icon>
|
|
2838
|
+
<h5>Responsive</h5>
|
|
2839
|
+
<p>Automatically wraps</p>
|
|
2840
|
+
</div>
|
|
2841
|
+
<div class="card">
|
|
2842
|
+
<pds-icon
|
|
2843
|
+
icon="device-mobile"
|
|
2844
|
+
size="lg"
|
|
2845
|
+
class="icon-info"
|
|
2846
|
+
></pds-icon>
|
|
2847
|
+
<h5>Adaptive</h5>
|
|
2848
|
+
<p>Based on space</p>
|
|
2849
|
+
</div>
|
|
2850
|
+
<div class="card">
|
|
2851
|
+
<pds-icon icon="globe" size="lg" class="icon-info"></pds-icon>
|
|
2852
|
+
<h5>Flexible</h5>
|
|
2853
|
+
<p>Resize the window</p>
|
|
2854
|
+
</div>
|
|
2855
|
+
<div class="card">
|
|
2856
|
+
<pds-icon icon="feather" size="lg" class="icon-info"></pds-icon>
|
|
2857
|
+
<h5>Dynamic</h5>
|
|
2858
|
+
<p>No breakpoints needed</p>
|
|
2859
|
+
</div>
|
|
2860
|
+
</div>
|
|
2861
|
+
|
|
2862
|
+
<h4><code>.grid-auto-md</code> (min 250px)</h4>
|
|
2863
|
+
<div
|
|
2864
|
+
class="grid grid-auto-md gap-lg"
|
|
2865
|
+
style="margin-bottom: var(--spacing-4);"
|
|
2866
|
+
>
|
|
2867
|
+
<div class="card surface-elevated">
|
|
2868
|
+
<pds-icon
|
|
2869
|
+
icon="rocket"
|
|
2870
|
+
size="xl"
|
|
2871
|
+
class="icon-accent"
|
|
2872
|
+
></pds-icon>
|
|
2873
|
+
<h5>Card 1</h5>
|
|
2874
|
+
<p>Larger minimum width means fewer columns on small screens</p>
|
|
2875
|
+
</div>
|
|
2876
|
+
<div class="card surface-elevated">
|
|
2877
|
+
<pds-icon
|
|
2878
|
+
icon="palette"
|
|
2879
|
+
size="xl"
|
|
2880
|
+
class="icon-accent"
|
|
2881
|
+
></pds-icon>
|
|
2882
|
+
<h5>Card 2</h5>
|
|
2883
|
+
<p>Smart surface tokens apply automatically</p>
|
|
2884
|
+
</div>
|
|
2885
|
+
<div class="card surface-elevated">
|
|
2886
|
+
<pds-icon icon="heart" size="xl" class="icon-accent"></pds-icon>
|
|
2887
|
+
<h5>Card 3</h5>
|
|
2888
|
+
<p>Consistent spacing with gap utilities</p>
|
|
2889
|
+
</div>
|
|
2890
|
+
</div>
|
|
2891
|
+
|
|
2892
|
+
<h3>Gap Utilities</h3>
|
|
2893
|
+
<p>
|
|
2894
|
+
Control spacing between grid items with
|
|
2895
|
+
<code>.gap-{size}</code> classes:
|
|
2896
|
+
</p>
|
|
2897
|
+
|
|
2898
|
+
<div
|
|
2899
|
+
style="display: grid; gap: var(--spacing-4); grid-template-columns: 1fr 1fr;"
|
|
2900
|
+
>
|
|
2901
|
+
<div>
|
|
2902
|
+
<p><strong>.gap-xs</strong> (spacing-1)</p>
|
|
2903
|
+
<div class="grid grid-cols-3 gap-xs">
|
|
2904
|
+
<div class="card"><p>A</p></div>
|
|
2905
|
+
<div class="card"><p>B</p></div>
|
|
2906
|
+
<div class="card"><p>C</p></div>
|
|
2907
|
+
</div>
|
|
2908
|
+
</div>
|
|
2909
|
+
|
|
2910
|
+
<div>
|
|
2911
|
+
<p><strong>.gap-sm</strong> (spacing-2)</p>
|
|
2912
|
+
<div class="grid grid-cols-3 gap-sm">
|
|
2913
|
+
<div class="card"><p>A</p></div>
|
|
2914
|
+
<div class="card"><p>B</p></div>
|
|
2915
|
+
<div class="card"><p>C</p></div>
|
|
2916
|
+
</div>
|
|
2917
|
+
</div>
|
|
2918
|
+
|
|
2919
|
+
<div>
|
|
2920
|
+
<p><strong>.gap-md</strong> (spacing-4)</p>
|
|
2921
|
+
<div class="grid grid-cols-3 gap-md">
|
|
2922
|
+
<div class="card"><p>A</p></div>
|
|
2923
|
+
<div class="card"><p>B</p></div>
|
|
2924
|
+
<div class="card"><p>C</p></div>
|
|
2925
|
+
</div>
|
|
2926
|
+
</div>
|
|
2927
|
+
|
|
2928
|
+
<div>
|
|
2929
|
+
<p><strong>.gap-lg</strong> (spacing-6)</p>
|
|
2930
|
+
<div class="grid grid-cols-3 gap-lg">
|
|
2931
|
+
<div class="card"><p>A</p></div>
|
|
2932
|
+
<div class="card"><p>B</p></div>
|
|
2933
|
+
<div class="card"><p>C</p></div>
|
|
2934
|
+
</div>
|
|
2935
|
+
</div>
|
|
2936
|
+
</div>
|
|
2937
|
+
|
|
2938
|
+
<h3>Code Inspector Support</h3>
|
|
2939
|
+
<p class="interactive-demo">
|
|
2940
|
+
<pds-icon
|
|
2941
|
+
icon="cursor-click"
|
|
2942
|
+
size="sm"
|
|
2943
|
+
class="icon-primary"
|
|
2944
|
+
></pds-icon>
|
|
2945
|
+
Enable the <strong>Code Inspector</strong> and click on any grid
|
|
2946
|
+
container above. The ontology now recognizes layout patterns like
|
|
2947
|
+
<code>grid</code>, <code>grid-cols</code>, and
|
|
2948
|
+
<code>grid-auto</code> for intelligent component detection.
|
|
2949
|
+
</p>
|
|
2950
|
+
</section>
|
|
2951
|
+
|
|
2952
|
+
<!-- Mesh Gradients Section -->
|
|
2953
|
+
<section class="showcase-section" data-section="mesh-gradients">
|
|
2954
|
+
<h2>
|
|
2955
|
+
<pds-icon
|
|
2956
|
+
icon="palette"
|
|
2957
|
+
size="lg"
|
|
2958
|
+
class="icon-primary"
|
|
2959
|
+
></pds-icon>
|
|
2960
|
+
Mesh Gradients
|
|
2961
|
+
</h2>
|
|
2962
|
+
<p>
|
|
2963
|
+
Subtle, beautiful mesh gradient backgrounds generated from your
|
|
2964
|
+
color palette. Using <code>--background-mesh-01</code> through
|
|
2965
|
+
<code>--background-mesh-05</code> custom properties. Automatically
|
|
2966
|
+
adapts to light and dark modes.
|
|
2967
|
+
</p>
|
|
2968
|
+
|
|
2969
|
+
<div
|
|
2970
|
+
class="grid grid-cols-2 gap-lg"
|
|
2971
|
+
style="margin-bottom: var(--spacing-6);"
|
|
2972
|
+
>
|
|
2973
|
+
<div
|
|
2974
|
+
style="position: relative; background: var(--background-mesh-01); padding: var(--spacing-6); border-radius: var(--radius-lg); min-height: 200px; display: flex; align-items: center; justify-content: center; border: 1px solid var(--color-border);"
|
|
2975
|
+
>
|
|
2976
|
+
<button
|
|
2977
|
+
class="btn-primary btn-xs"
|
|
2978
|
+
style="position: absolute; top: var(--spacing-2); right: var(--spacing-2);"
|
|
2979
|
+
@pointerdown=${() => this.previewMesh("01")}
|
|
2980
|
+
@pointerup=${this.clearMeshPreview}
|
|
2981
|
+
@pointerleave=${this.clearMeshPreview}
|
|
2982
|
+
title="Press and hold to preview on page background"
|
|
2983
|
+
>
|
|
2984
|
+
<pds-icon icon="eye" size="sm"></pds-icon>
|
|
2985
|
+
Preview
|
|
2986
|
+
</button>
|
|
2987
|
+
<div
|
|
2988
|
+
style="background: var(--color-surface-base); padding: var(--spacing-4); border-radius: var(--radius-md); box-shadow: var(--shadow-md);"
|
|
2989
|
+
>
|
|
2990
|
+
<h4 style="margin: 0;">Mesh 01</h4>
|
|
2991
|
+
<p style="margin: var(--spacing-2) 0 0 0; opacity: 0.7;">
|
|
2992
|
+
Subtle radial blend
|
|
2993
|
+
</p>
|
|
2994
|
+
</div>
|
|
2995
|
+
</div>
|
|
2996
|
+
<div
|
|
2997
|
+
style="position: relative; background: var(--background-mesh-02); padding: var(--spacing-6); border-radius: var(--radius-lg); min-height: 200px; display: flex; align-items: center; justify-content: center; border: 1px solid var(--color-border);"
|
|
2998
|
+
>
|
|
2999
|
+
<button
|
|
3000
|
+
class="btn-primary btn-xs"
|
|
3001
|
+
style="position: absolute; top: var(--spacing-2); right: var(--spacing-2);"
|
|
3002
|
+
@pointerdown=${() => this.previewMesh("02")}
|
|
3003
|
+
@pointerup=${this.clearMeshPreview}
|
|
3004
|
+
@pointerleave=${this.clearMeshPreview}
|
|
3005
|
+
title="Press and hold to preview on page background"
|
|
3006
|
+
>
|
|
3007
|
+
<pds-icon icon="eye" size="sm"></pds-icon>
|
|
3008
|
+
Preview
|
|
3009
|
+
</button>
|
|
3010
|
+
<div
|
|
3011
|
+
style="background: var(--color-surface-base); padding: var(--spacing-4); border-radius: var(--radius-md); box-shadow: var(--shadow-md);"
|
|
3012
|
+
>
|
|
3013
|
+
<h4 style="margin: 0;">Mesh 02</h4>
|
|
3014
|
+
<p style="margin: var(--spacing-2) 0 0 0; opacity: 0.7;">
|
|
3015
|
+
Corner accents
|
|
3016
|
+
</p>
|
|
3017
|
+
</div>
|
|
3018
|
+
</div>
|
|
3019
|
+
</div>
|
|
3020
|
+
|
|
3021
|
+
<div class="grid grid-cols-3 gap-md">
|
|
3022
|
+
<div
|
|
3023
|
+
style="position: relative; background: var(--background-mesh-03); padding: var(--spacing-5); border-radius: var(--radius-md); min-height: 150px; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; border: 1px solid var(--color-border);"
|
|
3024
|
+
>
|
|
3025
|
+
<button
|
|
3026
|
+
class="btn-primary btn-xs"
|
|
3027
|
+
style="position: absolute; top: var(--spacing-2); right: var(--spacing-2);"
|
|
3028
|
+
@pointerdown=${() => this.previewMesh("03")}
|
|
3029
|
+
@pointerup=${this.clearMeshPreview}
|
|
3030
|
+
@pointerleave=${this.clearMeshPreview}
|
|
3031
|
+
title="Press and hold to preview on page background"
|
|
3032
|
+
>
|
|
3033
|
+
<pds-icon icon="eye" size="sm"></pds-icon>
|
|
3034
|
+
</button>
|
|
3035
|
+
<pds-icon
|
|
3036
|
+
icon="sparkle"
|
|
3037
|
+
size="xl"
|
|
3038
|
+
style="opacity: 0.9; margin-bottom: var(--spacing-2);"
|
|
3039
|
+
></pds-icon>
|
|
3040
|
+
<code style="font-size: 0.75rem;">mesh-03</code>
|
|
3041
|
+
</div>
|
|
3042
|
+
<div
|
|
3043
|
+
style="position: relative; background: var(--background-mesh-04); padding: var(--spacing-5); border-radius: var(--radius-md); min-height: 150px; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; border: 1px solid var(--color-border);"
|
|
3044
|
+
>
|
|
3045
|
+
<button
|
|
3046
|
+
class="btn-primary btn-xs"
|
|
3047
|
+
style="position: absolute; top: var(--spacing-2); right: var(--spacing-2);"
|
|
3048
|
+
@pointerdown=${() => this.previewMesh("04")}
|
|
3049
|
+
@pointerup=${this.clearMeshPreview}
|
|
3050
|
+
@pointerleave=${this.clearMeshPreview}
|
|
3051
|
+
title="Press and hold to preview on page background"
|
|
3052
|
+
>
|
|
3053
|
+
<pds-icon icon="eye" size="sm"></pds-icon>
|
|
3054
|
+
</button>
|
|
3055
|
+
<pds-icon
|
|
3056
|
+
icon="sparkle"
|
|
3057
|
+
size="xl"
|
|
3058
|
+
style="opacity: 0.9; margin-bottom: var(--spacing-2);"
|
|
3059
|
+
></pds-icon>
|
|
3060
|
+
<code style="font-size: 0.75rem;">mesh-04</code>
|
|
3061
|
+
</div>
|
|
3062
|
+
<div
|
|
3063
|
+
style="position: relative; background: var(--background-mesh-05); padding: var(--spacing-5); border-radius: var(--radius-md); min-height: 150px; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; border: 1px solid var(--color-border);"
|
|
3064
|
+
>
|
|
3065
|
+
<button
|
|
3066
|
+
class="btn-primary btn-xs"
|
|
3067
|
+
style="position: absolute; top: var(--spacing-2); right: var(--spacing-2);"
|
|
3068
|
+
@pointerdown=${() => this.previewMesh("05")}
|
|
3069
|
+
@pointerup=${this.clearMeshPreview}
|
|
3070
|
+
@pointerleave=${this.clearMeshPreview}
|
|
3071
|
+
title="Press and hold to preview on page background"
|
|
3072
|
+
>
|
|
3073
|
+
<pds-icon icon="eye" size="sm"></pds-icon>
|
|
3074
|
+
</button>
|
|
3075
|
+
<pds-icon
|
|
3076
|
+
icon="sparkle"
|
|
3077
|
+
size="xl"
|
|
3078
|
+
style="opacity: 0.9; margin-bottom: var(--spacing-2);"
|
|
3079
|
+
></pds-icon>
|
|
3080
|
+
<code style="font-size: 0.75rem;">mesh-05</code>
|
|
3081
|
+
</div>
|
|
3082
|
+
</div>
|
|
3083
|
+
|
|
3084
|
+
<h3>Usage</h3>
|
|
3085
|
+
<pre
|
|
3086
|
+
class="code-block"
|
|
3087
|
+
style="margin-top: var(--spacing-4);"
|
|
3088
|
+
><code class="language-css">/* Apply as background */
|
|
3089
|
+
.hero-section {
|
|
3090
|
+
background: var(--background-mesh-01);
|
|
3091
|
+
}
|
|
3092
|
+
|
|
3093
|
+
/* Combine with surface colors */
|
|
3094
|
+
.card {
|
|
3095
|
+
background: var(--background-mesh-03);
|
|
3096
|
+
backdrop-filter: blur(10px);
|
|
3097
|
+
}
|
|
3098
|
+
|
|
3099
|
+
/* Layer over solid colors */
|
|
3100
|
+
.container {
|
|
3101
|
+
background-color: var(--color-surface-base);
|
|
3102
|
+
background-image: var(--background-mesh-02);
|
|
3103
|
+
}</code></pre>
|
|
3104
|
+
|
|
3105
|
+
<p class="interactive-demo" style="margin-top: var(--spacing-4);">
|
|
3106
|
+
<pds-icon
|
|
3107
|
+
icon="moon-stars"
|
|
3108
|
+
size="sm"
|
|
3109
|
+
class="icon-primary"
|
|
3110
|
+
></pds-icon>
|
|
3111
|
+
Toggle between light and dark modes to see how mesh gradients
|
|
3112
|
+
automatically adapt with reduced opacity in dark mode for subtle,
|
|
3113
|
+
non-interfering backgrounds.
|
|
3114
|
+
</p>
|
|
3115
|
+
</section>
|
|
3116
|
+
|
|
3117
|
+
<!-- Interactive States Section -->
|
|
3118
|
+
<section
|
|
3119
|
+
class="showcase-section alt-bg"
|
|
3120
|
+
data-section="interactive-states"
|
|
3121
|
+
>
|
|
3122
|
+
<h2>
|
|
3123
|
+
<pds-icon
|
|
3124
|
+
icon="cursor-click"
|
|
3125
|
+
size="lg"
|
|
3126
|
+
class="icon-primary"
|
|
3127
|
+
></pds-icon>
|
|
3128
|
+
Interactive States
|
|
3129
|
+
</h2>
|
|
3130
|
+
|
|
3131
|
+
<h3>Focus States</h3>
|
|
3132
|
+
<p class="interactive-demo">
|
|
3133
|
+
Press Tab to navigate and see focus rings on interactive elements:
|
|
3134
|
+
</p>
|
|
3135
|
+
<div class="flex-wrap">
|
|
3136
|
+
<button class="btn-primary">Button 1</button>
|
|
3137
|
+
<button class="btn-secondary">Button 2</button>
|
|
3138
|
+
<input type="text" placeholder="Focus me" />
|
|
3139
|
+
<select>
|
|
3140
|
+
<option>Option 1</option>
|
|
3141
|
+
<option>Option 2</option>
|
|
3142
|
+
</select>
|
|
3143
|
+
<a href="#">Link</a>
|
|
3144
|
+
</div>
|
|
3145
|
+
|
|
3146
|
+
<h3>Transition Speeds</h3>
|
|
3147
|
+
<p class="interactive-demo">
|
|
3148
|
+
Current setting:
|
|
3149
|
+
<strong
|
|
3150
|
+
>${this.config?.behavior?.transitionSpeed || "normal"}</strong
|
|
3151
|
+
>
|
|
3152
|
+
<br />
|
|
3153
|
+
Click the button to see the transition in action:
|
|
3154
|
+
</p>
|
|
3155
|
+
|
|
3156
|
+
<div class="transition-speed-demo">
|
|
3157
|
+
<button
|
|
3158
|
+
class="btn-primary"
|
|
3159
|
+
@click="${this.triggerTransitionDemo}"
|
|
3160
|
+
>
|
|
3161
|
+
<pds-icon icon="play" size="sm"></pds-icon>
|
|
3162
|
+
Animate Transition
|
|
3163
|
+
</button>
|
|
3164
|
+
|
|
3165
|
+
<div class="transition-demo-stage">
|
|
3166
|
+
<div class="transition-demo-ball" id="transition-ball">
|
|
3167
|
+
<pds-icon icon="cursor-click" size="lg"></pds-icon>
|
|
3168
|
+
</div>
|
|
3169
|
+
</div>
|
|
3170
|
+
</div>
|
|
3171
|
+
|
|
3172
|
+
<p class="interactive-demo" style="margin-top: var(--spacing-4);">
|
|
3173
|
+
Change the <em>Transition Speed</em> setting in the designer panel
|
|
3174
|
+
to see how it affects the animation.
|
|
3175
|
+
</p>
|
|
3176
|
+
</section>
|
|
3177
|
+
|
|
3178
|
+
<!-- Toast Notifications Section -->
|
|
3179
|
+
<section class="showcase-section">
|
|
3180
|
+
<h2>
|
|
3181
|
+
<pds-icon
|
|
3182
|
+
icon="bell-ringing"
|
|
3183
|
+
size="lg"
|
|
3184
|
+
class="icon-primary"
|
|
3185
|
+
></pds-icon>
|
|
3186
|
+
Toast Notifications
|
|
3187
|
+
</h2>
|
|
3188
|
+
|
|
3189
|
+
<p class="toast-demo-description">
|
|
3190
|
+
Toast notifications appear in the top-right corner and
|
|
3191
|
+
auto-dismiss after a few seconds. Click the buttons below to see
|
|
3192
|
+
them in action:
|
|
3193
|
+
</p>
|
|
3194
|
+
|
|
3195
|
+
<div class="flex flex-wrap gap-md">
|
|
3196
|
+
<button
|
|
3197
|
+
class="btn-primary btn-sm"
|
|
3198
|
+
@click="${this.showSuccessToast}"
|
|
3199
|
+
>
|
|
3200
|
+
<pds-icon icon="check-circle" size="sm"></pds-icon>
|
|
3201
|
+
Success
|
|
3202
|
+
</button>
|
|
3203
|
+
<button
|
|
3204
|
+
class="btn-secondary btn-sm"
|
|
3205
|
+
@click="${this.showInfoToast}"
|
|
3206
|
+
>
|
|
3207
|
+
<pds-icon icon="info" size="sm"></pds-icon>
|
|
3208
|
+
Info
|
|
3209
|
+
</button>
|
|
3210
|
+
<button
|
|
3211
|
+
class="btn-warning btn-sm"
|
|
3212
|
+
@click="${this.showWarningToast}"
|
|
3213
|
+
>
|
|
3214
|
+
<pds-icon icon="warning" size="sm"></pds-icon>
|
|
3215
|
+
Warning
|
|
3216
|
+
</button>
|
|
3217
|
+
<button class="btn-danger btn-sm" @click="${this.showErrorToast}">
|
|
3218
|
+
<pds-icon icon="x-circle" size="sm"></pds-icon>
|
|
3219
|
+
Error
|
|
3220
|
+
</button>
|
|
3221
|
+
<button class="btn-outline btn-sm" @click="${this.showLongToast}">
|
|
3222
|
+
<pds-icon icon="clock" size="sm"></pds-icon>
|
|
3223
|
+
Long
|
|
3224
|
+
</button>
|
|
3225
|
+
<button
|
|
3226
|
+
class="btn-outline btn-sm"
|
|
3227
|
+
@click="${this.showPersistentToast}"
|
|
3228
|
+
>
|
|
3229
|
+
<pds-icon icon="bell" size="sm"></pds-icon>
|
|
3230
|
+
Persistent
|
|
3231
|
+
</button>
|
|
3232
|
+
</div>
|
|
3233
|
+
</section>
|
|
3234
|
+
|
|
3235
|
+
<!-- Tab Strip Section -->
|
|
3236
|
+
<section class="showcase-section alt-bg" data-section="tabs">
|
|
3237
|
+
<h2>
|
|
3238
|
+
<pds-icon icon="tabs"></pds-icon>
|
|
3239
|
+
Tab Strip
|
|
3240
|
+
</h2>
|
|
3241
|
+
<p>
|
|
3242
|
+
Accessible tab navigation with hash-based routing and keyboard
|
|
3243
|
+
support.
|
|
3244
|
+
</p>
|
|
3245
|
+
|
|
3246
|
+
<div style="margin-top: var(--spacing-6);">
|
|
3247
|
+
<pds-tabstrip @tabchange="${this.handleTabChange}" label="Example Tabs">
|
|
3248
|
+
<pds-tabpanel id="overview" label="Overview">
|
|
3249
|
+
<h3>Overview</h3>
|
|
3250
|
+
<p>
|
|
3251
|
+
This is the overview tab. Tab strips provide organized
|
|
3252
|
+
navigation between related content.
|
|
3253
|
+
</p>
|
|
3254
|
+
</pds-tabpanel>
|
|
3255
|
+
|
|
3256
|
+
<pds-tabpanel id="features" label="Features">
|
|
3257
|
+
<h3>Features</h3>
|
|
3258
|
+
<p>
|
|
3259
|
+
Tab strips are built with modern web components and include:
|
|
3260
|
+
</p>
|
|
3261
|
+
<ul>
|
|
3262
|
+
<li>
|
|
3263
|
+
<strong>Deep linking:</strong> Each tab has a unique URL
|
|
3264
|
+
hash
|
|
3265
|
+
</li>
|
|
3266
|
+
<li>
|
|
3267
|
+
<strong>Progressive enhancement:</strong> Works without
|
|
3268
|
+
JavaScript
|
|
3269
|
+
</li>
|
|
3270
|
+
<li>
|
|
3271
|
+
<strong>Responsive:</strong> Adapts to mobile and desktop
|
|
3272
|
+
</li>
|
|
3273
|
+
<li>
|
|
3274
|
+
<strong>Customizable:</strong> Style with CSS variables
|
|
3275
|
+
</li>
|
|
3276
|
+
</ul>
|
|
3277
|
+
</pds-tabpanel>
|
|
3278
|
+
|
|
3279
|
+
<pds-tabpanel id="usage" label="Usage">
|
|
3280
|
+
<h3>Usage</h3>
|
|
3281
|
+
<p>Simple markup example:</p>
|
|
3282
|
+
<pre><code><pds-tabstrip label="My Tabs">
|
|
3283
|
+
<pds-tabpanel id="tab1" label="First Tab">
|
|
3284
|
+
Content for first tab
|
|
3285
|
+
</pds-tabpanel>
|
|
3286
|
+
<pds-tabpanel id="tab2" label="Second Tab">
|
|
3287
|
+
Content for second tab
|
|
3288
|
+
</pds-tabpanel>
|
|
3289
|
+
</pds-tabstrip></code></pre>
|
|
3290
|
+
</pds-tabpanel>
|
|
3291
|
+
|
|
3292
|
+
<pds-tabpanel id="accessibility" label="Accessibility">
|
|
3293
|
+
<h3>Accessibility</h3>
|
|
3294
|
+
<p>Built with accessibility in mind:</p>
|
|
3295
|
+
<ul>
|
|
3296
|
+
<li><code>aria-label</code> on navigation</li>
|
|
3297
|
+
<li><code>aria-current</code> on active tab</li>
|
|
3298
|
+
<li><code>aria-controls</code> linking tabs to panels</li>
|
|
3299
|
+
<li><code>role="region"</code> on tab panels</li>
|
|
3300
|
+
<li>Keyboard navigation with arrow keys</li>
|
|
3301
|
+
<li>Focus management</li>
|
|
3302
|
+
</ul>
|
|
3303
|
+
</pds-tabpanel>
|
|
3304
|
+
</pds-tabstrip>
|
|
3305
|
+
</div>
|
|
3306
|
+
</section>
|
|
3307
|
+
|
|
3308
|
+
<h3>Calendar</h3>
|
|
3309
|
+
<section class="card surface" >
|
|
3310
|
+
<pds-calendar></pds-calendar>
|
|
3311
|
+
</section>
|
|
3312
|
+
|
|
3313
|
+
|
|
3314
|
+
<!-- Drawer Section -->
|
|
3315
|
+
<section class="showcase-section">
|
|
3316
|
+
<h2>
|
|
3317
|
+
<pds-icon
|
|
3318
|
+
icon="squares-four"
|
|
3319
|
+
size="lg"
|
|
3320
|
+
class="icon-primary"
|
|
3321
|
+
></pds-icon>
|
|
3322
|
+
Drawer Example
|
|
3323
|
+
</h2>
|
|
3324
|
+
<p>Open the global drawer from different sides:</p>
|
|
3325
|
+
<div
|
|
3326
|
+
class="btn-group"
|
|
3327
|
+
style="gap: var(--spacing-3); flex-wrap: wrap;"
|
|
3328
|
+
>
|
|
3329
|
+
<button
|
|
3330
|
+
class="btn-primary"
|
|
3331
|
+
@click=${() => this.openDrawerInPos("bottom")}
|
|
3332
|
+
>
|
|
3333
|
+
<pds-icon icon="sidebar" rotate="-90" size="sm"></pds-icon>
|
|
3334
|
+
Bottom Drawer
|
|
3335
|
+
</button>
|
|
3336
|
+
<button
|
|
3337
|
+
class="btn-secondary"
|
|
3338
|
+
@click=${() => this.openDrawerInPos("left")}
|
|
3339
|
+
>
|
|
3340
|
+
<pds-icon icon="sidebar" size="sm"></pds-icon>
|
|
3341
|
+
Left Drawer
|
|
3342
|
+
</button>
|
|
3343
|
+
<button
|
|
3344
|
+
class="btn-secondary"
|
|
3345
|
+
@click=${() => this.openDrawerInPos("right")}
|
|
3346
|
+
>
|
|
3347
|
+
<pds-icon icon="sidebar" rotate="180" size="sm"></pds-icon>
|
|
3348
|
+
Right Drawer
|
|
3349
|
+
</button>
|
|
3350
|
+
<button
|
|
3351
|
+
class="btn-secondary"
|
|
3352
|
+
@click=${() => this.openDrawerInPos("top")}
|
|
3353
|
+
>
|
|
3354
|
+
<pds-icon icon="sidebar" rotate="90" size="sm"></pds-icon>
|
|
3355
|
+
Top Drawer
|
|
3356
|
+
</button>
|
|
3357
|
+
</div>
|
|
3358
|
+
</section>
|
|
3359
|
+
</div>
|
|
3360
|
+
`;
|
|
3361
|
+
}
|
|
3362
|
+
|
|
3363
|
+
renderDrawerContent() {
|
|
3364
|
+
return html`
|
|
3365
|
+
<figure class="media-figure">
|
|
3366
|
+
<img
|
|
3367
|
+
class="media-image"
|
|
3368
|
+
src="https://picsum.photos/800/600?random=1"
|
|
3369
|
+
alt="Random landscape"
|
|
3370
|
+
/>
|
|
3371
|
+
<figcaption class="media-caption">
|
|
3372
|
+
<strong>Figure 1:</strong> A beautiful landscape demonstrating image
|
|
3373
|
+
handling in the design system.
|
|
3374
|
+
</figcaption>
|
|
3375
|
+
</figure>
|
|
3376
|
+
`;
|
|
3377
|
+
}
|
|
3378
|
+
|
|
3379
|
+
async showDoc(doc) {
|
|
3380
|
+
const url = `/${doc}`;
|
|
3381
|
+
try {
|
|
3382
|
+
const res = await fetch(url, { cache: "no-store" });
|
|
3383
|
+
const text = await res.text();
|
|
3384
|
+
|
|
3385
|
+
// If server returned HTML (SPA fallback), show an error message
|
|
3386
|
+
const trimmed = text.trim();
|
|
3387
|
+
let htmlContent;
|
|
3388
|
+
if (trimmed.startsWith("<")) {
|
|
3389
|
+
htmlContent = `<div class="docs-error">Failed to load README at ${url}. Ensure readme.md exists under public/</div>`;
|
|
3390
|
+
} else {
|
|
3391
|
+
try {
|
|
3392
|
+
const conv = await this.getShowdownConverter();
|
|
3393
|
+
htmlContent = conv
|
|
3394
|
+
? conv.makeHtml(trimmed)
|
|
3395
|
+
: `<pre>${this.escapeHTML(trimmed)}</pre>`;
|
|
3396
|
+
} catch (err) {
|
|
3397
|
+
htmlContent = `<pre>${this.escapeHTML(trimmed)}</pre>`;
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
|
|
3401
|
+
const drawer = document.getElementById("global-drawer");
|
|
3402
|
+
drawer.show(html`${unsafeHTML(htmlContent)}`, {
|
|
3403
|
+
header: html`<h3>PDS Documentation</h3>`,
|
|
3404
|
+
});
|
|
3405
|
+
} catch (err) {
|
|
3406
|
+
console.error("Error fetching README:", err);
|
|
3407
|
+
const toaster = document.getElementById("global-toaster");
|
|
3408
|
+
toaster.toast("Error loading docs. See console.", { type: "danger" });
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
|
|
3412
|
+
handleTabChange(event) {
|
|
3413
|
+
toast(`Switched to tab: ${event.detail.newTab}`, { type: "info" });
|
|
3414
|
+
}
|
|
3415
|
+
|
|
3416
|
+
openDrawer() {
|
|
3417
|
+
const drawer = document.getElementById("global-drawer");
|
|
3418
|
+
|
|
3419
|
+
drawer.show(
|
|
3420
|
+
html`
|
|
3421
|
+
<figure class="media-figure">
|
|
3422
|
+
<img
|
|
3423
|
+
class="media-image"
|
|
3424
|
+
src="https://picsum.photos/800/600?random=1"
|
|
3425
|
+
alt="Random landscape"
|
|
3426
|
+
/>
|
|
3427
|
+
<figcaption class="media-caption">
|
|
3428
|
+
<strong>Figure 1:</strong> A beautiful landscape demonstrating
|
|
3429
|
+
image handling in the design system.
|
|
3430
|
+
</figcaption>
|
|
3431
|
+
</figure>
|
|
3432
|
+
`,
|
|
3433
|
+
{
|
|
3434
|
+
header: html`<h3>Example Drawer</h3>`,
|
|
3435
|
+
minHeight: "300px",
|
|
3436
|
+
position: "bottom",
|
|
3437
|
+
}
|
|
3438
|
+
);
|
|
3439
|
+
}
|
|
3440
|
+
|
|
3441
|
+
openDrawerInPos(position) {
|
|
3442
|
+
const drawer = document.getElementById("global-drawer");
|
|
3443
|
+
if (drawer) {
|
|
3444
|
+
drawer.show(this.renderDrawerContent(), {
|
|
3445
|
+
header: html`<h3>
|
|
3446
|
+
Example Drawer
|
|
3447
|
+
(${position.charAt(0).toUpperCase() + position.slice(1)})
|
|
3448
|
+
</h3>`,
|
|
3449
|
+
position: position,
|
|
3450
|
+
});
|
|
3451
|
+
}
|
|
3452
|
+
}
|
|
3453
|
+
|
|
3454
|
+
// Mesh gradient preview methods
|
|
3455
|
+
previewMesh(meshNumber) {
|
|
3456
|
+
const originalBg = document.body.style.background;
|
|
3457
|
+
this._originalBodyBg = originalBg;
|
|
3458
|
+
document.body.style.background = `var(--background-mesh-${meshNumber})`;
|
|
3459
|
+
document.body.style.backgroundAttachment = "fixed";
|
|
3460
|
+
|
|
3461
|
+
// Dim all content to make the mesh background more visible
|
|
3462
|
+
const mainContent = document.querySelector("pds-demo");
|
|
3463
|
+
if (mainContent && !this._originalOpacity) {
|
|
3464
|
+
this._originalOpacity = mainContent.style.opacity;
|
|
3465
|
+
mainContent.style.transition = "opacity 200ms ease-out";
|
|
3466
|
+
mainContent.style.opacity = "0.1";
|
|
3467
|
+
}
|
|
3468
|
+
}
|
|
3469
|
+
|
|
3470
|
+
clearMeshPreview() {
|
|
3471
|
+
if (this._originalBodyBg !== undefined) {
|
|
3472
|
+
if (this._originalBodyBg) {
|
|
3473
|
+
document.body.style.background = this._originalBodyBg;
|
|
3474
|
+
} else {
|
|
3475
|
+
document.body.style.removeProperty("background");
|
|
3476
|
+
document.body.style.removeProperty("background-attachment");
|
|
3477
|
+
}
|
|
3478
|
+
this._originalBodyBg = undefined;
|
|
3479
|
+
}
|
|
3480
|
+
|
|
3481
|
+
// Restore content opacity
|
|
3482
|
+
const mainContent = document.querySelector("pds-demo");
|
|
3483
|
+
if (mainContent && this._originalOpacity !== undefined) {
|
|
3484
|
+
mainContent.style.opacity = this._originalOpacity || "1";
|
|
3485
|
+
this._originalOpacity = undefined;
|
|
3486
|
+
// Remove transition after animation completes
|
|
3487
|
+
setTimeout(() => {
|
|
3488
|
+
if (mainContent.style.opacity !== "0.1") {
|
|
3489
|
+
mainContent.style.transition = "";
|
|
3490
|
+
}
|
|
3491
|
+
}, 200);
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3495
|
+
// Toast handler methods
|
|
3496
|
+
showSuccessToast() {
|
|
3497
|
+
toast("Your changes have been saved successfully!", {
|
|
3498
|
+
type: "success",
|
|
3499
|
+
});
|
|
3500
|
+
}
|
|
3501
|
+
|
|
3502
|
+
showInfoToast() {
|
|
3503
|
+
toast("This is an informational message with helpful context.", {
|
|
3504
|
+
type: "info",
|
|
3505
|
+
});
|
|
3506
|
+
}
|
|
3507
|
+
|
|
3508
|
+
showWarningToast() {
|
|
3509
|
+
toast("Warning: This action cannot be undone!", {
|
|
3510
|
+
type: "warning",
|
|
3511
|
+
});
|
|
3512
|
+
}
|
|
3513
|
+
|
|
3514
|
+
showErrorToast() {
|
|
3515
|
+
toast("Error: Something went wrong. Please try again.", {
|
|
3516
|
+
type: "error",
|
|
3517
|
+
});
|
|
3518
|
+
}
|
|
3519
|
+
|
|
3520
|
+
showLongToast() {
|
|
3521
|
+
toast(
|
|
3522
|
+
"This is a longer toast notification message that demonstrates how the duration is automatically calculated based on the message length. The toast will stay visible longer to give you enough time to read the entire message.",
|
|
3523
|
+
{ type: "info" }
|
|
3524
|
+
);
|
|
3525
|
+
}
|
|
3526
|
+
|
|
3527
|
+
showPersistentToast() {
|
|
3528
|
+
toast(
|
|
3529
|
+
"This is a persistent toast that won't auto-dismiss. Click the × to close it.",
|
|
3530
|
+
{
|
|
3531
|
+
type: "info",
|
|
3532
|
+
persistent: true,
|
|
3533
|
+
}
|
|
3534
|
+
);
|
|
3535
|
+
}
|
|
3536
|
+
|
|
3537
|
+
triggerTransitionDemo() {
|
|
3538
|
+
const ball = this.querySelector("#transition-ball");
|
|
3539
|
+
if (!ball) return;
|
|
3540
|
+
|
|
3541
|
+
// Remove the animated class to reset
|
|
3542
|
+
ball.classList.remove("animated");
|
|
3543
|
+
|
|
3544
|
+
// Force a reflow to restart the animation
|
|
3545
|
+
void ball.offsetWidth;
|
|
3546
|
+
|
|
3547
|
+
// Add the animated class to trigger the transition
|
|
3548
|
+
ball.classList.add("animated");
|
|
3549
|
+
|
|
3550
|
+
// Reset after animation completes (using slow transition as max)
|
|
3551
|
+
setTimeout(() => {
|
|
3552
|
+
ball.classList.remove("animated");
|
|
3553
|
+
}, 1000);
|
|
3554
|
+
}
|
|
3555
|
+
|
|
3556
|
+
renderColorCard(name, color) {
|
|
3557
|
+
return html`
|
|
3558
|
+
<div class="color-card">
|
|
3559
|
+
<div
|
|
3560
|
+
class="color-card-header"
|
|
3561
|
+
style="background-color: var(--color-${color}-600);"
|
|
3562
|
+
>
|
|
3563
|
+
${name}
|
|
3564
|
+
</div>
|
|
3565
|
+
<div class="color-card-body">
|
|
3566
|
+
<div class="color-scale-grid">
|
|
3567
|
+
${[50, 100, 200, 300, 400, 500, 600, 700, 800].map(
|
|
3568
|
+
(shade) => html`
|
|
3569
|
+
<div
|
|
3570
|
+
class="color-scale-swatch"
|
|
3571
|
+
style="background-color: var(--color-${color}-${shade});"
|
|
3572
|
+
title="${color}-${shade}"
|
|
3573
|
+
></div>
|
|
3574
|
+
`
|
|
3575
|
+
)}
|
|
3576
|
+
</div>
|
|
3577
|
+
<p class="color-card-footer">9-step scale from 50 to 800</p>
|
|
3578
|
+
</div>
|
|
3579
|
+
</div>
|
|
3580
|
+
`;
|
|
3581
|
+
}
|
|
3582
|
+
|
|
3583
|
+
renderColorScale(colorName) {
|
|
3584
|
+
return html`
|
|
3585
|
+
<div class="color-scale-container">
|
|
3586
|
+
<div class="color-scale-row">
|
|
3587
|
+
<div class="color-scale-label">${colorName}</div>
|
|
3588
|
+
<div class="color-scale-swatches">
|
|
3589
|
+
${[50, 100, 200, 300, 400, 500, 600, 700, 800].map((shade) => {
|
|
3590
|
+
const textColor =
|
|
3591
|
+
shade >= 400 ? "white" : `var(--color-${colorName}-900)`;
|
|
3592
|
+
return html`
|
|
3593
|
+
<div
|
|
3594
|
+
class="color-scale-swatch-interactive"
|
|
3595
|
+
style="
|
|
3596
|
+
background: var(--color-${colorName}-${shade});
|
|
3597
|
+
color: ${textColor};
|
|
3598
|
+
"
|
|
3599
|
+
@mouseover="${(e) => {
|
|
3600
|
+
e.currentTarget.style.transform = "translateY(-4px)";
|
|
3601
|
+
e.currentTarget.style.zIndex = "10";
|
|
3602
|
+
e.currentTarget.style.boxShadow = "var(--shadow-md)";
|
|
3603
|
+
}}"
|
|
3604
|
+
@mouseout="${(e) => {
|
|
3605
|
+
e.currentTarget.style.transform = "translateY(0)";
|
|
3606
|
+
e.currentTarget.style.zIndex = "1";
|
|
3607
|
+
e.currentTarget.style.boxShadow = "none";
|
|
3608
|
+
}}"
|
|
3609
|
+
title="${colorName}-${shade}"
|
|
3610
|
+
>
|
|
3611
|
+
${shade}
|
|
3612
|
+
</div>
|
|
3613
|
+
`;
|
|
3614
|
+
})}
|
|
3615
|
+
</div>
|
|
3616
|
+
</div>
|
|
3617
|
+
</div>
|
|
3618
|
+
`;
|
|
3619
|
+
}
|
|
3620
|
+
}
|
|
3621
|
+
);
|