@pure-ds/storybook 0.4.16 → 0.4.19
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/html-preview/Panel.jsx +80 -29
- package/.storybook/addons/html-preview/preview.js +4 -5
- package/.storybook/manager.js +337 -49
- package/.storybook/preview-head.html +2 -2
- package/.storybook/preview.js +2 -2
- package/README.md +2 -2
- package/dist/pds-reference.json +1916 -267
- package/package.json +2 -2
- package/public/assets/css/app.css +2 -2
- package/public/assets/js/app.js +645 -10574
- package/public/assets/js/lit.js +3 -1048
- package/public/assets/js/pds.js +429 -7368
- package/public/assets/pds/components/pds-calendar.js +1 -1
- package/public/assets/pds/components/{pds-jsonform.js → pds-form.js} +536 -45
- package/public/assets/pds/custom-elements.json +271 -26
- package/public/assets/pds/pds-runtime-config.json +1 -1
- package/public/assets/pds/styles/pds-components.css +83 -221
- package/public/assets/pds/styles/pds-components.css.js +166 -442
- package/public/assets/pds/styles/pds-styles.css +240 -437
- package/public/assets/pds/styles/pds-styles.css.js +479 -881
- package/public/assets/pds/styles/pds-utilities.css +151 -214
- package/public/assets/pds/styles/pds-utilities.css.js +302 -428
- package/public/assets/pds/vscode-custom-data.json +63 -63
- package/scripts/build-pds-reference.mjs +112 -38
- package/scripts/generate-stories.js +2 -2
- package/src/js/common/ask.js +48 -21
- package/src/js/pds-configurator/pds-config-form.js +9 -9
- package/src/js/pds-configurator/pds-demo.js +2 -2
- package/src/js/pds-core/pds-config.js +14 -14
- package/src/js/pds-core/pds-generator.js +113 -50
- package/src/js/pds-core/pds-ontology.js +6 -6
- package/src/js/pds.d.ts +2 -2
- package/stories/GettingStarted.stories.js +3 -0
- package/stories/WhatIsPDS.stories.js +3 -0
- package/stories/components/PdsCalendar.stories.js +2 -2
- package/stories/components/PdsDrawer.stories.js +15 -15
- package/stories/components/PdsForm.stories.js +4356 -0
- package/stories/components/{PdsJsonformUiSchema.md → PdsFormUiSchema.md} +2 -2
- package/stories/components/PdsRichtext.stories.js +4 -17
- package/stories/components/PdsScrollrow.stories.js +224 -72
- package/stories/components/PdsSplitpanel.stories.js +63 -28
- package/stories/components/PdsTabstrip.stories.js +7 -7
- package/stories/enhancements/Accordion.stories.js +2 -2
- package/stories/enhancements/Dropdowns.stories.js +13 -10
- package/stories/enhancements/RangeSliders.stories.js +9 -9
- package/stories/enhancements/RequiredFields.stories.js +8 -8
- package/stories/enhancements/Toggles.stories.js +45 -36
- package/stories/enhancements/_enhancement-header.js +2 -2
- package/stories/foundations/Colors.stories.js +13 -13
- package/stories/foundations/HTMLDefaults.stories.js +4 -4
- package/stories/foundations/Icons.stories.js +123 -288
- package/stories/foundations/MeshGradients.stories.js +161 -250
- package/stories/foundations/SmartSurfaces.stories.js +147 -64
- package/stories/foundations/Spacing.stories.js +30 -30
- package/stories/foundations/Typography.stories.js +352 -723
- package/stories/foundations/ZIndex.stories.js +124 -141
- package/stories/layout/LayoutOverview.stories.js +345 -250
- package/stories/layout/LayoutSystem.stories.js +60 -76
- package/stories/patterns/InteractiveStates.stories.js +29 -29
- package/stories/patterns/Utilities.stories.js +17 -5
- package/stories/primitives/Alerts.stories.js +6 -6
- package/stories/primitives/Cards.stories.js +22 -11
- package/stories/primitives/{Forms.stories.js → FormElements.stories.js} +20 -11
- package/stories/primitives/HtmlFormElements.stories.js +128 -0
- package/stories/primitives/{FormGroups.stories.js → HtmlFormGroups.stories.js} +70 -21
- package/stories/primitives/Media.stories.js +23 -20
- package/stories/utilities/Backdrop.stories.js +68 -27
- package/stories/utils/PdsAsk.stories.js +15 -14
- 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
- package/stories/components/PdsJsonform.stories.js +0 -1929
- /package/src/{pds-core → node-api}/pds-api.js +0 -0
|
@@ -0,0 +1,4356 @@
|
|
|
1
|
+
import { html, nothing } from "lit";
|
|
2
|
+
import { createComponentDocsPage } from "../reference/reference-docs.js";
|
|
3
|
+
import showdown from "showdown";
|
|
4
|
+
|
|
5
|
+
const markdownConverter = new showdown.Converter({ tables: true });
|
|
6
|
+
|
|
7
|
+
const uiSchemaReferenceMarkdown = `
|
|
8
|
+
# uiSchema Reference
|
|
9
|
+
|
|
10
|
+
Complete reference for all **uiSchema** configuration options in \`pds-form\`.
|
|
11
|
+
|
|
12
|
+
The uiSchema controls *how* fields are rendered while JSON Schema defines *what* data to collect.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Path Syntax
|
|
17
|
+
|
|
18
|
+
Use **slash notation** (JSON Pointer) to target fields:
|
|
19
|
+
|
|
20
|
+
| Target | Path |
|
|
21
|
+
|--------|------|
|
|
22
|
+
| Root form | \`"/"\` or root-level \`ui:*\` keys |
|
|
23
|
+
| Top-level field | \`"/fieldName"\` or \`"fieldName"\` |
|
|
24
|
+
| Nested field | \`"/parent/child"\` or \`"parent/child"\` |
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Root-Level Layout
|
|
29
|
+
|
|
30
|
+
Apply layout to the **entire form** without modifying your JSON Schema:
|
|
31
|
+
|
|
32
|
+
\`\`\`javascript
|
|
33
|
+
const uiSchema = {
|
|
34
|
+
'ui:layout': 'grid',
|
|
35
|
+
'ui:layoutOptions': {
|
|
36
|
+
columns: 2,
|
|
37
|
+
gap: 'md'
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
\`\`\`
|
|
41
|
+
|
|
42
|
+
You can combine root-level layout with field-specific options:
|
|
43
|
+
|
|
44
|
+
\`\`\`javascript
|
|
45
|
+
const uiSchema = {
|
|
46
|
+
// Root form layout
|
|
47
|
+
'ui:layout': 'grid',
|
|
48
|
+
'ui:layoutOptions': { columns: 2, gap: 'md' },
|
|
49
|
+
|
|
50
|
+
// Field-specific options
|
|
51
|
+
'/email': { 'ui:icon': 'envelope' },
|
|
52
|
+
'/bio': { 'ui:widget': 'textarea' }
|
|
53
|
+
};
|
|
54
|
+
\`\`\`
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Widget Selection
|
|
59
|
+
|
|
60
|
+
### \`ui:widget\`
|
|
61
|
+
|
|
62
|
+
Override the automatically selected widget for a field.
|
|
63
|
+
|
|
64
|
+
| Widget | Use For | Notes |
|
|
65
|
+
|--------|---------|-------|
|
|
66
|
+
| \`input-text\` | String | Default for strings |
|
|
67
|
+
| \`textarea\` | String | Multi-line text |
|
|
68
|
+
| \`password\` | String | Masked input |
|
|
69
|
+
| \`input-email\` | String | Email with validation |
|
|
70
|
+
| \`input-url\` | String | URL with validation |
|
|
71
|
+
| \`input-number\` | Number | Default for numbers |
|
|
72
|
+
| \`input-range\` | Number | Slider control |
|
|
73
|
+
| \`input-date\` | String | Date picker |
|
|
74
|
+
| \`input-time\` | String | Time picker |
|
|
75
|
+
| \`input-datetime\` | String | Date + time picker |
|
|
76
|
+
| \`input-color\` | String | Color picker |
|
|
77
|
+
| \`checkbox\` | Boolean | Default for booleans |
|
|
78
|
+
| \`toggle\` | Boolean | Toggle switch |
|
|
79
|
+
| \`select\` | String (enum) | Default for enums |
|
|
80
|
+
| \`radio\` | String (enum) | Radio button group |
|
|
81
|
+
| \`checkbox-group\` | Array (enum items) | Multi-select checkboxes |
|
|
82
|
+
| \`upload\` | String | File upload (pds-upload) |
|
|
83
|
+
| \`richtext\` | String | Rich text editor (pds-richtext) |
|
|
84
|
+
| \`const\` | Any | Read-only display |
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Widget Options
|
|
89
|
+
|
|
90
|
+
### \`ui:options\`
|
|
91
|
+
|
|
92
|
+
Widget-specific configuration object.
|
|
93
|
+
|
|
94
|
+
#### Textarea Options
|
|
95
|
+
| Option | Type | Description |
|
|
96
|
+
|--------|------|-------------|
|
|
97
|
+
| \`rows\` | number | Number of visible rows |
|
|
98
|
+
| \`cols\` | number | Number of visible columns |
|
|
99
|
+
|
|
100
|
+
#### Range Slider Options
|
|
101
|
+
| Option | Type | Description |
|
|
102
|
+
|--------|------|-------------|
|
|
103
|
+
| \`min\` | number | Minimum value |
|
|
104
|
+
| \`max\` | number | Maximum value |
|
|
105
|
+
| \`step\` | number | Step increment |
|
|
106
|
+
|
|
107
|
+
#### Upload Options (pds-upload)
|
|
108
|
+
| Option | Type | Description |
|
|
109
|
+
|--------|------|-------------|
|
|
110
|
+
| \`accept\` | string | MIME types (e.g., \`"image/*"\`) |
|
|
111
|
+
| \`maxSize\` | number | Max file size in bytes |
|
|
112
|
+
| \`multiple\` | boolean | Allow multiple files |
|
|
113
|
+
| \`label\` | string | Upload button label |
|
|
114
|
+
|
|
115
|
+
#### Rich Text Options (pds-richtext)
|
|
116
|
+
| Option | Type | Description |
|
|
117
|
+
|--------|------|-------------|
|
|
118
|
+
| \`toolbar\` | string | \`"minimal"\`, \`"standard"\`, \`"full"\` |
|
|
119
|
+
| \`spellcheck\` | boolean | Enable spellcheck |
|
|
120
|
+
| \`format\` | string | \`"html"\` or \`"markdown"\` for output |
|
|
121
|
+
| \`submitOnEnter\` | boolean | Submit form on Enter |
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Layout Options
|
|
126
|
+
|
|
127
|
+
### \`ui:layout\`
|
|
128
|
+
|
|
129
|
+
Control how nested object fields are arranged.
|
|
130
|
+
|
|
131
|
+
| Value | Description |
|
|
132
|
+
|-------|-------------|
|
|
133
|
+
| \`default\` | Standard vertical fieldset |
|
|
134
|
+
| \`flex\` | Flexbox row layout |
|
|
135
|
+
| \`grid\` | CSS Grid layout |
|
|
136
|
+
| \`accordion\` | Collapsible \`<details>\` sections |
|
|
137
|
+
| \`tabs\` | Tabbed interface (pds-tabstrip) |
|
|
138
|
+
|
|
139
|
+
### \`ui:layoutOptions\`
|
|
140
|
+
|
|
141
|
+
Layout-specific configuration.
|
|
142
|
+
|
|
143
|
+
#### Flex Layout Options
|
|
144
|
+
| Option | Type | Description |
|
|
145
|
+
|--------|------|-------------|
|
|
146
|
+
| \`wrap\` | boolean | Allow wrapping |
|
|
147
|
+
| \`gap\` | string | Gap size: \`"sm"\`, \`"md"\`, \`"lg"\` |
|
|
148
|
+
| \`direction\` | string | \`"row"\` or \`"column"\` |
|
|
149
|
+
|
|
150
|
+
#### Grid Layout Options
|
|
151
|
+
| Option | Type | Description |
|
|
152
|
+
|--------|------|-------------|
|
|
153
|
+
| \`columns\` | number | Number of columns |
|
|
154
|
+
| \`gap\` | string | Gap size: \`"sm"\`, \`"md"\`, \`"lg"\` |
|
|
155
|
+
|
|
156
|
+
#### Accordion Layout Options
|
|
157
|
+
| Option | Type | Description |
|
|
158
|
+
|--------|------|-------------|
|
|
159
|
+
| \`openFirst\` | boolean | First section open by default |
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Field Customization
|
|
164
|
+
|
|
165
|
+
### Display Options
|
|
166
|
+
|
|
167
|
+
| Option | Type | Description |
|
|
168
|
+
|--------|------|-------------|
|
|
169
|
+
| \`ui:help\` | string | Help text below field (overrides \`description\`) |
|
|
170
|
+
| \`ui:placeholder\` | string | Placeholder text (overrides \`examples\`) |
|
|
171
|
+
| \`ui:class\` | string | Additional CSS classes |
|
|
172
|
+
| \`ui:order\` | string[] | Field order within object |
|
|
173
|
+
|
|
174
|
+
### State Options
|
|
175
|
+
|
|
176
|
+
| Option | Type | Description |
|
|
177
|
+
|--------|------|-------------|
|
|
178
|
+
| \`ui:hidden\` | boolean | Hide field from UI (still in data) |
|
|
179
|
+
| \`ui:readonly\` | boolean | Make field read-only |
|
|
180
|
+
| \`ui:disabled\` | boolean | Disable field |
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Icon Enhancement
|
|
185
|
+
|
|
186
|
+
### \`ui:icon\`
|
|
187
|
+
|
|
188
|
+
Add an icon to the input field. Requires \`pds-icon\` component.
|
|
189
|
+
|
|
190
|
+
| Option | Type | Description |
|
|
191
|
+
|--------|------|-------------|
|
|
192
|
+
| \`ui:icon\` | string | Icon name from sprite |
|
|
193
|
+
| \`ui:iconPosition\` | string | \`"start"\` or \`"end"\` |
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Surface Wrapping
|
|
198
|
+
|
|
199
|
+
### \`ui:surface\`
|
|
200
|
+
|
|
201
|
+
Wrap a fieldset in a styled container.
|
|
202
|
+
|
|
203
|
+
| Value | Description |
|
|
204
|
+
|-------|-------------|
|
|
205
|
+
| \`card\` | Card surface |
|
|
206
|
+
| \`elevated\` | Elevated surface with shadow |
|
|
207
|
+
| \`sunken\` | Sunken/inset surface |
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Dialog Forms
|
|
212
|
+
|
|
213
|
+
### \`ui:dialog\`
|
|
214
|
+
|
|
215
|
+
Collect nested object data in a modal dialog instead of inline.
|
|
216
|
+
|
|
217
|
+
| Option | Type | Description |
|
|
218
|
+
|--------|------|-------------|
|
|
219
|
+
| \`ui:dialog\` | boolean | Enable dialog mode |
|
|
220
|
+
|
|
221
|
+
### \`ui:dialogOptions\`
|
|
222
|
+
|
|
223
|
+
| Option | Type | Description |
|
|
224
|
+
|--------|------|-------------|
|
|
225
|
+
| \`buttonLabel\` | string | Trigger button label |
|
|
226
|
+
| \`dialogTitle\` | string | Dialog title |
|
|
227
|
+
| \`icon\` | string | Icon for trigger button |
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Autocomplete
|
|
232
|
+
|
|
233
|
+
### \`ui:datalist\`
|
|
234
|
+
|
|
235
|
+
Provide autocomplete suggestions using native \`<datalist>\`.
|
|
236
|
+
|
|
237
|
+
| Option | Type | Description |
|
|
238
|
+
|--------|------|-------------|
|
|
239
|
+
| \`ui:datalist\` | string[] | Array of suggestion values |
|
|
240
|
+
|
|
241
|
+
### \`ui:autocomplete\`
|
|
242
|
+
|
|
243
|
+
HTML autocomplete attribute hint.
|
|
244
|
+
|
|
245
|
+
| Option | Type | Description |
|
|
246
|
+
|--------|------|-------------|
|
|
247
|
+
| \`ui:autocomplete\` | string | e.g., \`"email"\`, \`"new-password"\`, \`"tel"\` |
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## Enhanced Select
|
|
252
|
+
|
|
253
|
+
### \`ui:dropdown\`
|
|
254
|
+
|
|
255
|
+
Use enhanced dropdown menu instead of native select.
|
|
256
|
+
|
|
257
|
+
| Option | Type | Description |
|
|
258
|
+
|--------|------|-------------|
|
|
259
|
+
| \`ui:dropdown\` | boolean | Enable enhanced dropdown |
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## Conditional Logic (Interactions)
|
|
264
|
+
|
|
265
|
+
Declarative XForms-inspired conditions for dynamic form behavior.
|
|
266
|
+
|
|
267
|
+
### Visibility
|
|
268
|
+
|
|
269
|
+
| Option | Type | Description |
|
|
270
|
+
|--------|------|-------------|
|
|
271
|
+
| \`ui:visibleWhen\` | object | Show field when condition is true |
|
|
272
|
+
| \`ui:hidden\` | boolean | Always hide field |
|
|
273
|
+
|
|
274
|
+
### State
|
|
275
|
+
|
|
276
|
+
| Option | Type | Description |
|
|
277
|
+
|--------|------|-------------|
|
|
278
|
+
| \`ui:disabledWhen\` | object | Disable field when condition is true |
|
|
279
|
+
| \`ui:requiredWhen\` | object | Require field when condition is true |
|
|
280
|
+
|
|
281
|
+
### Calculated Values
|
|
282
|
+
|
|
283
|
+
| Option | Type | Description |
|
|
284
|
+
|--------|------|-------------|
|
|
285
|
+
| \`ui:calculate\` | object | Compute value from expression |
|
|
286
|
+
| \`ui:calculateOverride\` | boolean | Allow user to edit calculated value |
|
|
287
|
+
|
|
288
|
+
### Condition Operators
|
|
289
|
+
|
|
290
|
+
**Comparison**: \`$eq\`, \`$ne\`, \`$gt\`, \`$gte\`, \`$lt\`, \`$lte\`, \`$in\`, \`$nin\`, \`$exists\`, \`$regex\`
|
|
291
|
+
|
|
292
|
+
**Logical**: \`$and\`, \`$or\`, \`$not\`
|
|
293
|
+
|
|
294
|
+
**Calculation**: \`$concat\`, \`$sum\`, \`$subtract\`, \`$multiply\`, \`$divide\`, \`$if\`, \`$coalesce\`
|
|
295
|
+
|
|
296
|
+
### Examples
|
|
297
|
+
|
|
298
|
+
\`\`\`javascript
|
|
299
|
+
// Show field conditionally
|
|
300
|
+
"/otherReason": {
|
|
301
|
+
"ui:visibleWhen": { "/reason": "other" }
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Disable based on condition
|
|
305
|
+
"/email": {
|
|
306
|
+
"ui:disabledWhen": { "/usePhone": true }
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Conditional requirement
|
|
310
|
+
"/companyName": {
|
|
311
|
+
"ui:requiredWhen": { "/accountType": "business" }
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Calculated value
|
|
315
|
+
"/fullName": {
|
|
316
|
+
"ui:calculate": { "$concat": ["/firstName", " ", "/lastName"] }
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Complex condition
|
|
320
|
+
"/premiumFeature": {
|
|
321
|
+
"ui:visibleWhen": {
|
|
322
|
+
"$and": [
|
|
323
|
+
{ "/isPremium": true },
|
|
324
|
+
{ "/country": { "$in": ["US", "CA", "UK"] } }
|
|
325
|
+
]
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
\`\`\`
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## Custom Content Injection
|
|
333
|
+
|
|
334
|
+
Inject custom HTML content before/after fields, create fully custom renderers, or customize field wrappers.
|
|
335
|
+
|
|
336
|
+
### Content Injection
|
|
337
|
+
|
|
338
|
+
| Option | Type | Description |
|
|
339
|
+
|--------|------|-------------|
|
|
340
|
+
| \`ui:before\` | function \| string | Content rendered before the field |
|
|
341
|
+
| \`ui:after\` | function \| string | Content rendered after the field |
|
|
342
|
+
|
|
343
|
+
**Value Types:**
|
|
344
|
+
- **Function**: \`(field) => html\`...\`\` - receives render context
|
|
345
|
+
- **Slot reference**: \`"slot:mySlot"\` - renders slotted element by name
|
|
346
|
+
|
|
347
|
+
### Custom Rendering
|
|
348
|
+
|
|
349
|
+
| Option | Type | Description |
|
|
350
|
+
|--------|------|-------------|
|
|
351
|
+
| \`ui:render\` | function | Complete custom field renderer |
|
|
352
|
+
| \`ui:wrapper\` | function | Custom wrapper around the control |
|
|
353
|
+
|
|
354
|
+
### Render Context (\`field\` object)
|
|
355
|
+
|
|
356
|
+
| Property | Description |
|
|
357
|
+
|----------|-------------|
|
|
358
|
+
| \`id\` | Unique DOM ID for the field |
|
|
359
|
+
| \`path\` | JSON Pointer path (e.g., \`/address/city\`) |
|
|
360
|
+
| \`label\` | Display label |
|
|
361
|
+
| \`value\` | Current field value |
|
|
362
|
+
| \`schema\` | JSON Schema for this field |
|
|
363
|
+
| \`ui\` | UI schema for this path |
|
|
364
|
+
| \`attrs\` | Native constraint attributes |
|
|
365
|
+
| \`get\` | Get value at path: \`get("/otherField")\` |
|
|
366
|
+
| \`set\` | Set value: \`set(newValue)\` |
|
|
367
|
+
| \`host\` | Reference to pds-form element |
|
|
368
|
+
| \`control\` | (wrapper only) Rendered control template |
|
|
369
|
+
| \`help\` | (wrapper only) Rendered help text |
|
|
370
|
+
|
|
371
|
+
### Examples
|
|
372
|
+
|
|
373
|
+
\`\`\`javascript
|
|
374
|
+
// Add content before/after fields
|
|
375
|
+
"/username": {
|
|
376
|
+
"ui:before": (field) => html\`<div class="alert">Section header</div>\`,
|
|
377
|
+
"ui:after": (field) => field.value?.length < 3
|
|
378
|
+
? html\`<small class="text-danger">Too short</small>\`
|
|
379
|
+
: nothing
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Custom star rating widget
|
|
383
|
+
"/rating": {
|
|
384
|
+
"ui:render": (field) => html\`
|
|
385
|
+
<fieldset>
|
|
386
|
+
<legend>\${field.label}</legend>
|
|
387
|
+
<div class="flex gap-xs">
|
|
388
|
+
\${[1,2,3,4,5].map(n => html\`
|
|
389
|
+
<button type="button" @click=\${() => field.set(n)}>
|
|
390
|
+
\${n <= field.value ? '★' : '☆'}
|
|
391
|
+
</button>
|
|
392
|
+
\`)}
|
|
393
|
+
</div>
|
|
394
|
+
</fieldset>
|
|
395
|
+
\`
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Custom wrapper with colored border
|
|
399
|
+
"/email": {
|
|
400
|
+
"ui:wrapper": (field) => html\`
|
|
401
|
+
<div style="border-left: 5px solid var(--color-primary-500); padding-left: var(--spacing-sm);">
|
|
402
|
+
<label for=\${field.id}>
|
|
403
|
+
<span data-label>\${field.label}</span>
|
|
404
|
+
\${field.control}
|
|
405
|
+
</label>
|
|
406
|
+
</div>
|
|
407
|
+
\`
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Use slotted content
|
|
411
|
+
"/terms": {
|
|
412
|
+
"ui:before": "slot:terms-notice"
|
|
413
|
+
}
|
|
414
|
+
\`\`\`
|
|
415
|
+
`;
|
|
416
|
+
|
|
417
|
+
const docsParameters = {
|
|
418
|
+
description: {
|
|
419
|
+
component: `**⭐ Recommended for modern applications** - Automatically generate complete forms from [JSON Schema](https://json-schema.org/) definitions.
|
|
420
|
+
|
|
421
|
+
### Key Features
|
|
422
|
+
- 🎯 **Zero boilerplate** - Define form structure in JSON, get a working form with validation
|
|
423
|
+
- ✅ **Built-in validation** - Automatic validation based on schema rules (required, min/max, patterns, etc.)
|
|
424
|
+
- 🔄 **Data binding** - Two-way data binding with form state management
|
|
425
|
+
- 🎨 **PDS styled** - Uses all PDS design tokens automatically
|
|
426
|
+
- 📱 **Responsive** - Mobile-friendly layouts out of the box
|
|
427
|
+
- 🧩 **Conditional logic** - Show/hide/disable fields, computed values
|
|
428
|
+
- 🌐 **Nested objects** - Support for complex nested data structures
|
|
429
|
+
- 🔧 **Extensible** - Custom field types and validators
|
|
430
|
+
|
|
431
|
+
### Why Generate Forms from JSON Schema?
|
|
432
|
+
Instead of manually writing HTML for every form field, validation rule, and error message, you define your data schema once and get:
|
|
433
|
+
- Form UI generation
|
|
434
|
+
- Client-side validation
|
|
435
|
+
- Server-side validation (same schema)
|
|
436
|
+
- API documentation
|
|
437
|
+
- Type definitions
|
|
438
|
+
- Database schemas
|
|
439
|
+
|
|
440
|
+
See the examples below to get started, or check the [primitive forms](/story/primitives-form-elements--default) for manual form building.
|
|
441
|
+
`,
|
|
442
|
+
},
|
|
443
|
+
page: createComponentDocsPage("pds-form", {
|
|
444
|
+
hideStories: true,
|
|
445
|
+
additionalContent: markdownConverter.makeHtml(uiSchemaReferenceMarkdown),
|
|
446
|
+
}),
|
|
447
|
+
toc: true,
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
export default {
|
|
451
|
+
title: "Components/pds-form",
|
|
452
|
+
tags: ["autodocs", "forms", "json-schema", "validation", "input"],
|
|
453
|
+
parameters: {
|
|
454
|
+
pds: {
|
|
455
|
+
tags: [
|
|
456
|
+
"forms",
|
|
457
|
+
"json-schema",
|
|
458
|
+
"validation",
|
|
459
|
+
"input",
|
|
460
|
+
"pds-form",
|
|
461
|
+
"interaction",
|
|
462
|
+
],
|
|
463
|
+
},
|
|
464
|
+
docs: docsParameters,
|
|
465
|
+
},
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
const simpleSchema = {
|
|
469
|
+
type: "object",
|
|
470
|
+
properties: {
|
|
471
|
+
name: {
|
|
472
|
+
type: "string",
|
|
473
|
+
title: "Full Name",
|
|
474
|
+
examples: ["John Doe"],
|
|
475
|
+
},
|
|
476
|
+
email: {
|
|
477
|
+
type: "string",
|
|
478
|
+
format: "email",
|
|
479
|
+
title: "Email Address",
|
|
480
|
+
examples: ["john.doe@example.com"],
|
|
481
|
+
},
|
|
482
|
+
age: {
|
|
483
|
+
type: "number",
|
|
484
|
+
title: "Age",
|
|
485
|
+
minimum: 18,
|
|
486
|
+
examples: [25],
|
|
487
|
+
},
|
|
488
|
+
newsletter: {
|
|
489
|
+
type: "boolean",
|
|
490
|
+
title: "Subscribe to newsletter",
|
|
491
|
+
},
|
|
492
|
+
},
|
|
493
|
+
required: ["name", "email"],
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
const complexSchema = {
|
|
497
|
+
type: "object",
|
|
498
|
+
properties: {
|
|
499
|
+
personalInfo: {
|
|
500
|
+
type: "object",
|
|
501
|
+
title: "Personal Information",
|
|
502
|
+
properties: {
|
|
503
|
+
firstName: { type: "string", title: "First Name", examples: ["John"] },
|
|
504
|
+
lastName: { type: "string", title: "Last Name", examples: ["Doe"] },
|
|
505
|
+
dateOfBirth: { type: "string", format: "date", title: "Date of Birth" },
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
address: {
|
|
509
|
+
type: "object",
|
|
510
|
+
title: "Address",
|
|
511
|
+
properties: {
|
|
512
|
+
street: {
|
|
513
|
+
type: "string",
|
|
514
|
+
title: "Street",
|
|
515
|
+
examples: ["123 Main Street"],
|
|
516
|
+
},
|
|
517
|
+
city: { type: "string", title: "City", examples: ["New York"] },
|
|
518
|
+
country: {
|
|
519
|
+
type: "string",
|
|
520
|
+
title: "Country",
|
|
521
|
+
enum: ["USA", "UK", "Canada", "Australia"],
|
|
522
|
+
},
|
|
523
|
+
},
|
|
524
|
+
},
|
|
525
|
+
preferences: {
|
|
526
|
+
type: "array",
|
|
527
|
+
title: "Interests",
|
|
528
|
+
items: {
|
|
529
|
+
type: "string",
|
|
530
|
+
enum: ["Technology", "Sports", "Music", "Travel", "Reading"],
|
|
531
|
+
},
|
|
532
|
+
},
|
|
533
|
+
},
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
export const SimpleForm = {
|
|
537
|
+
render: () => {
|
|
538
|
+
return html`
|
|
539
|
+
<pds-form
|
|
540
|
+
data-required
|
|
541
|
+
.jsonSchema=${simpleSchema}
|
|
542
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
543
|
+
></pds-form>
|
|
544
|
+
`;
|
|
545
|
+
},
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
export const ComplexForm = {
|
|
549
|
+
render: () => {
|
|
550
|
+
return html`
|
|
551
|
+
<pds-form
|
|
552
|
+
.jsonSchema=${complexSchema}
|
|
553
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
554
|
+
></pds-form>
|
|
555
|
+
`;
|
|
556
|
+
},
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
export const WithInitialData = {
|
|
560
|
+
render: () => {
|
|
561
|
+
const initialValues = {
|
|
562
|
+
name: "John Doe",
|
|
563
|
+
email: "john@example.com",
|
|
564
|
+
age: 25,
|
|
565
|
+
newsletter: true,
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
return html`
|
|
569
|
+
<pds-form
|
|
570
|
+
.jsonSchema=${simpleSchema}
|
|
571
|
+
.values=${initialValues}
|
|
572
|
+
@pw:value-change=${(e) => console.log("🔄 Value changed:", e.detail)}
|
|
573
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
574
|
+
></pds-form>
|
|
575
|
+
`;
|
|
576
|
+
},
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
export const WithTogglesSwitches = {
|
|
580
|
+
name: "Toggles & Switches",
|
|
581
|
+
render: () => {
|
|
582
|
+
const schema = {
|
|
583
|
+
type: "object",
|
|
584
|
+
properties: {
|
|
585
|
+
toggles: {
|
|
586
|
+
type: "object",
|
|
587
|
+
title: "Preferences",
|
|
588
|
+
properties: {
|
|
589
|
+
emailNotifications: {
|
|
590
|
+
type: "boolean",
|
|
591
|
+
title: "Email Notifications",
|
|
592
|
+
description: "Receive notifications via email",
|
|
593
|
+
},
|
|
594
|
+
pushNotifications: {
|
|
595
|
+
type: "boolean",
|
|
596
|
+
title: "Push Notifications",
|
|
597
|
+
description: "Receive push notifications on your device",
|
|
598
|
+
},
|
|
599
|
+
darkMode: {
|
|
600
|
+
type: "boolean",
|
|
601
|
+
title: "Dark Mode",
|
|
602
|
+
description: "Enable dark theme",
|
|
603
|
+
},
|
|
604
|
+
autoSave: {
|
|
605
|
+
type: "boolean",
|
|
606
|
+
title: "Auto-save",
|
|
607
|
+
description: "Automatically save changes",
|
|
608
|
+
},
|
|
609
|
+
},
|
|
610
|
+
},
|
|
611
|
+
},
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
const uiSchema = {
|
|
615
|
+
toggles: {
|
|
616
|
+
"ui:layout": "flex",
|
|
617
|
+
"ui:layoutOptions": {
|
|
618
|
+
direction: "column",
|
|
619
|
+
gap: "md",
|
|
620
|
+
},
|
|
621
|
+
},
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
const options = {
|
|
625
|
+
widgets: {
|
|
626
|
+
booleans: "toggle", // Use toggle switches instead of checkboxes
|
|
627
|
+
},
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
return html`
|
|
631
|
+
<pds-form
|
|
632
|
+
.jsonSchema=${schema}
|
|
633
|
+
.uiSchema=${uiSchema}
|
|
634
|
+
.options=${options}
|
|
635
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
636
|
+
></pds-form>
|
|
637
|
+
`;
|
|
638
|
+
},
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
export const WithRangeSliders = {
|
|
642
|
+
name: "Range Sliders with Output",
|
|
643
|
+
render: () => {
|
|
644
|
+
const schema = {
|
|
645
|
+
type: "object",
|
|
646
|
+
properties: {
|
|
647
|
+
volume: {
|
|
648
|
+
type: "number",
|
|
649
|
+
title: "Volume",
|
|
650
|
+
minimum: 0,
|
|
651
|
+
maximum: 100,
|
|
652
|
+
default: 50,
|
|
653
|
+
},
|
|
654
|
+
brightness: {
|
|
655
|
+
type: "number",
|
|
656
|
+
title: "Brightness",
|
|
657
|
+
minimum: 0,
|
|
658
|
+
maximum: 100,
|
|
659
|
+
default: 75,
|
|
660
|
+
},
|
|
661
|
+
fontSize: {
|
|
662
|
+
type: "number",
|
|
663
|
+
title: "Font Size",
|
|
664
|
+
minimum: 10,
|
|
665
|
+
maximum: 24,
|
|
666
|
+
default: 16,
|
|
667
|
+
},
|
|
668
|
+
quality: {
|
|
669
|
+
type: "integer",
|
|
670
|
+
title: "Quality",
|
|
671
|
+
minimum: 1,
|
|
672
|
+
maximum: 10,
|
|
673
|
+
default: 7,
|
|
674
|
+
},
|
|
675
|
+
},
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
const uiSchema = {
|
|
679
|
+
volume: { "ui:widget": "input-range" },
|
|
680
|
+
brightness: { "ui:widget": "input-range" },
|
|
681
|
+
fontSize: { "ui:widget": "input-range" },
|
|
682
|
+
quality: { "ui:widget": "input-range" },
|
|
683
|
+
};
|
|
684
|
+
|
|
685
|
+
const options = {
|
|
686
|
+
enhancements: {
|
|
687
|
+
rangeOutput: true, // Add live value display
|
|
688
|
+
},
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
return html`
|
|
692
|
+
<pds-form
|
|
693
|
+
.jsonSchema=${schema}
|
|
694
|
+
.uiSchema=${uiSchema}
|
|
695
|
+
.options=${options}
|
|
696
|
+
@pw:value-change=${(e) => console.log("🎚️ Value changed:", e.detail)}
|
|
697
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
698
|
+
></pds-form>
|
|
699
|
+
`;
|
|
700
|
+
},
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
export const WithIcons = {
|
|
704
|
+
name: "Icon-Enhanced Inputs",
|
|
705
|
+
render: () => {
|
|
706
|
+
const schema = {
|
|
707
|
+
type: "object",
|
|
708
|
+
properties: {
|
|
709
|
+
username: {
|
|
710
|
+
type: "string",
|
|
711
|
+
title: "Username",
|
|
712
|
+
examples: ["Enter your username"],
|
|
713
|
+
},
|
|
714
|
+
email: {
|
|
715
|
+
type: "string",
|
|
716
|
+
format: "email",
|
|
717
|
+
title: "Email",
|
|
718
|
+
examples: ["your.email@example.com"],
|
|
719
|
+
},
|
|
720
|
+
password: {
|
|
721
|
+
type: "string",
|
|
722
|
+
title: "Password",
|
|
723
|
+
examples: ["••••••••"],
|
|
724
|
+
},
|
|
725
|
+
website: {
|
|
726
|
+
type: "string",
|
|
727
|
+
format: "uri",
|
|
728
|
+
title: "Website",
|
|
729
|
+
examples: ["https://yourwebsite.com"],
|
|
730
|
+
},
|
|
731
|
+
phone: {
|
|
732
|
+
type: "string",
|
|
733
|
+
title: "Phone",
|
|
734
|
+
examples: ["+1 (555) 123-4567"],
|
|
735
|
+
},
|
|
736
|
+
location: {
|
|
737
|
+
type: "string",
|
|
738
|
+
title: "Location",
|
|
739
|
+
examples: ["City, Country"],
|
|
740
|
+
},
|
|
741
|
+
},
|
|
742
|
+
required: ["username", "email", "password"],
|
|
743
|
+
};
|
|
744
|
+
|
|
745
|
+
const uiSchema = {
|
|
746
|
+
username: {
|
|
747
|
+
"ui:icon": "user",
|
|
748
|
+
"ui:iconPosition": "start",
|
|
749
|
+
},
|
|
750
|
+
email: {
|
|
751
|
+
"ui:icon": "envelope",
|
|
752
|
+
"ui:iconPosition": "start",
|
|
753
|
+
},
|
|
754
|
+
password: {
|
|
755
|
+
"ui:icon": "lock",
|
|
756
|
+
"ui:iconPosition": "start",
|
|
757
|
+
"ui:widget": "password",
|
|
758
|
+
},
|
|
759
|
+
website: {
|
|
760
|
+
"ui:icon": "globe",
|
|
761
|
+
"ui:iconPosition": "start",
|
|
762
|
+
},
|
|
763
|
+
phone: {
|
|
764
|
+
"ui:icon": "phone",
|
|
765
|
+
"ui:iconPosition": "start",
|
|
766
|
+
},
|
|
767
|
+
location: {
|
|
768
|
+
"ui:icon": "map-pin",
|
|
769
|
+
"ui:iconPosition": "start",
|
|
770
|
+
},
|
|
771
|
+
};
|
|
772
|
+
|
|
773
|
+
return html`
|
|
774
|
+
<pds-form
|
|
775
|
+
.jsonSchema=${schema}
|
|
776
|
+
.uiSchema=${uiSchema}
|
|
777
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
778
|
+
></pds-form>
|
|
779
|
+
`;
|
|
780
|
+
},
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
export const WithPdsUpload = {
|
|
784
|
+
name: "File Upload (pds-upload)",
|
|
785
|
+
render: () => {
|
|
786
|
+
const schema = {
|
|
787
|
+
type: "object",
|
|
788
|
+
properties: {
|
|
789
|
+
profilePicture: {
|
|
790
|
+
type: "string",
|
|
791
|
+
title: "Profile Picture",
|
|
792
|
+
description: "Upload your profile photo (JPG, PNG)",
|
|
793
|
+
contentMediaType: "image/*",
|
|
794
|
+
contentEncoding: "base64",
|
|
795
|
+
},
|
|
796
|
+
resume: {
|
|
797
|
+
type: "string",
|
|
798
|
+
title: "Resume",
|
|
799
|
+
description: "Upload your resume (PDF)",
|
|
800
|
+
contentMediaType: "application/pdf",
|
|
801
|
+
contentEncoding: "base64",
|
|
802
|
+
},
|
|
803
|
+
portfolio: {
|
|
804
|
+
type: "string",
|
|
805
|
+
title: "Portfolio Files",
|
|
806
|
+
description: "Upload multiple portfolio items",
|
|
807
|
+
contentMediaType: "image/*,application/pdf",
|
|
808
|
+
contentEncoding: "base64",
|
|
809
|
+
},
|
|
810
|
+
},
|
|
811
|
+
};
|
|
812
|
+
|
|
813
|
+
const uiSchema = {
|
|
814
|
+
profilePicture: {
|
|
815
|
+
"ui:options": {
|
|
816
|
+
accept: "image/jpeg,image/png",
|
|
817
|
+
maxSize: 5242880, // 5MB
|
|
818
|
+
label: "Choose photo",
|
|
819
|
+
},
|
|
820
|
+
},
|
|
821
|
+
resume: {
|
|
822
|
+
"ui:options": {
|
|
823
|
+
accept: "application/pdf",
|
|
824
|
+
maxSize: 10485760, // 10MB
|
|
825
|
+
label: "Choose PDF",
|
|
826
|
+
},
|
|
827
|
+
},
|
|
828
|
+
portfolio: {
|
|
829
|
+
"ui:options": {
|
|
830
|
+
multiple: true,
|
|
831
|
+
accept: "image/*,application/pdf",
|
|
832
|
+
label: "Choose files",
|
|
833
|
+
},
|
|
834
|
+
},
|
|
835
|
+
};
|
|
836
|
+
|
|
837
|
+
return html`
|
|
838
|
+
<pds-form
|
|
839
|
+
.jsonSchema=${schema}
|
|
840
|
+
.uiSchema=${uiSchema}
|
|
841
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
842
|
+
></pds-form>
|
|
843
|
+
`;
|
|
844
|
+
},
|
|
845
|
+
};
|
|
846
|
+
|
|
847
|
+
export const WithPdsRichtext = {
|
|
848
|
+
name: "Rich Text Editor (pds-richtext)",
|
|
849
|
+
render: () => {
|
|
850
|
+
const schema = {
|
|
851
|
+
type: "object",
|
|
852
|
+
properties: {
|
|
853
|
+
bio: {
|
|
854
|
+
type: "string",
|
|
855
|
+
title: "Biography",
|
|
856
|
+
description: "Tell us about yourself",
|
|
857
|
+
examples: ["Write your biography..."],
|
|
858
|
+
},
|
|
859
|
+
coverLetter: {
|
|
860
|
+
type: "string",
|
|
861
|
+
title: "Cover Letter",
|
|
862
|
+
description: "Write your cover letter",
|
|
863
|
+
examples: ["Write your cover letter..."],
|
|
864
|
+
},
|
|
865
|
+
jobDescription: {
|
|
866
|
+
type: "string",
|
|
867
|
+
title: "Job Description",
|
|
868
|
+
examples: ["Describe the position..."],
|
|
869
|
+
},
|
|
870
|
+
},
|
|
871
|
+
};
|
|
872
|
+
|
|
873
|
+
const uiSchema = {
|
|
874
|
+
bio: {
|
|
875
|
+
"ui:widget": "richtext",
|
|
876
|
+
"ui:options": {
|
|
877
|
+
toolbar: "minimal",
|
|
878
|
+
},
|
|
879
|
+
},
|
|
880
|
+
coverLetter: {
|
|
881
|
+
"ui:widget": "richtext",
|
|
882
|
+
"ui:options": {
|
|
883
|
+
toolbar: "standard",
|
|
884
|
+
},
|
|
885
|
+
},
|
|
886
|
+
jobDescription: {
|
|
887
|
+
"ui:widget": "richtext",
|
|
888
|
+
"ui:options": {
|
|
889
|
+
toolbar: "full",
|
|
890
|
+
},
|
|
891
|
+
},
|
|
892
|
+
};
|
|
893
|
+
|
|
894
|
+
return html`
|
|
895
|
+
<pds-form
|
|
896
|
+
.jsonSchema=${schema}
|
|
897
|
+
.uiSchema=${uiSchema}
|
|
898
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
899
|
+
></pds-form>
|
|
900
|
+
`;
|
|
901
|
+
},
|
|
902
|
+
};
|
|
903
|
+
|
|
904
|
+
export const WithFlexLayout = {
|
|
905
|
+
name: "Flex Layout",
|
|
906
|
+
render: () => {
|
|
907
|
+
const schema = {
|
|
908
|
+
type: "object",
|
|
909
|
+
properties: {
|
|
910
|
+
contactInfo: {
|
|
911
|
+
type: "object",
|
|
912
|
+
title: "Contact Information",
|
|
913
|
+
properties: {
|
|
914
|
+
firstName: {
|
|
915
|
+
type: "string",
|
|
916
|
+
title: "First Name",
|
|
917
|
+
examples: ["Jane"],
|
|
918
|
+
},
|
|
919
|
+
lastName: {
|
|
920
|
+
type: "string",
|
|
921
|
+
title: "Last Name",
|
|
922
|
+
examples: ["Smith"],
|
|
923
|
+
},
|
|
924
|
+
email: {
|
|
925
|
+
type: "string",
|
|
926
|
+
format: "email",
|
|
927
|
+
title: "Email",
|
|
928
|
+
examples: ["jane.smith@example.com"],
|
|
929
|
+
},
|
|
930
|
+
phone: {
|
|
931
|
+
type: "string",
|
|
932
|
+
title: "Phone",
|
|
933
|
+
examples: ["+1 (555) 987-6543"],
|
|
934
|
+
},
|
|
935
|
+
},
|
|
936
|
+
},
|
|
937
|
+
},
|
|
938
|
+
};
|
|
939
|
+
|
|
940
|
+
const uiSchema = {
|
|
941
|
+
contactInfo: {
|
|
942
|
+
"ui:layout": "flex",
|
|
943
|
+
"ui:layoutOptions": {
|
|
944
|
+
gap: "md",
|
|
945
|
+
wrap: true,
|
|
946
|
+
direction: "row",
|
|
947
|
+
},
|
|
948
|
+
},
|
|
949
|
+
};
|
|
950
|
+
|
|
951
|
+
return html`
|
|
952
|
+
<pds-form
|
|
953
|
+
.jsonSchema=${schema}
|
|
954
|
+
.uiSchema=${uiSchema}
|
|
955
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
956
|
+
></pds-form>
|
|
957
|
+
`;
|
|
958
|
+
},
|
|
959
|
+
};
|
|
960
|
+
|
|
961
|
+
export const WithGridLayout = {
|
|
962
|
+
name: "Grid Layout",
|
|
963
|
+
render: () => {
|
|
964
|
+
const schema = {
|
|
965
|
+
type: "object",
|
|
966
|
+
properties: {
|
|
967
|
+
productInfo: {
|
|
968
|
+
type: "object",
|
|
969
|
+
title: "Product Information",
|
|
970
|
+
properties: {
|
|
971
|
+
name: {
|
|
972
|
+
type: "string",
|
|
973
|
+
title: "Product Name",
|
|
974
|
+
examples: ["Wireless Headphones"],
|
|
975
|
+
},
|
|
976
|
+
sku: { type: "string", title: "SKU", examples: ["WH-1000XM4"] },
|
|
977
|
+
price: { type: "number", title: "Price", examples: [299.99] },
|
|
978
|
+
quantity: { type: "integer", title: "Quantity", examples: [50] },
|
|
979
|
+
category: {
|
|
980
|
+
type: "string",
|
|
981
|
+
title: "Category",
|
|
982
|
+
enum: [
|
|
983
|
+
"Electronics",
|
|
984
|
+
"Clothing",
|
|
985
|
+
"Books",
|
|
986
|
+
"Home",
|
|
987
|
+
"Sports",
|
|
988
|
+
"Garden",
|
|
989
|
+
],
|
|
990
|
+
},
|
|
991
|
+
brand: { type: "string", title: "Brand", examples: ["Sony"] },
|
|
992
|
+
weight: { type: "number", title: "Weight (kg)", examples: [0.25] },
|
|
993
|
+
dimensions: {
|
|
994
|
+
type: "string",
|
|
995
|
+
title: "Dimensions",
|
|
996
|
+
examples: ["20 x 18 x 8 cm"],
|
|
997
|
+
},
|
|
998
|
+
},
|
|
999
|
+
},
|
|
1000
|
+
},
|
|
1001
|
+
};
|
|
1002
|
+
|
|
1003
|
+
const uiSchema = {
|
|
1004
|
+
productInfo: {
|
|
1005
|
+
"ui:layout": "grid",
|
|
1006
|
+
"ui:layoutOptions": {
|
|
1007
|
+
columns: 3,
|
|
1008
|
+
gap: "md",
|
|
1009
|
+
},
|
|
1010
|
+
},
|
|
1011
|
+
};
|
|
1012
|
+
|
|
1013
|
+
return html`
|
|
1014
|
+
<pds-form
|
|
1015
|
+
.jsonSchema=${schema}
|
|
1016
|
+
.uiSchema=${uiSchema}
|
|
1017
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
1018
|
+
></pds-form>
|
|
1019
|
+
`;
|
|
1020
|
+
},
|
|
1021
|
+
};
|
|
1022
|
+
|
|
1023
|
+
export const WithAccordionLayout = {
|
|
1024
|
+
name: "Accordion Layout",
|
|
1025
|
+
render: () => {
|
|
1026
|
+
const schema = {
|
|
1027
|
+
type: "object",
|
|
1028
|
+
properties: {
|
|
1029
|
+
name: {
|
|
1030
|
+
type: "string",
|
|
1031
|
+
title: "Full Name",
|
|
1032
|
+
examples: ["Alex Rodriguez"],
|
|
1033
|
+
},
|
|
1034
|
+
email: {
|
|
1035
|
+
type: "string",
|
|
1036
|
+
format: "email",
|
|
1037
|
+
title: "Email",
|
|
1038
|
+
examples: ["alex.rodriguez@example.com"],
|
|
1039
|
+
},
|
|
1040
|
+
settings: {
|
|
1041
|
+
type: "object",
|
|
1042
|
+
title: "Settings",
|
|
1043
|
+
properties: {
|
|
1044
|
+
displaySettings: {
|
|
1045
|
+
type: "object",
|
|
1046
|
+
title: "Display Settings",
|
|
1047
|
+
properties: {
|
|
1048
|
+
theme: {
|
|
1049
|
+
type: "string",
|
|
1050
|
+
title: "Theme",
|
|
1051
|
+
enum: ["Light", "Dark", "Auto"],
|
|
1052
|
+
default: "Auto",
|
|
1053
|
+
},
|
|
1054
|
+
fontSize: {
|
|
1055
|
+
type: "number",
|
|
1056
|
+
title: "Font Size (px)",
|
|
1057
|
+
minimum: 12,
|
|
1058
|
+
maximum: 24,
|
|
1059
|
+
default: 16,
|
|
1060
|
+
},
|
|
1061
|
+
density: {
|
|
1062
|
+
type: "string",
|
|
1063
|
+
title: "Density",
|
|
1064
|
+
enum: ["Compact", "Comfortable", "Spacious"],
|
|
1065
|
+
default: "Comfortable",
|
|
1066
|
+
},
|
|
1067
|
+
animations: {
|
|
1068
|
+
type: "boolean",
|
|
1069
|
+
title: "Enable Animations",
|
|
1070
|
+
default: true,
|
|
1071
|
+
},
|
|
1072
|
+
},
|
|
1073
|
+
},
|
|
1074
|
+
notificationSettings: {
|
|
1075
|
+
type: "object",
|
|
1076
|
+
title: "Notification Settings",
|
|
1077
|
+
properties: {
|
|
1078
|
+
email: {
|
|
1079
|
+
type: "boolean",
|
|
1080
|
+
title: "Email Notifications",
|
|
1081
|
+
default: true,
|
|
1082
|
+
},
|
|
1083
|
+
push: {
|
|
1084
|
+
type: "boolean",
|
|
1085
|
+
title: "Push Notifications",
|
|
1086
|
+
default: false,
|
|
1087
|
+
},
|
|
1088
|
+
sms: {
|
|
1089
|
+
type: "boolean",
|
|
1090
|
+
title: "SMS Notifications",
|
|
1091
|
+
default: false,
|
|
1092
|
+
},
|
|
1093
|
+
frequency: {
|
|
1094
|
+
type: "string",
|
|
1095
|
+
title: "Frequency",
|
|
1096
|
+
enum: ["Real-time", "Daily", "Weekly"],
|
|
1097
|
+
default: "Daily",
|
|
1098
|
+
},
|
|
1099
|
+
},
|
|
1100
|
+
},
|
|
1101
|
+
privacySettings: {
|
|
1102
|
+
type: "object",
|
|
1103
|
+
title: "Privacy Settings",
|
|
1104
|
+
properties: {
|
|
1105
|
+
profileVisibility: {
|
|
1106
|
+
type: "string",
|
|
1107
|
+
title: "Profile Visibility",
|
|
1108
|
+
enum: ["Public", "Friends", "Private"],
|
|
1109
|
+
default: "Friends",
|
|
1110
|
+
},
|
|
1111
|
+
showEmail: {
|
|
1112
|
+
type: "boolean",
|
|
1113
|
+
title: "Show Email",
|
|
1114
|
+
default: false,
|
|
1115
|
+
},
|
|
1116
|
+
showActivity: {
|
|
1117
|
+
type: "boolean",
|
|
1118
|
+
title: "Show Activity",
|
|
1119
|
+
default: true,
|
|
1120
|
+
},
|
|
1121
|
+
allowMessages: {
|
|
1122
|
+
type: "boolean",
|
|
1123
|
+
title: "Allow Messages",
|
|
1124
|
+
default: true,
|
|
1125
|
+
},
|
|
1126
|
+
},
|
|
1127
|
+
},
|
|
1128
|
+
},
|
|
1129
|
+
},
|
|
1130
|
+
},
|
|
1131
|
+
required: ["name", "email"],
|
|
1132
|
+
};
|
|
1133
|
+
|
|
1134
|
+
const uiSchema = {
|
|
1135
|
+
settings: { "ui:layout": "accordion" },
|
|
1136
|
+
};
|
|
1137
|
+
|
|
1138
|
+
return html`
|
|
1139
|
+
<pds-form
|
|
1140
|
+
.jsonSchema=${schema}
|
|
1141
|
+
.uiSchema=${uiSchema}
|
|
1142
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
1143
|
+
></pds-form>
|
|
1144
|
+
`;
|
|
1145
|
+
},
|
|
1146
|
+
};
|
|
1147
|
+
|
|
1148
|
+
export const WithTabsLayout = {
|
|
1149
|
+
name: "Tabs Layout (pds-tabstrip)",
|
|
1150
|
+
render: () => {
|
|
1151
|
+
const schema = {
|
|
1152
|
+
type: "object",
|
|
1153
|
+
properties: {
|
|
1154
|
+
userSettings: {
|
|
1155
|
+
type: "object",
|
|
1156
|
+
title: "User Settings",
|
|
1157
|
+
properties: {
|
|
1158
|
+
account: {
|
|
1159
|
+
type: "object",
|
|
1160
|
+
title: "Account",
|
|
1161
|
+
properties: {
|
|
1162
|
+
username: {
|
|
1163
|
+
type: "string",
|
|
1164
|
+
title: "Username",
|
|
1165
|
+
examples: ["coolguy123"],
|
|
1166
|
+
},
|
|
1167
|
+
email: {
|
|
1168
|
+
type: "string",
|
|
1169
|
+
format: "email",
|
|
1170
|
+
title: "Email",
|
|
1171
|
+
examples: ["user@example.com"],
|
|
1172
|
+
},
|
|
1173
|
+
password: {
|
|
1174
|
+
type: "string",
|
|
1175
|
+
title: "New Password",
|
|
1176
|
+
examples: ["••••••••"],
|
|
1177
|
+
},
|
|
1178
|
+
},
|
|
1179
|
+
},
|
|
1180
|
+
profile: {
|
|
1181
|
+
type: "object",
|
|
1182
|
+
title: "Profile",
|
|
1183
|
+
properties: {
|
|
1184
|
+
displayName: {
|
|
1185
|
+
type: "string",
|
|
1186
|
+
title: "Display Name",
|
|
1187
|
+
examples: ["Cool Guy"],
|
|
1188
|
+
},
|
|
1189
|
+
bio: {
|
|
1190
|
+
type: "string",
|
|
1191
|
+
title: "Bio",
|
|
1192
|
+
examples: ["Tell us about yourself..."],
|
|
1193
|
+
},
|
|
1194
|
+
location: {
|
|
1195
|
+
type: "string",
|
|
1196
|
+
title: "Location",
|
|
1197
|
+
examples: ["San Francisco, CA"],
|
|
1198
|
+
},
|
|
1199
|
+
website: {
|
|
1200
|
+
type: "string",
|
|
1201
|
+
format: "uri",
|
|
1202
|
+
title: "Website",
|
|
1203
|
+
examples: ["https://mywebsite.com"],
|
|
1204
|
+
},
|
|
1205
|
+
},
|
|
1206
|
+
},
|
|
1207
|
+
privacy: {
|
|
1208
|
+
type: "object",
|
|
1209
|
+
title: "Privacy",
|
|
1210
|
+
properties: {
|
|
1211
|
+
publicProfile: { type: "boolean", title: "Public Profile" },
|
|
1212
|
+
showEmail: { type: "boolean", title: "Show Email" },
|
|
1213
|
+
allowMessages: { type: "boolean", title: "Allow Messages" },
|
|
1214
|
+
searchable: { type: "boolean", title: "Searchable" },
|
|
1215
|
+
},
|
|
1216
|
+
},
|
|
1217
|
+
},
|
|
1218
|
+
},
|
|
1219
|
+
},
|
|
1220
|
+
};
|
|
1221
|
+
|
|
1222
|
+
const uiSchema = {
|
|
1223
|
+
userSettings: { "ui:layout": "tabs" },
|
|
1224
|
+
};
|
|
1225
|
+
|
|
1226
|
+
return html`
|
|
1227
|
+
<pds-form
|
|
1228
|
+
.jsonSchema=${schema}
|
|
1229
|
+
.uiSchema=${uiSchema}
|
|
1230
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
1231
|
+
></pds-form>
|
|
1232
|
+
`;
|
|
1233
|
+
},
|
|
1234
|
+
};
|
|
1235
|
+
|
|
1236
|
+
export const WithSurfaces = {
|
|
1237
|
+
name: "Surface Wrapping (Cards)",
|
|
1238
|
+
render: () => {
|
|
1239
|
+
const schema = {
|
|
1240
|
+
type: "object",
|
|
1241
|
+
properties: {
|
|
1242
|
+
cardGroups: {
|
|
1243
|
+
type: "object",
|
|
1244
|
+
title: "Product Catalog",
|
|
1245
|
+
properties: {
|
|
1246
|
+
product1: {
|
|
1247
|
+
type: "object",
|
|
1248
|
+
title: "Premium Membership",
|
|
1249
|
+
properties: {
|
|
1250
|
+
name: {
|
|
1251
|
+
type: "string",
|
|
1252
|
+
title: "Product Name",
|
|
1253
|
+
default: "Premium Plan",
|
|
1254
|
+
examples: ["Premium Plan"],
|
|
1255
|
+
},
|
|
1256
|
+
price: {
|
|
1257
|
+
type: "number",
|
|
1258
|
+
title: "Price (USD)",
|
|
1259
|
+
default: 29.99,
|
|
1260
|
+
minimum: 0,
|
|
1261
|
+
examples: [29.99],
|
|
1262
|
+
},
|
|
1263
|
+
billing: {
|
|
1264
|
+
type: "string",
|
|
1265
|
+
title: "Billing Cycle",
|
|
1266
|
+
enum: ["Monthly", "Quarterly", "Yearly"],
|
|
1267
|
+
default: "Monthly",
|
|
1268
|
+
},
|
|
1269
|
+
autoRenew: {
|
|
1270
|
+
type: "boolean",
|
|
1271
|
+
title: "Auto-Renew",
|
|
1272
|
+
default: true,
|
|
1273
|
+
},
|
|
1274
|
+
},
|
|
1275
|
+
},
|
|
1276
|
+
product2: {
|
|
1277
|
+
type: "object",
|
|
1278
|
+
title: "Enterprise Solution",
|
|
1279
|
+
properties: {
|
|
1280
|
+
name: {
|
|
1281
|
+
type: "string",
|
|
1282
|
+
title: "Product Name",
|
|
1283
|
+
default: "Enterprise",
|
|
1284
|
+
examples: ["Enterprise"],
|
|
1285
|
+
},
|
|
1286
|
+
seats: {
|
|
1287
|
+
type: "integer",
|
|
1288
|
+
title: "Number of Seats",
|
|
1289
|
+
default: 10,
|
|
1290
|
+
minimum: 1,
|
|
1291
|
+
examples: [10],
|
|
1292
|
+
},
|
|
1293
|
+
support: {
|
|
1294
|
+
type: "string",
|
|
1295
|
+
title: "Support Level",
|
|
1296
|
+
enum: ["Standard", "Priority", "24/7"],
|
|
1297
|
+
default: "Priority",
|
|
1298
|
+
},
|
|
1299
|
+
sla: { type: "boolean", title: "SLA Agreement", default: true },
|
|
1300
|
+
},
|
|
1301
|
+
},
|
|
1302
|
+
product3: {
|
|
1303
|
+
type: "object",
|
|
1304
|
+
title: "Developer Tools",
|
|
1305
|
+
properties: {
|
|
1306
|
+
name: {
|
|
1307
|
+
type: "string",
|
|
1308
|
+
title: "Product Name",
|
|
1309
|
+
default: "Dev Tools Pro",
|
|
1310
|
+
examples: ["Dev Tools Pro"],
|
|
1311
|
+
},
|
|
1312
|
+
apiCalls: {
|
|
1313
|
+
type: "integer",
|
|
1314
|
+
title: "API Calls/Month",
|
|
1315
|
+
default: 100000,
|
|
1316
|
+
minimum: 1000,
|
|
1317
|
+
examples: [100000],
|
|
1318
|
+
},
|
|
1319
|
+
environments: {
|
|
1320
|
+
type: "integer",
|
|
1321
|
+
title: "Environments",
|
|
1322
|
+
default: 3,
|
|
1323
|
+
minimum: 1,
|
|
1324
|
+
maximum: 10,
|
|
1325
|
+
examples: [3],
|
|
1326
|
+
},
|
|
1327
|
+
monitoring: {
|
|
1328
|
+
type: "boolean",
|
|
1329
|
+
title: "Performance Monitoring",
|
|
1330
|
+
default: true,
|
|
1331
|
+
},
|
|
1332
|
+
},
|
|
1333
|
+
},
|
|
1334
|
+
product4: {
|
|
1335
|
+
type: "object",
|
|
1336
|
+
title: "Storage Package",
|
|
1337
|
+
properties: {
|
|
1338
|
+
name: {
|
|
1339
|
+
type: "string",
|
|
1340
|
+
title: "Product Name",
|
|
1341
|
+
default: "Cloud Storage+",
|
|
1342
|
+
examples: ["Cloud Storage+"],
|
|
1343
|
+
},
|
|
1344
|
+
storage: {
|
|
1345
|
+
type: "number",
|
|
1346
|
+
title: "Storage (TB)",
|
|
1347
|
+
default: 5,
|
|
1348
|
+
minimum: 1,
|
|
1349
|
+
maximum: 100,
|
|
1350
|
+
examples: [5],
|
|
1351
|
+
},
|
|
1352
|
+
bandwidth: {
|
|
1353
|
+
type: "number",
|
|
1354
|
+
title: "Bandwidth (TB)",
|
|
1355
|
+
default: 10,
|
|
1356
|
+
minimum: 1,
|
|
1357
|
+
examples: [10],
|
|
1358
|
+
},
|
|
1359
|
+
backup: {
|
|
1360
|
+
type: "boolean",
|
|
1361
|
+
title: "Automated Backup",
|
|
1362
|
+
default: true,
|
|
1363
|
+
},
|
|
1364
|
+
},
|
|
1365
|
+
},
|
|
1366
|
+
},
|
|
1367
|
+
},
|
|
1368
|
+
},
|
|
1369
|
+
};
|
|
1370
|
+
|
|
1371
|
+
const uiSchema = {
|
|
1372
|
+
cardGroups: {
|
|
1373
|
+
"ui:layout": "grid",
|
|
1374
|
+
"ui:layoutOptions": {
|
|
1375
|
+
columns: "auto",
|
|
1376
|
+
autoSize: "md",
|
|
1377
|
+
gap: "md",
|
|
1378
|
+
},
|
|
1379
|
+
},
|
|
1380
|
+
"cardGroups/product1": {
|
|
1381
|
+
"ui:surface": "surface-sunken",
|
|
1382
|
+
},
|
|
1383
|
+
"cardGroups/product2": {
|
|
1384
|
+
"ui:surface": "surface-inverse",
|
|
1385
|
+
},
|
|
1386
|
+
"cardGroups/product3": {
|
|
1387
|
+
"ui:surface": "card",
|
|
1388
|
+
},
|
|
1389
|
+
"cardGroups/product4": {
|
|
1390
|
+
"ui:surface": "elevated",
|
|
1391
|
+
},
|
|
1392
|
+
};
|
|
1393
|
+
|
|
1394
|
+
return html`
|
|
1395
|
+
<pds-form
|
|
1396
|
+
.jsonSchema=${schema}
|
|
1397
|
+
.uiSchema=${uiSchema}
|
|
1398
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
1399
|
+
></pds-form>
|
|
1400
|
+
`;
|
|
1401
|
+
},
|
|
1402
|
+
};
|
|
1403
|
+
|
|
1404
|
+
export const WithDialogForms = {
|
|
1405
|
+
name: "Dialog-Based Nested Forms",
|
|
1406
|
+
parameters: {
|
|
1407
|
+
docs: {
|
|
1408
|
+
description: {
|
|
1409
|
+
story:
|
|
1410
|
+
'Dialog-based forms use `ui:dialog` to edit nested objects in modal dialogs. State is transferred via FormData when using `PDS.ask()` with `useForm: true`. Click "Edit" buttons to modify nested data, then submit the main form to see all changes preserved.',
|
|
1411
|
+
},
|
|
1412
|
+
},
|
|
1413
|
+
},
|
|
1414
|
+
render: () => {
|
|
1415
|
+
const schema = {
|
|
1416
|
+
type: "object",
|
|
1417
|
+
properties: {
|
|
1418
|
+
projectName: {
|
|
1419
|
+
type: "string",
|
|
1420
|
+
title: "Project Name",
|
|
1421
|
+
examples: ["Digital Transformation Initiative"],
|
|
1422
|
+
},
|
|
1423
|
+
teamLead: {
|
|
1424
|
+
type: "object",
|
|
1425
|
+
title: "Team Lead",
|
|
1426
|
+
properties: {
|
|
1427
|
+
name: {
|
|
1428
|
+
type: "string",
|
|
1429
|
+
title: "Full Name",
|
|
1430
|
+
examples: ["Sarah Johnson"],
|
|
1431
|
+
},
|
|
1432
|
+
email: {
|
|
1433
|
+
type: "string",
|
|
1434
|
+
format: "email",
|
|
1435
|
+
title: "Email Address",
|
|
1436
|
+
examples: ["sarah.johnson@company.com"],
|
|
1437
|
+
},
|
|
1438
|
+
phone: {
|
|
1439
|
+
type: "string",
|
|
1440
|
+
title: "Phone Number",
|
|
1441
|
+
examples: ["+1-555-0123"],
|
|
1442
|
+
},
|
|
1443
|
+
department: {
|
|
1444
|
+
type: "string",
|
|
1445
|
+
title: "Department",
|
|
1446
|
+
examples: ["Engineering"],
|
|
1447
|
+
},
|
|
1448
|
+
location: {
|
|
1449
|
+
type: "string",
|
|
1450
|
+
title: "Office Location",
|
|
1451
|
+
examples: ["New York Office"],
|
|
1452
|
+
},
|
|
1453
|
+
},
|
|
1454
|
+
},
|
|
1455
|
+
budget: {
|
|
1456
|
+
type: "object",
|
|
1457
|
+
title: "Budget Details",
|
|
1458
|
+
properties: {
|
|
1459
|
+
amount: {
|
|
1460
|
+
type: "number",
|
|
1461
|
+
title: "Budget Amount",
|
|
1462
|
+
examples: [250000],
|
|
1463
|
+
},
|
|
1464
|
+
currency: {
|
|
1465
|
+
type: "string",
|
|
1466
|
+
title: "Currency",
|
|
1467
|
+
enum: ["USD", "EUR", "GBP", "JPY", "AUD"],
|
|
1468
|
+
},
|
|
1469
|
+
fiscalYear: {
|
|
1470
|
+
type: "string",
|
|
1471
|
+
title: "Fiscal Year",
|
|
1472
|
+
examples: ["2025"],
|
|
1473
|
+
},
|
|
1474
|
+
department: {
|
|
1475
|
+
type: "string",
|
|
1476
|
+
title: "Cost Center",
|
|
1477
|
+
examples: ["IT-001"],
|
|
1478
|
+
},
|
|
1479
|
+
approved: { type: "boolean", title: "Budget Approved" },
|
|
1480
|
+
},
|
|
1481
|
+
},
|
|
1482
|
+
timeline: {
|
|
1483
|
+
type: "object",
|
|
1484
|
+
title: "Project Timeline",
|
|
1485
|
+
properties: {
|
|
1486
|
+
startDate: { type: "string", format: "date", title: "Start Date" },
|
|
1487
|
+
endDate: { type: "string", format: "date", title: "End Date" },
|
|
1488
|
+
milestones: {
|
|
1489
|
+
type: "integer",
|
|
1490
|
+
title: "Number of Milestones",
|
|
1491
|
+
minimum: 1,
|
|
1492
|
+
maximum: 20,
|
|
1493
|
+
examples: [8],
|
|
1494
|
+
},
|
|
1495
|
+
status: {
|
|
1496
|
+
type: "string",
|
|
1497
|
+
title: "Status",
|
|
1498
|
+
enum: ["Planning", "In Progress", "On Hold", "Completed"],
|
|
1499
|
+
default: "Planning",
|
|
1500
|
+
},
|
|
1501
|
+
},
|
|
1502
|
+
},
|
|
1503
|
+
},
|
|
1504
|
+
};
|
|
1505
|
+
|
|
1506
|
+
// Initial values to test state persistence
|
|
1507
|
+
const initialValues = {
|
|
1508
|
+
projectName: "Digital Transformation Initiative",
|
|
1509
|
+
teamLead: {
|
|
1510
|
+
name: "Sarah Johnson",
|
|
1511
|
+
email: "sarah.johnson@company.com",
|
|
1512
|
+
phone: "+1-555-0123",
|
|
1513
|
+
department: "Engineering",
|
|
1514
|
+
location: "New York Office",
|
|
1515
|
+
},
|
|
1516
|
+
budget: {
|
|
1517
|
+
amount: 250000,
|
|
1518
|
+
currency: "USD",
|
|
1519
|
+
fiscalYear: "2025",
|
|
1520
|
+
department: "IT-001",
|
|
1521
|
+
approved: true,
|
|
1522
|
+
},
|
|
1523
|
+
timeline: {
|
|
1524
|
+
startDate: "2025-01-15",
|
|
1525
|
+
endDate: "2025-12-31",
|
|
1526
|
+
milestones: 8,
|
|
1527
|
+
status: "In Progress",
|
|
1528
|
+
},
|
|
1529
|
+
};
|
|
1530
|
+
|
|
1531
|
+
const uiSchema = {
|
|
1532
|
+
projectName: {
|
|
1533
|
+
"ui:icon": "folder",
|
|
1534
|
+
"ui:iconPosition": "start",
|
|
1535
|
+
},
|
|
1536
|
+
teamLead: {
|
|
1537
|
+
"ui:dialog": true,
|
|
1538
|
+
"ui:dialogOptions": {
|
|
1539
|
+
buttonLabel: "Edit Team Lead",
|
|
1540
|
+
dialogTitle: "Team Lead Information",
|
|
1541
|
+
icon: "user-gear",
|
|
1542
|
+
},
|
|
1543
|
+
name: { "ui:icon": "user", "ui:iconPosition": "start" },
|
|
1544
|
+
email: { "ui:icon": "envelope", "ui:iconPosition": "start" },
|
|
1545
|
+
phone: { "ui:icon": "phone", "ui:iconPosition": "start" },
|
|
1546
|
+
department: { "ui:icon": "building", "ui:iconPosition": "start" },
|
|
1547
|
+
location: { "ui:icon": "map-pin", "ui:iconPosition": "start" },
|
|
1548
|
+
},
|
|
1549
|
+
budget: {
|
|
1550
|
+
"ui:dialog": true,
|
|
1551
|
+
"ui:dialogOptions": {
|
|
1552
|
+
buttonLabel: "Edit Budget",
|
|
1553
|
+
dialogTitle: "Budget Details",
|
|
1554
|
+
icon: "currency-dollar",
|
|
1555
|
+
},
|
|
1556
|
+
amount: { "ui:icon": "dollar-sign", "ui:iconPosition": "start" },
|
|
1557
|
+
currency: { "ui:icon": "coins", "ui:iconPosition": "start" },
|
|
1558
|
+
fiscalYear: { "ui:icon": "calendar", "ui:iconPosition": "start" },
|
|
1559
|
+
department: { "ui:icon": "building", "ui:iconPosition": "start" },
|
|
1560
|
+
},
|
|
1561
|
+
// Flat path for dialog inner form - currency field inside budget dialog
|
|
1562
|
+
"/currency": { "ui:widget": "select" },
|
|
1563
|
+
// Flat path for dialog inner form - status field inside timeline dialog
|
|
1564
|
+
"/status": { "ui:class": "buttons" },
|
|
1565
|
+
// Flat path for dialog inner form - email field inside teamLead dialog
|
|
1566
|
+
"/email": { "ui:icon": "at", "ui:iconPosition": "start" },
|
|
1567
|
+
// Flat path for dialog inner form - phone field inside teamLead dialog
|
|
1568
|
+
"/phone": { "ui:icon": "phone", "ui:iconPosition": "start" },
|
|
1569
|
+
timeline: {
|
|
1570
|
+
"ui:dialog": true,
|
|
1571
|
+
"ui:dialogOptions": {
|
|
1572
|
+
buttonLabel: "Edit Timeline",
|
|
1573
|
+
dialogTitle: "Project Timeline",
|
|
1574
|
+
icon: "calendar",
|
|
1575
|
+
},
|
|
1576
|
+
startDate: { "ui:icon": "calendar-check", "ui:iconPosition": "start" },
|
|
1577
|
+
endDate: { "ui:icon": "calendar-xmark", "ui:iconPosition": "start" },
|
|
1578
|
+
milestones: { "ui:icon": "flag", "ui:iconPosition": "start" },
|
|
1579
|
+
status: { "ui:icon": "list-check", "ui:iconPosition": "start" },
|
|
1580
|
+
},
|
|
1581
|
+
};
|
|
1582
|
+
|
|
1583
|
+
return html`
|
|
1584
|
+
<pds-form
|
|
1585
|
+
.jsonSchema=${schema}
|
|
1586
|
+
.uiSchema=${uiSchema}
|
|
1587
|
+
.values=${initialValues}
|
|
1588
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
1589
|
+
@pw:dialog-submit=${(e) => console.log("📝 Dialog saved:", e.detail)}
|
|
1590
|
+
></pds-form>
|
|
1591
|
+
`;
|
|
1592
|
+
},
|
|
1593
|
+
};
|
|
1594
|
+
|
|
1595
|
+
export const WithRadioGroupOpen = {
|
|
1596
|
+
name: "Radio Group Open (Single Selection)",
|
|
1597
|
+
parameters: {
|
|
1598
|
+
docs: {
|
|
1599
|
+
description: {
|
|
1600
|
+
story: `When an array has \`maxItems: 1\`, it renders as a Radio Group Open, allowing single selection with the ability to add custom options.
|
|
1601
|
+
|
|
1602
|
+
This is perfect for scenarios where users can choose one option from predefined choices or add their own custom value. The \`data-open\` enhancement automatically provides an input field to add new options dynamically.
|
|
1603
|
+
|
|
1604
|
+
### Key Features:
|
|
1605
|
+
- **Single selection** - Only one option can be selected at a time (radio buttons)
|
|
1606
|
+
- **Add custom options** - Users can type new options in the input field
|
|
1607
|
+
- **Remove options** - Click the × button to remove options
|
|
1608
|
+
- **Pre-populated** - Start with default options from the schema
|
|
1609
|
+
|
|
1610
|
+
This pattern is ideal for fields like "Priority", "Status", "Category", or any single-choice field where users might need custom values.`,
|
|
1611
|
+
},
|
|
1612
|
+
},
|
|
1613
|
+
},
|
|
1614
|
+
render: () => {
|
|
1615
|
+
const schema = {
|
|
1616
|
+
type: "object",
|
|
1617
|
+
properties: {
|
|
1618
|
+
priority: {
|
|
1619
|
+
type: "array",
|
|
1620
|
+
title: "Project Priority",
|
|
1621
|
+
description: "Select one priority level or add your own",
|
|
1622
|
+
items: {
|
|
1623
|
+
type: "string",
|
|
1624
|
+
examples: ["High", "Medium", "Low"],
|
|
1625
|
+
},
|
|
1626
|
+
default: ["High", "Medium", "Low"],
|
|
1627
|
+
uniqueItems: true,
|
|
1628
|
+
maxItems: 1,
|
|
1629
|
+
},
|
|
1630
|
+
status: {
|
|
1631
|
+
type: "array",
|
|
1632
|
+
title: "Current Status",
|
|
1633
|
+
description: "Choose the current project status",
|
|
1634
|
+
items: {
|
|
1635
|
+
type: "string",
|
|
1636
|
+
examples: ["Planning", "In Progress", "Review", "Completed"],
|
|
1637
|
+
},
|
|
1638
|
+
default: ["Planning", "In Progress", "Review", "Completed"],
|
|
1639
|
+
uniqueItems: true,
|
|
1640
|
+
maxItems: 1,
|
|
1641
|
+
},
|
|
1642
|
+
department: {
|
|
1643
|
+
type: "array",
|
|
1644
|
+
title: "Department",
|
|
1645
|
+
description: "Select your department",
|
|
1646
|
+
items: {
|
|
1647
|
+
type: "string",
|
|
1648
|
+
examples: ["Engineering", "Design", "Marketing", "Sales"],
|
|
1649
|
+
},
|
|
1650
|
+
default: ["Engineering", "Design", "Marketing", "Sales"],
|
|
1651
|
+
uniqueItems: true,
|
|
1652
|
+
maxItems: 1,
|
|
1653
|
+
},
|
|
1654
|
+
},
|
|
1655
|
+
required: ["priority", "status"],
|
|
1656
|
+
};
|
|
1657
|
+
|
|
1658
|
+
const initialValues = {
|
|
1659
|
+
priority: ["High", "Medium", "Low"],
|
|
1660
|
+
status: ["Planning", "In Progress", "Review", "Completed"],
|
|
1661
|
+
department: ["Engineering", "Design", "Marketing", "Sales"],
|
|
1662
|
+
};
|
|
1663
|
+
|
|
1664
|
+
return html`
|
|
1665
|
+
<pds-form
|
|
1666
|
+
.jsonSchema=${schema}
|
|
1667
|
+
.values=${initialValues}
|
|
1668
|
+
@pw:value-change=${(e) => console.log("🔄 Value changed:", e.detail)}
|
|
1669
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
1670
|
+
></pds-form>
|
|
1671
|
+
`;
|
|
1672
|
+
},
|
|
1673
|
+
};
|
|
1674
|
+
|
|
1675
|
+
export const WithDatalistAutocomplete = {
|
|
1676
|
+
name: "Datalist Autocomplete",
|
|
1677
|
+
render: () => {
|
|
1678
|
+
const schema = {
|
|
1679
|
+
type: "object",
|
|
1680
|
+
properties: {
|
|
1681
|
+
country: {
|
|
1682
|
+
type: "string",
|
|
1683
|
+
title: "Country",
|
|
1684
|
+
examples: ["United States"],
|
|
1685
|
+
},
|
|
1686
|
+
city: {
|
|
1687
|
+
type: "string",
|
|
1688
|
+
title: "City",
|
|
1689
|
+
examples: ["New York"],
|
|
1690
|
+
},
|
|
1691
|
+
skillset: {
|
|
1692
|
+
type: "string",
|
|
1693
|
+
title: "Primary Skill",
|
|
1694
|
+
examples: ["JavaScript"],
|
|
1695
|
+
},
|
|
1696
|
+
company: {
|
|
1697
|
+
type: "string",
|
|
1698
|
+
title: "Company",
|
|
1699
|
+
examples: ["Microsoft"],
|
|
1700
|
+
},
|
|
1701
|
+
},
|
|
1702
|
+
};
|
|
1703
|
+
|
|
1704
|
+
const uiSchema = {
|
|
1705
|
+
country: {
|
|
1706
|
+
"ui:datalist": [
|
|
1707
|
+
"United States",
|
|
1708
|
+
"United Kingdom",
|
|
1709
|
+
"Canada",
|
|
1710
|
+
"Australia",
|
|
1711
|
+
"Germany",
|
|
1712
|
+
"France",
|
|
1713
|
+
"Spain",
|
|
1714
|
+
"Italy",
|
|
1715
|
+
"Japan",
|
|
1716
|
+
"China",
|
|
1717
|
+
"India",
|
|
1718
|
+
"Brazil",
|
|
1719
|
+
],
|
|
1720
|
+
},
|
|
1721
|
+
city: {
|
|
1722
|
+
"ui:datalist": [
|
|
1723
|
+
"New York",
|
|
1724
|
+
"London",
|
|
1725
|
+
"Tokyo",
|
|
1726
|
+
"Paris",
|
|
1727
|
+
"Berlin",
|
|
1728
|
+
"Sydney",
|
|
1729
|
+
"Toronto",
|
|
1730
|
+
"Amsterdam",
|
|
1731
|
+
"Singapore",
|
|
1732
|
+
"Dubai",
|
|
1733
|
+
],
|
|
1734
|
+
},
|
|
1735
|
+
skillset: {
|
|
1736
|
+
"ui:datalist": [
|
|
1737
|
+
"JavaScript",
|
|
1738
|
+
"Python",
|
|
1739
|
+
"Java",
|
|
1740
|
+
"C++",
|
|
1741
|
+
"Go",
|
|
1742
|
+
"Rust",
|
|
1743
|
+
"TypeScript",
|
|
1744
|
+
"Ruby",
|
|
1745
|
+
"PHP",
|
|
1746
|
+
"Swift",
|
|
1747
|
+
"Kotlin",
|
|
1748
|
+
],
|
|
1749
|
+
},
|
|
1750
|
+
company: {
|
|
1751
|
+
"ui:datalist": [
|
|
1752
|
+
"Microsoft",
|
|
1753
|
+
"Google",
|
|
1754
|
+
"Apple",
|
|
1755
|
+
"Amazon",
|
|
1756
|
+
"Meta",
|
|
1757
|
+
"Tesla",
|
|
1758
|
+
"Netflix",
|
|
1759
|
+
"Adobe",
|
|
1760
|
+
"Salesforce",
|
|
1761
|
+
"Oracle",
|
|
1762
|
+
],
|
|
1763
|
+
},
|
|
1764
|
+
};
|
|
1765
|
+
|
|
1766
|
+
return html`
|
|
1767
|
+
<pds-form
|
|
1768
|
+
.jsonSchema=${schema}
|
|
1769
|
+
.uiSchema=${uiSchema}
|
|
1770
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
1771
|
+
></pds-form>
|
|
1772
|
+
`;
|
|
1773
|
+
},
|
|
1774
|
+
};
|
|
1775
|
+
|
|
1776
|
+
export const WithArrayFields = {
|
|
1777
|
+
name: "Dynamic Arrays (Add/Remove)",
|
|
1778
|
+
parameters: {
|
|
1779
|
+
docs: {
|
|
1780
|
+
description: {
|
|
1781
|
+
story: `Arrays in JSON Schema forms allow users to dynamically add and remove items. This is perfect for managing lists like team members, tasks, or any collection that can grow or shrink.
|
|
1782
|
+
|
|
1783
|
+
### Features:
|
|
1784
|
+
- **Add items** - Click "Add" button to create new entries
|
|
1785
|
+
- **Remove items** - Delete individual items with the "Remove" button
|
|
1786
|
+
- **Reorder items** - Use up/down arrows to change order
|
|
1787
|
+
- **Nested objects** - Each array item can contain complex nested data
|
|
1788
|
+
- **Initial values** - Pre-populate with default items
|
|
1789
|
+
- **Radio Group Open** - Arrays with \`maxItems: 1\` render as radio buttons for single selection`,
|
|
1790
|
+
},
|
|
1791
|
+
},
|
|
1792
|
+
},
|
|
1793
|
+
render: () => {
|
|
1794
|
+
const schema = {
|
|
1795
|
+
type: "object",
|
|
1796
|
+
properties: {
|
|
1797
|
+
projectName: {
|
|
1798
|
+
type: "string",
|
|
1799
|
+
title: "Project Name",
|
|
1800
|
+
examples: ["Website Redesign Project"],
|
|
1801
|
+
},
|
|
1802
|
+
priority: {
|
|
1803
|
+
type: "array",
|
|
1804
|
+
title: "Project Priority",
|
|
1805
|
+
items: {
|
|
1806
|
+
type: "string",
|
|
1807
|
+
examples: ["High", "Medium", "Low"],
|
|
1808
|
+
},
|
|
1809
|
+
default: ["High", "Medium", "Low"],
|
|
1810
|
+
uniqueItems: true,
|
|
1811
|
+
maxItems: 1,
|
|
1812
|
+
},
|
|
1813
|
+
tags: {
|
|
1814
|
+
type: "array",
|
|
1815
|
+
title: "Project Tags",
|
|
1816
|
+
items: {
|
|
1817
|
+
type: "string",
|
|
1818
|
+
},
|
|
1819
|
+
default: ["web", "design", "frontend"],
|
|
1820
|
+
},
|
|
1821
|
+
teamMembers: {
|
|
1822
|
+
type: "array",
|
|
1823
|
+
title: "Team Members",
|
|
1824
|
+
items: {
|
|
1825
|
+
type: "object",
|
|
1826
|
+
properties: {
|
|
1827
|
+
name: {
|
|
1828
|
+
type: "string",
|
|
1829
|
+
title: "Full Name",
|
|
1830
|
+
examples: ["Alice Johnson"],
|
|
1831
|
+
},
|
|
1832
|
+
role: {
|
|
1833
|
+
type: "string",
|
|
1834
|
+
title: "Role",
|
|
1835
|
+
enum: [
|
|
1836
|
+
"Developer",
|
|
1837
|
+
"Designer",
|
|
1838
|
+
"Project Manager",
|
|
1839
|
+
"QA Engineer",
|
|
1840
|
+
"DevOps",
|
|
1841
|
+
],
|
|
1842
|
+
default: "Developer",
|
|
1843
|
+
},
|
|
1844
|
+
email: {
|
|
1845
|
+
type: "string",
|
|
1846
|
+
format: "email",
|
|
1847
|
+
title: "Email",
|
|
1848
|
+
examples: ["alice.johnson@company.com"],
|
|
1849
|
+
},
|
|
1850
|
+
hours: {
|
|
1851
|
+
type: "number",
|
|
1852
|
+
title: "Hours/Week",
|
|
1853
|
+
minimum: 1,
|
|
1854
|
+
maximum: 40,
|
|
1855
|
+
default: 40,
|
|
1856
|
+
},
|
|
1857
|
+
},
|
|
1858
|
+
required: ["name", "role", "email"],
|
|
1859
|
+
},
|
|
1860
|
+
minItems: 1,
|
|
1861
|
+
},
|
|
1862
|
+
milestones: {
|
|
1863
|
+
type: "array",
|
|
1864
|
+
title: "Project Milestones",
|
|
1865
|
+
items: {
|
|
1866
|
+
type: "object",
|
|
1867
|
+
properties: {
|
|
1868
|
+
title: {
|
|
1869
|
+
type: "string",
|
|
1870
|
+
title: "Milestone Title",
|
|
1871
|
+
examples: ["MVP Launch"],
|
|
1872
|
+
},
|
|
1873
|
+
dueDate: {
|
|
1874
|
+
type: "string",
|
|
1875
|
+
format: "date",
|
|
1876
|
+
title: "Due Date",
|
|
1877
|
+
},
|
|
1878
|
+
completed: {
|
|
1879
|
+
type: "boolean",
|
|
1880
|
+
title: "Completed",
|
|
1881
|
+
default: false,
|
|
1882
|
+
},
|
|
1883
|
+
},
|
|
1884
|
+
required: ["title", "dueDate"],
|
|
1885
|
+
},
|
|
1886
|
+
},
|
|
1887
|
+
},
|
|
1888
|
+
required: ["projectName", "teamMembers"],
|
|
1889
|
+
};
|
|
1890
|
+
|
|
1891
|
+
// Initial values to demonstrate pre-populated arrays
|
|
1892
|
+
const initialValues = {
|
|
1893
|
+
projectName: "Website Redesign Project",
|
|
1894
|
+
priority: ["High", "Medium", "Low"],
|
|
1895
|
+
teamMembers: [
|
|
1896
|
+
{
|
|
1897
|
+
name: "Alice Johnson",
|
|
1898
|
+
role: "Project Manager",
|
|
1899
|
+
email: "alice.johnson@company.com",
|
|
1900
|
+
hours: 40,
|
|
1901
|
+
},
|
|
1902
|
+
{
|
|
1903
|
+
name: "Bob Smith",
|
|
1904
|
+
role: "Developer",
|
|
1905
|
+
email: "bob.smith@company.com",
|
|
1906
|
+
hours: 35,
|
|
1907
|
+
},
|
|
1908
|
+
],
|
|
1909
|
+
milestones: [
|
|
1910
|
+
{
|
|
1911
|
+
title: "Design Phase Complete",
|
|
1912
|
+
dueDate: "2025-02-01",
|
|
1913
|
+
completed: true,
|
|
1914
|
+
},
|
|
1915
|
+
{
|
|
1916
|
+
title: "MVP Launch",
|
|
1917
|
+
dueDate: "2025-04-15",
|
|
1918
|
+
completed: false,
|
|
1919
|
+
},
|
|
1920
|
+
],
|
|
1921
|
+
tags: ["web", "design", "frontend", "responsive"],
|
|
1922
|
+
};
|
|
1923
|
+
|
|
1924
|
+
const uiSchema = {
|
|
1925
|
+
teamMembers: {
|
|
1926
|
+
"ui:layout": "default",
|
|
1927
|
+
role: {
|
|
1928
|
+
"ui:widget": "select",
|
|
1929
|
+
},
|
|
1930
|
+
},
|
|
1931
|
+
};
|
|
1932
|
+
|
|
1933
|
+
return html`
|
|
1934
|
+
<pds-form
|
|
1935
|
+
.jsonSchema=${schema}
|
|
1936
|
+
.uiSchema=${uiSchema}
|
|
1937
|
+
.values=${initialValues}
|
|
1938
|
+
@pw:array-add=${(e) => console.log("➕ Item added to:", e.detail.path)}
|
|
1939
|
+
@pw:array-remove=${(e) =>
|
|
1940
|
+
console.log(
|
|
1941
|
+
"➖ Item removed from:",
|
|
1942
|
+
e.detail.path,
|
|
1943
|
+
"at index:",
|
|
1944
|
+
e.detail.index
|
|
1945
|
+
)}
|
|
1946
|
+
@pw:array-reorder=${(e) =>
|
|
1947
|
+
console.log(
|
|
1948
|
+
"🔄 Item moved from",
|
|
1949
|
+
e.detail.from,
|
|
1950
|
+
"to",
|
|
1951
|
+
e.detail.to,
|
|
1952
|
+
"in:",
|
|
1953
|
+
e.detail.path
|
|
1954
|
+
)}
|
|
1955
|
+
@pw:value-change=${(e) => console.log("🔄 Value changed:", e.detail)}
|
|
1956
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
1957
|
+
></pds-form>
|
|
1958
|
+
`;
|
|
1959
|
+
},
|
|
1960
|
+
};
|
|
1961
|
+
|
|
1962
|
+
export const ComprehensiveExample = {
|
|
1963
|
+
name: "All Features Combined",
|
|
1964
|
+
render: () => {
|
|
1965
|
+
const schema = {
|
|
1966
|
+
type: "object",
|
|
1967
|
+
properties: {
|
|
1968
|
+
userProfile: {
|
|
1969
|
+
type: "object",
|
|
1970
|
+
title: "User Profile",
|
|
1971
|
+
properties: {
|
|
1972
|
+
personalInfo: {
|
|
1973
|
+
type: "object",
|
|
1974
|
+
title: "Personal Information",
|
|
1975
|
+
properties: {
|
|
1976
|
+
firstName: {
|
|
1977
|
+
type: "string",
|
|
1978
|
+
title: "First Name",
|
|
1979
|
+
examples: ["John"],
|
|
1980
|
+
},
|
|
1981
|
+
lastName: {
|
|
1982
|
+
type: "string",
|
|
1983
|
+
title: "Last Name",
|
|
1984
|
+
examples: ["Doe"],
|
|
1985
|
+
},
|
|
1986
|
+
email: {
|
|
1987
|
+
type: "string",
|
|
1988
|
+
format: "email",
|
|
1989
|
+
title: "Email",
|
|
1990
|
+
examples: ["john.doe@example.com"],
|
|
1991
|
+
},
|
|
1992
|
+
phone: {
|
|
1993
|
+
type: "string",
|
|
1994
|
+
title: "Phone",
|
|
1995
|
+
examples: ["+1 (555) 123-4567"],
|
|
1996
|
+
},
|
|
1997
|
+
dateOfBirth: {
|
|
1998
|
+
type: "string",
|
|
1999
|
+
format: "date",
|
|
2000
|
+
title: "Date of Birth",
|
|
2001
|
+
},
|
|
2002
|
+
},
|
|
2003
|
+
required: ["firstName", "lastName", "email"],
|
|
2004
|
+
},
|
|
2005
|
+
settings: {
|
|
2006
|
+
type: "object",
|
|
2007
|
+
title: "Settings",
|
|
2008
|
+
properties: {
|
|
2009
|
+
accountSettings: {
|
|
2010
|
+
type: "object",
|
|
2011
|
+
title: "Account Settings",
|
|
2012
|
+
properties: {
|
|
2013
|
+
notifications: {
|
|
2014
|
+
type: "boolean",
|
|
2015
|
+
title: "Email Notifications",
|
|
2016
|
+
},
|
|
2017
|
+
newsletter: {
|
|
2018
|
+
type: "boolean",
|
|
2019
|
+
title: "Newsletter Subscription",
|
|
2020
|
+
},
|
|
2021
|
+
twoFactor: {
|
|
2022
|
+
type: "boolean",
|
|
2023
|
+
title: "Two-Factor Authentication",
|
|
2024
|
+
},
|
|
2025
|
+
},
|
|
2026
|
+
},
|
|
2027
|
+
preferences: {
|
|
2028
|
+
type: "object",
|
|
2029
|
+
title: "Preferences",
|
|
2030
|
+
properties: {
|
|
2031
|
+
theme: {
|
|
2032
|
+
type: "string",
|
|
2033
|
+
title: "Theme",
|
|
2034
|
+
enum: ["Light", "Dark", "Auto"],
|
|
2035
|
+
},
|
|
2036
|
+
language: {
|
|
2037
|
+
type: "string",
|
|
2038
|
+
title: "Language",
|
|
2039
|
+
enum: [
|
|
2040
|
+
"English",
|
|
2041
|
+
"Spanish",
|
|
2042
|
+
"French",
|
|
2043
|
+
"German",
|
|
2044
|
+
"Chinese",
|
|
2045
|
+
"Japanese",
|
|
2046
|
+
],
|
|
2047
|
+
},
|
|
2048
|
+
fontSize: {
|
|
2049
|
+
type: "number",
|
|
2050
|
+
title: "Font Size",
|
|
2051
|
+
minimum: 12,
|
|
2052
|
+
maximum: 20,
|
|
2053
|
+
default: 14,
|
|
2054
|
+
},
|
|
2055
|
+
lineHeight: {
|
|
2056
|
+
type: "number",
|
|
2057
|
+
title: "Line Height",
|
|
2058
|
+
minimum: 1.0,
|
|
2059
|
+
maximum: 2.0,
|
|
2060
|
+
default: 1.5,
|
|
2061
|
+
},
|
|
2062
|
+
},
|
|
2063
|
+
},
|
|
2064
|
+
},
|
|
2065
|
+
},
|
|
2066
|
+
},
|
|
2067
|
+
},
|
|
2068
|
+
profile: {
|
|
2069
|
+
type: "object",
|
|
2070
|
+
title: "Profile",
|
|
2071
|
+
properties: {
|
|
2072
|
+
avatar: {
|
|
2073
|
+
type: "string",
|
|
2074
|
+
title: "Profile Picture",
|
|
2075
|
+
contentMediaType: "image/*",
|
|
2076
|
+
contentEncoding: "base64",
|
|
2077
|
+
},
|
|
2078
|
+
bio: {
|
|
2079
|
+
type: "string",
|
|
2080
|
+
title: "Biography",
|
|
2081
|
+
examples: ["Tell us about yourself..."],
|
|
2082
|
+
},
|
|
2083
|
+
website: {
|
|
2084
|
+
type: "string",
|
|
2085
|
+
format: "uri",
|
|
2086
|
+
title: "Website",
|
|
2087
|
+
examples: ["https://yourwebsite.com"],
|
|
2088
|
+
},
|
|
2089
|
+
},
|
|
2090
|
+
},
|
|
2091
|
+
},
|
|
2092
|
+
};
|
|
2093
|
+
|
|
2094
|
+
const uiSchema = {
|
|
2095
|
+
userProfile: {
|
|
2096
|
+
"ui:layout": "accordion",
|
|
2097
|
+
"ui:layoutOptions": { openFirst: true },
|
|
2098
|
+
"ui:surface": "elevated",
|
|
2099
|
+
personalInfo: {
|
|
2100
|
+
"ui:layout": "grid",
|
|
2101
|
+
"ui:layoutOptions": { columns: 2, gap: "md" },
|
|
2102
|
+
"ui:surface": "sunken",
|
|
2103
|
+
email: { "ui:icon": "envelope", "ui:iconPosition": "start" },
|
|
2104
|
+
phone: { "ui:icon": "phone", "ui:iconPosition": "start" },
|
|
2105
|
+
},
|
|
2106
|
+
settings: {
|
|
2107
|
+
accountSettings: {
|
|
2108
|
+
"ui:surface": "sunken",
|
|
2109
|
+
},
|
|
2110
|
+
preferences: {
|
|
2111
|
+
"ui:surface": "sunken",
|
|
2112
|
+
theme: { "ui:class": "buttons" },
|
|
2113
|
+
fontSize: { "ui:widget": "input-range" },
|
|
2114
|
+
lineHeight: { "ui:widget": "input-range" },
|
|
2115
|
+
},
|
|
2116
|
+
},
|
|
2117
|
+
},
|
|
2118
|
+
profile: {
|
|
2119
|
+
"ui:dialog": true,
|
|
2120
|
+
"ui:dialogOptions": {
|
|
2121
|
+
buttonLabel: "Edit Profile",
|
|
2122
|
+
dialogTitle: "Your Profile Information",
|
|
2123
|
+
},
|
|
2124
|
+
avatar: {
|
|
2125
|
+
"ui:options": {
|
|
2126
|
+
accept: "image/*",
|
|
2127
|
+
maxSize: 5242880,
|
|
2128
|
+
label: "Upload Avatar",
|
|
2129
|
+
},
|
|
2130
|
+
},
|
|
2131
|
+
bio: {
|
|
2132
|
+
"ui:widget": "richtext",
|
|
2133
|
+
"ui:options": {
|
|
2134
|
+
toolbar: "standard",
|
|
2135
|
+
},
|
|
2136
|
+
},
|
|
2137
|
+
website: { "ui:icon": "globe", "ui:iconPosition": "start" },
|
|
2138
|
+
},
|
|
2139
|
+
};
|
|
2140
|
+
|
|
2141
|
+
const options = {
|
|
2142
|
+
widgets: {
|
|
2143
|
+
booleans: "toggle",
|
|
2144
|
+
},
|
|
2145
|
+
enhancements: {
|
|
2146
|
+
rangeOutput: true,
|
|
2147
|
+
},
|
|
2148
|
+
};
|
|
2149
|
+
|
|
2150
|
+
return html`
|
|
2151
|
+
<pds-form
|
|
2152
|
+
.jsonSchema=${schema}
|
|
2153
|
+
.uiSchema=${uiSchema}
|
|
2154
|
+
.options=${options}
|
|
2155
|
+
@pw:value-change=${(e) => console.log("🔄 Changed:", e.detail)}
|
|
2156
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
2157
|
+
></pds-form>
|
|
2158
|
+
`;
|
|
2159
|
+
},
|
|
2160
|
+
};
|
|
2161
|
+
|
|
2162
|
+
export const CustomFormActions = {
|
|
2163
|
+
name: "Custom Form Actions",
|
|
2164
|
+
parameters: {
|
|
2165
|
+
docs: {
|
|
2166
|
+
description: {
|
|
2167
|
+
story: `Demonstrates using \`hide-actions\` to provide custom form submission buttons and handling.
|
|
2168
|
+
|
|
2169
|
+
When \`hide-actions\` is set, the default Submit and Reset buttons are hidden, allowing you to create custom action buttons in the \`actions\` slot. You can then handle form submission programmatically using the form's \`submit()\` method or by manually triggering the form element.`,
|
|
2170
|
+
},
|
|
2171
|
+
},
|
|
2172
|
+
},
|
|
2173
|
+
render: () => {
|
|
2174
|
+
const schema = {
|
|
2175
|
+
type: "object",
|
|
2176
|
+
properties: {
|
|
2177
|
+
username: {
|
|
2178
|
+
type: "string",
|
|
2179
|
+
title: "Username",
|
|
2180
|
+
minLength: 3,
|
|
2181
|
+
examples: ["johndoe"],
|
|
2182
|
+
},
|
|
2183
|
+
email: {
|
|
2184
|
+
type: "string",
|
|
2185
|
+
format: "email",
|
|
2186
|
+
title: "Email",
|
|
2187
|
+
examples: ["john@example.com"],
|
|
2188
|
+
},
|
|
2189
|
+
password: {
|
|
2190
|
+
type: "string",
|
|
2191
|
+
format: "password",
|
|
2192
|
+
title: "Password",
|
|
2193
|
+
minLength: 8,
|
|
2194
|
+
},
|
|
2195
|
+
terms: {
|
|
2196
|
+
type: "boolean",
|
|
2197
|
+
title: "I agree to the terms and conditions",
|
|
2198
|
+
},
|
|
2199
|
+
},
|
|
2200
|
+
required: ["username", "email", "password", "terms"],
|
|
2201
|
+
};
|
|
2202
|
+
|
|
2203
|
+
const handleSaveDraft = (e) => {
|
|
2204
|
+
const form = e.target.closest("pds-form");
|
|
2205
|
+
const data = form.serialize();
|
|
2206
|
+
console.log("💾 Saving draft:", data.json);
|
|
2207
|
+
};
|
|
2208
|
+
|
|
2209
|
+
return html`
|
|
2210
|
+
<pds-form
|
|
2211
|
+
.jsonSchema=${schema}
|
|
2212
|
+
hide-actions
|
|
2213
|
+
@pw:value-change=${(e) => console.log("🔄 Field changed:", e.detail)}
|
|
2214
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
2215
|
+
>
|
|
2216
|
+
<div slot="actions" class="flex gap-sm items-center">
|
|
2217
|
+
<button type="submit" class="btn btn-primary">
|
|
2218
|
+
<pds-icon icon="check"></pds-icon>
|
|
2219
|
+
Create Account
|
|
2220
|
+
</button>
|
|
2221
|
+
<button type="button" class="btn" @click=${handleSaveDraft}>
|
|
2222
|
+
<pds-icon icon="file"></pds-icon>
|
|
2223
|
+
Save Draft
|
|
2224
|
+
</button>
|
|
2225
|
+
<button
|
|
2226
|
+
type="button"
|
|
2227
|
+
class="btn btn-secondary btn-outline grow text-right"
|
|
2228
|
+
@click=${() => console.log("Registration cancelled")}
|
|
2229
|
+
>
|
|
2230
|
+
Cancel
|
|
2231
|
+
</button>
|
|
2232
|
+
</div>
|
|
2233
|
+
</pds-form>
|
|
2234
|
+
`;
|
|
2235
|
+
},
|
|
2236
|
+
};
|
|
2237
|
+
|
|
2238
|
+
// =============================================================================
|
|
2239
|
+
// Root-Level Layout Stories
|
|
2240
|
+
// =============================================================================
|
|
2241
|
+
|
|
2242
|
+
export const RootGridLayout = {
|
|
2243
|
+
name: "Root-Level Grid Layout",
|
|
2244
|
+
parameters: {
|
|
2245
|
+
docs: {
|
|
2246
|
+
description: {
|
|
2247
|
+
story: `Apply a grid layout to the entire form using root-level \`ui:layout\` and \`ui:layoutOptions\`.
|
|
2248
|
+
|
|
2249
|
+
This allows you to control the form layout **without modifying your JSON Schema** — keeping data structure separate from presentation.
|
|
2250
|
+
|
|
2251
|
+
\`\`\`javascript
|
|
2252
|
+
const uiSchema = {
|
|
2253
|
+
'ui:layout': 'grid',
|
|
2254
|
+
'ui:layoutOptions': {
|
|
2255
|
+
columns: 2,
|
|
2256
|
+
gap: 'md'
|
|
2257
|
+
}
|
|
2258
|
+
};
|
|
2259
|
+
\`\`\``,
|
|
2260
|
+
},
|
|
2261
|
+
},
|
|
2262
|
+
},
|
|
2263
|
+
render: () => {
|
|
2264
|
+
const schema = {
|
|
2265
|
+
type: "object",
|
|
2266
|
+
title: "Contact Form",
|
|
2267
|
+
properties: {
|
|
2268
|
+
firstName: { type: "string", title: "First Name", examples: ["John"] },
|
|
2269
|
+
lastName: { type: "string", title: "Last Name", examples: ["Doe"] },
|
|
2270
|
+
email: {
|
|
2271
|
+
type: "string",
|
|
2272
|
+
format: "email",
|
|
2273
|
+
title: "Email",
|
|
2274
|
+
examples: ["john.doe@example.com"],
|
|
2275
|
+
},
|
|
2276
|
+
phone: {
|
|
2277
|
+
type: "string",
|
|
2278
|
+
title: "Phone",
|
|
2279
|
+
examples: ["+1 (555) 123-4567"],
|
|
2280
|
+
},
|
|
2281
|
+
company: { type: "string", title: "Company", examples: ["Acme Inc."] },
|
|
2282
|
+
role: { type: "string", title: "Role", examples: ["Developer"] },
|
|
2283
|
+
},
|
|
2284
|
+
required: ["firstName", "lastName", "email"],
|
|
2285
|
+
};
|
|
2286
|
+
|
|
2287
|
+
const uiSchema = {
|
|
2288
|
+
"ui:layout": "grid",
|
|
2289
|
+
"ui:layoutOptions": {
|
|
2290
|
+
columns: 2,
|
|
2291
|
+
gap: "md",
|
|
2292
|
+
},
|
|
2293
|
+
};
|
|
2294
|
+
|
|
2295
|
+
return html`
|
|
2296
|
+
<pds-form
|
|
2297
|
+
.jsonSchema=${schema}
|
|
2298
|
+
.uiSchema=${uiSchema}
|
|
2299
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
2300
|
+
></pds-form>
|
|
2301
|
+
`;
|
|
2302
|
+
},
|
|
2303
|
+
};
|
|
2304
|
+
|
|
2305
|
+
export const RootFlexLayout = {
|
|
2306
|
+
name: "Root-Level Flex Layout",
|
|
2307
|
+
parameters: {
|
|
2308
|
+
docs: {
|
|
2309
|
+
description: {
|
|
2310
|
+
story: `Apply a flex layout to the entire form using root-level \`ui:layout\` and \`ui:layoutOptions\`.
|
|
2311
|
+
|
|
2312
|
+
\`\`\`javascript
|
|
2313
|
+
const uiSchema = {
|
|
2314
|
+
'ui:layout': 'flex',
|
|
2315
|
+
'ui:layoutOptions': {
|
|
2316
|
+
gap: 'lg',
|
|
2317
|
+
wrap: true
|
|
2318
|
+
}
|
|
2319
|
+
};
|
|
2320
|
+
\`\`\``,
|
|
2321
|
+
},
|
|
2322
|
+
},
|
|
2323
|
+
},
|
|
2324
|
+
render: () => {
|
|
2325
|
+
const schema = {
|
|
2326
|
+
type: "object",
|
|
2327
|
+
title: "Quick Settings",
|
|
2328
|
+
properties: {
|
|
2329
|
+
theme: {
|
|
2330
|
+
type: "string",
|
|
2331
|
+
enum: ["light", "dark", "system"],
|
|
2332
|
+
title: "Theme",
|
|
2333
|
+
default: "system",
|
|
2334
|
+
},
|
|
2335
|
+
notifications: {
|
|
2336
|
+
type: "boolean",
|
|
2337
|
+
title: "Notifications",
|
|
2338
|
+
default: true,
|
|
2339
|
+
},
|
|
2340
|
+
language: {
|
|
2341
|
+
type: "string",
|
|
2342
|
+
enum: ["en", "es", "fr", "de"],
|
|
2343
|
+
title: "Language",
|
|
2344
|
+
default: "en",
|
|
2345
|
+
},
|
|
2346
|
+
},
|
|
2347
|
+
};
|
|
2348
|
+
|
|
2349
|
+
const uiSchema = {
|
|
2350
|
+
"ui:layout": "flex",
|
|
2351
|
+
"ui:layoutOptions": {
|
|
2352
|
+
gap: "lg",
|
|
2353
|
+
wrap: true,
|
|
2354
|
+
},
|
|
2355
|
+
};
|
|
2356
|
+
|
|
2357
|
+
return html`
|
|
2358
|
+
<pds-form
|
|
2359
|
+
.jsonSchema=${schema}
|
|
2360
|
+
.uiSchema=${uiSchema}
|
|
2361
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
2362
|
+
></pds-form>
|
|
2363
|
+
`;
|
|
2364
|
+
},
|
|
2365
|
+
};
|
|
2366
|
+
|
|
2367
|
+
export const RootLayoutWithFieldOptions = {
|
|
2368
|
+
name: "Root Layout + Field Options",
|
|
2369
|
+
parameters: {
|
|
2370
|
+
docs: {
|
|
2371
|
+
description: {
|
|
2372
|
+
story: `Combine root-level layout with field-specific UI options.
|
|
2373
|
+
|
|
2374
|
+
Root-level \`ui:*\` properties control the overall form layout, while path-keyed entries (like \`'/email'\`) customize individual fields.
|
|
2375
|
+
|
|
2376
|
+
\`\`\`javascript
|
|
2377
|
+
const uiSchema = {
|
|
2378
|
+
// Root form layout
|
|
2379
|
+
'ui:layout': 'grid',
|
|
2380
|
+
'ui:layoutOptions': { columns: 2, gap: 'md' },
|
|
2381
|
+
|
|
2382
|
+
// Field-specific options
|
|
2383
|
+
'/email': { 'ui:icon': 'envelope' },
|
|
2384
|
+
'/bio': { 'ui:widget': 'textarea', 'ui:options': { rows: 4 } }
|
|
2385
|
+
};
|
|
2386
|
+
\`\`\``,
|
|
2387
|
+
},
|
|
2388
|
+
},
|
|
2389
|
+
},
|
|
2390
|
+
render: () => {
|
|
2391
|
+
const schema = {
|
|
2392
|
+
type: "object",
|
|
2393
|
+
title: "User Profile",
|
|
2394
|
+
properties: {
|
|
2395
|
+
username: {
|
|
2396
|
+
type: "string",
|
|
2397
|
+
title: "Username",
|
|
2398
|
+
minLength: 3,
|
|
2399
|
+
examples: ["johndoe"],
|
|
2400
|
+
},
|
|
2401
|
+
email: {
|
|
2402
|
+
type: "string",
|
|
2403
|
+
format: "email",
|
|
2404
|
+
title: "Email",
|
|
2405
|
+
examples: ["john@example.com"],
|
|
2406
|
+
},
|
|
2407
|
+
bio: {
|
|
2408
|
+
type: "string",
|
|
2409
|
+
title: "Bio",
|
|
2410
|
+
maxLength: 500,
|
|
2411
|
+
examples: ["Tell us about yourself..."],
|
|
2412
|
+
},
|
|
2413
|
+
website: {
|
|
2414
|
+
type: "string",
|
|
2415
|
+
format: "uri",
|
|
2416
|
+
title: "Website",
|
|
2417
|
+
examples: ["https://yoursite.com"],
|
|
2418
|
+
},
|
|
2419
|
+
},
|
|
2420
|
+
required: ["username", "email"],
|
|
2421
|
+
};
|
|
2422
|
+
|
|
2423
|
+
const uiSchema = {
|
|
2424
|
+
// Root form layout
|
|
2425
|
+
"ui:layout": "grid",
|
|
2426
|
+
"ui:layoutOptions": {
|
|
2427
|
+
columns: 2,
|
|
2428
|
+
gap: "md",
|
|
2429
|
+
},
|
|
2430
|
+
// Field-specific options
|
|
2431
|
+
"/username": {
|
|
2432
|
+
"ui:icon": "user",
|
|
2433
|
+
},
|
|
2434
|
+
"/email": {
|
|
2435
|
+
"ui:icon": "envelope",
|
|
2436
|
+
"ui:help": "We will never share your email",
|
|
2437
|
+
},
|
|
2438
|
+
"/bio": {
|
|
2439
|
+
"ui:widget": "textarea",
|
|
2440
|
+
"ui:options": { rows: 4 },
|
|
2441
|
+
"ui:class": "grid-col-span-2",
|
|
2442
|
+
},
|
|
2443
|
+
"/website": {
|
|
2444
|
+
"ui:icon": "globe",
|
|
2445
|
+
"ui:class": "grid-col-span-2",
|
|
2446
|
+
},
|
|
2447
|
+
};
|
|
2448
|
+
|
|
2449
|
+
return html`
|
|
2450
|
+
<pds-form
|
|
2451
|
+
.jsonSchema=${schema}
|
|
2452
|
+
.uiSchema=${uiSchema}
|
|
2453
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
2454
|
+
></pds-form>
|
|
2455
|
+
`;
|
|
2456
|
+
},
|
|
2457
|
+
};
|
|
2458
|
+
|
|
2459
|
+
export const RootThreeColumnGrid = {
|
|
2460
|
+
name: "Root 3-Column Grid",
|
|
2461
|
+
parameters: {
|
|
2462
|
+
docs: {
|
|
2463
|
+
description: {
|
|
2464
|
+
story: `A more complex form using a 3-column root grid layout with various field types.`,
|
|
2465
|
+
},
|
|
2466
|
+
},
|
|
2467
|
+
},
|
|
2468
|
+
render: () => {
|
|
2469
|
+
const schema = {
|
|
2470
|
+
type: "object",
|
|
2471
|
+
title: "Product Registration",
|
|
2472
|
+
properties: {
|
|
2473
|
+
productName: {
|
|
2474
|
+
type: "string",
|
|
2475
|
+
title: "Product Name",
|
|
2476
|
+
examples: ["Widget Pro"],
|
|
2477
|
+
},
|
|
2478
|
+
serialNumber: {
|
|
2479
|
+
type: "string",
|
|
2480
|
+
title: "Serial Number",
|
|
2481
|
+
examples: ["SN-12345"],
|
|
2482
|
+
},
|
|
2483
|
+
purchaseDate: {
|
|
2484
|
+
type: "string",
|
|
2485
|
+
format: "date",
|
|
2486
|
+
title: "Purchase Date",
|
|
2487
|
+
},
|
|
2488
|
+
retailer: { type: "string", title: "Retailer", examples: ["Amazon"] },
|
|
2489
|
+
price: {
|
|
2490
|
+
type: "number",
|
|
2491
|
+
title: "Price",
|
|
2492
|
+
minimum: 0,
|
|
2493
|
+
examples: [99.99],
|
|
2494
|
+
},
|
|
2495
|
+
currency: {
|
|
2496
|
+
type: "string",
|
|
2497
|
+
enum: ["USD", "EUR", "GBP"],
|
|
2498
|
+
title: "Currency",
|
|
2499
|
+
default: "USD",
|
|
2500
|
+
},
|
|
2501
|
+
condition: {
|
|
2502
|
+
type: "string",
|
|
2503
|
+
enum: ["new", "refurbished", "used"],
|
|
2504
|
+
title: "Condition",
|
|
2505
|
+
default: "new",
|
|
2506
|
+
},
|
|
2507
|
+
extendedWarranty: { type: "boolean", title: "Extended Warranty" },
|
|
2508
|
+
newsletter: { type: "boolean", title: "Subscribe to Newsletter" },
|
|
2509
|
+
},
|
|
2510
|
+
required: ["productName", "serialNumber", "purchaseDate"],
|
|
2511
|
+
};
|
|
2512
|
+
|
|
2513
|
+
const uiSchema = {
|
|
2514
|
+
"ui:layout": "grid",
|
|
2515
|
+
"ui:layoutOptions": {
|
|
2516
|
+
columns: 3,
|
|
2517
|
+
gap: "md",
|
|
2518
|
+
},
|
|
2519
|
+
};
|
|
2520
|
+
|
|
2521
|
+
const options = {
|
|
2522
|
+
widgets: { booleans: "toggle" },
|
|
2523
|
+
};
|
|
2524
|
+
|
|
2525
|
+
return html`
|
|
2526
|
+
<pds-form
|
|
2527
|
+
.jsonSchema=${schema}
|
|
2528
|
+
.uiSchema=${uiSchema}
|
|
2529
|
+
.options=${options}
|
|
2530
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
2531
|
+
></pds-form>
|
|
2532
|
+
`;
|
|
2533
|
+
},
|
|
2534
|
+
};
|
|
2535
|
+
|
|
2536
|
+
export const EnumWithOneOfAnyOf = {
|
|
2537
|
+
name: "Selection Fields with Custom Labels (oneOf/anyOf)",
|
|
2538
|
+
parameters: {
|
|
2539
|
+
docs: {
|
|
2540
|
+
description: {
|
|
2541
|
+
story: `Use \`oneOf\` or \`anyOf\` with \`const\` and \`title\` to provide human-friendly display labels for enum values.
|
|
2542
|
+
|
|
2543
|
+
This is useful when you want to store technical values (like language codes or IDs) while showing user-friendly labels.
|
|
2544
|
+
|
|
2545
|
+
\`\`\`javascript
|
|
2546
|
+
const schema = {
|
|
2547
|
+
properties: {
|
|
2548
|
+
language: {
|
|
2549
|
+
type: 'string',
|
|
2550
|
+
title: 'Language',
|
|
2551
|
+
oneOf: [ // Use oneOf or anyOf
|
|
2552
|
+
{ const: 'en', title: 'English' },
|
|
2553
|
+
{ const: 'es', title: 'Spanish' },
|
|
2554
|
+
{ const: 'fr', title: 'French' },
|
|
2555
|
+
{ const: 'de', title: 'German' },
|
|
2556
|
+
{ const: 'zh', title: 'Chinese' },
|
|
2557
|
+
{ const: 'ja', title: 'Japanese' }
|
|
2558
|
+
]
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
};
|
|
2562
|
+
\`\`\`
|
|
2563
|
+
|
|
2564
|
+
Both \`oneOf\` and \`anyOf\` work identically for this purpose. Each option should have \`const\` (the value stored) and \`title\` (the label displayed). Works with select dropdowns, radio buttons, and checkbox groups.`,
|
|
2565
|
+
},
|
|
2566
|
+
},
|
|
2567
|
+
},
|
|
2568
|
+
render: () => {
|
|
2569
|
+
const schema = {
|
|
2570
|
+
type: "object",
|
|
2571
|
+
title: "Preferences",
|
|
2572
|
+
properties: {
|
|
2573
|
+
language: {
|
|
2574
|
+
type: "string",
|
|
2575
|
+
title: "Language",
|
|
2576
|
+
oneOf: [
|
|
2577
|
+
{ const: "en", title: "English" },
|
|
2578
|
+
{ const: "es", title: "Spanish" },
|
|
2579
|
+
{ const: "fr", title: "French" },
|
|
2580
|
+
{ const: "de", title: "German" },
|
|
2581
|
+
{ const: "zh", title: "Chinese" },
|
|
2582
|
+
{ const: "ja", title: "Japanese" },
|
|
2583
|
+
],
|
|
2584
|
+
default: "en",
|
|
2585
|
+
},
|
|
2586
|
+
country: {
|
|
2587
|
+
type: "string",
|
|
2588
|
+
title: "Country",
|
|
2589
|
+
anyOf: [
|
|
2590
|
+
{ const: "US", title: "United States" },
|
|
2591
|
+
{ const: "GB", title: "United Kingdom" },
|
|
2592
|
+
{ const: "CA", title: "Canada" },
|
|
2593
|
+
{ const: "AU", title: "Australia" },
|
|
2594
|
+
{ const: "DE", title: "Germany" },
|
|
2595
|
+
{ const: "FR", title: "France" },
|
|
2596
|
+
],
|
|
2597
|
+
},
|
|
2598
|
+
priority: {
|
|
2599
|
+
type: "string",
|
|
2600
|
+
title: "Priority Level",
|
|
2601
|
+
oneOf: [
|
|
2602
|
+
{ const: "p1", title: "🔴 Critical" },
|
|
2603
|
+
{ const: "p2", title: "🟠 High" },
|
|
2604
|
+
{ const: "p3", title: "🟡 Medium" },
|
|
2605
|
+
{ const: "p4", title: "🟢 Low" },
|
|
2606
|
+
],
|
|
2607
|
+
default: "p3",
|
|
2608
|
+
},
|
|
2609
|
+
interests: {
|
|
2610
|
+
type: "array",
|
|
2611
|
+
title: "Interests (checkbox group)",
|
|
2612
|
+
items: {
|
|
2613
|
+
type: "string",
|
|
2614
|
+
oneOf: [
|
|
2615
|
+
{ const: "dev", title: "Development" },
|
|
2616
|
+
{ const: "design", title: "Design" },
|
|
2617
|
+
{ const: "pm", title: "Project Management" },
|
|
2618
|
+
{ const: "qa", title: "Quality Assurance" },
|
|
2619
|
+
{ const: "devops", title: "DevOps" },
|
|
2620
|
+
],
|
|
2621
|
+
},
|
|
2622
|
+
uniqueItems: true,
|
|
2623
|
+
},
|
|
2624
|
+
},
|
|
2625
|
+
};
|
|
2626
|
+
|
|
2627
|
+
const uiSchema = {
|
|
2628
|
+
"/priority": { "ui:widget": "radio" },
|
|
2629
|
+
};
|
|
2630
|
+
|
|
2631
|
+
return html`
|
|
2632
|
+
<pds-form
|
|
2633
|
+
.jsonSchema=${schema}
|
|
2634
|
+
.uiSchema=${uiSchema}
|
|
2635
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
2636
|
+
></pds-form>
|
|
2637
|
+
`;
|
|
2638
|
+
},
|
|
2639
|
+
};
|
|
2640
|
+
|
|
2641
|
+
export const ConditionalShowHide = {
|
|
2642
|
+
parameters: {
|
|
2643
|
+
docs: {
|
|
2644
|
+
description: {
|
|
2645
|
+
story: `Use \`ui:visibleWhen\` to show/hide fields based on other field values.
|
|
2646
|
+
|
|
2647
|
+
In this example, selecting "Other" from the dropdown reveals a text field to specify details.
|
|
2648
|
+
|
|
2649
|
+
\`\`\`javascript
|
|
2650
|
+
const uiSchema = {
|
|
2651
|
+
'/otherReason': {
|
|
2652
|
+
'ui:visibleWhen': { '/reason': 'other' },
|
|
2653
|
+
'ui:requiredWhen': { '/reason': 'other' }
|
|
2654
|
+
}
|
|
2655
|
+
};
|
|
2656
|
+
\`\`\``,
|
|
2657
|
+
},
|
|
2658
|
+
},
|
|
2659
|
+
},
|
|
2660
|
+
render: () => {
|
|
2661
|
+
const schema = {
|
|
2662
|
+
type: "object",
|
|
2663
|
+
title: "Feedback Form",
|
|
2664
|
+
properties: {
|
|
2665
|
+
reason: {
|
|
2666
|
+
type: "string",
|
|
2667
|
+
title: "How did you hear about us?",
|
|
2668
|
+
oneOf: [
|
|
2669
|
+
{ const: "search", title: "Search Engine" },
|
|
2670
|
+
{ const: "social", title: "Social Media" },
|
|
2671
|
+
{ const: "friend", title: "Friend Referral" },
|
|
2672
|
+
{ const: "other", title: "Other... (please specify)" },
|
|
2673
|
+
],
|
|
2674
|
+
},
|
|
2675
|
+
otherReason: {
|
|
2676
|
+
type: "string",
|
|
2677
|
+
title: "Please specify",
|
|
2678
|
+
examples: ["Tell us more..."],
|
|
2679
|
+
},
|
|
2680
|
+
},
|
|
2681
|
+
};
|
|
2682
|
+
|
|
2683
|
+
const uiSchema = {
|
|
2684
|
+
"/otherReason": {
|
|
2685
|
+
"ui:visibleWhen": { "/reason": "other" },
|
|
2686
|
+
"ui:requiredWhen": { "/reason": "other" },
|
|
2687
|
+
},
|
|
2688
|
+
};
|
|
2689
|
+
|
|
2690
|
+
return html`
|
|
2691
|
+
<pds-form
|
|
2692
|
+
data-required
|
|
2693
|
+
.jsonSchema=${schema}
|
|
2694
|
+
.uiSchema=${uiSchema}
|
|
2695
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
2696
|
+
></pds-form>
|
|
2697
|
+
`;
|
|
2698
|
+
},
|
|
2699
|
+
};
|
|
2700
|
+
|
|
2701
|
+
export const ConditionalRequired = {
|
|
2702
|
+
parameters: {
|
|
2703
|
+
docs: {
|
|
2704
|
+
description: {
|
|
2705
|
+
story: `Use \`ui:requiredWhen\` to make fields conditionally required.
|
|
2706
|
+
|
|
2707
|
+
In this example, toggling "Prefer phone contact" makes the phone field required and disables the email field.
|
|
2708
|
+
|
|
2709
|
+
\`\`\`javascript
|
|
2710
|
+
const uiSchema = {
|
|
2711
|
+
'/email': {
|
|
2712
|
+
'ui:disabledWhen': { '/preferPhone': true }
|
|
2713
|
+
},
|
|
2714
|
+
'/phone': {
|
|
2715
|
+
'ui:requiredWhen': { '/preferPhone': true }
|
|
2716
|
+
}
|
|
2717
|
+
};
|
|
2718
|
+
\`\`\``,
|
|
2719
|
+
},
|
|
2720
|
+
},
|
|
2721
|
+
},
|
|
2722
|
+
render: () => {
|
|
2723
|
+
const schema = {
|
|
2724
|
+
type: "object",
|
|
2725
|
+
title: "Contact Preferences",
|
|
2726
|
+
properties: {
|
|
2727
|
+
preferPhone: {
|
|
2728
|
+
type: "boolean",
|
|
2729
|
+
title: "I prefer to be contacted by phone",
|
|
2730
|
+
default: false,
|
|
2731
|
+
},
|
|
2732
|
+
email: {
|
|
2733
|
+
type: "string",
|
|
2734
|
+
format: "email",
|
|
2735
|
+
title: "Email Address",
|
|
2736
|
+
examples: ["you@example.com"],
|
|
2737
|
+
},
|
|
2738
|
+
phone: {
|
|
2739
|
+
type: "string",
|
|
2740
|
+
title: "Phone Number",
|
|
2741
|
+
examples: ["555-123-4567"],
|
|
2742
|
+
},
|
|
2743
|
+
},
|
|
2744
|
+
required: ["email"],
|
|
2745
|
+
};
|
|
2746
|
+
|
|
2747
|
+
const uiSchema = {
|
|
2748
|
+
"/email": {
|
|
2749
|
+
"ui:disabledWhen": { "/preferPhone": true },
|
|
2750
|
+
"ui:icon": "envelope",
|
|
2751
|
+
},
|
|
2752
|
+
"/phone": {
|
|
2753
|
+
"ui:requiredWhen": { "/preferPhone": true },
|
|
2754
|
+
"ui:icon": "phone",
|
|
2755
|
+
},
|
|
2756
|
+
};
|
|
2757
|
+
|
|
2758
|
+
return html`
|
|
2759
|
+
<pds-form
|
|
2760
|
+
data-required
|
|
2761
|
+
.jsonSchema=${schema}
|
|
2762
|
+
.uiSchema=${uiSchema}
|
|
2763
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
2764
|
+
></pds-form>
|
|
2765
|
+
`;
|
|
2766
|
+
},
|
|
2767
|
+
};
|
|
2768
|
+
|
|
2769
|
+
export const ConditionalComplex = {
|
|
2770
|
+
parameters: {
|
|
2771
|
+
docs: {
|
|
2772
|
+
description: {
|
|
2773
|
+
story: `A comprehensive example combining multiple conditional features: visibility, required states, and calculated values.
|
|
2774
|
+
|
|
2775
|
+
### Features demonstrated:
|
|
2776
|
+
- **Show/Hide**: Company name appears for business accounts; shipping address hidden for pickup
|
|
2777
|
+
- **Conditional Required**: Company name required for business; phone required when preferred
|
|
2778
|
+
- **Disable**: Email disabled when phone is preferred
|
|
2779
|
+
- **Calculations**: Full name, subtotal, shipping cost, and total are all computed automatically`,
|
|
2780
|
+
},
|
|
2781
|
+
},
|
|
2782
|
+
},
|
|
2783
|
+
render: () => {
|
|
2784
|
+
const schema = {
|
|
2785
|
+
type: "object",
|
|
2786
|
+
title: "Order Form",
|
|
2787
|
+
properties: {
|
|
2788
|
+
accountType: {
|
|
2789
|
+
type: "string",
|
|
2790
|
+
title: "Account Type",
|
|
2791
|
+
oneOf: [
|
|
2792
|
+
{ const: "personal", title: "Personal" },
|
|
2793
|
+
{ const: "business", title: "Business" },
|
|
2794
|
+
],
|
|
2795
|
+
default: "personal",
|
|
2796
|
+
},
|
|
2797
|
+
companyName: {
|
|
2798
|
+
type: "string",
|
|
2799
|
+
title: "Company Name",
|
|
2800
|
+
examples: ["Acme Inc."],
|
|
2801
|
+
},
|
|
2802
|
+
preferPhone: {
|
|
2803
|
+
type: "boolean",
|
|
2804
|
+
title: "I prefer to be contacted by phone",
|
|
2805
|
+
default: false,
|
|
2806
|
+
},
|
|
2807
|
+
email: {
|
|
2808
|
+
type: "string",
|
|
2809
|
+
format: "email",
|
|
2810
|
+
title: "Email",
|
|
2811
|
+
examples: ["you@example.com"],
|
|
2812
|
+
},
|
|
2813
|
+
phone: {
|
|
2814
|
+
type: "string",
|
|
2815
|
+
title: "Phone",
|
|
2816
|
+
examples: ["555-123-4567"],
|
|
2817
|
+
},
|
|
2818
|
+
firstName: {
|
|
2819
|
+
type: "string",
|
|
2820
|
+
title: "First Name",
|
|
2821
|
+
examples: ["John"],
|
|
2822
|
+
},
|
|
2823
|
+
lastName: {
|
|
2824
|
+
type: "string",
|
|
2825
|
+
title: "Last Name",
|
|
2826
|
+
examples: ["Doe"],
|
|
2827
|
+
},
|
|
2828
|
+
fullName: {
|
|
2829
|
+
type: "string",
|
|
2830
|
+
title: "Full Name (calculated)",
|
|
2831
|
+
},
|
|
2832
|
+
deliveryType: {
|
|
2833
|
+
type: "string",
|
|
2834
|
+
title: "Delivery Type",
|
|
2835
|
+
oneOf: [
|
|
2836
|
+
{ const: "standard", title: "Standard (5-7 days)" },
|
|
2837
|
+
{ const: "express", title: "Express (1-2 days)" },
|
|
2838
|
+
{ const: "pickup", title: "Pickup" },
|
|
2839
|
+
],
|
|
2840
|
+
default: "standard",
|
|
2841
|
+
},
|
|
2842
|
+
quantity: {
|
|
2843
|
+
type: "integer",
|
|
2844
|
+
title: "Quantity",
|
|
2845
|
+
minimum: 1,
|
|
2846
|
+
default: 1,
|
|
2847
|
+
},
|
|
2848
|
+
unitPrice: {
|
|
2849
|
+
type: "number",
|
|
2850
|
+
title: "Unit Price",
|
|
2851
|
+
default: 29.99,
|
|
2852
|
+
},
|
|
2853
|
+
subtotal: {
|
|
2854
|
+
type: "number",
|
|
2855
|
+
title: "Subtotal",
|
|
2856
|
+
},
|
|
2857
|
+
shippingCost: {
|
|
2858
|
+
type: "number",
|
|
2859
|
+
title: "Shipping Cost",
|
|
2860
|
+
},
|
|
2861
|
+
total: {
|
|
2862
|
+
type: "number",
|
|
2863
|
+
title: "Total",
|
|
2864
|
+
},
|
|
2865
|
+
shippingAddress: {
|
|
2866
|
+
type: "object",
|
|
2867
|
+
title: "Shipping Address",
|
|
2868
|
+
properties: {
|
|
2869
|
+
street: {
|
|
2870
|
+
type: "string",
|
|
2871
|
+
title: "Street",
|
|
2872
|
+
examples: ["123 Main St"],
|
|
2873
|
+
},
|
|
2874
|
+
city: { type: "string", title: "City", examples: ["New York"] },
|
|
2875
|
+
zip: { type: "string", title: "ZIP Code", examples: ["10001"] },
|
|
2876
|
+
},
|
|
2877
|
+
},
|
|
2878
|
+
},
|
|
2879
|
+
required: ["email", "firstName", "lastName", "deliveryType"],
|
|
2880
|
+
};
|
|
2881
|
+
|
|
2882
|
+
const uiSchema = {
|
|
2883
|
+
"ui:layout": "grid",
|
|
2884
|
+
"ui:layoutOptions": { columns: 2, gap: "md" },
|
|
2885
|
+
|
|
2886
|
+
"/companyName": {
|
|
2887
|
+
"ui:visibleWhen": { "/accountType": "business" },
|
|
2888
|
+
"ui:requiredWhen": { "/accountType": "business" },
|
|
2889
|
+
},
|
|
2890
|
+
"/email": {
|
|
2891
|
+
"ui:disabledWhen": { "/preferPhone": true },
|
|
2892
|
+
"ui:icon": "envelope",
|
|
2893
|
+
},
|
|
2894
|
+
"/phone": {
|
|
2895
|
+
"ui:requiredWhen": { "/preferPhone": true },
|
|
2896
|
+
"ui:icon": "phone",
|
|
2897
|
+
},
|
|
2898
|
+
"/fullName": {
|
|
2899
|
+
"ui:calculate": { $concat: ["/firstName", " ", "/lastName"] },
|
|
2900
|
+
},
|
|
2901
|
+
"/subtotal": {
|
|
2902
|
+
"ui:calculate": { $multiply: ["/quantity", "/unitPrice"] },
|
|
2903
|
+
},
|
|
2904
|
+
"/shippingCost": {
|
|
2905
|
+
"ui:calculate": {
|
|
2906
|
+
$if: {
|
|
2907
|
+
cond: { "/deliveryType": "express" },
|
|
2908
|
+
then: 25,
|
|
2909
|
+
else: {
|
|
2910
|
+
$if: {
|
|
2911
|
+
cond: { "/deliveryType": "pickup" },
|
|
2912
|
+
then: 0,
|
|
2913
|
+
else: 10,
|
|
2914
|
+
},
|
|
2915
|
+
},
|
|
2916
|
+
},
|
|
2917
|
+
},
|
|
2918
|
+
},
|
|
2919
|
+
"/total": {
|
|
2920
|
+
"ui:calculate": { $sum: ["/subtotal", "/shippingCost"] },
|
|
2921
|
+
},
|
|
2922
|
+
"/shippingAddress": {
|
|
2923
|
+
"ui:visibleWhen": { "/deliveryType": { $ne: "pickup" } },
|
|
2924
|
+
"ui:layout": "flex",
|
|
2925
|
+
"ui:layoutOptions": { gap: "sm", wrap: true },
|
|
2926
|
+
},
|
|
2927
|
+
};
|
|
2928
|
+
|
|
2929
|
+
return html`
|
|
2930
|
+
<div class="alert alert-info">
|
|
2931
|
+
<p><strong>Try these interactions:</strong></p>
|
|
2932
|
+
<ul>
|
|
2933
|
+
<li>
|
|
2934
|
+
Change <strong>Account Type</strong> to "Business" → Company Name
|
|
2935
|
+
field appears and becomes required
|
|
2936
|
+
</li>
|
|
2937
|
+
<li>
|
|
2938
|
+
Toggle <strong>"Prefer phone contact"</strong> → Email is disabled,
|
|
2939
|
+
Phone becomes required
|
|
2940
|
+
</li>
|
|
2941
|
+
<li>
|
|
2942
|
+
Type in <strong>First/Last Name</strong> → Full Name is calculated
|
|
2943
|
+
automatically
|
|
2944
|
+
</li>
|
|
2945
|
+
<li>
|
|
2946
|
+
Change <strong>Quantity</strong> or <strong>Unit Price</strong> →
|
|
2947
|
+
Subtotal and Total update
|
|
2948
|
+
</li>
|
|
2949
|
+
<li>
|
|
2950
|
+
Select <strong>Delivery Type</strong> → Shipping cost changes
|
|
2951
|
+
(Express: $25, Standard: $10, Pickup: $0)
|
|
2952
|
+
</li>
|
|
2953
|
+
<li>
|
|
2954
|
+
Select <strong>"Pickup"</strong> → Shipping Address section is
|
|
2955
|
+
hidden
|
|
2956
|
+
</li>
|
|
2957
|
+
</ul>
|
|
2958
|
+
</div>
|
|
2959
|
+
<pds-form
|
|
2960
|
+
data-required
|
|
2961
|
+
.jsonSchema=${schema}
|
|
2962
|
+
.uiSchema=${uiSchema}
|
|
2963
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
2964
|
+
></pds-form>
|
|
2965
|
+
`;
|
|
2966
|
+
},
|
|
2967
|
+
};
|
|
2968
|
+
|
|
2969
|
+
export const CalculatedValues = {
|
|
2970
|
+
parameters: {
|
|
2971
|
+
docs: {
|
|
2972
|
+
description: {
|
|
2973
|
+
story: `Use \`ui:calculate\` to compute values from other fields. Calculated fields are **read-only by default**.
|
|
2974
|
+
|
|
2975
|
+
### Available Operators
|
|
2976
|
+
- \`$concat\`: Join strings → \`{ "$concat": ["/first", " ", "/last"] }\`
|
|
2977
|
+
- \`$sum\`: Add numbers → \`{ "$sum": ["/a", "/b", "/c"] }\`
|
|
2978
|
+
- \`$subtract\`: Subtract → \`{ "$subtract": ["/total", "/discount"] }\`
|
|
2979
|
+
- \`$multiply\`: Multiply → \`{ "$multiply": ["/qty", "/price"] }\`
|
|
2980
|
+
- \`$divide\`: Divide → \`{ "$divide": ["/total", "/count"] }\`
|
|
2981
|
+
- \`$coalesce\`: First non-empty → \`{ "$coalesce": ["/nickname", "/firstName"] }\``,
|
|
2982
|
+
},
|
|
2983
|
+
},
|
|
2984
|
+
},
|
|
2985
|
+
render: () => {
|
|
2986
|
+
const schema = {
|
|
2987
|
+
type: "object",
|
|
2988
|
+
title: "Invoice Calculator",
|
|
2989
|
+
properties: {
|
|
2990
|
+
firstName: { type: "string", title: "First Name", examples: ["John"] },
|
|
2991
|
+
lastName: { type: "string", title: "Last Name", examples: ["Doe"] },
|
|
2992
|
+
nickname: {
|
|
2993
|
+
type: "string",
|
|
2994
|
+
title: "Nickname (optional)",
|
|
2995
|
+
examples: ["Johnny"],
|
|
2996
|
+
},
|
|
2997
|
+
displayName: {
|
|
2998
|
+
type: "string",
|
|
2999
|
+
title: "Display Name (nickname or first name)",
|
|
3000
|
+
},
|
|
3001
|
+
fullName: { type: "string", title: "Full Name" },
|
|
3002
|
+
quantity: {
|
|
3003
|
+
type: "integer",
|
|
3004
|
+
title: "Quantity",
|
|
3005
|
+
minimum: 1,
|
|
3006
|
+
default: 2,
|
|
3007
|
+
},
|
|
3008
|
+
unitPrice: { type: "number", title: "Unit Price ($)", default: 49.99 },
|
|
3009
|
+
subtotal: { type: "number", title: "Subtotal" },
|
|
3010
|
+
taxRate: { type: "number", title: "Tax Rate (%)", default: 8.5 },
|
|
3011
|
+
taxAmount: { type: "number", title: "Tax Amount" },
|
|
3012
|
+
discount: { type: "number", title: "Discount ($)", default: 10 },
|
|
3013
|
+
total: { type: "number", title: "Grand Total" },
|
|
3014
|
+
},
|
|
3015
|
+
};
|
|
3016
|
+
|
|
3017
|
+
const uiSchema = {
|
|
3018
|
+
"ui:layout": "grid",
|
|
3019
|
+
"ui:layoutOptions": { columns: 2, gap: "md" },
|
|
3020
|
+
|
|
3021
|
+
// String concatenation
|
|
3022
|
+
"/fullName": {
|
|
3023
|
+
"ui:calculate": { $concat: ["/firstName", " ", "/lastName"] },
|
|
3024
|
+
},
|
|
3025
|
+
|
|
3026
|
+
// Coalesce: use nickname if provided, otherwise first name
|
|
3027
|
+
"/displayName": {
|
|
3028
|
+
"ui:calculate": { $coalesce: ["/nickname", "/firstName"] },
|
|
3029
|
+
},
|
|
3030
|
+
|
|
3031
|
+
// Multiply: quantity × price
|
|
3032
|
+
"/subtotal": {
|
|
3033
|
+
"ui:calculate": { $multiply: ["/quantity", "/unitPrice"] },
|
|
3034
|
+
},
|
|
3035
|
+
|
|
3036
|
+
// Divide + multiply for tax: (subtotal × taxRate) / 100
|
|
3037
|
+
"/taxAmount": {
|
|
3038
|
+
"ui:calculate": {
|
|
3039
|
+
$divide: [{ $multiply: ["/subtotal", "/taxRate"] }, 100],
|
|
3040
|
+
},
|
|
3041
|
+
},
|
|
3042
|
+
|
|
3043
|
+
// Sum and subtract: subtotal + tax - discount
|
|
3044
|
+
"/total": {
|
|
3045
|
+
"ui:calculate": {
|
|
3046
|
+
$subtract: [{ $sum: ["/subtotal", "/taxAmount"] }, "/discount"],
|
|
3047
|
+
},
|
|
3048
|
+
},
|
|
3049
|
+
};
|
|
3050
|
+
|
|
3051
|
+
return html`
|
|
3052
|
+
<div class="alert alert-info">
|
|
3053
|
+
<p><strong>All calculated fields update automatically:</strong></p>
|
|
3054
|
+
<ul>
|
|
3055
|
+
<li>
|
|
3056
|
+
<strong>Full Name</strong> = First + Last (using
|
|
3057
|
+
<code>$concat</code>)
|
|
3058
|
+
</li>
|
|
3059
|
+
<li>
|
|
3060
|
+
<strong>Display Name</strong> = Nickname if set, otherwise First
|
|
3061
|
+
Name (using <code>$coalesce</code>)
|
|
3062
|
+
</li>
|
|
3063
|
+
<li>
|
|
3064
|
+
<strong>Subtotal</strong> = Quantity × Unit Price (using
|
|
3065
|
+
<code>$multiply</code>)
|
|
3066
|
+
</li>
|
|
3067
|
+
<li>
|
|
3068
|
+
<strong>Tax Amount</strong> = Subtotal × Tax Rate ÷ 100 (using
|
|
3069
|
+
<code>$divide</code>)
|
|
3070
|
+
</li>
|
|
3071
|
+
<li>
|
|
3072
|
+
<strong>Grand Total</strong> = Subtotal + Tax - Discount (using
|
|
3073
|
+
<code>$sum</code> and <code>$subtract</code>)
|
|
3074
|
+
</li>
|
|
3075
|
+
</ul>
|
|
3076
|
+
</div>
|
|
3077
|
+
<pds-form
|
|
3078
|
+
.jsonSchema=${schema}
|
|
3079
|
+
.uiSchema=${uiSchema}
|
|
3080
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
3081
|
+
></pds-form>
|
|
3082
|
+
`;
|
|
3083
|
+
},
|
|
3084
|
+
};
|
|
3085
|
+
|
|
3086
|
+
export const CalculateWithOverride = {
|
|
3087
|
+
parameters: {
|
|
3088
|
+
docs: {
|
|
3089
|
+
description: {
|
|
3090
|
+
story: `Use \`ui:calculateOverride: true\` to allow users to edit calculated values.
|
|
3091
|
+
|
|
3092
|
+
The field starts with a computed value but the user can modify it. This is useful for:
|
|
3093
|
+
- Suggested values that can be customized
|
|
3094
|
+
- Default calculations that may need manual adjustment
|
|
3095
|
+
- "Smart defaults" that users can override
|
|
3096
|
+
|
|
3097
|
+
\`\`\`javascript
|
|
3098
|
+
'/suggestedPrice': {
|
|
3099
|
+
'ui:calculate': { '$multiply': ['/baseCost', 1.25] },
|
|
3100
|
+
'ui:calculateOverride': true // User can edit
|
|
3101
|
+
}
|
|
3102
|
+
\`\`\``,
|
|
3103
|
+
},
|
|
3104
|
+
},
|
|
3105
|
+
},
|
|
3106
|
+
render: () => {
|
|
3107
|
+
const schema = {
|
|
3108
|
+
type: "object",
|
|
3109
|
+
title: "Product Pricing",
|
|
3110
|
+
properties: {
|
|
3111
|
+
productName: {
|
|
3112
|
+
type: "string",
|
|
3113
|
+
title: "Product Name",
|
|
3114
|
+
examples: ["Widget Pro"],
|
|
3115
|
+
},
|
|
3116
|
+
baseCost: { type: "number", title: "Base Cost ($)", default: 100 },
|
|
3117
|
+
suggestedPrice: { type: "number", title: "Suggested Price (editable)" },
|
|
3118
|
+
finalPrice: { type: "number", title: "Final Price (read-only)" },
|
|
3119
|
+
profit: { type: "number", title: "Profit Margin" },
|
|
3120
|
+
},
|
|
3121
|
+
};
|
|
3122
|
+
|
|
3123
|
+
const uiSchema = {
|
|
3124
|
+
// Suggested price: calculated but editable
|
|
3125
|
+
"/suggestedPrice": {
|
|
3126
|
+
"ui:calculate": { $multiply: ["/baseCost", 1.5] },
|
|
3127
|
+
"ui:calculateOverride": true,
|
|
3128
|
+
"ui:help": "💡 Calculated as 1.5× base cost, but you can adjust it",
|
|
3129
|
+
},
|
|
3130
|
+
|
|
3131
|
+
// Final price: read-only calculation
|
|
3132
|
+
"/finalPrice": {
|
|
3133
|
+
"ui:calculate": {
|
|
3134
|
+
$coalesce: ["/suggestedPrice", { $multiply: ["/baseCost", 1.5] }],
|
|
3135
|
+
},
|
|
3136
|
+
"ui:help": "🔒 Read-only: uses your suggested price",
|
|
3137
|
+
},
|
|
3138
|
+
|
|
3139
|
+
// Profit: final - base
|
|
3140
|
+
"/profit": {
|
|
3141
|
+
"ui:calculate": { $subtract: ["/finalPrice", "/baseCost"] },
|
|
3142
|
+
},
|
|
3143
|
+
};
|
|
3144
|
+
|
|
3145
|
+
return html`
|
|
3146
|
+
<div class="alert alert-info">
|
|
3147
|
+
<p><strong>Compare editable vs read-only calculations:</strong></p>
|
|
3148
|
+
<ul>
|
|
3149
|
+
<li>
|
|
3150
|
+
<strong>Suggested Price</strong> starts at 1.5× base cost but
|
|
3151
|
+
<em>you can edit it</em>
|
|
3152
|
+
</li>
|
|
3153
|
+
<li>
|
|
3154
|
+
<strong>Final Price</strong> is read-only and reflects your
|
|
3155
|
+
suggested price
|
|
3156
|
+
</li>
|
|
3157
|
+
<li>
|
|
3158
|
+
<strong>Profit Margin</strong> updates based on final price - base
|
|
3159
|
+
cost
|
|
3160
|
+
</li>
|
|
3161
|
+
</ul>
|
|
3162
|
+
<p>
|
|
3163
|
+
Try changing the base cost, then manually adjusting the suggested
|
|
3164
|
+
price!
|
|
3165
|
+
</p>
|
|
3166
|
+
</div>
|
|
3167
|
+
<pds-form
|
|
3168
|
+
.jsonSchema=${schema}
|
|
3169
|
+
.uiSchema=${uiSchema}
|
|
3170
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
3171
|
+
></pds-form>
|
|
3172
|
+
`;
|
|
3173
|
+
},
|
|
3174
|
+
};
|
|
3175
|
+
|
|
3176
|
+
export const ConditionalWithIf = {
|
|
3177
|
+
parameters: {
|
|
3178
|
+
docs: {
|
|
3179
|
+
description: {
|
|
3180
|
+
story: `Use \`$if\` for conditional calculations based on other field values.
|
|
3181
|
+
|
|
3182
|
+
\`\`\`javascript
|
|
3183
|
+
'/shippingCost': {
|
|
3184
|
+
'ui:calculate': {
|
|
3185
|
+
'$if': {
|
|
3186
|
+
'cond': { '/membership': 'premium' },
|
|
3187
|
+
'then': 0,
|
|
3188
|
+
'else': 9.99
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
\`\`\`
|
|
3193
|
+
|
|
3194
|
+
You can nest \`$if\` expressions for multiple conditions.`,
|
|
3195
|
+
},
|
|
3196
|
+
},
|
|
3197
|
+
},
|
|
3198
|
+
render: () => {
|
|
3199
|
+
const schema = {
|
|
3200
|
+
type: "object",
|
|
3201
|
+
title: "Membership Pricing",
|
|
3202
|
+
properties: {
|
|
3203
|
+
membership: {
|
|
3204
|
+
type: "string",
|
|
3205
|
+
title: "Membership Level",
|
|
3206
|
+
oneOf: [
|
|
3207
|
+
{ const: "basic", title: "Basic" },
|
|
3208
|
+
{ const: "standard", title: "Standard" },
|
|
3209
|
+
{ const: "premium", title: "Premium" },
|
|
3210
|
+
],
|
|
3211
|
+
default: "basic",
|
|
3212
|
+
},
|
|
3213
|
+
orderAmount: { type: "number", title: "Order Amount ($)", default: 75 },
|
|
3214
|
+
discountPercent: { type: "number", title: "Discount (%)" },
|
|
3215
|
+
discountAmount: { type: "number", title: "Discount Amount ($)" },
|
|
3216
|
+
shippingCost: { type: "number", title: "Shipping Cost ($)" },
|
|
3217
|
+
finalTotal: { type: "number", title: "Final Total ($)" },
|
|
3218
|
+
},
|
|
3219
|
+
};
|
|
3220
|
+
|
|
3221
|
+
const uiSchema = {
|
|
3222
|
+
// Discount percent based on membership: basic=0%, standard=10%, premium=20%
|
|
3223
|
+
"/discountPercent": {
|
|
3224
|
+
"ui:calculate": {
|
|
3225
|
+
$if: {
|
|
3226
|
+
cond: { "/membership": "premium" },
|
|
3227
|
+
then: 20,
|
|
3228
|
+
else: {
|
|
3229
|
+
$if: {
|
|
3230
|
+
cond: { "/membership": "standard" },
|
|
3231
|
+
then: 10,
|
|
3232
|
+
else: 0,
|
|
3233
|
+
},
|
|
3234
|
+
},
|
|
3235
|
+
},
|
|
3236
|
+
},
|
|
3237
|
+
},
|
|
3238
|
+
|
|
3239
|
+
// Discount amount
|
|
3240
|
+
"/discountAmount": {
|
|
3241
|
+
"ui:calculate": {
|
|
3242
|
+
$divide: [{ $multiply: ["/orderAmount", "/discountPercent"] }, 100],
|
|
3243
|
+
},
|
|
3244
|
+
},
|
|
3245
|
+
|
|
3246
|
+
// Shipping: free for premium, $5.99 for standard, $9.99 for basic
|
|
3247
|
+
"/shippingCost": {
|
|
3248
|
+
"ui:calculate": {
|
|
3249
|
+
$if: {
|
|
3250
|
+
cond: { "/membership": "premium" },
|
|
3251
|
+
then: 0,
|
|
3252
|
+
else: {
|
|
3253
|
+
$if: {
|
|
3254
|
+
cond: { "/membership": "standard" },
|
|
3255
|
+
then: 5.99,
|
|
3256
|
+
else: 9.99,
|
|
3257
|
+
},
|
|
3258
|
+
},
|
|
3259
|
+
},
|
|
3260
|
+
},
|
|
3261
|
+
},
|
|
3262
|
+
|
|
3263
|
+
// Final total
|
|
3264
|
+
"/finalTotal": {
|
|
3265
|
+
"ui:calculate": {
|
|
3266
|
+
$sum: [
|
|
3267
|
+
{ $subtract: ["/orderAmount", "/discountAmount"] },
|
|
3268
|
+
"/shippingCost",
|
|
3269
|
+
],
|
|
3270
|
+
},
|
|
3271
|
+
},
|
|
3272
|
+
};
|
|
3273
|
+
|
|
3274
|
+
return html`
|
|
3275
|
+
<div class="alert alert-info">
|
|
3276
|
+
<p><strong>Pricing varies by membership level:</strong></p>
|
|
3277
|
+
<table style="width: 100%; text-align: left;">
|
|
3278
|
+
<thead>
|
|
3279
|
+
<tr>
|
|
3280
|
+
<th>Level</th>
|
|
3281
|
+
<th>Discount</th>
|
|
3282
|
+
<th>Shipping</th>
|
|
3283
|
+
</tr>
|
|
3284
|
+
</thead>
|
|
3285
|
+
<tbody>
|
|
3286
|
+
<tr>
|
|
3287
|
+
<td>Basic</td>
|
|
3288
|
+
<td>0%</td>
|
|
3289
|
+
<td>$9.99</td>
|
|
3290
|
+
</tr>
|
|
3291
|
+
<tr>
|
|
3292
|
+
<td>Standard</td>
|
|
3293
|
+
<td>10%</td>
|
|
3294
|
+
<td>$5.99</td>
|
|
3295
|
+
</tr>
|
|
3296
|
+
<tr>
|
|
3297
|
+
<td>Premium</td>
|
|
3298
|
+
<td>20%</td>
|
|
3299
|
+
<td>FREE</td>
|
|
3300
|
+
</tr>
|
|
3301
|
+
</tbody>
|
|
3302
|
+
</table>
|
|
3303
|
+
<p>
|
|
3304
|
+
Change the <strong>Membership Level</strong> to see all values update!
|
|
3305
|
+
</p>
|
|
3306
|
+
</div>
|
|
3307
|
+
<pds-form
|
|
3308
|
+
.jsonSchema=${schema}
|
|
3309
|
+
.uiSchema=${uiSchema}
|
|
3310
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
3311
|
+
></pds-form>
|
|
3312
|
+
`;
|
|
3313
|
+
},
|
|
3314
|
+
};
|
|
3315
|
+
|
|
3316
|
+
export const ConditionalWithRegex = {
|
|
3317
|
+
parameters: {
|
|
3318
|
+
docs: {
|
|
3319
|
+
description: {
|
|
3320
|
+
story: `Use \`$regex\` to match patterns in field values.
|
|
3321
|
+
|
|
3322
|
+
\`\`\`javascript
|
|
3323
|
+
'/warning': {
|
|
3324
|
+
'ui:visibleWhen': {
|
|
3325
|
+
'/email': { '$regex': '@(gmail|yahoo|hotmail)\\\\.com$' }
|
|
3326
|
+
}
|
|
3327
|
+
}
|
|
3328
|
+
\`\`\`
|
|
3329
|
+
|
|
3330
|
+
This is useful for:
|
|
3331
|
+
- Validating formats (emails, phone numbers, URLs)
|
|
3332
|
+
- Showing warnings for specific patterns
|
|
3333
|
+
- Enabling features based on input format`,
|
|
3334
|
+
},
|
|
3335
|
+
},
|
|
3336
|
+
},
|
|
3337
|
+
render: () => {
|
|
3338
|
+
const schema = {
|
|
3339
|
+
type: "object",
|
|
3340
|
+
title: "Email Verification",
|
|
3341
|
+
properties: {
|
|
3342
|
+
email: {
|
|
3343
|
+
type: "string",
|
|
3344
|
+
format: "email",
|
|
3345
|
+
title: "Email Address",
|
|
3346
|
+
examples: ["you@company.com"],
|
|
3347
|
+
},
|
|
3348
|
+
personalEmailWarning: {
|
|
3349
|
+
type: "string",
|
|
3350
|
+
title: "Personal Email Notice",
|
|
3351
|
+
default:
|
|
3352
|
+
"⚠️ Personal email detected. Consider using a work email for business accounts.",
|
|
3353
|
+
},
|
|
3354
|
+
workEmailConfirmation: {
|
|
3355
|
+
type: "string",
|
|
3356
|
+
title: "Work Email Confirmed",
|
|
3357
|
+
default:
|
|
3358
|
+
"✅ Work email detected. You qualify for enterprise features.",
|
|
3359
|
+
},
|
|
3360
|
+
websiteUrl: {
|
|
3361
|
+
type: "string",
|
|
3362
|
+
title: "Website URL",
|
|
3363
|
+
examples: ["https://example.com"],
|
|
3364
|
+
},
|
|
3365
|
+
secureUrlBadge: {
|
|
3366
|
+
type: "string",
|
|
3367
|
+
title: "Security Status",
|
|
3368
|
+
default: "🔒 Secure connection (HTTPS)",
|
|
3369
|
+
},
|
|
3370
|
+
insecureUrlWarning: {
|
|
3371
|
+
type: "string",
|
|
3372
|
+
title: "Security Warning",
|
|
3373
|
+
default: "⚠️ Insecure connection. Consider using HTTPS.",
|
|
3374
|
+
},
|
|
3375
|
+
},
|
|
3376
|
+
};
|
|
3377
|
+
|
|
3378
|
+
const uiSchema = {
|
|
3379
|
+
// Show warning for personal email providers
|
|
3380
|
+
"/personalEmailWarning": {
|
|
3381
|
+
"ui:visibleWhen": {
|
|
3382
|
+
"/email": {
|
|
3383
|
+
$regex: "@(gmail|yahoo|hotmail|outlook|aol)\\.(com|net|org)$",
|
|
3384
|
+
},
|
|
3385
|
+
},
|
|
3386
|
+
"ui:widget": "const",
|
|
3387
|
+
},
|
|
3388
|
+
|
|
3389
|
+
// Show confirmation for non-personal emails (work domains)
|
|
3390
|
+
"/workEmailConfirmation": {
|
|
3391
|
+
"ui:visibleWhen": {
|
|
3392
|
+
$and: [
|
|
3393
|
+
{ "/email": { $regex: "@.+\\..+$" } }, // Has @ and domain
|
|
3394
|
+
{
|
|
3395
|
+
"/email": {
|
|
3396
|
+
$regex: "^(?!.*@(gmail|yahoo|hotmail|outlook|aol)\\.)",
|
|
3397
|
+
},
|
|
3398
|
+
}, // NOT personal
|
|
3399
|
+
],
|
|
3400
|
+
},
|
|
3401
|
+
"ui:widget": "const",
|
|
3402
|
+
},
|
|
3403
|
+
|
|
3404
|
+
// Show secure badge for HTTPS URLs
|
|
3405
|
+
"/secureUrlBadge": {
|
|
3406
|
+
"ui:visibleWhen": { "/websiteUrl": { $regex: "^https://" } },
|
|
3407
|
+
"ui:widget": "const",
|
|
3408
|
+
},
|
|
3409
|
+
|
|
3410
|
+
// Show warning for HTTP URLs
|
|
3411
|
+
"/insecureUrlWarning": {
|
|
3412
|
+
"ui:visibleWhen": { "/websiteUrl": { $regex: "^http://[^s]" } },
|
|
3413
|
+
"ui:widget": "const",
|
|
3414
|
+
},
|
|
3415
|
+
};
|
|
3416
|
+
|
|
3417
|
+
return html`
|
|
3418
|
+
<div class="alert alert-info">
|
|
3419
|
+
<p>
|
|
3420
|
+
<strong>Pattern matching with <code>$regex</code>:</strong>
|
|
3421
|
+
</p>
|
|
3422
|
+
<ul>
|
|
3423
|
+
<li>
|
|
3424
|
+
Type a <strong>personal email</strong> (gmail, yahoo, etc.) →
|
|
3425
|
+
Warning appears
|
|
3426
|
+
</li>
|
|
3427
|
+
<li>
|
|
3428
|
+
Type a <strong>work email</strong> (company.com) → Confirmation
|
|
3429
|
+
appears
|
|
3430
|
+
</li>
|
|
3431
|
+
<li>Enter an <strong>https://</strong> URL → Secure badge shown</li>
|
|
3432
|
+
<li>
|
|
3433
|
+
Enter an <strong>http://</strong> URL → Insecure warning shown
|
|
3434
|
+
</li>
|
|
3435
|
+
</ul>
|
|
3436
|
+
</div>
|
|
3437
|
+
<pds-form
|
|
3438
|
+
.jsonSchema=${schema}
|
|
3439
|
+
.uiSchema=${uiSchema}
|
|
3440
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
3441
|
+
></pds-form>
|
|
3442
|
+
`;
|
|
3443
|
+
},
|
|
3444
|
+
};
|
|
3445
|
+
|
|
3446
|
+
export const ConditionalWithLogicalOperators = {
|
|
3447
|
+
parameters: {
|
|
3448
|
+
docs: {
|
|
3449
|
+
description: {
|
|
3450
|
+
story: `Combine conditions with \`$and\`, \`$or\`, and \`$not\` for complex logic.
|
|
3451
|
+
|
|
3452
|
+
\`\`\`javascript
|
|
3453
|
+
// AND: All conditions must be true
|
|
3454
|
+
'ui:visibleWhen': {
|
|
3455
|
+
'$and': [
|
|
3456
|
+
{ '/age': { '$gte': 18 } },
|
|
3457
|
+
{ '/country': 'US' }
|
|
3458
|
+
]
|
|
3459
|
+
}
|
|
3460
|
+
|
|
3461
|
+
// OR: Any condition can be true
|
|
3462
|
+
'ui:visibleWhen': {
|
|
3463
|
+
'$or': [
|
|
3464
|
+
{ '/role': 'admin' },
|
|
3465
|
+
{ '/role': 'moderator' }
|
|
3466
|
+
]
|
|
3467
|
+
}
|
|
3468
|
+
|
|
3469
|
+
// NOT: Invert a condition
|
|
3470
|
+
'ui:visibleWhen': {
|
|
3471
|
+
'$not': { '/status': 'banned' }
|
|
3472
|
+
}
|
|
3473
|
+
\`\`\``,
|
|
3474
|
+
},
|
|
3475
|
+
},
|
|
3476
|
+
},
|
|
3477
|
+
render: () => {
|
|
3478
|
+
const schema = {
|
|
3479
|
+
type: "object",
|
|
3480
|
+
title: "Feature Access Control",
|
|
3481
|
+
properties: {
|
|
3482
|
+
userRole: {
|
|
3483
|
+
type: "string",
|
|
3484
|
+
title: "User Role",
|
|
3485
|
+
oneOf: [
|
|
3486
|
+
{ const: "guest", title: "Guest" },
|
|
3487
|
+
{ const: "member", title: "Member" },
|
|
3488
|
+
{ const: "moderator", title: "Moderator" },
|
|
3489
|
+
{ const: "admin", title: "Admin" },
|
|
3490
|
+
],
|
|
3491
|
+
default: "guest",
|
|
3492
|
+
},
|
|
3493
|
+
accountStatus: {
|
|
3494
|
+
type: "string",
|
|
3495
|
+
title: "Account Status",
|
|
3496
|
+
oneOf: [
|
|
3497
|
+
{ const: "pending", title: "Pending Verification" },
|
|
3498
|
+
{ const: "active", title: "Active" },
|
|
3499
|
+
{ const: "suspended", title: "Suspended" },
|
|
3500
|
+
],
|
|
3501
|
+
default: "active",
|
|
3502
|
+
},
|
|
3503
|
+
premiumMember: {
|
|
3504
|
+
type: "boolean",
|
|
3505
|
+
title: "Premium Membership",
|
|
3506
|
+
default: false,
|
|
3507
|
+
},
|
|
3508
|
+
age: {
|
|
3509
|
+
type: "integer",
|
|
3510
|
+
title: "Age",
|
|
3511
|
+
minimum: 13,
|
|
3512
|
+
default: 25,
|
|
3513
|
+
},
|
|
3514
|
+
// Feature flags (shown/hidden based on conditions)
|
|
3515
|
+
basicFeatures: {
|
|
3516
|
+
type: "string",
|
|
3517
|
+
title: "Basic Features",
|
|
3518
|
+
default: "✅ You have access to basic features",
|
|
3519
|
+
},
|
|
3520
|
+
premiumFeatures: {
|
|
3521
|
+
type: "string",
|
|
3522
|
+
title: "Premium Features",
|
|
3523
|
+
default: "⭐ Premium features unlocked!",
|
|
3524
|
+
},
|
|
3525
|
+
moderatorTools: {
|
|
3526
|
+
type: "string",
|
|
3527
|
+
title: "Moderator Tools",
|
|
3528
|
+
default: "🛡️ Moderator tools available",
|
|
3529
|
+
},
|
|
3530
|
+
adminPanel: {
|
|
3531
|
+
type: "string",
|
|
3532
|
+
title: "Admin Panel",
|
|
3533
|
+
default: "👑 Full admin access granted",
|
|
3534
|
+
},
|
|
3535
|
+
ageRestrictedContent: {
|
|
3536
|
+
type: "string",
|
|
3537
|
+
title: "Age-Restricted Content",
|
|
3538
|
+
default: "🔞 Adult content unlocked",
|
|
3539
|
+
},
|
|
3540
|
+
suspendedNotice: {
|
|
3541
|
+
type: "string",
|
|
3542
|
+
title: "Account Suspended",
|
|
3543
|
+
default: "🚫 Your account is suspended. Contact support.",
|
|
3544
|
+
},
|
|
3545
|
+
},
|
|
3546
|
+
};
|
|
3547
|
+
|
|
3548
|
+
const uiSchema = {
|
|
3549
|
+
"ui:layout": "grid",
|
|
3550
|
+
"ui:layoutOptions": { columns: 2, gap: "md" },
|
|
3551
|
+
|
|
3552
|
+
// Basic features: available to active non-guest users
|
|
3553
|
+
// $and + $not combined
|
|
3554
|
+
"/basicFeatures": {
|
|
3555
|
+
"ui:visibleWhen": {
|
|
3556
|
+
$and: [
|
|
3557
|
+
{ $not: { "/userRole": "guest" } },
|
|
3558
|
+
{ "/accountStatus": "active" },
|
|
3559
|
+
],
|
|
3560
|
+
},
|
|
3561
|
+
"ui:widget": "const",
|
|
3562
|
+
},
|
|
3563
|
+
|
|
3564
|
+
// Premium features: premium member OR admin (admins get everything)
|
|
3565
|
+
// $or operator
|
|
3566
|
+
"/premiumFeatures": {
|
|
3567
|
+
"ui:visibleWhen": {
|
|
3568
|
+
$and: [
|
|
3569
|
+
{ "/accountStatus": "active" },
|
|
3570
|
+
{
|
|
3571
|
+
$or: [{ "/premiumMember": true }, { "/userRole": "admin" }],
|
|
3572
|
+
},
|
|
3573
|
+
],
|
|
3574
|
+
},
|
|
3575
|
+
"ui:widget": "const",
|
|
3576
|
+
},
|
|
3577
|
+
|
|
3578
|
+
// Moderator tools: moderator OR admin
|
|
3579
|
+
"/moderatorTools": {
|
|
3580
|
+
"ui:visibleWhen": {
|
|
3581
|
+
$and: [
|
|
3582
|
+
{ "/accountStatus": "active" },
|
|
3583
|
+
{
|
|
3584
|
+
$or: [{ "/userRole": "moderator" }, { "/userRole": "admin" }],
|
|
3585
|
+
},
|
|
3586
|
+
],
|
|
3587
|
+
},
|
|
3588
|
+
"ui:widget": "const",
|
|
3589
|
+
},
|
|
3590
|
+
|
|
3591
|
+
// Admin panel: admin only
|
|
3592
|
+
"/adminPanel": {
|
|
3593
|
+
"ui:visibleWhen": {
|
|
3594
|
+
$and: [{ "/userRole": "admin" }, { "/accountStatus": "active" }],
|
|
3595
|
+
},
|
|
3596
|
+
"ui:widget": "const",
|
|
3597
|
+
},
|
|
3598
|
+
|
|
3599
|
+
// Age-restricted: 18+ and active
|
|
3600
|
+
"/ageRestrictedContent": {
|
|
3601
|
+
"ui:visibleWhen": {
|
|
3602
|
+
$and: [
|
|
3603
|
+
{ "/age": { $gte: 18 } },
|
|
3604
|
+
{ "/accountStatus": "active" },
|
|
3605
|
+
{ $not: { "/userRole": "guest" } },
|
|
3606
|
+
],
|
|
3607
|
+
},
|
|
3608
|
+
"ui:widget": "const",
|
|
3609
|
+
},
|
|
3610
|
+
|
|
3611
|
+
// Suspended notice: shown when suspended
|
|
3612
|
+
"/suspendedNotice": {
|
|
3613
|
+
"ui:visibleWhen": { "/accountStatus": "suspended" },
|
|
3614
|
+
"ui:widget": "const",
|
|
3615
|
+
},
|
|
3616
|
+
};
|
|
3617
|
+
|
|
3618
|
+
return html`
|
|
3619
|
+
<div class="alert alert-info">
|
|
3620
|
+
<p><strong>Combine conditions with logical operators:</strong></p>
|
|
3621
|
+
<ul>
|
|
3622
|
+
<li><strong>$and</strong>: All conditions must be true</li>
|
|
3623
|
+
<li><strong>$or</strong>: Any condition can be true</li>
|
|
3624
|
+
<li><strong>$not</strong>: Inverts a condition</li>
|
|
3625
|
+
</ul>
|
|
3626
|
+
<p>
|
|
3627
|
+
Try different combinations of Role, Status, Premium, and Age to see
|
|
3628
|
+
which features appear!
|
|
3629
|
+
</p>
|
|
3630
|
+
</div>
|
|
3631
|
+
<pds-form
|
|
3632
|
+
.jsonSchema=${schema}
|
|
3633
|
+
.uiSchema=${uiSchema}
|
|
3634
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
3635
|
+
></pds-form>
|
|
3636
|
+
`;
|
|
3637
|
+
},
|
|
3638
|
+
};
|
|
3639
|
+
|
|
3640
|
+
export const ConditionalWithComparison = {
|
|
3641
|
+
parameters: {
|
|
3642
|
+
docs: {
|
|
3643
|
+
description: {
|
|
3644
|
+
story: `Use comparison operators for numeric and value-based conditions.
|
|
3645
|
+
|
|
3646
|
+
| Operator | Description | Example |
|
|
3647
|
+
|----------|-------------|---------|
|
|
3648
|
+
| \`$eq\` | Equals | \`{ "/status": { "$eq": "active" } }\` |
|
|
3649
|
+
| \`$ne\` | Not equals | \`{ "/role": { "$ne": "guest" } }\` |
|
|
3650
|
+
| \`$gt\` | Greater than | \`{ "/age": { "$gt": 18 } }\` |
|
|
3651
|
+
| \`$gte\` | Greater or equal | \`{ "/score": { "$gte": 80 } }\` |
|
|
3652
|
+
| \`$lt\` | Less than | \`{ "/qty": { "$lt": 10 } }\` |
|
|
3653
|
+
| \`$lte\` | Less or equal | \`{ "/price": { "$lte": 100 } }\` |
|
|
3654
|
+
| \`$in\` | In array | \`{ "/tier": { "$in": ["gold", "platinum"] } }\` |
|
|
3655
|
+
| \`$nin\` | Not in array | \`{ "/country": { "$nin": ["XX", "YY"] } }\` |
|
|
3656
|
+
| \`$exists\` | Has value | \`{ "/email": { "$exists": true } }\` |`,
|
|
3657
|
+
},
|
|
3658
|
+
},
|
|
3659
|
+
},
|
|
3660
|
+
render: () => {
|
|
3661
|
+
const schema = {
|
|
3662
|
+
type: "object",
|
|
3663
|
+
title: "Order Validation",
|
|
3664
|
+
properties: {
|
|
3665
|
+
quantity: {
|
|
3666
|
+
type: "integer",
|
|
3667
|
+
title: "Quantity",
|
|
3668
|
+
minimum: 1,
|
|
3669
|
+
default: 5,
|
|
3670
|
+
},
|
|
3671
|
+
lowStockWarning: {
|
|
3672
|
+
type: "string",
|
|
3673
|
+
title: "Low Stock",
|
|
3674
|
+
default: "⚠️ Low quantity - ships within 24h",
|
|
3675
|
+
},
|
|
3676
|
+
bulkOrderNotice: {
|
|
3677
|
+
type: "string",
|
|
3678
|
+
title: "Bulk Order",
|
|
3679
|
+
default: "📦 Bulk order! Contact sales for discount.",
|
|
3680
|
+
},
|
|
3681
|
+
outOfStockError: {
|
|
3682
|
+
type: "string",
|
|
3683
|
+
title: "Out of Stock",
|
|
3684
|
+
default: "❌ Quantity too high - only 100 in stock",
|
|
3685
|
+
},
|
|
3686
|
+
|
|
3687
|
+
country: {
|
|
3688
|
+
type: "string",
|
|
3689
|
+
title: "Shipping Country",
|
|
3690
|
+
oneOf: [
|
|
3691
|
+
{ const: "US", title: "United States" },
|
|
3692
|
+
{ const: "CA", title: "Canada" },
|
|
3693
|
+
{ const: "UK", title: "United Kingdom" },
|
|
3694
|
+
{ const: "DE", title: "Germany" },
|
|
3695
|
+
{ const: "FR", title: "France" },
|
|
3696
|
+
{ const: "AU", title: "Australia" },
|
|
3697
|
+
{ const: "JP", title: "Japan" },
|
|
3698
|
+
{ const: "OTHER", title: "Other" },
|
|
3699
|
+
],
|
|
3700
|
+
default: "US",
|
|
3701
|
+
},
|
|
3702
|
+
domesticShipping: {
|
|
3703
|
+
type: "string",
|
|
3704
|
+
title: "Domestic Shipping",
|
|
3705
|
+
default: "🚚 Free domestic shipping (US/CA)",
|
|
3706
|
+
},
|
|
3707
|
+
euShipping: {
|
|
3708
|
+
type: "string",
|
|
3709
|
+
title: "EU Shipping",
|
|
3710
|
+
default: "🇪🇺 EU shipping available - 5-7 days",
|
|
3711
|
+
},
|
|
3712
|
+
internationalShipping: {
|
|
3713
|
+
type: "string",
|
|
3714
|
+
title: "International",
|
|
3715
|
+
default: "✈️ International shipping - 10-14 days",
|
|
3716
|
+
},
|
|
3717
|
+
restrictedCountry: {
|
|
3718
|
+
type: "string",
|
|
3719
|
+
title: "Restricted",
|
|
3720
|
+
default: "🚫 Sorry, we cannot ship to this location",
|
|
3721
|
+
},
|
|
3722
|
+
|
|
3723
|
+
couponCode: {
|
|
3724
|
+
type: "string",
|
|
3725
|
+
title: "Coupon Code (optional)",
|
|
3726
|
+
examples: ["SAVE20"],
|
|
3727
|
+
},
|
|
3728
|
+
couponApplied: {
|
|
3729
|
+
type: "string",
|
|
3730
|
+
title: "Coupon Status",
|
|
3731
|
+
default: "🎟️ Coupon code detected! Will be validated at checkout.",
|
|
3732
|
+
},
|
|
3733
|
+
},
|
|
3734
|
+
};
|
|
3735
|
+
|
|
3736
|
+
const uiSchema = {
|
|
3737
|
+
// $lt: Low quantity warning (1-3)
|
|
3738
|
+
"/lowStockWarning": {
|
|
3739
|
+
"ui:visibleWhen": { "/quantity": { $lte: 3 } },
|
|
3740
|
+
"ui:widget": "const",
|
|
3741
|
+
},
|
|
3742
|
+
|
|
3743
|
+
// $gte: Bulk order notice (50+)
|
|
3744
|
+
"/bulkOrderNotice": {
|
|
3745
|
+
"ui:visibleWhen": { "/quantity": { $gte: 50 } },
|
|
3746
|
+
"ui:widget": "const",
|
|
3747
|
+
},
|
|
3748
|
+
|
|
3749
|
+
// $gt: Out of stock (>100)
|
|
3750
|
+
"/outOfStockError": {
|
|
3751
|
+
"ui:visibleWhen": { "/quantity": { $gt: 100 } },
|
|
3752
|
+
"ui:widget": "const",
|
|
3753
|
+
},
|
|
3754
|
+
|
|
3755
|
+
// $in: Domestic shipping (US, CA)
|
|
3756
|
+
"/domesticShipping": {
|
|
3757
|
+
"ui:visibleWhen": { "/country": { $in: ["US", "CA"] } },
|
|
3758
|
+
"ui:widget": "const",
|
|
3759
|
+
},
|
|
3760
|
+
|
|
3761
|
+
// $in: EU shipping
|
|
3762
|
+
"/euShipping": {
|
|
3763
|
+
"ui:visibleWhen": { "/country": { $in: ["UK", "DE", "FR"] } },
|
|
3764
|
+
"ui:widget": "const",
|
|
3765
|
+
},
|
|
3766
|
+
|
|
3767
|
+
// $in: International (AU, JP)
|
|
3768
|
+
"/internationalShipping": {
|
|
3769
|
+
"ui:visibleWhen": { "/country": { $in: ["AU", "JP"] } },
|
|
3770
|
+
"ui:widget": "const",
|
|
3771
|
+
},
|
|
3772
|
+
|
|
3773
|
+
// $eq: Restricted country
|
|
3774
|
+
"/restrictedCountry": {
|
|
3775
|
+
"ui:visibleWhen": { "/country": { $eq: "OTHER" } },
|
|
3776
|
+
"ui:widget": "const",
|
|
3777
|
+
},
|
|
3778
|
+
|
|
3779
|
+
// $exists: Coupon code entered
|
|
3780
|
+
"/couponApplied": {
|
|
3781
|
+
"ui:visibleWhen": { "/couponCode": { $exists: true } },
|
|
3782
|
+
"ui:widget": "const",
|
|
3783
|
+
},
|
|
3784
|
+
};
|
|
3785
|
+
|
|
3786
|
+
return html`
|
|
3787
|
+
<div class="alert alert-info">
|
|
3788
|
+
<p><strong>Comparison operators in action:</strong></p>
|
|
3789
|
+
<ul>
|
|
3790
|
+
<li>
|
|
3791
|
+
Set <strong>Quantity</strong> to 1-3 → Low stock warning
|
|
3792
|
+
(<code>$lte</code>)
|
|
3793
|
+
</li>
|
|
3794
|
+
<li>
|
|
3795
|
+
Set <strong>Quantity</strong> to 50+ → Bulk order notice
|
|
3796
|
+
(<code>$gte</code>)
|
|
3797
|
+
</li>
|
|
3798
|
+
<li>
|
|
3799
|
+
Set <strong>Quantity</strong> over 100 → Out of stock error
|
|
3800
|
+
(<code>$gt</code>)
|
|
3801
|
+
</li>
|
|
3802
|
+
<li>
|
|
3803
|
+
Select <strong>Country</strong> → Different shipping messages
|
|
3804
|
+
(<code>$in</code>)
|
|
3805
|
+
</li>
|
|
3806
|
+
<li>
|
|
3807
|
+
Enter a <strong>Coupon Code</strong> → Confirmation appears
|
|
3808
|
+
(<code>$exists</code>)
|
|
3809
|
+
</li>
|
|
3810
|
+
</ul>
|
|
3811
|
+
</div>
|
|
3812
|
+
<pds-form
|
|
3813
|
+
.jsonSchema=${schema}
|
|
3814
|
+
.uiSchema=${uiSchema}
|
|
3815
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
3816
|
+
></pds-form>
|
|
3817
|
+
`;
|
|
3818
|
+
},
|
|
3819
|
+
};
|
|
3820
|
+
|
|
3821
|
+
export const ConditionalDisabledFields = {
|
|
3822
|
+
parameters: {
|
|
3823
|
+
docs: {
|
|
3824
|
+
description: {
|
|
3825
|
+
story: `Use \`ui:disabledWhen\` to disable fields based on conditions.
|
|
3826
|
+
|
|
3827
|
+
Disabled fields remain visible but cannot be edited. Use this for:
|
|
3828
|
+
- Mutually exclusive options
|
|
3829
|
+
- Fields that become irrelevant based on other choices
|
|
3830
|
+
- Read-only states based on form context
|
|
3831
|
+
|
|
3832
|
+
\`\`\`javascript
|
|
3833
|
+
'/billingAddress': {
|
|
3834
|
+
'ui:disabledWhen': { '/sameAsShipping': true }
|
|
3835
|
+
}
|
|
3836
|
+
\`\`\``,
|
|
3837
|
+
},
|
|
3838
|
+
},
|
|
3839
|
+
},
|
|
3840
|
+
render: () => {
|
|
3841
|
+
const schema = {
|
|
3842
|
+
type: "object",
|
|
3843
|
+
title: "Checkout Form",
|
|
3844
|
+
properties: {
|
|
3845
|
+
shippingAddress: {
|
|
3846
|
+
type: "string",
|
|
3847
|
+
title: "Shipping Address",
|
|
3848
|
+
examples: ["123 Main St, City, ST 12345"],
|
|
3849
|
+
},
|
|
3850
|
+
sameAsShipping: {
|
|
3851
|
+
type: "boolean",
|
|
3852
|
+
title: "Billing address same as shipping",
|
|
3853
|
+
default: true,
|
|
3854
|
+
},
|
|
3855
|
+
billingAddress: {
|
|
3856
|
+
type: "string",
|
|
3857
|
+
title: "Billing Address",
|
|
3858
|
+
examples: ["456 Oak Ave, Town, ST 67890"],
|
|
3859
|
+
},
|
|
3860
|
+
|
|
3861
|
+
paymentMethod: {
|
|
3862
|
+
type: "string",
|
|
3863
|
+
title: "Payment Method",
|
|
3864
|
+
oneOf: [
|
|
3865
|
+
{ const: "credit", title: "Credit Card" },
|
|
3866
|
+
{ const: "debit", title: "Debit Card" },
|
|
3867
|
+
{ const: "paypal", title: "PayPal" },
|
|
3868
|
+
{ const: "crypto", title: "Cryptocurrency" },
|
|
3869
|
+
],
|
|
3870
|
+
default: "credit",
|
|
3871
|
+
},
|
|
3872
|
+
cardNumber: {
|
|
3873
|
+
type: "string",
|
|
3874
|
+
title: "Card Number",
|
|
3875
|
+
examples: ["4111 1111 1111 1111"],
|
|
3876
|
+
},
|
|
3877
|
+
cardExpiry: {
|
|
3878
|
+
type: "string",
|
|
3879
|
+
title: "Expiry (MM/YY)",
|
|
3880
|
+
examples: ["12/25"],
|
|
3881
|
+
},
|
|
3882
|
+
cardCvv: { type: "string", title: "CVV", examples: ["123"] },
|
|
3883
|
+
paypalEmail: {
|
|
3884
|
+
type: "string",
|
|
3885
|
+
format: "email",
|
|
3886
|
+
title: "PayPal Email",
|
|
3887
|
+
examples: ["you@paypal.com"],
|
|
3888
|
+
},
|
|
3889
|
+
cryptoWallet: {
|
|
3890
|
+
type: "string",
|
|
3891
|
+
title: "Wallet Address",
|
|
3892
|
+
examples: ["0x1234..."],
|
|
3893
|
+
},
|
|
3894
|
+
|
|
3895
|
+
orderLocked: {
|
|
3896
|
+
type: "boolean",
|
|
3897
|
+
title: "🔒 Lock order (prevent changes)",
|
|
3898
|
+
default: false,
|
|
3899
|
+
},
|
|
3900
|
+
notes: {
|
|
3901
|
+
type: "string",
|
|
3902
|
+
title: "Order Notes",
|
|
3903
|
+
examples: ["Special instructions..."],
|
|
3904
|
+
},
|
|
3905
|
+
},
|
|
3906
|
+
};
|
|
3907
|
+
|
|
3908
|
+
const uiSchema = {
|
|
3909
|
+
// Billing address disabled when "same as shipping" is checked
|
|
3910
|
+
"/billingAddress": {
|
|
3911
|
+
"ui:disabledWhen": { "/sameAsShipping": true },
|
|
3912
|
+
"ui:help": 'Uncheck "same as shipping" to edit',
|
|
3913
|
+
},
|
|
3914
|
+
|
|
3915
|
+
// Card fields disabled when not using card payment
|
|
3916
|
+
"/cardNumber": {
|
|
3917
|
+
"ui:disabledWhen": { "/paymentMethod": { $nin: ["credit", "debit"] } },
|
|
3918
|
+
},
|
|
3919
|
+
"/cardExpiry": {
|
|
3920
|
+
"ui:disabledWhen": { "/paymentMethod": { $nin: ["credit", "debit"] } },
|
|
3921
|
+
},
|
|
3922
|
+
"/cardCvv": {
|
|
3923
|
+
"ui:disabledWhen": { "/paymentMethod": { $nin: ["credit", "debit"] } },
|
|
3924
|
+
},
|
|
3925
|
+
|
|
3926
|
+
// PayPal email disabled when not using PayPal
|
|
3927
|
+
"/paypalEmail": {
|
|
3928
|
+
"ui:disabledWhen": { "/paymentMethod": { $ne: "paypal" } },
|
|
3929
|
+
},
|
|
3930
|
+
|
|
3931
|
+
// Crypto wallet disabled when not using crypto
|
|
3932
|
+
"/cryptoWallet": {
|
|
3933
|
+
"ui:disabledWhen": { "/paymentMethod": { $ne: "crypto" } },
|
|
3934
|
+
},
|
|
3935
|
+
|
|
3936
|
+
// Notes disabled when order is locked
|
|
3937
|
+
"/notes": {
|
|
3938
|
+
"ui:disabledWhen": { "/orderLocked": true },
|
|
3939
|
+
"ui:widget": "textarea",
|
|
3940
|
+
"ui:help": "Lock order to prevent changes",
|
|
3941
|
+
},
|
|
3942
|
+
};
|
|
3943
|
+
|
|
3944
|
+
return html`
|
|
3945
|
+
<div class="alert alert-info">
|
|
3946
|
+
<p><strong>Fields become disabled based on conditions:</strong></p>
|
|
3947
|
+
<ul>
|
|
3948
|
+
<li>
|
|
3949
|
+
Check <strong>"Same as shipping"</strong> → Billing address disabled
|
|
3950
|
+
</li>
|
|
3951
|
+
<li>
|
|
3952
|
+
Change <strong>Payment Method</strong> → Only relevant fields stay
|
|
3953
|
+
enabled
|
|
3954
|
+
</li>
|
|
3955
|
+
<li>Check <strong>"Lock order"</strong> → Notes field disabled</li>
|
|
3956
|
+
</ul>
|
|
3957
|
+
<p>Disabled fields show their value but prevent editing.</p>
|
|
3958
|
+
</div>
|
|
3959
|
+
<pds-form
|
|
3960
|
+
.jsonSchema=${schema}
|
|
3961
|
+
.uiSchema=${uiSchema}
|
|
3962
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
3963
|
+
></pds-form>
|
|
3964
|
+
`;
|
|
3965
|
+
},
|
|
3966
|
+
};
|
|
3967
|
+
|
|
3968
|
+
// ============================================
|
|
3969
|
+
// Custom Content Injection Stories
|
|
3970
|
+
// ============================================
|
|
3971
|
+
|
|
3972
|
+
export const CustomContentBeforeAfter = {
|
|
3973
|
+
parameters: {
|
|
3974
|
+
docs: {
|
|
3975
|
+
description: {
|
|
3976
|
+
story: `Use \`ui:before\` and \`ui:after\` to inject custom content around fields or fieldsets.
|
|
3977
|
+
|
|
3978
|
+
### Value Types
|
|
3979
|
+
- **Function**: \`(field) => html\\\`...\\\`\` - full access to render context
|
|
3980
|
+
- **Slot reference**: \`"slot:mySlot"\` - renders a slotted element by name
|
|
3981
|
+
|
|
3982
|
+
### Field Context
|
|
3983
|
+
The function receives a \`field\` object with: \`{ path, schema, value, label, id, get, set, attrs, host }\``,
|
|
3984
|
+
},
|
|
3985
|
+
},
|
|
3986
|
+
},
|
|
3987
|
+
render: () => {
|
|
3988
|
+
const schema = {
|
|
3989
|
+
type: "object",
|
|
3990
|
+
title: "User Registration",
|
|
3991
|
+
properties: {
|
|
3992
|
+
username: { type: "string", title: "Username", minLength: 3 },
|
|
3993
|
+
email: { type: "string", title: "Email", format: "email" },
|
|
3994
|
+
password: { type: "string", title: "Password", minLength: 8 },
|
|
3995
|
+
confirmPassword: { type: "string", title: "Confirm Password" },
|
|
3996
|
+
acceptTerms: { type: "boolean", title: "I accept the terms" },
|
|
3997
|
+
},
|
|
3998
|
+
};
|
|
3999
|
+
|
|
4000
|
+
const uiSchema = {
|
|
4001
|
+
// Add a header before the username field
|
|
4002
|
+
"/username": {
|
|
4003
|
+
"ui:before": (field) => html`
|
|
4004
|
+
<div class="alert alert-info">
|
|
4005
|
+
<span class="alert-icon">
|
|
4006
|
+
<pds-icon icon="info" size="md"> </pds-icon>
|
|
4007
|
+
</span>
|
|
4008
|
+
<div>
|
|
4009
|
+
<h4 class="alert-title">Account Info</h4>
|
|
4010
|
+
<p>Choose a unique username and secure password.</p>
|
|
4011
|
+
</div>
|
|
4012
|
+
</div>
|
|
4013
|
+
`,
|
|
4014
|
+
},
|
|
4015
|
+
|
|
4016
|
+
// Add validation hint after email
|
|
4017
|
+
"/email": {
|
|
4018
|
+
"ui:after": (field) =>
|
|
4019
|
+
field.value && !field.value.includes("@")
|
|
4020
|
+
? html`<div class="text-sm text-danger">
|
|
4021
|
+
Please enter a valid email address
|
|
4022
|
+
</div>`
|
|
4023
|
+
: nothing,
|
|
4024
|
+
},
|
|
4025
|
+
|
|
4026
|
+
// Add password strength indicator after password
|
|
4027
|
+
"/password": {
|
|
4028
|
+
"ui:widget": "password",
|
|
4029
|
+
"ui:after": (field) => {
|
|
4030
|
+
if (!field.value) return nothing;
|
|
4031
|
+
const strength =
|
|
4032
|
+
field.value.length < 8
|
|
4033
|
+
? "Weak"
|
|
4034
|
+
: field.value.length < 12
|
|
4035
|
+
? "Medium"
|
|
4036
|
+
: "Strong";
|
|
4037
|
+
const color =
|
|
4038
|
+
strength === "Weak"
|
|
4039
|
+
? "danger"
|
|
4040
|
+
: strength === "Medium"
|
|
4041
|
+
? "warning"
|
|
4042
|
+
: "success";
|
|
4043
|
+
return html`<div class="text-sm text-${color}">
|
|
4044
|
+
Password strength: ${strength}
|
|
4045
|
+
</div>`;
|
|
4046
|
+
},
|
|
4047
|
+
},
|
|
4048
|
+
|
|
4049
|
+
// Add legal notice before terms checkbox
|
|
4050
|
+
"/acceptTerms": {
|
|
4051
|
+
"ui:before": (field) => html`
|
|
4052
|
+
<hr style="margin: var(--spacing-md) 0;" />
|
|
4053
|
+
<p class="text-sm text-muted">
|
|
4054
|
+
By registering, you agree to our
|
|
4055
|
+
<a href="#terms">Terms of Service</a> and
|
|
4056
|
+
<a href="#privacy">Privacy Policy</a>.
|
|
4057
|
+
</p>
|
|
4058
|
+
`,
|
|
4059
|
+
},
|
|
4060
|
+
};
|
|
4061
|
+
|
|
4062
|
+
return html`
|
|
4063
|
+
<pds-form
|
|
4064
|
+
.jsonSchema=${schema}
|
|
4065
|
+
.uiSchema=${uiSchema}
|
|
4066
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
4067
|
+
></pds-form>
|
|
4068
|
+
`;
|
|
4069
|
+
},
|
|
4070
|
+
};
|
|
4071
|
+
|
|
4072
|
+
export const CustomContentRender = {
|
|
4073
|
+
parameters: {
|
|
4074
|
+
docs: {
|
|
4075
|
+
description: {
|
|
4076
|
+
story: `Use \`ui:render\` to completely replace a field's rendering with your own template.
|
|
4077
|
+
|
|
4078
|
+
The render function receives a \`field\` object with: \`{ id, path, label, value, schema, ui, attrs, get, set, host }\`
|
|
4079
|
+
|
|
4080
|
+
This is like using \`defineRenderer()\` but inline in the uiSchema.`,
|
|
4081
|
+
},
|
|
4082
|
+
},
|
|
4083
|
+
},
|
|
4084
|
+
render: () => {
|
|
4085
|
+
const schema = {
|
|
4086
|
+
type: "object",
|
|
4087
|
+
title: "Product Review",
|
|
4088
|
+
properties: {
|
|
4089
|
+
productName: {
|
|
4090
|
+
type: "string",
|
|
4091
|
+
title: "Product",
|
|
4092
|
+
default: "Wireless Headphones",
|
|
4093
|
+
},
|
|
4094
|
+
rating: {
|
|
4095
|
+
type: "integer",
|
|
4096
|
+
title: "Rating",
|
|
4097
|
+
minimum: 1,
|
|
4098
|
+
maximum: 5,
|
|
4099
|
+
default: 4,
|
|
4100
|
+
},
|
|
4101
|
+
review: { type: "string", title: "Review" },
|
|
4102
|
+
recommend: { type: "boolean", title: "Would recommend", default: true },
|
|
4103
|
+
},
|
|
4104
|
+
};
|
|
4105
|
+
|
|
4106
|
+
const uiSchema = {
|
|
4107
|
+
// Custom star rating widget
|
|
4108
|
+
"/rating": {
|
|
4109
|
+
"ui:render": (field) => {
|
|
4110
|
+
const stars = [1, 2, 3, 4, 5];
|
|
4111
|
+
return html`
|
|
4112
|
+
<fieldset data-path=${field.path}>
|
|
4113
|
+
<legend>${field.label}</legend>
|
|
4114
|
+
<div
|
|
4115
|
+
class="flex gap-xs"
|
|
4116
|
+
role="radiogroup"
|
|
4117
|
+
aria-label="${field.label}"
|
|
4118
|
+
>
|
|
4119
|
+
${stars.map(
|
|
4120
|
+
(star) => html`
|
|
4121
|
+
<button
|
|
4122
|
+
type="button"
|
|
4123
|
+
class="btn btn-sm ${star <= (field.value || 0)
|
|
4124
|
+
? "btn-primary"
|
|
4125
|
+
: "btn-outline"}"
|
|
4126
|
+
@click=${() => field.set(star)}
|
|
4127
|
+
aria-label="${star} star${star > 1 ? "s" : ""}"
|
|
4128
|
+
aria-pressed=${star <= (field.value || 0)}
|
|
4129
|
+
>
|
|
4130
|
+
★
|
|
4131
|
+
</button>
|
|
4132
|
+
`
|
|
4133
|
+
)}
|
|
4134
|
+
</div>
|
|
4135
|
+
<input
|
|
4136
|
+
type="hidden"
|
|
4137
|
+
name=${field.path}
|
|
4138
|
+
.value=${String(field.value || "")}
|
|
4139
|
+
/>
|
|
4140
|
+
</fieldset>
|
|
4141
|
+
`;
|
|
4142
|
+
},
|
|
4143
|
+
},
|
|
4144
|
+
|
|
4145
|
+
// Custom toggle card for recommendation
|
|
4146
|
+
"/recommend": {
|
|
4147
|
+
"ui:render": (field) => html`
|
|
4148
|
+
<div
|
|
4149
|
+
class="card surface-elevated p-md cursor-pointer flex items-center gap-md"
|
|
4150
|
+
@click=${() => field.set(!field.value)}
|
|
4151
|
+
role="checkbox"
|
|
4152
|
+
aria-checked=${!!field.value}
|
|
4153
|
+
tabindex="0"
|
|
4154
|
+
@keydown=${(e) =>
|
|
4155
|
+
e.key === " " && (e.preventDefault(), field.set(!field.value))}
|
|
4156
|
+
>
|
|
4157
|
+
<span
|
|
4158
|
+
style="cursor: pointer; font-size: 3rem; color: var(--color-${field.value
|
|
4159
|
+
? "success"
|
|
4160
|
+
: "neutral"}-500)"
|
|
4161
|
+
>${field.value ? "👍🏼" : "👎🏼"}</span
|
|
4162
|
+
>
|
|
4163
|
+
<div>
|
|
4164
|
+
<strong>${field.label}</strong>
|
|
4165
|
+
<p class="text-sm text-muted">
|
|
4166
|
+
${field.value
|
|
4167
|
+
? "Yes, I would recommend this!"
|
|
4168
|
+
: "No, I would not recommend this"}
|
|
4169
|
+
</p>
|
|
4170
|
+
</div>
|
|
4171
|
+
<input
|
|
4172
|
+
type="hidden"
|
|
4173
|
+
name=${field.path}
|
|
4174
|
+
.value=${String(!!field.value)}
|
|
4175
|
+
/>
|
|
4176
|
+
</div>
|
|
4177
|
+
`,
|
|
4178
|
+
},
|
|
4179
|
+
|
|
4180
|
+
"/review": {
|
|
4181
|
+
"ui:widget": "textarea",
|
|
4182
|
+
"ui:help": "Share your experience with this product",
|
|
4183
|
+
},
|
|
4184
|
+
};
|
|
4185
|
+
|
|
4186
|
+
return html`
|
|
4187
|
+
<div class="alert alert-info">
|
|
4188
|
+
<p><strong>Custom rendered fields:</strong></p>
|
|
4189
|
+
<ul>
|
|
4190
|
+
<li><strong>Rating</strong> uses a custom star button widget</li>
|
|
4191
|
+
<li><strong>Would recommend</strong> uses a custom toggle card</li>
|
|
4192
|
+
</ul>
|
|
4193
|
+
</div>
|
|
4194
|
+
<pds-form
|
|
4195
|
+
.jsonSchema=${schema}
|
|
4196
|
+
.uiSchema=${uiSchema}
|
|
4197
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
4198
|
+
></pds-form>
|
|
4199
|
+
`;
|
|
4200
|
+
},
|
|
4201
|
+
};
|
|
4202
|
+
|
|
4203
|
+
export const CustomContentWrapper = {
|
|
4204
|
+
parameters: {
|
|
4205
|
+
docs: {
|
|
4206
|
+
description: {
|
|
4207
|
+
story: `Use \`ui:wrapper\` to customize how a field is wrapped (replacing the default \`<label>\` structure).
|
|
4208
|
+
|
|
4209
|
+
A common use case is adding a **live character counter** to textarea fields.
|
|
4210
|
+
|
|
4211
|
+
The wrapper function receives a \`field\` object with: \`{ control, label, help, id, value, schema, ...context }\``,
|
|
4212
|
+
},
|
|
4213
|
+
},
|
|
4214
|
+
},
|
|
4215
|
+
render: () => {
|
|
4216
|
+
const schema = {
|
|
4217
|
+
type: "object",
|
|
4218
|
+
title: "Author Profile",
|
|
4219
|
+
properties: {
|
|
4220
|
+
name: { type: "string", title: "Display Name", maxLength: 50 },
|
|
4221
|
+
bio: { type: "string", title: "Bio", maxLength: 280 },
|
|
4222
|
+
about: { type: "string", title: "About Me", maxLength: 1000 },
|
|
4223
|
+
},
|
|
4224
|
+
};
|
|
4225
|
+
|
|
4226
|
+
// Wrapper with live character counter
|
|
4227
|
+
const charCountWrapper = (field) => {
|
|
4228
|
+
const maxLength = field.schema.maxLength;
|
|
4229
|
+
const currentLength = (field.value || "").length;
|
|
4230
|
+
const remaining = maxLength - currentLength;
|
|
4231
|
+
const isWarning = remaining <= 20 && remaining > 0;
|
|
4232
|
+
const isOver = remaining <= 0;
|
|
4233
|
+
|
|
4234
|
+
return html`
|
|
4235
|
+
<label for=${field.id}>
|
|
4236
|
+
<div class="flex justify-between items-center">
|
|
4237
|
+
<span data-label>${field.label}</span>
|
|
4238
|
+
<span
|
|
4239
|
+
class="text-sm"
|
|
4240
|
+
style="color: var(${isOver
|
|
4241
|
+
? "--color-danger-600"
|
|
4242
|
+
: isWarning
|
|
4243
|
+
? "--color-warning-600"
|
|
4244
|
+
: "--color-text-muted"})"
|
|
4245
|
+
>
|
|
4246
|
+
${currentLength}/${maxLength}
|
|
4247
|
+
</span>
|
|
4248
|
+
</div>
|
|
4249
|
+
${field.control} ${field.help}
|
|
4250
|
+
</label>
|
|
4251
|
+
`;
|
|
4252
|
+
};
|
|
4253
|
+
|
|
4254
|
+
const uiSchema = {
|
|
4255
|
+
"/name": { "ui:wrapper": charCountWrapper },
|
|
4256
|
+
"/bio": {
|
|
4257
|
+
"ui:widget": "textarea",
|
|
4258
|
+
"ui:wrapper": charCountWrapper,
|
|
4259
|
+
"ui:help": "A short bio for your profile card",
|
|
4260
|
+
},
|
|
4261
|
+
"/about": {
|
|
4262
|
+
"ui:widget": "textarea",
|
|
4263
|
+
"ui:wrapper": charCountWrapper,
|
|
4264
|
+
"ui:help": "Tell readers more about yourself",
|
|
4265
|
+
},
|
|
4266
|
+
};
|
|
4267
|
+
|
|
4268
|
+
return html`
|
|
4269
|
+
<div class="alert alert-info">
|
|
4270
|
+
<p><strong>Live character counter wrapper:</strong></p>
|
|
4271
|
+
<ul>
|
|
4272
|
+
<li>
|
|
4273
|
+
Counter shows <strong class="text-muted">gray</strong> normally
|
|
4274
|
+
</li>
|
|
4275
|
+
<li>
|
|
4276
|
+
Turns <strong class="text-warning">yellow</strong> when ≤20
|
|
4277
|
+
characters remain
|
|
4278
|
+
</li>
|
|
4279
|
+
<li>
|
|
4280
|
+
Turns <strong class="text-danger">red</strong> when over the limit
|
|
4281
|
+
</li>
|
|
4282
|
+
</ul>
|
|
4283
|
+
<p>Try typing in any field to see the counter update!</p>
|
|
4284
|
+
</div>
|
|
4285
|
+
<pds-form
|
|
4286
|
+
.jsonSchema=${schema}
|
|
4287
|
+
.uiSchema=${uiSchema}
|
|
4288
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
4289
|
+
></pds-form>
|
|
4290
|
+
`;
|
|
4291
|
+
},
|
|
4292
|
+
};
|
|
4293
|
+
|
|
4294
|
+
export const CustomContentSlots = {
|
|
4295
|
+
parameters: {
|
|
4296
|
+
docs: {
|
|
4297
|
+
description: {
|
|
4298
|
+
story: `Use slot references (\`"slot:name"\`) in \`ui:before\` or \`ui:after\` to inject pre-defined HTML content.
|
|
4299
|
+
|
|
4300
|
+
This is useful when you want to define content in the HTML rather than in JavaScript.`,
|
|
4301
|
+
},
|
|
4302
|
+
},
|
|
4303
|
+
},
|
|
4304
|
+
render: () => {
|
|
4305
|
+
const schema = {
|
|
4306
|
+
type: "object",
|
|
4307
|
+
title: "Newsletter Signup",
|
|
4308
|
+
properties: {
|
|
4309
|
+
email: { type: "string", title: "Email", format: "email" },
|
|
4310
|
+
frequency: {
|
|
4311
|
+
type: "string",
|
|
4312
|
+
title: "Email Frequency",
|
|
4313
|
+
enum: ["daily", "weekly", "monthly"],
|
|
4314
|
+
enumNames: ["Daily digest", "Weekly roundup", "Monthly newsletter"],
|
|
4315
|
+
default: "weekly",
|
|
4316
|
+
},
|
|
4317
|
+
interests: { type: "string", title: "Interests" },
|
|
4318
|
+
},
|
|
4319
|
+
};
|
|
4320
|
+
|
|
4321
|
+
const uiSchema = {
|
|
4322
|
+
"/email": {
|
|
4323
|
+
"ui:before": "slot:email-header",
|
|
4324
|
+
},
|
|
4325
|
+
"/interests": {
|
|
4326
|
+
"ui:after": "slot:interests-footer",
|
|
4327
|
+
},
|
|
4328
|
+
};
|
|
4329
|
+
|
|
4330
|
+
return html`
|
|
4331
|
+
<pds-form
|
|
4332
|
+
.jsonSchema=${schema}
|
|
4333
|
+
.uiSchema=${uiSchema}
|
|
4334
|
+
@pw:submit=${(e) => toastFormData(e.detail)}
|
|
4335
|
+
>
|
|
4336
|
+
<!-- Slotted content referenced by ui:before/ui:after -->
|
|
4337
|
+
<div
|
|
4338
|
+
slot="email-header"
|
|
4339
|
+
class="alert alert-success"
|
|
4340
|
+
style="margin-bottom: var(--spacing-sm);"
|
|
4341
|
+
>
|
|
4342
|
+
<strong>Join 50,000+ subscribers!</strong>
|
|
4343
|
+
</div>
|
|
4344
|
+
|
|
4345
|
+
<p
|
|
4346
|
+
slot="interests-footer"
|
|
4347
|
+
class="text-sm text-muted"
|
|
4348
|
+
style="margin-top: var(--spacing-sm);"
|
|
4349
|
+
>
|
|
4350
|
+
We'll personalize your newsletter based on your interests.
|
|
4351
|
+
<a href="#privacy">Learn more about how we use your data</a>.
|
|
4352
|
+
</p>
|
|
4353
|
+
</pds-form>
|
|
4354
|
+
`;
|
|
4355
|
+
},
|
|
4356
|
+
};
|