@pure-ds/storybook 0.7.26 → 0.7.29

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.
@@ -74,6 +74,76 @@ const formDataToObject = (formData) => {
74
74
  return entries;
75
75
  };
76
76
 
77
+ const askDefaultConfirmSource = `const confirmed = await PDS.ask('This uses the default buttons and styling.');`;
78
+
79
+ const askMiniFormSource = `const result = await PDS.ask(
80
+ html\`
81
+ <form method="dialog" class="min-w-xs">
82
+ <label class="gap-xs">
83
+ <span>Name</span>
84
+ <input name="name" required placeholder="Alex Rivera" />
85
+ </label>
86
+ <label class="gap-xs">
87
+ <span>Email</span>
88
+ <input type="email" name="email" required placeholder="alex@example.com" />
89
+ </label>
90
+ </form>
91
+ \`,
92
+ {
93
+ title: 'Share your details',
94
+ useForm: true,
95
+ buttons: {
96
+ ok: { name: 'Submit', primary: true },
97
+ cancel: { name: 'Cancel', cancel: true }
98
+ }
99
+ }
100
+ );`;
101
+
102
+ const askBooleanConfirmSource = `const decision = await PDS.ask('Archive analytics project?', {
103
+ buttons: {
104
+ ok: { name: 'Archive project', primary: true },
105
+ cancel: { name: 'Keep active', cancel: true }
106
+ }
107
+ });`;
108
+
109
+ const askBeforeCloseSource = `const dialogResult = await PDS.ask(formTemplate, {
110
+ title: 'Publish release update?',
111
+ useForm: true,
112
+ buttons: {
113
+ ok: { name: 'Publish', primary: true },
114
+ cancel: { name: 'Cancel', cancel: true }
115
+ },
116
+ beforeClose: async ({ actionKind }) => {
117
+ if (actionKind !== 'ok') return true;
118
+ const response = await fetch('/api/releases/validate-close', { method: 'POST' });
119
+ if (!response.ok) return { allow: false };
120
+ const payload = await response.json();
121
+ return { allow: payload.ok === true };
122
+ }
123
+ });`;
124
+
125
+ const askPdsFormSource = `const dialogResult = await PDS.ask(
126
+ html\`
127
+ <form method="dialog" class="min-w-sm">
128
+ <pds-form
129
+ .jsonSchema=
130
+ .uiSchema=
131
+ .values=
132
+ hide-actions
133
+ ></pds-form>
134
+ <input type="hidden" name="spotlight" value="" />
135
+ </form>
136
+ \`,
137
+ {
138
+ title: 'Marketing spotlight',
139
+ useForm: true,
140
+ buttons: {
141
+ ok: { name: 'Save changes', primary: true },
142
+ cancel: { name: 'Cancel', cancel: true }
143
+ }
144
+ }
145
+ );`;
146
+
77
147
  export default {
78
148
  title: 'PDS/PDS Object',
79
149
  tags: ['interaction', 'dialogs', 'forms', 'modal', 'dialog', 'alert', 'confirm', 'prompt', 'popup', 'overlay'],
@@ -116,7 +186,10 @@ const AreYouSure = {
116
186
  return html`
117
187
  <section
118
188
  data-ask-example
119
- class="card stack-md max-w-sm"
189
+ class="card max-w-sm"
190
+ .pdsCodeHeading=${'PDS.ask()'}
191
+ .pdsCodeLabel=${'Default confirm dialog'}
192
+ .pdsCodeSource=${askDefaultConfirmSource}
120
193
  >
121
194
  <h3>Default confirm dialog</h3>
122
195
  <p>
@@ -143,16 +216,16 @@ const MiniFormSubmission = {
143
216
 
144
217
  const result = await ask(
145
218
  html`
146
- <form method="dialog" class="stack-md min-w-xs">
147
- <label class="stack-md gap-xs">
219
+ <form method="dialog" class="min-w-xs">
220
+ <label class="gap-xs">
148
221
  <span>Name</span>
149
222
  <input name="name" required placeholder="Alex Rivera" />
150
223
  </label>
151
- <label class="stack-md gap-xs">
224
+ <label class="gap-xs">
152
225
  <span>Email</span>
153
226
  <input type="email" name="email" required placeholder="alex@example.com" />
154
227
  </label>
155
- <label class="stack-md gap-xs">
228
+ <label class="gap-xs">
156
229
  <span>Team size</span>
157
230
  <select class="select" name="teamSize">
158
231
  <option value="1-5">1-5</option>
@@ -189,7 +262,10 @@ const MiniFormSubmission = {
189
262
  return html`
190
263
  <section
191
264
  data-ask-example
192
- class="card stack-md max-w-md"
265
+ class="card max-w-sm"
266
+ .pdsCodeHeading=${'PDS.ask()'}
267
+ .pdsCodeLabel=${'Mini form submission'}
268
+ .pdsCodeSource=${askMiniFormSource}
193
269
  >
194
270
  <h3>Collect a few fields</h3>
195
271
  <p>
@@ -250,7 +326,10 @@ const BooleanConfirmFlow = {
250
326
  return html`
251
327
  <section
252
328
  data-ask-example
253
- class="card stack-md max-w-md"
329
+ class="card max-w-sm"
330
+ .pdsCodeHeading=${'PDS.ask()'}
331
+ .pdsCodeLabel=${'Boolean confirm flow'}
332
+ .pdsCodeSource=${askBooleanConfirmSource}
254
333
  >
255
334
  <h3>Lightweight confirmations</h3>
256
335
  <p>
@@ -264,6 +343,115 @@ const BooleanConfirmFlow = {
264
343
  }
265
344
  };
266
345
 
346
+ const AsyncServerValidationGate = {
347
+ name: 'Async server validation gate',
348
+ render: () => {
349
+ const handleClick = async (event) => {
350
+ const ask = ensureAsk();
351
+ const container = event.currentTarget.closest('[data-ask-example]');
352
+ const status = container?.querySelector('[data-status]');
353
+ let validationAttempts = 0;
354
+
355
+ if (status) {
356
+ status.textContent = 'Opening publish gate…';
357
+ }
358
+
359
+ const dialogResult = await ask(
360
+ html`
361
+ <form method="dialog" class="min-w-sm">
362
+ <label>
363
+ <span>Release title</span>
364
+ <input name="releaseTitle" required placeholder="Q2 Launch Readiness" />
365
+ </label>
366
+ <p data-server-feedback class="text-muted">
367
+ The first submit is rejected to simulate server-side validation.
368
+ </p>
369
+ </form>
370
+ `,
371
+ {
372
+ title: 'Publish release update?',
373
+ useForm: true,
374
+ buttons: {
375
+ ok: { name: 'Publish', primary: true },
376
+ cancel: { name: 'Cancel', cancel: true }
377
+ },
378
+ beforeClose: async ({ actionKind, dialog }) => {
379
+ if (actionKind !== 'ok') {
380
+ return true;
381
+ }
382
+
383
+ const submitBtn = dialog.querySelector('button[value="ok"]');
384
+ const feedback = dialog.querySelector('[data-server-feedback]');
385
+ submitBtn?.classList.add('btn-working');
386
+
387
+ try {
388
+ if (feedback) {
389
+ feedback.textContent = 'Validating with server…';
390
+ }
391
+
392
+ await new Promise((resolve) => setTimeout(resolve, 900));
393
+ validationAttempts += 1;
394
+
395
+ if (validationAttempts === 1) {
396
+ if (feedback) {
397
+ feedback.textContent = 'Server rejected this attempt. Click Publish again to pass.';
398
+ }
399
+
400
+ return {
401
+ allow: false,
402
+ reason: 'simulated-server-reject'
403
+ };
404
+ }
405
+
406
+ if (feedback) {
407
+ feedback.textContent = 'Server validation passed. Closing…';
408
+ }
409
+
410
+ return { allow: true };
411
+ } finally {
412
+ submitBtn?.classList.remove('btn-working');
413
+ }
414
+ }
415
+ }
416
+ );
417
+
418
+ if (dialogResult instanceof FormData) {
419
+ const payload = formDataToObject(dialogResult);
420
+ if (status) {
421
+ status.textContent = `✅ Published: ${payload.releaseTitle || 'Untitled release'}`;
422
+ }
423
+ await toastFormData({
424
+ ...payload,
425
+ scenario: 'before-close-server-gate'
426
+ });
427
+ } else {
428
+ if (status) {
429
+ status.textContent = 'Publish flow cancelled';
430
+ }
431
+ await toastFormData({ cancelled: true, scenario: 'before-close-server-gate' });
432
+ }
433
+ };
434
+
435
+ return html`
436
+ <section
437
+ data-ask-example
438
+ class="card max-w-sm"
439
+ .pdsCodeHeading=${'PDS.ask()'}
440
+ .pdsCodeLabel=${'Server-side close validation'}
441
+ .pdsCodeSource=${askBeforeCloseSource}
442
+ >
443
+ <h3>Server-side close validation</h3>
444
+ <p>
445
+ This scenario uses <code>beforeClose</code> to run an async check before the dialog can close.
446
+ The first publish attempt is rejected and keeps the dialog open; the second attempt passes.
447
+ </p>
448
+ <button class="btn btn-primary" @click=${handleClick}>Open validation gate</button>
449
+ <small data-status class="text-muted">No validation run yet.</small>
450
+ </section>
451
+ `;
452
+ }
453
+ };
454
+
267
455
  const EmbedPdsFormSubform = {
268
456
  name: 'Embed a pds-form subform',
269
457
  render: () => {
@@ -278,7 +466,7 @@ const EmbedPdsFormSubform = {
278
466
 
279
467
  const dialogResult = await ask(
280
468
  html`
281
- <form method="dialog" class="stack-md min-w-sm">
469
+ <form method="dialog">
282
470
  <pds-form
283
471
  id="spotlight-form"
284
472
  .jsonSchema=${marketingSchema}
@@ -364,7 +552,10 @@ const EmbedPdsFormSubform = {
364
552
  return html`
365
553
  <section
366
554
  data-ask-example
367
- class="card stack-md max-w-lg"
555
+ class="card max-w-sm"
556
+ .pdsCodeHeading=${'PDS.ask()'}
557
+ .pdsCodeLabel=${'Embed a pds-form subform'}
558
+ .pdsCodeSource=${askPdsFormSource}
368
559
  >
369
560
  <h3>Deep editing workflows</h3>
370
561
  <p>
@@ -385,6 +576,7 @@ export const PDSAsk = {
385
576
  ${AreYouSure.render()}
386
577
  ${MiniFormSubmission.render()}
387
578
  ${BooleanConfirmFlow.render()}
579
+ ${AsyncServerValidationGate.render()}
388
580
  ${EmbedPdsFormSubform.render()}
389
581
  </section>
390
582
  `
@@ -5,6 +5,31 @@ const ensureToast = () => {
5
5
  return toastFallback;
6
6
  };
7
7
 
8
+ const toastQuickSource = `await PDS.toast('This is a success toast.', {
9
+ type: 'success'
10
+ });`;
11
+
12
+ const toastPersistentSource = `await PDS.toast('Heads up! This toast stays until dismissed.', {
13
+ type: 'warning',
14
+ persistent: true
15
+ });`;
16
+
17
+ const toastHtmlActionSource = `await PDS.toast(
18
+ '<strong>File archived.</strong><br><span class="text-muted">You can restore it within 30 days.</span>',
19
+ {
20
+ type: 'information',
21
+ html: true,
22
+ action: {
23
+ label: 'Undo',
24
+ icon: 'arrow-counter-clockwise',
25
+ onClick: async () => {
26
+ await PDS.toast('Archive action was undone.', { type: 'success' });
27
+ },
28
+ dismissOnClick: true
29
+ }
30
+ }
31
+ );`;
32
+
8
33
  export default {
9
34
  title: 'PDS/PDS Object',
10
35
  tags: ['notifications', 'toast', 'utilities', 'feedback'],
@@ -30,7 +55,12 @@ const QuickToasts = {
30
55
  };
31
56
 
32
57
  return html`
33
- <section class="card max-w-sm">
58
+ <section
59
+ class="card max-w-sm"
60
+ .pdsCodeHeading=${'PDS.toast()'}
61
+ .pdsCodeLabel=${'Quick toasts'}
62
+ .pdsCodeSource=${toastQuickSource}
63
+ >
34
64
  <header>
35
65
  <h3>Quick toasts</h3>
36
66
  <small class="text-muted">Trigger basic toast types using the shared helper.</small>
@@ -58,7 +88,12 @@ const PersistentToast = {
58
88
  };
59
89
 
60
90
  return html`
61
- <section class="card max-w-sm">
91
+ <section
92
+ class="card max-w-sm"
93
+ .pdsCodeHeading=${'PDS.toast()'}
94
+ .pdsCodeLabel=${'Persistent toast'}
95
+ .pdsCodeSource=${toastPersistentSource}
96
+ >
62
97
  <header>
63
98
  <h3>Persistent toast</h3>
64
99
  <small class="text-muted">Use persistent mode for messages that require attention.</small>
@@ -115,7 +150,12 @@ const CustomHtmlToasts = {
115
150
  };
116
151
 
117
152
  return html`
118
- <section class="card max-w-sm">
153
+ <section
154
+ class="card max-w-sm"
155
+ .pdsCodeHeading=${'PDS.toast()'}
156
+ .pdsCodeLabel=${'Custom HTML toasts'}
157
+ .pdsCodeSource=${toastHtmlActionSource}
158
+ >
119
159
  <header>
120
160
  <h3>Custom HTML toasts</h3>
121
161
  <small class="text-muted">Render trusted rich content in toast messages with the <code>html</code> option.</small>