@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.
@@ -42,37 +42,77 @@ pds-icon {
42
42
  .icon-text-end { flex-direction: row-reverse; }
43
43
 
44
44
  /* Button icon utilities */
45
- button, a {
46
- pds-icon {
45
+ button,
46
+ a.btn,
47
+ a.btn-primary,
48
+ a.btn-secondary,
49
+ a.btn-outline,
50
+ a.btn-danger,
51
+ a.icon-only {
52
+ pds-icon,
53
+ pds-icon[size] {
47
54
  flex-shrink: 0;
55
+ width: 1em;
56
+ height: 1em;
48
57
  }
49
58
 
50
59
  &.icon-only {
51
- padding: var(--spacing-2);
52
- min-width: 30px;
53
- width: 30px;
54
- height: 30px;
60
+ padding: calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 0.72);
61
+ min-width: max(30px, calc(var(--font-size-base) + (max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 2) + (var(--border-width-medium) * 2)));
62
+ min-height: max(30px, calc(var(--font-size-base) + (max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 2) + (var(--border-width-medium) * 2)));
63
+ width: max(30px, calc(var(--font-size-base) + (max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 2) + (var(--border-width-medium) * 2)));
64
+ height: max(30px, calc(var(--font-size-base) + (max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 2) + (var(--border-width-medium) * 2)));
55
65
  display: inline-flex;
56
66
  align-items: center;
57
67
  justify-content: center;
68
+
69
+ pds-icon,
70
+ pds-icon[size] {
71
+ width: 1.2em;
72
+ height: 1.2em;
73
+ }
58
74
  }
59
75
 
60
76
  &.btn-sm.icon-only {
61
- min-width: calc(30px * 0.8);
62
- width: calc(30px * 0.8);
63
- height: calc(30px * 0.8);
77
+ padding: calc(calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 0.85) * 0.72);
78
+ min-width: max(calc(30px * 0.85), calc(var(--font-size-sm) + (calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 0.85) * 2) + (var(--border-width-medium) * 2)));
79
+ min-height: max(calc(30px * 0.85), calc(var(--font-size-sm) + (calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 0.85) * 2) + (var(--border-width-medium) * 2)));
80
+ width: max(calc(30px * 0.85), calc(var(--font-size-sm) + (calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 0.85) * 2) + (var(--border-width-medium) * 2)));
81
+ height: max(calc(30px * 0.85), calc(var(--font-size-sm) + (calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 0.85) * 2) + (var(--border-width-medium) * 2)));
82
+
83
+ pds-icon,
84
+ pds-icon[size] {
85
+ width: 1.15em;
86
+ height: 1.15em;
87
+ }
64
88
  }
65
89
 
66
90
  &.btn-xs.icon-only {
67
- min-width: calc(30px * 0.6);
68
- width: calc(30px * 0.6);
69
- height: calc(30px * 0.6);
91
+ padding: calc(calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 0.7) * 0.72);
92
+ min-width: max(calc(30px * 0.7), calc(var(--font-size-xs) + (calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 0.7) * 2) + (var(--border-width-medium) * 2)));
93
+ min-height: max(calc(30px * 0.7), calc(var(--font-size-xs) + (calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 0.7) * 2) + (var(--border-width-medium) * 2)));
94
+ width: max(calc(30px * 0.7), calc(var(--font-size-xs) + (calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 0.7) * 2) + (var(--border-width-medium) * 2)));
95
+ height: max(calc(30px * 0.7), calc(var(--font-size-xs) + (calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 0.7) * 2) + (var(--border-width-medium) * 2)));
96
+
97
+ pds-icon,
98
+ pds-icon[size] {
99
+ width: 1.1em;
100
+ height: 1.1em;
101
+ }
70
102
  }
71
103
 
