@pure-ds/storybook 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.storybook/addons/description/preview.js +15 -0
- package/.storybook/addons/description/register.js +60 -0
- package/.storybook/addons/html-preview/Panel.jsx +327 -0
- package/.storybook/addons/html-preview/constants.js +6 -0
- package/.storybook/addons/html-preview/preview.js +178 -0
- package/.storybook/addons/html-preview/register.js +16 -0
- package/.storybook/addons/pds-configurator/SearchTool.js +44 -0
- package/.storybook/addons/pds-configurator/Tool.js +30 -0
- package/.storybook/addons/pds-configurator/constants.js +9 -0
- package/.storybook/addons/pds-configurator/preview.js +159 -0
- package/.storybook/addons/pds-configurator/register.js +24 -0
- package/.storybook/docs.css +35 -0
- package/.storybook/htmlPreview.css +103 -0
- package/.storybook/htmlPreview.js +271 -0
- package/.storybook/main.js +160 -0
- package/.storybook/preview-body.html +48 -0
- package/.storybook/preview-head.html +11 -0
- package/.storybook/preview.js +1563 -0
- package/README.md +266 -0
- package/bin/index.js +40 -0
- package/dist/pds-reference.json +2101 -0
- package/package.json +45 -0
- package/pds.config.js +6 -0
- package/public/assets/css/app.css +1216 -0
- package/public/assets/data/auto-design-advanced.json +704 -0
- package/public/assets/data/auto-design-simple.json +123 -0
- package/public/assets/img/icon-512x512.png +0 -0
- package/public/assets/img/logo-trans.png +0 -0
- package/public/assets/img/logo.png +0 -0
- package/public/assets/js/app.js +15088 -0
- package/public/assets/js/app.js.map +7 -0
- package/public/assets/js/lit.js +1176 -0
- package/public/assets/js/lit.js.map +7 -0
- package/public/assets/js/pds.js +9801 -0
- package/public/assets/js/pds.js.map +7 -0
- package/public/assets/pds/components/pds-calendar.js +837 -0
- package/public/assets/pds/components/pds-drawer.js +857 -0
- package/public/assets/pds/components/pds-icon.js +338 -0
- package/public/assets/pds/components/pds-jsonform.js +1775 -0
- package/public/assets/pds/components/pds-richtext.js +1035 -0
- package/public/assets/pds/components/pds-scrollrow.js +331 -0
- package/public/assets/pds/components/pds-splitpanel.js +401 -0
- package/public/assets/pds/components/pds-tabstrip.js +251 -0
- package/public/assets/pds/components/pds-toaster.js +446 -0
- package/public/assets/pds/components/pds-upload.js +657 -0
- package/public/assets/pds/custom-elements.json +2003 -0
- package/public/assets/pds/icons/pds-icons.svg +498 -0
- package/public/assets/pds/pds-css-complete.json +1861 -0
- package/public/assets/pds/pds-runtime-config.json +11 -0
- package/public/assets/pds/pds.css-data.json +2152 -0
- package/public/assets/pds/styles/pds-components.css +1944 -0
- package/public/assets/pds/styles/pds-components.css.js +3895 -0
- package/public/assets/pds/styles/pds-primitives.css +352 -0
- package/public/assets/pds/styles/pds-primitives.css.js +711 -0
- package/public/assets/pds/styles/pds-styles.css +3761 -0
- package/public/assets/pds/styles/pds-styles.css.js +7529 -0
- package/public/assets/pds/styles/pds-tokens.css +699 -0
- package/public/assets/pds/styles/pds-tokens.css.js +1405 -0
- package/public/assets/pds/styles/pds-utilities.css +763 -0
- package/public/assets/pds/styles/pds-utilities.css.js +1533 -0
- package/public/assets/pds/vscode-custom-data.json +824 -0
- package/scripts/build-pds-reference.mjs +807 -0
- package/scripts/generate-stories.js +542 -0
- package/scripts/package-build.js +86 -0
- package/src/js/app.js +17 -0
- package/src/js/common/ask.js +208 -0
- package/src/js/common/common.js +20 -0
- package/src/js/common/font-loader.js +200 -0
- package/src/js/common/msg.js +90 -0
- package/src/js/lit.js +40 -0
- package/src/js/pds-core/pds-config.js +1162 -0
- package/src/js/pds-core/pds-enhancer-metadata.js +75 -0
- package/src/js/pds-core/pds-enhancers.js +357 -0
- package/src/js/pds-core/pds-enums.js +86 -0
- package/src/js/pds-core/pds-generator.js +5317 -0
- package/src/js/pds-core/pds-ontology.js +256 -0
- package/src/js/pds-core/pds-paths.js +109 -0
- package/src/js/pds-core/pds-query.js +571 -0
- package/src/js/pds-core/pds-registry.js +129 -0
- package/src/js/pds-core/pds.d.ts +129 -0
- package/src/js/pds.d.ts +408 -0
- package/src/js/pds.js +1579 -0
- package/src/pds-core/pds-api.js +105 -0
- package/stories/GettingStarted.md +96 -0
- package/stories/GettingStarted.stories.js +144 -0
- package/stories/WhatIsPDS.md +194 -0
- package/stories/WhatIsPDS.stories.js +144 -0
- package/stories/components/PdsCalendar.stories.js +263 -0
- package/stories/components/PdsDrawer.stories.js +623 -0
- package/stories/components/PdsIcon.stories.js +78 -0
- package/stories/components/PdsJsonform.stories.js +1444 -0
- package/stories/components/PdsRichtext.stories.js +367 -0
- package/stories/components/PdsScrollrow.stories.js +140 -0
- package/stories/components/PdsSplitpanel.stories.js +502 -0
- package/stories/components/PdsTabstrip.stories.js +442 -0
- package/stories/components/PdsToaster.stories.js +186 -0
- package/stories/components/PdsUpload.stories.js +66 -0
- package/stories/enhancements/Dropdowns.stories.js +185 -0
- package/stories/enhancements/InteractiveStates.stories.js +625 -0
- package/stories/enhancements/MeshGradients.stories.js +320 -0
- package/stories/enhancements/OpenGroups.stories.js +227 -0
- package/stories/enhancements/RangeSliders.stories.js +232 -0
- package/stories/enhancements/RequiredFields.stories.js +189 -0
- package/stories/enhancements/Toggles.stories.js +167 -0
- package/stories/foundations/Colors.stories.js +283 -0
- package/stories/foundations/Icons.stories.js +305 -0
- package/stories/foundations/SmartSurfaces.stories.js +367 -0
- package/stories/foundations/Spacing.stories.js +175 -0
- package/stories/foundations/Typography.stories.js +960 -0
- package/stories/foundations/ZIndex.stories.js +325 -0
- package/stories/patterns/BorderEffects.stories.js +72 -0
- package/stories/patterns/Layout.stories.js +99 -0
- package/stories/patterns/Utilities.stories.js +107 -0
- package/stories/primitives/Accordion.stories.js +359 -0
- package/stories/primitives/Alerts.stories.js +64 -0
- package/stories/primitives/Badges.stories.js +183 -0
- package/stories/primitives/Buttons.stories.js +229 -0
- package/stories/primitives/Cards.stories.js +353 -0
- package/stories/primitives/FormGroups.stories.js +569 -0
- package/stories/primitives/Forms.stories.js +131 -0
- package/stories/primitives/Media.stories.js +203 -0
- package/stories/primitives/Tables.stories.js +232 -0
- package/stories/reference/ReferenceCatalog.stories.js +28 -0
- package/stories/reference/reference-catalog.js +413 -0
- package/stories/reference/reference-docs.js +302 -0
- package/stories/reference/reference-helpers.js +310 -0
- package/stories/utilities/GridSystem.stories.js +208 -0
- package/stories/utils/PdsAsk.stories.js +420 -0
- package/stories/utils/toast-utils.js +148 -0
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PDSQuery - Smart query engine for the Pure Design System
|
|
3
|
+
*
|
|
4
|
+
* Interprets natural language questions and maps them to PDS runtime data
|
|
5
|
+
* structures (tokens, components, utilities, patterns) using keyword matching,
|
|
6
|
+
* intent detection, and semantic scoring.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* const query = new PDSQuery(PDS);
|
|
10
|
+
* const results = await query.search("what is the focus border color on inputs?");
|
|
11
|
+
* // Returns array of scored results with text, value, icon, category
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export class PDSQuery {
|
|
15
|
+
constructor(pds) {
|
|
16
|
+
this.pds = pds;
|
|
17
|
+
|
|
18
|
+
// Keyword dictionaries for intent detection
|
|
19
|
+
this.intents = {
|
|
20
|
+
color: ['color', 'colours', 'shade', 'tint', 'hue', 'foreground', 'background', 'text', 'fill', 'bg', 'fg'],
|
|
21
|
+
spacing: ['spacing', 'space', 'gap', 'padding', 'margin', 'distance', 'rhythm'],
|
|
22
|
+
typography: ['font', 'text', 'type', 'typography', 'heading', 'body', 'size', 'weight', 'family'],
|
|
23
|
+
border: ['border', 'outline', 'stroke', 'edge', 'frame'],
|
|
24
|
+
radius: ['radius', 'rounded', 'corner', 'curve', 'round'],
|
|
25
|
+
shadow: ['shadow', 'elevation', 'depth', 'glow', 'drop-shadow'],
|
|
26
|
+
component: ['component', 'element', 'widget'],
|
|
27
|
+
utility: ['utility', 'class', 'helper', 'css'],
|
|
28
|
+
layout: ['layout', 'container', 'grid', 'flex', 'group', 'arrange', 'organize'],
|
|
29
|
+
pattern: ['pattern', 'example', 'template', 'structure'],
|
|
30
|
+
interaction: ['hover', 'focus', 'active', 'disabled', 'pressed', 'selected', 'checked'],
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Entity/element keywords
|
|
34
|
+
this.entities = {
|
|
35
|
+
button: ['button', 'btn', 'cta'],
|
|
36
|
+
input: ['input', 'field', 'textbox', 'text-field', 'form-control'],
|
|
37
|
+
card: ['card', 'panel'],
|
|
38
|
+
badge: ['badge', 'pill', 'tag', 'chip'],
|
|
39
|
+
surface: ['surface', 'background', 'layer', 'container'],
|
|
40
|
+
icon: ['icon', 'svg', 'glyph', 'symbol'],
|
|
41
|
+
link: ['link', 'anchor', 'hyperlink'],
|
|
42
|
+
nav: ['nav', 'navigation', 'menu'],
|
|
43
|
+
modal: ['modal', 'dialog', 'popup', 'overlay'],
|
|
44
|
+
drawer: ['drawer', 'sidebar', 'panel'],
|
|
45
|
+
tab: ['tab', 'tabstrip'],
|
|
46
|
+
toast: ['toast', 'notification', 'alert', 'message'],
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Question patterns
|
|
50
|
+
this.questionWords = ['what', 'which', 'how', 'where', 'when', 'show', 'find', 'get', 'give', 'tell'];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Main search entry point
|
|
55
|
+
* @param {string} query - Natural language question
|
|
56
|
+
* @returns {Promise<Array>} Array of results with text, value, icon, category, score
|
|
57
|
+
*/
|
|
58
|
+
async search(query) {
|
|
59
|
+
if (!query || query.length < 2) return [];
|
|
60
|
+
|
|
61
|
+
const normalized = query.toLowerCase().trim();
|
|
62
|
+
const tokens = this.tokenize(normalized);
|
|
63
|
+
|
|
64
|
+
// Detect intent and entities from query
|
|
65
|
+
const context = this.analyzeQuery(tokens, normalized);
|
|
66
|
+
|
|
67
|
+
// Generate results from multiple strategies
|
|
68
|
+
const results = [];
|
|
69
|
+
|
|
70
|
+
// Strategy 1: Direct token/color queries
|
|
71
|
+
if (context.intents.has('color')) {
|
|
72
|
+
results.push(...this.queryColors(context, normalized));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Strategy 2: Utility class queries
|
|
76
|
+
if (context.intents.has('utility') || context.intents.has('border') ||
|
|
77
|
+
context.intents.has('layout') || normalized.includes('class')) {
|
|
78
|
+
results.push(...this.queryUtilities(context, normalized));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Strategy 3: Component queries
|
|
82
|
+
if (context.intents.has('component') || context.entities.size > 0) {
|
|
83
|
+
results.push(...this.queryComponents(context, normalized));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Strategy 4: Pattern/layout queries
|
|
87
|
+
if (context.intents.has('layout') || context.intents.has('pattern')) {
|
|
88
|
+
results.push(...this.queryPatterns(context, normalized));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Strategy 5: Typography queries
|
|
92
|
+
if (context.intents.has('typography')) {
|
|
93
|
+
results.push(...this.queryTypography(context, normalized));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Strategy 6: Spacing queries
|
|
97
|
+
if (context.intents.has('spacing')) {
|
|
98
|
+
results.push(...this.querySpacing(context, normalized));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Deduplicate by value, keeping highest score
|
|
102
|
+
const seen = new Map();
|
|
103
|
+
for (const result of results) {
|
|
104
|
+
const key = result.value;
|
|
105
|
+
if (!seen.has(key) || seen.get(key).score < result.score) {
|
|
106
|
+
seen.set(key, result);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Sort by score descending, limit to top 10
|
|
111
|
+
return Array.from(seen.values())
|
|
112
|
+
.sort((a, b) => b.score - a.score)
|
|
113
|
+
.slice(0, 10);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Tokenize and normalize query string
|
|
118
|
+
*/
|
|
119
|
+
tokenize(text) {
|
|
120
|
+
return text.toLowerCase()
|
|
121
|
+
.replace(/[?!.]/g, '')
|
|
122
|
+
.split(/\s+/)
|
|
123
|
+
.filter(t => t.length > 0);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Analyze query to extract intents and entities
|
|
128
|
+
*/
|
|
129
|
+
analyzeQuery(tokens, fullText) {
|
|
130
|
+
const context = {
|
|
131
|
+
intents: new Set(),
|
|
132
|
+
entities: new Set(),
|
|
133
|
+
modifiers: new Set(),
|
|
134
|
+
isQuestion: false,
|
|
135
|
+
tokens,
|
|
136
|
+
fullText
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Check if it's a question
|
|
140
|
+
context.isQuestion = this.questionWords.some(qw => tokens.includes(qw));
|
|
141
|
+
|
|
142
|
+
// Match intents
|
|
143
|
+
for (const [intent, keywords] of Object.entries(this.intents)) {
|
|
144
|
+
if (keywords.some(kw => tokens.includes(kw) || fullText.includes(kw))) {
|
|
145
|
+
context.intents.add(intent);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Match entities
|
|
150
|
+
for (const [entity, keywords] of Object.entries(this.entities)) {
|
|
151
|
+
if (keywords.some(kw => tokens.includes(kw) || fullText.includes(kw))) {
|
|
152
|
+
context.entities.add(entity);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Extract interaction modifiers
|
|
157
|
+
if (tokens.includes('hover') || fullText.includes('hover')) context.modifiers.add('hover');
|
|
158
|
+
if (tokens.includes('focus') || fullText.includes('focus')) context.modifiers.add('focus');
|
|
159
|
+
if (tokens.includes('active') || fullText.includes('active')) context.modifiers.add('active');
|
|
160
|
+
if (tokens.includes('disabled') || fullText.includes('disabled')) context.modifiers.add('disabled');
|
|
161
|
+
|
|
162
|
+
return context;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Query color tokens and surfaces
|
|
167
|
+
*/
|
|
168
|
+
queryColors(context, query) {
|
|
169
|
+
const results = [];
|
|
170
|
+
const compiled = this.pds.compiled;
|
|
171
|
+
|
|
172
|
+
if (!compiled?.tokens?.colors) return results;
|
|
173
|
+
|
|
174
|
+
const colors = compiled.tokens.colors;
|
|
175
|
+
const entities = Array.from(context.entities);
|
|
176
|
+
const modifiers = Array.from(context.modifiers);
|
|
177
|
+
|
|
178
|
+
// Specific color + element queries: "focus border color on inputs"
|
|
179
|
+
if (modifiers.includes('focus') && context.intents.has('border') && entities.includes('input')) {
|
|
180
|
+
results.push({
|
|
181
|
+
text: 'Focus border color: var(--color-primary-500)',
|
|
182
|
+
value: '--color-primary-500',
|
|
183
|
+
icon: 'palette',
|
|
184
|
+
category: 'Color Token',
|
|
185
|
+
score: 100,
|
|
186
|
+
cssVar: 'var(--color-primary-500)',
|
|
187
|
+
description: 'Primary color used for focus states on form inputs'
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Foreground on surface queries
|
|
192
|
+
if ((query.includes('foreground') || query.includes('text')) &&
|
|
193
|
+
(query.includes('surface') || context.entities.has('surface'))) {
|
|
194
|
+
results.push({
|
|
195
|
+
text: 'Text on surface: var(--surface-text)',
|
|
196
|
+
value: '--surface-text',
|
|
197
|
+
icon: 'palette',
|
|
198
|
+
category: 'Surface Token',
|
|
199
|
+
score: 95,
|
|
200
|
+
cssVar: 'var(--surface-text)',
|
|
201
|
+
description: 'Default text color for current surface'
|
|
202
|
+
});
|
|
203
|
+
results.push({
|
|
204
|
+
text: 'Secondary text: var(--surface-text-secondary)',
|
|
205
|
+
value: '--surface-text-secondary',
|
|
206
|
+
icon: 'palette',
|
|
207
|
+
category: 'Surface Token',
|
|
208
|
+
score: 90,
|
|
209
|
+
cssVar: 'var(--surface-text-secondary)',
|
|
210
|
+
description: 'Secondary/muted text on surface'
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Generic color scale queries
|
|
215
|
+
if (query.includes('primary') || query.includes('accent') || query.includes('secondary')) {
|
|
216
|
+
const scale = query.includes('primary') ? 'primary' :
|
|
217
|
+
query.includes('accent') ? 'accent' : 'secondary';
|
|
218
|
+
|
|
219
|
+
for (const shade of [500, 600, 700]) {
|
|
220
|
+
const varName = `--color-${scale}-${shade}`;
|
|
221
|
+
results.push({
|
|
222
|
+
text: `${scale.charAt(0).toUpperCase() + scale.slice(1)} ${shade}: var(${varName})`,
|
|
223
|
+
value: varName,
|
|
224
|
+
icon: 'palette',
|
|
225
|
+
category: 'Color Scale',
|
|
226
|
+
score: 80 - (shade - 500) / 100,
|
|
227
|
+
cssVar: `var(${varName})`,
|
|
228
|
+
description: `${scale} color scale shade ${shade}`
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Button color queries
|
|
234
|
+
if (entities.includes('button') && context.intents.has('color')) {
|
|
235
|
+
const modifier = modifiers[0];
|
|
236
|
+
if (modifier) {
|
|
237
|
+
results.push({
|
|
238
|
+
text: `Button ${modifier} fill: var(--${modifier === 'hover' ? 'primary' : 'primary'}-fill-${modifier})`,
|
|
239
|
+
value: `--primary-fill-${modifier}`,
|
|
240
|
+
icon: 'palette',
|
|
241
|
+
category: 'Interactive Token',
|
|
242
|
+
score: 92,
|
|
243
|
+
description: `Button background color in ${modifier} state`
|
|
244
|
+
});
|
|
245
|
+
} else {
|
|
246
|
+
results.push({
|
|
247
|
+
text: 'Button fill: var(--primary-fill)',
|
|
248
|
+
value: '--primary-fill',
|
|
249
|
+
icon: 'palette',
|
|
250
|
+
category: 'Interactive Token',
|
|
251
|
+
score: 88,
|
|
252
|
+
description: 'Default button background color'
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return results;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Query utility classes
|
|
262
|
+
*/
|
|
263
|
+
queryUtilities(context, query) {
|
|
264
|
+
const results = [];
|
|
265
|
+
const ontology = this.pds.ontology;
|
|
266
|
+
|
|
267
|
+
if (!ontology?.utilities) return results;
|
|
268
|
+
|
|
269
|
+
const utilities = ontology.utilities;
|
|
270
|
+
|
|
271
|
+
// Border utilities
|
|
272
|
+
if (context.intents.has('border')) {
|
|
273
|
+
const borderUtils = utilities.filter(u =>
|
|
274
|
+
u.includes('border') || u.includes('outline')
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
borderUtils.forEach(util => {
|
|
278
|
+
let score = 80;
|
|
279
|
+
if (query.includes('gradient') && util.includes('gradient')) score = 95;
|
|
280
|
+
if (query.includes('glow') && util.includes('glow')) score = 95;
|
|
281
|
+
|
|
282
|
+
results.push({
|
|
283
|
+
text: `${util} - Border utility class`,
|
|
284
|
+
value: util,
|
|
285
|
+
icon: 'code',
|
|
286
|
+
category: 'Utility Class',
|
|
287
|
+
score,
|
|
288
|
+
code: `<div class="${util}">...</div>`,
|
|
289
|
+
description: this.describeUtility(util)
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Layout utilities
|
|
295
|
+
if (context.intents.has('layout')) {
|
|
296
|
+
const layoutUtils = utilities.filter(u =>
|
|
297
|
+
u.includes('flex') || u.includes('grid') || u.includes('items-') ||
|
|
298
|
+
u.includes('justify-') || u.includes('gap-')
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
layoutUtils.forEach(util => {
|
|
302
|
+
results.push({
|
|
303
|
+
text: `${util} - Layout utility`,
|
|
304
|
+
value: util,
|
|
305
|
+
icon: 'layout',
|
|
306
|
+
category: 'Utility Class',
|
|
307
|
+
score: 85,
|
|
308
|
+
code: `<div class="${util}">...</div>`,
|
|
309
|
+
description: this.describeUtility(util)
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Button group utilities
|
|
315
|
+
if (query.includes('group') && context.entities.has('button')) {
|
|
316
|
+
results.push({
|
|
317
|
+
text: '.btn-group - Group buttons together',
|
|
318
|
+
value: '.btn-group',
|
|
319
|
+
icon: 'code',
|
|
320
|
+
category: 'Utility Class',
|
|
321
|
+
score: 90,
|
|
322
|
+
code: `<div class="btn-group">\n <button class="btn-primary">One</button>\n <button class="btn-primary">Two</button>\n</div>`,
|
|
323
|
+
description: 'Container for grouped buttons with connected styling'
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return results;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Query components
|
|
332
|
+
*/
|
|
333
|
+
queryComponents(context, query) {
|
|
334
|
+
const results = [];
|
|
335
|
+
const ontology = this.pds.ontology;
|
|
336
|
+
|
|
337
|
+
if (!ontology?.components && !ontology?.primitives) return results;
|
|
338
|
+
|
|
339
|
+
// Search custom components
|
|
340
|
+
if (ontology.components) {
|
|
341
|
+
ontology.components.forEach(comp => {
|
|
342
|
+
const matchScore = this.scoreMatch(query, comp.name + ' ' + comp.id);
|
|
343
|
+
if (matchScore > 50) {
|
|
344
|
+
results.push({
|
|
345
|
+
text: `<${comp.id}> - ${comp.name}`,
|
|
346
|
+
value: comp.id,
|
|
347
|
+
icon: 'brackets-curly',
|
|
348
|
+
category: 'Web Component',
|
|
349
|
+
score: matchScore,
|
|
350
|
+
code: `<${comp.id}></${comp.id}>`,
|
|
351
|
+
description: comp.description || `${comp.name} web component`
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Search primitives (native HTML elements with PDS styling)
|
|
358
|
+
if (ontology.primitives) {
|
|
359
|
+
ontology.primitives.forEach(prim => {
|
|
360
|
+
const matchScore = this.scoreMatch(query, prim.name + ' ' + prim.id);
|
|
361
|
+
if (matchScore > 50) {
|
|
362
|
+
const selector = prim.selectors?.[0] || prim.id;
|
|
363
|
+
results.push({
|
|
364
|
+
text: `${selector} - ${prim.name}`,
|
|
365
|
+
value: prim.id,
|
|
366
|
+
icon: 'tag',
|
|
367
|
+
category: 'Primitive',
|
|
368
|
+
score: matchScore - 5,
|
|
369
|
+
code: this.generatePrimitiveExample(prim),
|
|
370
|
+
description: prim.description || `${prim.name} primitive element`
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Icon-specific queries
|
|
377
|
+
if (query.includes('icon') && (query.includes('only') || query.includes('button'))) {
|
|
378
|
+
results.push({
|
|
379
|
+
text: 'Icon-only button: <button class="btn-icon">',
|
|
380
|
+
value: 'btn-icon',
|
|
381
|
+
icon: 'star',
|
|
382
|
+
category: 'Pattern',
|
|
383
|
+
score: 95,
|
|
384
|
+
code: `<button class="btn-icon btn-primary">\n <pds-icon icon="heart"></pds-icon>\n</button>`,
|
|
385
|
+
description: 'Button with only an icon, no text label'
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return results;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Query layout patterns
|
|
394
|
+
*/
|
|
395
|
+
queryPatterns(context, query) {
|
|
396
|
+
const results = [];
|
|
397
|
+
const ontology = this.pds.ontology;
|
|
398
|
+
|
|
399
|
+
if (!ontology?.layoutPatterns) return results;
|
|
400
|
+
|
|
401
|
+
ontology.layoutPatterns.forEach(pattern => {
|
|
402
|
+
const matchScore = this.scoreMatch(query, pattern.name + ' ' + pattern.id + ' ' + (pattern.description || ''));
|
|
403
|
+
if (matchScore > 50) {
|
|
404
|
+
const selector = pattern.selectors?.[0] || `.${pattern.id}`;
|
|
405
|
+
results.push({
|
|
406
|
+
text: `${pattern.name} - ${pattern.description || 'Layout pattern'}`,
|
|
407
|
+
value: pattern.id,
|
|
408
|
+
icon: 'layout',
|
|
409
|
+
category: 'Layout Pattern',
|
|
410
|
+
score: matchScore,
|
|
411
|
+
code: `<div class="${selector.replace('.', '')}">\n <!-- content -->\n</div>`,
|
|
412
|
+
description: pattern.description || pattern.name
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
// Container queries
|
|
418
|
+
if (query.includes('container') || query.includes('group')) {
|
|
419
|
+
results.push({
|
|
420
|
+
text: 'Card - Container for grouping content',
|
|
421
|
+
value: 'card',
|
|
422
|
+
icon: 'layout',
|
|
423
|
+
category: 'Primitive',
|
|
424
|
+
score: 88,
|
|
425
|
+
code: `<article class="card">\n <header>\n <h3>Title</h3>\n </header>\n <p>Content...</p>\n</article>`,
|
|
426
|
+
description: 'Card container with optional header, body, and footer'
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
results.push({
|
|
430
|
+
text: 'Section - Semantic container for grouping',
|
|
431
|
+
value: 'section',
|
|
432
|
+
icon: 'layout',
|
|
433
|
+
category: 'Pattern',
|
|
434
|
+
score: 85,
|
|
435
|
+
code: `<section>\n <h2>Section Title</h2>\n <!-- content -->\n</section>`,
|
|
436
|
+
description: 'Semantic section element for content grouping'
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return results;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Query typography tokens
|
|
445
|
+
*/
|
|
446
|
+
queryTypography(context, query) {
|
|
447
|
+
const results = [];
|
|
448
|
+
const compiled = this.pds.compiled;
|
|
449
|
+
|
|
450
|
+
if (!compiled?.tokens?.typography) return results;
|
|
451
|
+
|
|
452
|
+
const typo = compiled.tokens.typography;
|
|
453
|
+
|
|
454
|
+
if (query.includes('heading') || query.includes('title')) {
|
|
455
|
+
results.push({
|
|
456
|
+
text: 'Heading font: var(--font-family-heading)',
|
|
457
|
+
value: '--font-family-heading',
|
|
458
|
+
icon: 'text-aa',
|
|
459
|
+
category: 'Typography Token',
|
|
460
|
+
score: 85,
|
|
461
|
+
cssVar: 'var(--font-family-heading)',
|
|
462
|
+
description: 'Font family for headings'
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (query.includes('body') || query.includes('text')) {
|
|
467
|
+
results.push({
|
|
468
|
+
text: 'Body font: var(--font-family-body)',
|
|
469
|
+
value: '--font-family-body',
|
|
470
|
+
icon: 'text-aa',
|
|
471
|
+
category: 'Typography Token',
|
|
472
|
+
score: 85,
|
|
473
|
+
cssVar: 'var(--font-family-body)',
|
|
474
|
+
description: 'Font family for body text'
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return results;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Query spacing tokens
|
|
483
|
+
*/
|
|
484
|
+
querySpacing(context, query) {
|
|
485
|
+
const results = [];
|
|
486
|
+
const compiled = this.pds.compiled;
|
|
487
|
+
|
|
488
|
+
if (!compiled?.tokens?.spacing) return results;
|
|
489
|
+
|
|
490
|
+
const spacing = compiled.tokens.spacing;
|
|
491
|
+
|
|
492
|
+
// Show common spacing values
|
|
493
|
+
for (const [key, value] of Object.entries(spacing)) {
|
|
494
|
+
if (['2', '4', '6', '8'].includes(key)) {
|
|
495
|
+
results.push({
|
|
496
|
+
text: `Spacing ${key}: var(--spacing-${key})`,
|
|
497
|
+
value: `--spacing-${key}`,
|
|
498
|
+
icon: 'ruler',
|
|
499
|
+
category: 'Spacing Token',
|
|
500
|
+
score: 75,
|
|
501
|
+
cssVar: `var(--spacing-${key})`,
|
|
502
|
+
description: `Spacing value: ${value}`
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
return results;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Calculate match score between query and target text
|
|
512
|
+
*/
|
|
513
|
+
scoreMatch(query, target) {
|
|
514
|
+
const queryLower = query.toLowerCase();
|
|
515
|
+
const targetLower = target.toLowerCase();
|
|
516
|
+
|
|
517
|
+
let score = 0;
|
|
518
|
+
|
|
519
|
+
// Exact match
|
|
520
|
+
if (queryLower === targetLower) return 100;
|
|
521
|
+
|
|
522
|
+
// Contains full query
|
|
523
|
+
if (targetLower.includes(queryLower)) score += 80;
|
|
524
|
+
|
|
525
|
+
// Word overlap
|
|
526
|
+
const queryWords = this.tokenize(queryLower);
|
|
527
|
+
const targetWords = this.tokenize(targetLower);
|
|
528
|
+
const overlap = queryWords.filter(w => targetWords.includes(w)).length;
|
|
529
|
+
score += (overlap / queryWords.length) * 40;
|
|
530
|
+
|
|
531
|
+
// Starts with
|
|
532
|
+
if (targetLower.startsWith(queryLower)) score += 20;
|
|
533
|
+
|
|
534
|
+
return Math.min(100, score);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Generate example code for a primitive
|
|
539
|
+
*/
|
|
540
|
+
generatePrimitiveExample(primitive) {
|
|
541
|
+
const selector = primitive.selectors?.[0] || primitive.id;
|
|
542
|
+
|
|
543
|
+
if (selector.includes('button') || primitive.id === 'button') {
|
|
544
|
+
return '<button class="btn-primary">Click me</button>';
|
|
545
|
+
}
|
|
546
|
+
if (selector.includes('card') || primitive.id === 'card') {
|
|
547
|
+
return '<article class="card">\n <h3>Title</h3>\n <p>Content</p>\n</article>';
|
|
548
|
+
}
|
|
549
|
+
if (selector.includes('badge') || primitive.id === 'badge') {
|
|
550
|
+
return '<span class="badge">New</span>';
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
return `<${selector}>Content</${selector}>`;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Describe utility class purpose
|
|
558
|
+
*/
|
|
559
|
+
describeUtility(utilClass) {
|
|
560
|
+
if (utilClass.includes('border-gradient')) return 'Apply animated gradient border effect';
|
|
561
|
+
if (utilClass.includes('border-glow')) return 'Apply glowing border effect';
|
|
562
|
+
if (utilClass.includes('flex')) return 'Flexbox container utility';
|
|
563
|
+
if (utilClass.includes('grid')) return 'Grid container utility';
|
|
564
|
+
if (utilClass.includes('gap-')) return 'Set gap between flex/grid children';
|
|
565
|
+
if (utilClass.includes('items-')) return 'Align items in flex container';
|
|
566
|
+
if (utilClass.includes('justify-')) return 'Justify content in flex container';
|
|
567
|
+
if (utilClass === '.btn-group') return 'Group buttons with connected styling';
|
|
568
|
+
|
|
569
|
+
return 'Utility class for styling';
|
|
570
|
+
}
|
|
571
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
|
|
2
|
+
// ============================================================================
|
|
3
|
+
// PDS REGISTRY - Global mode manager for live vs static mode
|
|
4
|
+
// ============================================================================
|
|
5
|
+
|
|
6
|
+
class PDSRegistry {
|
|
7
|
+
constructor() {
|
|
8
|
+
this._mode = "static"; // Default to static mode
|
|
9
|
+
this._designer = null;
|
|
10
|
+
this._staticPaths = {
|
|
11
|
+
tokens: "/assets/pds/styles/pds-tokens.css.js",
|
|
12
|
+
primitives: "/assets/pds/styles/pds-primitives.css.js",
|
|
13
|
+
components: "/assets/pds/styles/pds-components.css.js",
|
|
14
|
+
utilities: "/assets/pds/styles/pds-utilities.css.js",
|
|
15
|
+
styles: "/assets/pds/styles/pds-styles.css.js",
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Set the designer instance and switch to live mode
|
|
21
|
+
*/
|
|
22
|
+
setDesigner(designer, meta = {}) {
|
|
23
|
+
this._designer = designer;
|
|
24
|
+
this._mode = "live";
|
|
25
|
+
const presetName = meta?.presetName;
|
|
26
|
+
if (presetName) {
|
|
27
|
+
designer?.options?.log?.("log", `PDS live with preset "${presetName}"`);
|
|
28
|
+
} else {
|
|
29
|
+
designer?.options?.log?.("log", "PDS live with custom config");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Switch to static mode with custom paths
|
|
35
|
+
* Called by consumers who want to use static CSS files
|
|
36
|
+
*/
|
|
37
|
+
setStaticMode(paths = {}) {
|
|
38
|
+
this._mode = "static";
|
|
39
|
+
this._staticPaths = { ...this._staticPaths, ...paths };
|
|
40
|
+
// Note: No access to config in static mode, using console
|
|
41
|
+
console.log("[PDS Registry] Switched to STATIC mode", this._staticPaths);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get stylesheet for adoption in shadow DOM
|
|
46
|
+
* Returns CSSStyleSheet object (constructable stylesheet)
|
|
47
|
+
*/
|
|
48
|
+
async getStylesheet(layer) {
|
|
49
|
+
if (this._mode === "live" && this._designer) {
|
|
50
|
+
// Return constructable stylesheet from live designer
|
|
51
|
+
switch (layer) {
|
|
52
|
+
case "tokens":
|
|
53
|
+
return this._designer.tokensStylesheet;
|
|
54
|
+
case "primitives":
|
|
55
|
+
return this._designer.primitivesStylesheet;
|
|
56
|
+
case "components":
|
|
57
|
+
return this._designer.componentsStylesheet;
|
|
58
|
+
case "utilities":
|
|
59
|
+
return this._designer.utilitiesStylesheet;
|
|
60
|
+
default:
|
|
61
|
+
this._designer?.options?.log?.("warn", `[PDS Registry] Unknown layer: ${layer}`);
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
// Import from static path
|
|
66
|
+
try {
|
|
67
|
+
const module = await import(this._staticPaths[layer]);
|
|
68
|
+
return module[layer]; // Return exported stylesheet
|
|
69
|
+
} catch (error) {
|
|
70
|
+
// No access to config in static mode, fall back to console
|
|
71
|
+
console.error(`[PDS Registry] Failed to load static ${layer}:`, error);
|
|
72
|
+
console.error(`[PDS Registry] Looking for: ${this._staticPaths[layer]}`);
|
|
73
|
+
console.error(`[PDS Registry] Make sure you've run 'npm run pds:export' and configured PDS.start() with the correct static.root path`);
|
|
74
|
+
// Return empty stylesheet as fallback
|
|
75
|
+
const fallback = new CSSStyleSheet();
|
|
76
|
+
fallback.replaceSync("/* Failed to load " + layer + " */");
|
|
77
|
+
return fallback;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// /**
|
|
83
|
+
// * Get BLOB URL for a layer (live mode only)
|
|
84
|
+
// * Used for @import statements in CSS
|
|
85
|
+
// */
|
|
86
|
+
// getBlobURL(layer) {
|
|
87
|
+
// if (this._mode === "live" && this._designer) {
|
|
88
|
+
// switch (layer) {
|
|
89
|
+
// case "tokens":
|
|
90
|
+
// return this._designer.tokensBlobURL;
|
|
91
|
+
// case "primitives":
|
|
92
|
+
// return this._designer.primitivesBlobURL;
|
|
93
|
+
// case "components":
|
|
94
|
+
// return this._designer.componentsBlobURL;
|
|
95
|
+
// case "utilities":
|
|
96
|
+
// return this._designer.utilitiesBlobURL;
|
|
97
|
+
// case "styles":
|
|
98
|
+
// return this._designer.stylesBlobURL;
|
|
99
|
+
// default:
|
|
100
|
+
// return null;
|
|
101
|
+
// }
|
|
102
|
+
// }
|
|
103
|
+
// return null;
|
|
104
|
+
// }
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get current mode
|
|
108
|
+
*/
|
|
109
|
+
get mode() {
|
|
110
|
+
return this._mode;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Check if in live mode
|
|
115
|
+
*/
|
|
116
|
+
get isLive() {
|
|
117
|
+
return this._mode === "live" && this._designer !== null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Check if designer is available
|
|
122
|
+
*/
|
|
123
|
+
get hasDesigner() {
|
|
124
|
+
return this._designer !== null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Export singleton instance
|
|
129
|
+
export const registry = new PDSRegistry();
|