72
104
  &.btn-lg.icon-only {
73
- min-width: calc(30px * 1.2);
74
- width: calc(30px * 1.2);
75
- height: calc(30px * 1.2);
105
+ padding: calc(calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 1.15) * 0.72);
106
+ min-width: max(calc(30px * 1.15), calc(var(--font-size-lg) + (calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 1.15) * 2) + (var(--border-width-medium) * 2)));
107
+ min-height: max(calc(30px * 1.15), calc(var(--font-size-lg) + (calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 1.15) * 2) + (var(--border-width-medium) * 2)));
108
+ width: max(calc(30px * 1.15), calc(var(--font-size-lg) + (calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 1.15) * 2) + (var(--border-width-medium) * 2)));
109
+ height: max(calc(30px * 1.15), calc(var(--font-size-lg) + (calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 1.15) * 2) + (var(--border-width-medium) * 2)));
110
+
111
+ pds-icon,
112
+ pds-icon[size] {
113
+ width: 1.25em;
114
+ height: 1.25em;
115
+ }
76
116
  }
77
117
  }
78
118
 
@@ -591,7 +631,7 @@ html[data-theme="dark"] .surface-inverse {
591
631
  /* Touch device optimizations */
592
632
  @media (hover: none) and (pointer: coarse) {
593
633
  /* Touch devices - larger touch targets for interactive elements */
594
- button:not(.icon-only), a:not(.icon-only), select, textarea,
634
+ button, a, select, textarea,
595
635
  input:not([type="radio"]):not([type="checkbox"]) {
596
636
  min-height: 44px;
597
637
  min-width: 44px;
@@ -720,37 +760,77 @@ pds-icon {
720
760
  .icon-text-end { flex-direction: row-reverse; }
721
761
 
722
762
  /* Button icon utilities */
723
- button, a {
724
- pds-icon {
763
+ button,
764
+ a.btn,
765
+ a.btn-primary,
766
+ a.btn-secondary,
767
+ a.btn-outline,
768
+ a.btn-danger,
769
+ a.icon-only {
770
+ pds-icon,
771
+ pds-icon[size] {
725
772
  flex-shrink: 0;
773
+ width: 1em;
774
+ height: 1em;
726
775
  }
727
776
 
728
777
  &.icon-only {
729
- padding: var(--spacing-2);
730
- min-width: 30px;
731
- width: 30px;
732
- height: 30px;
778
+ padding: calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 0.72);
779
+ min-width: max(30px, calc(var(--font-size-base) + (max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 2) + (var(--border-width-medium) * 2)));
780
+ min-height: max(30px, calc(var(--font-size-base) + (max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 2) + (var(--border-width-medium) * 2)));
781
+ width: max(30px, calc(var(--font-size-base) + (max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 2) + (var(--border-width-medium) * 2)));
782
+ height: max(30px, calc(var(--font-size-base) + (max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 2) + (var(--border-width-medium) * 2)));
733
783
  display: inline-flex;
734
784
  align-items: center;
735
785
  justify-content: center;
786
+
787
+ pds-icon,
788
+ pds-icon[size] {
789
+ width: 1.2em;
790
+ height: 1.2em;
791
+ }
736
792
  }
737
793
 
738
794
  &.btn-sm.icon-only {
739
- min-width: calc(30px * 0.8);
740
- width: calc(30px * 0.8);
741
- height: calc(30px * 0.8);
795
+ padding: calc(calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 0.85) * 0.72);
796
+ min-width: max(calc(30px * 0.85), calc(var(--font-size-sm) + (calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 0.85) * 2) + (var(--border-width-medium) * 2)));
797
+ min-height: max(calc(30px * 0.85), calc(var(--font-size-sm) + (calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 0.85) * 2) + (var(--border-width-medium) * 2)));
798
+ width: max(calc(30px * 0.85), calc(var(--font-size-sm) + (calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 0.85) * 2) + (var(--border-width-medium) * 2)));
799
+ height: max(calc(30px * 0.85), calc(var(--font-size-sm) + (calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 0.85) * 2) + (var(--border-width-medium) * 2)));
800
+
801
+ pds-icon,
802
+ pds-icon[size] {
803
+ width: 1.15em;
804
+ height: 1.15em;
805
+ }
742
806
  }
743
807
 
744
808
  &.btn-xs.icon-only {
745
- min-width: calc(30px * 0.6);
746
- width: calc(30px * 0.6);
747
- height: calc(30px * 0.6);
809
+ padding: calc(calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 0.7) * 0.72);
810
+ min-width: max(calc(30px * 0.7), calc(var(--font-size-xs) + (calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 0.7) * 2) + (var(--border-width-medium) * 2)));
811
+ min-height: max(calc(30px * 0.7), calc(var(--font-size-xs) + (calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 0.7) * 2) + (var(--border-width-medium) * 2)));
812
+ width: max(calc(30px * 0.7), calc(var(--font-size-xs) + (calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 0.7) * 2) + (var(--border-width-medium) * 2)));
813
+ height: max(calc(30px * 0.7), calc(var(--font-size-xs) + (calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 0.7) * 2) + (var(--border-width-medium) * 2)));
814
+
815
+ pds-icon,
816
+ pds-icon[size] {
817
+ width: 1.1em;
818
+ height: 1.1em;
819
+ }
748
820
  }
749
821
 
750
822
  &.btn-lg.icon-only {
751
- min-width: calc(30px * 1.2);
752
- width: calc(30px * 1.2);
753
- height: calc(30px * 1.2);
823
+ padding: calc(calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 1.15) * 0.72);
824
+ min-width: max(calc(30px * 1.15), calc(var(--font-size-lg) + (calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 1.15) * 2) + (var(--border-width-medium) * 2)));
825
+ min-height: max(calc(30px * 1.15), calc(var(--font-size-lg) + (calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 1.15) * 2) + (var(--border-width-medium) * 2)));
826
+ width: max(calc(30px * 1.15), calc(var(--font-size-lg) + (calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 1.15) * 2) + (var(--border-width-medium) * 2)));
827
+ height: max(calc(30px * 1.15), calc(var(--font-size-lg) + (calc(max(calc(var(--spacing-1) * 1), var(--spacing-2)) * 1.15) * 2) + (var(--border-width-medium) * 2)));
828
+
829
+ pds-icon,
830
+ pds-icon[size] {
831
+ width: 1.25em;
832
+ height: 1.25em;
833
+ }
754
834
  }
755
835
  }
756
836
 
@@ -1269,7 +1349,7 @@ html[data-theme="dark"] .surface-inverse {
1269
1349
  /* Touch device optimizations */
1270
1350
  @media (hover: none) and (pointer: coarse) {
1271
1351
  /* Touch devices - larger touch targets for interactive elements */
1272
- button:not(.icon-only), a:not(.icon-only), select, textarea,
1352
+ button, a, select, textarea,
1273
1353
  input:not([type="radio"]):not([type="checkbox"]) {
1274
1354
  min-height: 44px;
1275
1355
  min-width: 44px;
@@ -950,6 +950,38 @@
950
950
  }
951
951
  ]
952
952
  },
953
+ {
954
+ "name": "pds-tags",
955
+ "description": "Form-associated multi-select tags control built on top of `pds-omnibox`.\r\n\r\nUsers select suggestions from the omnibox and each selection is rendered as a removable\r\nchip. The component keeps selection state synchronized across:\r\n- the `value` attribute (comma-separated ids)\r\n- the `value` property (`string[]`)\r\n- form value via ElementInternals\r\n\r\nThe `options` object uses the same structure as `pds-omnibox.settings`.\r\nSee `pds-omnibox` JSDoc for the full options schema.\r\nBefore applying it, the component deep-merges internal defaults:\r\n- `hideCategory: true`\r\n- `itemGrid: \"0 1fr 0\"`\r\n- `iconHandler: () => \"\"`\r\n\r\nWhen `required` is set, at least one selected value is required for validity.",
956
+ "attributes": [
957
+ {
958
+ "name": "name",
959
+ "description": "Form field name; selected values are submitted under this name"
960
+ },
961
+ {
962
+ "name": "placeholder",
963
+ "description": "Placeholder shown in omnibox input (default: \"Search tags...\")"
964
+ },
965
+ {
966
+ "name": "value",
967
+ "description": "Comma-separated selected item ids"
968
+ },
969
+ {
970
+ "name": "options",
971
+ "description": "JSON object with the same shape as `pds-omnibox.settings`"
972
+ },
973
+ {
974
+ "name": "disabled",
975
+ "description": "Disables omnibox interaction and chip remove actions",
976
+ "valueSet": "v"
977
+ },
978
+ {
979
+ "name": "required",
980
+ "description": "Requires at least one selected value for validity",
981
+ "valueSet": "v"
982
+ }
983
+ ]
984
+ },
953
985
  {
954
986
  "name": "pds-theme",
955
987
  "description": "PdsTheme component"
@@ -162,14 +162,150 @@ export async function ask(message, options = {}) {
162
162
  };
163
163
 
164
164
  options = { ...defaults, ...options };
165
+
166
+ const buttonConfigs = options.buttons && typeof options.buttons === "object"
167
+ ? options.buttons
168
+ : defaults.buttons;
169
+
170
+ const resolveActionMeta = (actionCode) => {
171
+ if (actionCode == null) {
172
+ return {
173
+ actionCode: "dismiss",
174
+ actionKind: "dismiss",
175
+ button: null,
176
+ };
177
+ }
178
+
179
+ const button = buttonConfigs?.[actionCode] ?? null;
180
+ const actionKind = actionCode === "ok"
181
+ ? "ok"
182
+ : actionCode === "dismiss"
183
+ ? "dismiss"
184
+ : (button?.cancel || actionCode === "cancel")
185
+ ? "cancel"
186
+ : "custom";
187
+
188
+ return {
189
+ actionCode,
190
+ actionKind,
191
+ button,
192
+ };
193
+ };
194
+
195
+ const normalizeBeforeCloseResult = (result) => {
196
+ if (typeof result === "undefined" || result === null || result === true) {
197
+ return { allow: true };
198
+ }
199
+
200
+ if (result === false) {
201
+ return { allow: false };
202
+ }
203
+
204
+ if (typeof result === "object") {
205
+ const hasResult = Object.prototype.hasOwnProperty.call(result, "result")
206
+ || Object.prototype.hasOwnProperty.call(result, "value");
207
+
208
+ return {
209
+ allow: result.allow !== false,
210
+ hasResult,
211
+ result: Object.prototype.hasOwnProperty.call(result, "result")
212
+ ? result.result
213
+ : result.value,
214
+ };
215
+ }
216
+
217
+ return { allow: Boolean(result) };
218
+ };
165
219
 
166
220
  return new Promise((resolve) => {
167
221
  let settled = false;
168
- const settle = (value, dialog) => {
222
+ const settle = (value, dialog, { shouldClose = true } = {}) => {
169
223
  if (settled) return;
170
224
  settled = true;
171
- dialog.close();
172
225
  resolve(value);
226
+
227
+ if (!shouldClose || !dialog?.open) {
228
+ return;
229
+ }
230
+
231
+ try {
232
+ dialog.close();
233
+ } catch (error) {
234
+ console.warn("ask: dialog.close() failed", error);
235
+ }
236
+ };
237
+
238
+ const runBeforeClose = async (context) => {
239
+ if (context.actionKind !== "ok" || typeof options.beforeClose !== "function") {
240
+ return { allow: true };
241
+ }
242
+
243
+ try {
244
+ const beforeCloseResult = await options.beforeClose(context);
245
+ return normalizeBeforeCloseResult(beforeCloseResult);
246
+ } catch (error) {
247
+ console.error("ask.beforeClose: validation failed", error);
248
+ return { allow: false };
249
+ }
250
+ };
251
+
252
+ const resolveDefaultResult = ({ actionKind, form }) => {
253
+ if (actionKind === "ok") {
254
+ if (options.useForm && form) {
255
+ return new FormData(form);
256
+ }
257
+ return true;
258
+ }
259
+
260
+ return false;
261
+ };
262
+
263
+ const attemptResolve = async ({
264
+ actionCode,
265
+ form,
266
+ submitter,
267
+ originalEvent,
268
+ bypassValidation = false,
269
+ shouldClose = true,
270
+ }) => {
271
+ if (settled) return;
272
+
273
+ const { actionKind, button } = resolveActionMeta(actionCode);
274
+ const activeForm = form || dialog.querySelector("form") || null;
275
+
276
+ if (options.useForm && actionKind === "ok" && activeForm && !bypassValidation) {
277
+ const valid = validateDialogFormTree(activeForm);
278
+ if (!valid) {
279
+ return;
280
+ }
281
+ }
282
+
283
+ const defaultResult = resolveDefaultResult({
284
+ actionKind,
285
+ form: activeForm,
286
+ });
287
+
288
+ const guard = await runBeforeClose({
289
+ actionCode,
290
+ actionKind,
291
+ dialog,
292
+ form: activeForm,
293
+ formData: options.useForm && actionKind === "ok" && activeForm
294
+ ? defaultResult
295
+ : null,
296
+ submitter,
297
+ originalEvent,
298
+ options,
299
+ button,
300
+ defaultResult,
301
+ });
302
+
303
+ if (!guard.allow) {
304
+ return;
305
+ }
306
+
307
+ const result = guard.hasResult ? guard.result : defaultResult;
308
+ settle(result, dialog, { shouldClose });
173
309
  };
174
310
 
175
311
  // Create native dialog element
@@ -203,7 +339,7 @@ export async function ask(message, options = {}) {
203
339
  }
204
340
 
205
341
  // Build button elements
206
- const buttons = Object.entries(options.buttons).map(([code, obj]) => {
342
+ const buttons = Object.entries(buttonConfigs).map(([code, obj]) => {
207
343
  const btnClass = obj.primary ? "btn-primary btn-sm" : "btn-outline btn-sm";
208
344
  const btnType = obj.cancel ? "button" : "submit";
209
345
  const formNoValidate = obj.formNoValidate ? " formnovalidate" : "";
@@ -278,26 +414,14 @@ export async function ask(message, options = {}) {
278
414
 
279
415
  // Handle cancel button clicks
280
416
  dialog.addEventListener("click", (e) => {
281
- const okBtn = e.target.closest('button[value="ok"]');
282
- if (okBtn && options.useForm) {
283
- e.preventDefault();
284
- const form = dialog.querySelector("form");
285
- if (!form) return;
286
-
287
- const bypassValidation = Boolean(okBtn.hasAttribute("formnovalidate"));
288
- if (!bypassValidation) {
289
- const valid = validateDialogFormTree(form);
290
- if (!valid) return;
291
- }
292
-
293
- const result = new FormData(form);
294
- settle(result, dialog);
295
- return;
296
- }
297
-
298
417
  const btn = e.target.closest('button[value="cancel"]');
299
418
  if (btn) {
300
- settle(false, dialog);
419
+ attemptResolve({
420
+ actionCode: "cancel",
421
+ form: dialog.querySelector("form"),
422
+ submitter: btn,
423
+ originalEvent: e,
424
+ });
301
425
  }
302
426
  });
303
427
 
@@ -316,22 +440,13 @@ export async function ask(message, options = {}) {
316
440
  const submitValue = event.submitter?.value ?? (options.useForm ? "ok" : undefined);
317
441
  const bypassValidation = Boolean(event.submitter?.hasAttribute("formnovalidate"));
318
442
 
319
- if (options.useForm && submitValue === "ok" && !bypassValidation) {
320
- const valid = validateDialogFormTree(form);
321
-
322
- if (!valid) {
323
- return;
324
- }
325
- }
326
-
327
- let result;
328
- if (options.useForm && submitValue === "ok") {
329
- result = new FormData(form);
330
- } else {
331
- result = (submitValue === "ok");
332
- }
333
-
334
- settle(result, dialog);
443
+ attemptResolve({
444
+ actionCode: submitValue,
445
+ form,
446
+ submitter: event.submitter,
447
+ originalEvent: event,
448
+ bypassValidation,
449
+ });
335
450
  });
336
451
  } else {
337
452
  // Form doesn't exist yet, wait and try again
@@ -339,8 +454,21 @@ export async function ask(message, options = {}) {
339
454
  }
340
455
  };
341
456
 
457
+ dialog.addEventListener("cancel", (event) => {
458
+ event.preventDefault();
459
+ attemptResolve({
460
+ actionCode: "dismiss",
461
+ form: dialog.querySelector("form"),
462
+ originalEvent: event,
463
+ });
464
+ });
465
+
342
466
  // Handle dialog close event
343
467
  dialog.addEventListener("close", () => {
468
+ if (!settled) {
469
+ settle(false, dialog, { shouldClose: false });
470
+ }
471
+
344
472
  // Small delay to allow exit animation
345
473
  setTimeout(() => dialog.remove(), 200);
346
474
  });
@@ -2731,18 +2731,23 @@ select {
2731
2731
 
2732
2732
  /* Button styling */
2733
2733
  button, .btn, input[type="submit"], input[type="button"], input[type="reset"] {
2734
+ --btn-pad-y: max(calc(var(--spacing-1) * ${buttonPaddingValue}), var(--spacing-2));
2735
+ --btn-target-h: max(${minButtonHeight}px, calc(var(--font-size-base) + (var(--btn-pad-y) * 2) + (var(--border-width-medium) * 2)));
2734
2736
  display: inline-flex;
2735
2737
  gap: var(--spacing-1);
2736
2738
  align-items: center;
2737
2739
  justify-content: center;
2738
- min-height: ${minButtonHeight}px;
2739
- padding: max(calc(var(--spacing-1) * ${buttonPaddingValue}), var(--spacing-2)) var(--spacing-6);
2740
+ min-height: var(--btn-target-h);
2741
+ height: var(--btn-target-h);
2742
+ padding: var(--btn-pad-y) var(--spacing-6);
2740
2743
  border: var(--border-width-medium) solid transparent;
2741
2744
  border-radius: var(--radius-md);
2745
+ box-sizing: border-box;
2742
2746
  font-family: var(--font-family-body);
2743
2747
  font-size: var(--font-size-base);
2744
2748
  font-weight: var(--font-weight-medium);
2745
2749
  line-height: 1;
2750
+ white-space: nowrap;
2746
2751
  cursor: pointer;
2747
2752
  transition: all var(--transition-fast);
2748
2753
  text-decoration: none;
@@ -2896,22 +2901,31 @@ button, .btn, input[type="submit"], input[type="button"], input[type="reset"] {
2896
2901
  }
2897
2902
 
2898
2903
  .btn-sm {
2899
- padding: calc(max(calc(var(--spacing-1) * ${buttonPaddingValue}), var(--spacing-2)) * 0.85) calc(var(--spacing-6) * 0.8);
2904
+ --btn-pad-y: calc(max(calc(var(--spacing-1) * ${buttonPaddingValue}), var(--spacing-2)) * 0.85);
2905
+ --btn-target-h: max(calc(${minButtonHeight}px * 0.85), calc(var(--font-size-sm) + (var(--btn-pad-y) * 2) + (var(--border-width-medium) * 2)));
2906
+ padding: var(--btn-pad-y) calc(var(--spacing-6) * 0.8);
2900
2907
  font-size: var(--font-size-sm);
2901
- min-height: calc(${minButtonHeight}px * 0.85);
2908
+ min-height: var(--btn-target-h);
2909
+ height: var(--btn-target-h);
2902
2910
  }
2903
2911
 
2904
2912
  .btn-xs {
2905
- padding: calc(max(calc(var(--spacing-1) * ${buttonPaddingValue}), var(--spacing-2)) * 0.7) calc(var(--spacing-6) * 0.65);
2913
+ --btn-pad-y: calc(max(calc(var(--spacing-1) * ${buttonPaddingValue}), var(--spacing-2)) * 0.7);
2914
+ --btn-target-h: max(calc(${minButtonHeight}px * 0.7), calc(var(--font-size-xs) + (var(--btn-pad-y) * 2) + (var(--border-width-medium) * 2)));
2915
+ padding: var(--btn-pad-y) calc(var(--spacing-6) * 0.65);
2906
2916
  font-size: var(--font-size-xs);
2907
- min-height: calc(${minButtonHeight}px * 0.7);
2917
+ min-height: var(--btn-target-h);
2918
+ height: var(--btn-target-h);
2908
2919
  }
2909
2920
 
2910
2921
 
2911
2922
  .btn-lg {
2912
- padding: calc(max(calc(var(--spacing-1) * ${buttonPaddingValue}), var(--spacing-2)) * 1.15) calc(var(--spacing-6) * 1.35);
2923
+ --btn-pad-y: calc(max(calc(var(--spacing-1) * ${buttonPaddingValue}), var(--spacing-2)) * 1.15);
2924
+ --btn-target-h: max(calc(${minButtonHeight}px * 1.15), calc(var(--font-size-lg) + (var(--btn-pad-y) * 2) + (var(--border-width-medium) * 2)));
2925
+ padding: var(--btn-pad-y) calc(var(--spacing-6) * 1.35);
2913
2926
  font-size: var(--font-size-lg);
2914
- min-height: calc(${minButtonHeight}px * 1.15);
2927
+ min-height: var(--btn-target-h);
2928
+ height: var(--btn-target-h);
2915
2929
  }
2916
2930
 
2917
2931
  /* Working/loading state for buttons */
@@ -3887,7 +3901,25 @@ pds-tabstrip {
3887
3901
 
3888
3902
  #generateIconStyles() {
3889
3903
  const { layout = {} } = this.options.design;
3890
- const iconOnlySize = layout.buttonMinHeight || 30;
3904
+ const minButtonHeight = layout.buttonMinHeight || 30;
3905
+ const buttonPaddingValue = layout.buttonPadding || 1.0;
3906
+ const buttonPaddingDefault = `max(calc(var(--spacing-1) * ${buttonPaddingValue}), var(--spacing-2))`;
3907
+ const buttonPaddingSm = `calc(max(calc(var(--spacing-1) * ${buttonPaddingValue}), var(--spacing-2)) * 0.85)`;
3908
+ const buttonPaddingXs = `calc(max(calc(var(--spacing-1) * ${buttonPaddingValue}), var(--spacing-2)) * 0.7)`;
3909
+ const buttonPaddingLg = `calc(max(calc(var(--spacing-1) * ${buttonPaddingValue}), var(--spacing-2)) * 1.15)`;
3910
+ const iconOnlyPaddingScale = 0.72;
3911
+ const iconOnlyPaddingDefault = `calc(${buttonPaddingDefault} * ${iconOnlyPaddingScale})`;
3912
+ const iconOnlyPaddingSm = `calc(${buttonPaddingSm} * ${iconOnlyPaddingScale})`;
3913
+ const iconOnlyPaddingXs = `calc(${buttonPaddingXs} * ${iconOnlyPaddingScale})`;
3914
+ const iconOnlyPaddingLg = `calc(${buttonPaddingLg} * ${iconOnlyPaddingScale})`;
3915
+ const iconOnlyMinDefault = `${minButtonHeight}px`;
3916
+ const iconOnlyMinSm = `calc(${minButtonHeight}px * 0.85)`;
3917
+ const iconOnlyMinXs = `calc(${minButtonHeight}px * 0.7)`;
3918
+ const iconOnlyMinLg = `calc(${minButtonHeight}px * 1.15)`;
3919
+ const iconOnlySizeDefault = `max(${iconOnlyMinDefault}, calc(var(--font-size-base) + (${buttonPaddingDefault} * 2) + (var(--border-width-medium) * 2)))`;
3920
+ const iconOnlySizeSm = `max(${iconOnlyMinSm}, calc(var(--font-size-sm) + (${buttonPaddingSm} * 2) + (var(--border-width-medium) * 2)))`;
3921
+ const iconOnlySizeXs = `max(${iconOnlyMinXs}, calc(var(--font-size-xs) + (${buttonPaddingXs} * 2) + (var(--border-width-medium) * 2)))`;
3922
+ const iconOnlySizeLg = `max(${iconOnlyMinLg}, calc(var(--font-size-lg) + (${buttonPaddingLg} * 2) + (var(--border-width-medium) * 2)))`;
3891
3923
 
3892
3924
  return /*css*/ `/* Icon System */
3893
3925
 
@@ -3927,37 +3959,77 @@ pds-icon {
3927
3959
  .icon-text-end { flex-direction: row-reverse; }
3928
3960
 
3929
3961
  /* Button icon utilities */
3930
- button, a {
3931
- pds-icon {
3962
+ button,
3963
+ a.btn,
3964
+ a.btn-primary,
3965
+ a.btn-secondary,
3966
+ a.btn-outline,
3967
+ a.btn-danger,
3968
+ a.icon-only {
3969
+ pds-icon,
3970
+ pds-icon[size] {
3932
3971
  flex-shrink: 0;
3972
+ width: 1em;
3973
+ height: 1em;
3933
3974
  }
3934
3975
 
3935
3976
  &.icon-only {
3936
- padding: var(--spacing-2);
3937
- min-width: ${iconOnlySize}px;
3938
- width: ${iconOnlySize}px;
3939
- height: ${iconOnlySize}px;
3977
+ padding: ${iconOnlyPaddingDefault};
3978
+ min-width: ${iconOnlySizeDefault};
3979
+ min-height: ${iconOnlySizeDefault};
3980
+ width: ${iconOnlySizeDefault};
3981
+ height: ${iconOnlySizeDefault};
3940
3982
  display: inline-flex;
3941
3983
  align-items: center;
3942
3984
  justify-content: center;
3985
+
3986
+ pds-icon,
3987
+ pds-icon[size] {
3988
+ width: 1.2em;
3989
+ height: 1.2em;
3990
+ }
3943
3991
  }
3944
3992
 
3945
3993
  &.btn-sm.icon-only {
3946
- min-width: calc(${iconOnlySize}px * 0.8);
3947
- width: calc(${iconOnlySize}px * 0.8);
3948
- height: calc(${iconOnlySize}px * 0.8);
3994
+ padding: ${iconOnlyPaddingSm};
3995
+ min-width: ${iconOnlySizeSm};
3996
+ min-height: ${iconOnlySizeSm};
3997
+ width: ${iconOnlySizeSm};
3998
+ height: ${iconOnlySizeSm};
3999
+
4000
+ pds-icon,
4001
+ pds-icon[size] {
4002
+ width: 1.15em;
4003
+ height: 1.15em;
4004
+ }
3949
4005
  }
3950
4006
 
3951
4007
  &.btn-xs.icon-only {
3952
- min-width: calc(${iconOnlySize}px * 0.6);
3953
- width: calc(${iconOnlySize}px * 0.6);
3954
- height: calc(${iconOnlySize}px * 0.6);
4008
+ padding: ${iconOnlyPaddingXs};
4009
+ min-width: ${iconOnlySizeXs};
4010
+ min-height: ${iconOnlySizeXs};
4011
+ width: ${iconOnlySizeXs};
4012
+ height: ${iconOnlySizeXs};
4013
+
4014
+ pds-icon,
4015
+ pds-icon[size] {
4016
+ width: 1.1em;
4017
+ height: 1.1em;
4018
+ }
3955
4019
  }
3956
4020
 
3957
4021
  &.btn-lg.icon-only {
3958
- min-width: calc(${iconOnlySize}px * 1.2);
3959
- width: calc(${iconOnlySize}px * 1.2);
3960
- height: calc(${iconOnlySize}px * 1.2);
4022
+ padding: ${iconOnlyPaddingLg};
4023
+ min-width: ${iconOnlySizeLg};
4024
+ min-height: ${iconOnlySizeLg};
4025
+ width: ${iconOnlySizeLg};
4026
+ height: ${iconOnlySizeLg};
4027
+
4028
+ pds-icon,
4029
+ pds-icon[size] {
4030
+ width: 1.25em;
4031
+ height: 1.25em;
4032
+ }
3961
4033
  }
3962
4034
  }
3963
4035
 
@@ -4354,9 +4426,9 @@ nav[data-dropdown] {
4354
4426
  }
4355
4427
 
4356
4428
  /* Touch device optimizations */
4357
- @media (hover: none) and (pointer: coarse) {
4429
+ @media (pointer: coarse) {
4358
4430
  /* Touch devices - larger touch targets for interactive elements */
4359
- button:not(.icon-only), a:not(.icon-only), select, textarea,
4431
+ select, textarea,
4360
4432
  input:not([type="radio"]):not([type="checkbox"]) {
4361
4433
  min-height: ${minTouchTarget}px;
4362
4434
  min-width: ${minTouchTarget}px;