@inglorious/ui 9.0.0 → 9.1.1

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.
Files changed (64) hide show
  1. package/package.json +2 -2
  2. package/src/controls/button/button.stories.js +7 -1
  3. package/src/controls/button/style.css +11 -0
  4. package/src/controls/button/template.js +13 -12
  5. package/src/controls/button-group/template.js +6 -6
  6. package/src/controls/checkbox/checkbox.stories.js +12 -0
  7. package/src/controls/checkbox/style.css +11 -0
  8. package/src/controls/checkbox/template.js +11 -1
  9. package/src/controls/file-upload/template.js +27 -26
  10. package/src/controls/icon-button/template.js +6 -6
  11. package/src/controls/image-cropper/template.js +3 -1
  12. package/src/controls/radio-group/radio-group.stories.js +21 -1
  13. package/src/controls/radio-group/style.css +11 -0
  14. package/src/controls/radio-group/template.js +15 -6
  15. package/src/controls/rating/rating.stories.js +8 -0
  16. package/src/controls/select/template.js +5 -6
  17. package/src/controls/slider/slider.stories.js +28 -1
  18. package/src/controls/slider/style.css +102 -0
  19. package/src/controls/slider/template.js +131 -29
  20. package/src/controls/switch/style.css +11 -0
  21. package/src/controls/switch/switch.stories.js +10 -0
  22. package/src/controls/switch/template.js +15 -1
  23. package/src/controls/textarea/template.js +29 -26
  24. package/src/data-display/avatar/template.js +6 -5
  25. package/src/data-display/chip/chip.stories.js +17 -0
  26. package/src/data-display/chip/style.css +5 -0
  27. package/src/data-display/chip/template.js +11 -8
  28. package/src/data-display/data-grid/template.js +1 -1
  29. package/src/data-display/divider/divider.stories.js +9 -0
  30. package/src/data-display/divider/template.js +12 -4
  31. package/src/data-display/list/template.js +4 -5
  32. package/src/examples/dashboard/primitive-section.js +18 -18
  33. package/src/feedback/alert/alert.stories.js +9 -1
  34. package/src/feedback/alert/style.css +5 -0
  35. package/src/feedback/alert/template.js +28 -27
  36. package/src/feedback/dialog/dialog.stories.js +22 -0
  37. package/src/feedback/dialog/style.css +15 -2
  38. package/src/feedback/dialog/template.js +37 -30
  39. package/src/feedback/progress/progress.stories.js +5 -0
  40. package/src/feedback/snackbar/template.js +14 -13
  41. package/src/layout/flex/flex.stories.js +1 -1
  42. package/src/layout/flex/style.css +8 -0
  43. package/src/navigation/bottom-navigation/template.js +11 -12
  44. package/src/navigation/breadcrumbs/template.js +28 -25
  45. package/src/navigation/menu/template.js +9 -10
  46. package/src/navigation/pagination/template.js +15 -18
  47. package/src/navigation/stepper/template.js +5 -5
  48. package/src/navigation/tabs/template.js +2 -1
  49. package/src/surfaces/accordion/accordion.stories.js +9 -0
  50. package/src/surfaces/accordion/template.js +7 -2
  51. package/src/surfaces/card/card.stories.js +24 -0
  52. package/src/surfaces/card/style.css +15 -0
  53. package/src/surfaces/card/template.js +5 -2
  54. package/types/controls/button.d.ts +1 -1
  55. package/types/controls/checkbox.d.ts +2 -0
  56. package/types/controls/radio-group.d.ts +2 -0
  57. package/types/controls/slider.d.ts +6 -2
  58. package/types/controls/switch.d.ts +2 -0
  59. package/types/data-display/chip.d.ts +2 -0
  60. package/types/data-display/divider.d.ts +2 -0
  61. package/types/feedback/alert.d.ts +1 -1
  62. package/types/feedback/dialog.d.ts +2 -0
  63. package/types/layout/flex.d.ts +1 -1
  64. package/types/surfaces/card.d.ts +5 -0
@@ -6,12 +6,14 @@
6
6
  import { html } from "@inglorious/web"
7
7
  import { classMap } from "@inglorious/web/directives/class-map"
8
8
  import { ref } from "@inglorious/web/directives/ref"
9
+ import { when } from "@inglorious/web/directives/when"
9
10
 
10
11
  import { applyElementProps } from "../../shared/apply-element-props.js"
11
12
 
12
13
  /**
13
14
  * Renders an alert message with optional title, description, icon, and actions.
14
- * Supports severity and variant styles for info, success, warning, and error.
15
+ * Supports severity and variant styles for info, success, warning, error and
16
+ * neutral, a plain note.
15
17
  * @param {AlertProps} props
16
18
  * @returns {TemplateResult}
17
19
  */
@@ -52,33 +54,32 @@ export function render(props) {
52
54
  role="alert"
53
55
  ${ref((el) => applyElementProps(el, rest))}
54
56
  >
55
- ${icon ? html`<div class="iw-alert-icon">${icon}</div>` : null}
57
+ ${when(icon, () => html`<div class="iw-alert-icon">${icon}</div>`)}
56
58
  <div class="iw-alert-content">
57
- ${title ? html`<div class="iw-alert-title">${title}</div>` : null}
58
- ${
59
- content
60
- ? html`<div class="iw-alert-description">${content}</div>`
61
- : null
62
- }
59
+ ${when(title, () => html`<div class="iw-alert-title">${title}</div>`)}
60
+ ${when(
61
+ content,
62
+ () => html`<div class="iw-alert-description">${content}</div>`,
63
+ )}
63
64
  </div>
64
- ${
65
- hasTrailing
66
- ? html`<div class="iw-alert-trailing">
67
- ${action ? html`<div class="iw-alert-action">${action}</div>` : null}
68
- ${
69
- onClose
70
- ? html`<button
71
- type="button"
72
- class="iw-alert-close"
73
- aria-label="Dismiss"
74
- @click=${onClose}
75
- >
76
- ×
77
- </button>`
78
- : null
79
- }
80
- </div>`
81
- : null
82
- }
65
+ ${when(
66
+ hasTrailing,
67
+ () =>
68
+ html`<div class="iw-alert-trailing">
69
+ ${when(action, () => html`<div class="iw-alert-action">${action}</div>`)}
70
+ ${when(
71
+ onClose,
72
+ () =>
73
+ html`<button
74
+ type="button"
75
+ class="iw-alert-close"
76
+ aria-label="Dismiss"
77
+ @click=${onClose}
78
+ >
79
+ ×
80
+ </button>`,
81
+ )}
82
+ </div>`,
83
+ )}
83
84
  </div>`
84
85
  }
@@ -10,6 +10,7 @@ export default {
10
10
  argTypes: {
11
11
  isOpen: { control: "boolean" },
12
12
  isModal: { control: "boolean" },
13
+ size: { control: "select", options: ["sm", "md", "lg"] },
13
14
  title: { control: "text" },
14
15
  description: { control: "text" },
15
16
  onClose: { action: "onClose" },
@@ -29,6 +30,27 @@ export const Open = {
29
30
  },
30
31
  }
31
32
 
33
+ export const Small = {
34
+ args: { ...Open.args, size: "sm" },
35
+ }
36
+
37
+ export const Medium = {
38
+ args: { ...Open.args, size: "md" },
39
+ }
40
+
41
+ export const Large = {
42
+ args: {
43
+ ...Open.args,
44
+ size: "lg",
45
+ title: "Release notes",
46
+ description: "What changed in this version.",
47
+ children: html`<p>
48
+ A wider dialog, for content that needs the room: tables, previews or
49
+ longer text.
50
+ </p>`,
51
+ },
52
+ }
53
+
32
54
  // Modal takes over the page, so it is left out of the docs page.
33
55
  export const Modal = {
34
56
  tags: ["!autodocs"],
@@ -7,12 +7,25 @@
7
7
  color: var(--iw-color-text);
8
8
  border-radius: var(--iw-radius-md);
9
9
  border: 1px solid color-mix(in srgb, var(--iw-color-border) 70%, transparent);
10
- min-width: min(32rem, 90vw);
11
- max-width: min(48rem, 90vw);
12
10
  box-shadow: var(--iw-shadow-lg, 0 12px 40px rgba(0, 0, 0, 0.3));
13
11
  padding: 0;
14
12
  }
15
13
 
14
+ .iw-dialog-sm {
15
+ min-width: min(20rem, 90vw);
16
+ max-width: min(28rem, 90vw);
17
+ }
18
+
19
+ .iw-dialog-md {
20
+ min-width: min(32rem, 90vw);
21
+ max-width: min(48rem, 90vw);
22
+ }
23
+
24
+ .iw-dialog-lg {
25
+ min-width: min(40rem, 90vw);
26
+ max-width: min(64rem, 90vw);
27
+ }
28
+
16
29
  .iw-dialog::backdrop {
17
30
  background: color-mix(in srgb, var(--iw-color-black) 55%, transparent);
18
31
  backdrop-filter: blur(2px);
@@ -6,6 +6,7 @@
6
6
  import { html, nothing } from "@inglorious/web"
7
7
  import { classMap } from "@inglorious/web/directives/class-map"
8
8
  import { ref } from "@inglorious/web/directives/ref"
9
+ import { when } from "@inglorious/web/directives/when"
9
10
 
10
11
  import { syncDialog } from "../../shared/top-layer.js"
11
12
 
@@ -28,6 +29,7 @@ export function render(props) {
28
29
  id,
29
30
  isOpen = false,
30
31
  isModal = true,
32
+ size = "md",
31
33
  title,
32
34
  description,
33
35
  children,
@@ -54,7 +56,11 @@ export function render(props) {
54
56
  // also clicks the dialog, and must not close it.
55
57
  return html`<dialog
56
58
  id=${id || nothing}
57
- class=${classMap({ "iw-dialog": true, ...extraClasses })}
59
+ class=${classMap({
60
+ "iw-dialog": true,
61
+ [`iw-dialog-${size}`]: true,
62
+ ...extraClasses,
63
+ })}
58
64
  aria-labelledby=${titleId || nothing}
59
65
  aria-describedby=${descriptionId || nothing}
60
66
  @pointerdown=${(event) => {
@@ -77,42 +83,43 @@ export function render(props) {
77
83
  ${ref((element) => syncDialog(element, { isOpen, isModal, onClose }))}
78
84
  >
79
85
  <div class="iw-dialog-surface">
80
- ${
81
- (title || onClose) &&
82
- html`<div class="iw-dialog-header">
83
- ${
84
- title
85
- ? html`<div id=${titleId || nothing} class="iw-dialog-title">
86
+ ${when(
87
+ title || onClose,
88
+ () =>
89
+ html`<div class="iw-dialog-header">
90
+ ${when(
91
+ title,
92
+ () =>
93
+ html`<div id=${titleId || nothing} class="iw-dialog-title">
86
94
  ${title}
87
- </div>`
88
- : null
89
- }
90
- ${
91
- onClose
92
- ? html`<button
95
+ </div>`,
96
+ )}
97
+ ${when(
98
+ onClose,
99
+ () =>
100
+ html`<button
93
101
  type="button"
94
102
  class="iw-dialog-close"
95
103
  aria-label="Close"
96
104
  @click=${onClose}
97
105
  >
98
106
  ×
99
- </button>`
100
- : null
101
- }
102
- </div>`
103
- }
104
- ${
105
- description
106
- ? html`<div
107
- id=${descriptionId || nothing}
108
- class="iw-dialog-description"
109
- >
110
- ${description}
111
- </div>`
112
- : null
113
- }
114
- ${children ? html`<div class="iw-dialog-body">${children}</div>` : null}
115
- ${actions ? html`<div class="iw-dialog-actions">${actions}</div>` : null}
107
+ </button>`,
108
+ )}
109
+ </div>`,
110
+ )}
111
+ ${when(
112
+ description,
113
+ () =>
114
+ html`<div
115
+ id=${descriptionId || nothing}
116
+ class="iw-dialog-description"
117
+ >
118
+ ${description}
119
+ </div>`,
120
+ )}
121
+ ${when(children, () => html`<div class="iw-dialog-body">${children}</div>`)}
122
+ ${when(actions, () => html`<div class="iw-dialog-actions">${actions}</div>`)}
116
123
  </div>
117
124
  </dialog>`
118
125
  }
@@ -8,6 +8,11 @@ export default {
8
8
  argTypes: {
9
9
  variant: { control: "select", options: ["linear", "circular"] },
10
10
  value: { control: "number" },
11
+ ariaLabel: {
12
+ control: "text",
13
+ description:
14
+ 'Names the indicator for assistive tech, e.g. "Uploading photo".',
15
+ },
11
16
  size: { control: "number" },
12
17
  thickness: { control: "number" },
13
18
  color: {
@@ -6,6 +6,7 @@
6
6
  import { html } from "@inglorious/web"
7
7
  import { classMap } from "@inglorious/web/directives/class-map"
8
8
  import { ref } from "@inglorious/web/directives/ref"
9
+ import { when } from "@inglorious/web/directives/when"
9
10
 
10
11
  import { syncPopover } from "../../shared/top-layer.js"
11
12
 
@@ -49,19 +50,19 @@ export function render(props) {
49
50
  >
50
51
  <div class="iw-snackbar-content">
51
52
  <span class="iw-snackbar-message">${message}</span>
52
- ${action ? html`<span class="iw-snackbar-action">${action}</span>` : null}
53
- ${
54
- onClose
55
- ? html`<button
56
- type="button"
57
- class="iw-snackbar-close"
58
- aria-label="Dismiss"
59
- @click=${onClose}
60
- >
61
- ×
62
- </button>`
63
- : null
64
- }
53
+ ${when(action, () => html`<span class="iw-snackbar-action">${action}</span>`)}
54
+ ${when(
55
+ onClose,
56
+ () =>
57
+ html`<button
58
+ type="button"
59
+ class="iw-snackbar-close"
60
+ aria-label="Dismiss"
61
+ @click=${onClose}
62
+ >
63
+ ×
64
+ </button>`,
65
+ )}
65
66
  </div>
66
67
  </div>`
67
68
  }
@@ -30,7 +30,7 @@ export default {
30
30
  },
31
31
  gap: {
32
32
  control: "select",
33
- options: ["none", "sm", "md", "lg", "xl"],
33
+ options: ["none", "xs", "sm", "md", "lg", "xl", "2xl"],
34
34
  description: "Spacing between children.",
35
35
  },
36
36
  padding: {
@@ -85,6 +85,10 @@
85
85
  gap: 0;
86
86
  }
87
87
 
88
+ &.iw-flex-gap-xs {
89
+ gap: var(--iw-space-1);
90
+ }
91
+
88
92
  &.iw-flex-gap-sm {
89
93
  gap: var(--iw-space-2);
90
94
  }
@@ -101,6 +105,10 @@
101
105
  gap: var(--iw-space-8);
102
106
  }
103
107
 
108
+ &.iw-flex-gap-2xl {
109
+ gap: var(--iw-space-12);
110
+ }
111
+
104
112
  &.iw-flex-padding-none {
105
113
  padding: 0;
106
114
  }
@@ -7,6 +7,7 @@
7
7
  import { html } from "@inglorious/web"
8
8
  import { classMap } from "@inglorious/web/directives/class-map"
9
9
  import { ref } from "@inglorious/web/directives/ref"
10
+ import { when } from "@inglorious/web/directives/when"
10
11
 
11
12
  import { applyElementProps } from "../../shared/apply-element-props.js"
12
13
 
@@ -79,18 +80,16 @@ export const BottomNavigation = {
79
80
  props.onChange?.(currentValue)
80
81
  }}
81
82
  >
82
- ${
83
- action.icon
84
- ? html`<span class="iw-bottom-navigation-icon">${action.icon}</span>`
85
- : null
86
- }
87
- ${
88
- props.hasLabels
89
- ? html`<span class="iw-bottom-navigation-label"
90
- >${action.label}</span
91
- >`
92
- : null
93
- }
83
+ ${when(
84
+ action.icon,
85
+ () =>
86
+ html`<span class="iw-bottom-navigation-icon">${action.icon}</span>`,
87
+ )}
88
+ ${when(
89
+ props.hasLabels,
90
+ () =>
91
+ html`<span class="iw-bottom-navigation-label">${action.label}</span>`,
92
+ )}
94
93
  </button>`
95
94
  },
96
95
  }
@@ -7,6 +7,7 @@
7
7
  import { html } from "@inglorious/web"
8
8
  import { classMap } from "@inglorious/web/directives/class-map"
9
9
  import { ref } from "@inglorious/web/directives/ref"
10
+ import { when } from "@inglorious/web/directives/when"
10
11
 
11
12
  import { applyElementProps } from "../../shared/apply-element-props.js"
12
13
 
@@ -51,13 +52,13 @@ export const Breadcrumbs = {
51
52
  <ol class="iw-breadcrumbs-list">
52
53
  ${items.map(
53
54
  (item, index) =>
54
- html`${this.renderItem(item, index, props)}${
55
- index < items.length - 1
56
- ? html`<li class="iw-breadcrumbs-separator" aria-hidden="true">
57
- ${separator}
58
- </li>`
59
- : null
60
- }`,
55
+ html`${this.renderItem(item, index, props)}${when(
56
+ index < items.length - 1,
57
+ () =>
58
+ html`<li class="iw-breadcrumbs-separator" aria-hidden="true">
59
+ ${separator}
60
+ </li>`,
61
+ )}`,
61
62
  )}
62
63
  </ol>
63
64
  </nav>`
@@ -75,24 +76,26 @@ export const Breadcrumbs = {
75
76
  const isLast = index === props.items.length - 1
76
77
 
77
78
  return html`<li class="iw-breadcrumbs-item iw-typography">
78
- ${
79
- item.href && !isLast
80
- ? html`<a
81
- href=${item.href}
82
- class="iw-breadcrumbs-link"
83
- @click=${item.onClick ?? null}
84
- >
85
- ${item.label}
86
- </a>`
87
- : html`<span
88
- class=${classMap({
89
- "iw-breadcrumbs-current": isLast,
90
- })}
91
- aria-current=${isLast ? "page" : "false"}
92
- >
93
- ${item.label}
94
- </span>`
95
- }
79
+ ${when(
80
+ item.href && !isLast,
81
+ () =>
82
+ html`<a
83
+ href=${item.href}
84
+ class="iw-breadcrumbs-link"
85
+ @click=${item.onClick ?? null}
86
+ >
87
+ ${item.label}
88
+ </a>`,
89
+ () =>
90
+ html`<span
91
+ class=${classMap({
92
+ "iw-breadcrumbs-current": isLast,
93
+ })}
94
+ aria-current=${isLast ? "page" : "false"}
95
+ >
96
+ ${item.label}
97
+ </span>`,
98
+ )}
96
99
  </li>`
97
100
  },
98
101
  }
@@ -7,6 +7,7 @@
7
7
  import { html, nothing } from "@inglorious/web"
8
8
  import { classMap } from "@inglorious/web/directives/class-map"
9
9
  import { ref } from "@inglorious/web/directives/ref"
10
+ import { when } from "@inglorious/web/directives/when"
10
11
 
11
12
  import { Button } from "../../controls/button/index.js"
12
13
  import { anchorName } from "../../shared/anchor.js"
@@ -138,17 +139,15 @@ export const Menu = {
138
139
  props.onItemClick?.(item.value ?? index, item)
139
140
  }}
140
141
  >
141
- ${
142
- item.icon
143
- ? html`<span class="iw-menu-item-icon">${item.icon}</span>`
144
- : null
145
- }
142
+ ${when(
143
+ item.icon,
144
+ () => html`<span class="iw-menu-item-icon">${item.icon}</span>`,
145
+ )}
146
146
  <span class="iw-menu-item-label">${item.label}</span>
147
- ${
148
- item.trailing
149
- ? html`<span class="iw-menu-item-trailing">${item.trailing}</span>`
150
- : null
151
- }
147
+ ${when(
148
+ item.trailing,
149
+ () => html`<span class="iw-menu-item-trailing">${item.trailing}</span>`,
150
+ )}
152
151
  </button>`
153
152
  },
154
153
  }
@@ -7,6 +7,7 @@
7
7
 
8
8
  import { html } from "@inglorious/web"
9
9
  import { classMap } from "@inglorious/web/directives/class-map"
10
+ import { when } from "@inglorious/web/directives/when"
10
11
 
11
12
  import { Button } from "../../controls/button/index.js"
12
13
 
@@ -52,15 +53,13 @@ export const Pagination = {
52
53
  class=${classMap({ "iw-pagination": true, ...extraClasses })}
53
54
  aria-label="Pagination"
54
55
  >
55
- ${
56
- isFirstButtonVisible
57
- ? this.renderControl(props, {
58
- label: "«",
59
- target: 1,
60
- isDisabled: page <= PRETTY_PAGE || isDisabled,
61
- })
62
- : null
63
- }
56
+ ${when(isFirstButtonVisible, () =>
57
+ this.renderControl(props, {
58
+ label: "«",
59
+ target: 1,
60
+ isDisabled: page <= PRETTY_PAGE || isDisabled,
61
+ }),
62
+ )}
64
63
  ${this.renderControl(props, {
65
64
  label: "‹",
66
65
  target: Math.max(PRETTY_PAGE, page - PRETTY_PAGE),
@@ -72,15 +71,13 @@ export const Pagination = {
72
71
  target: Math.min(count, page + PRETTY_PAGE),
73
72
  isDisabled: page >= count || isDisabled,
74
73
  })}
75
- ${
76
- isLastButtonVisible
77
- ? this.renderControl(props, {
78
- label: "»",
79
- target: count,
80
- isDisabled: page >= count || isDisabled,
81
- })
82
- : null
83
- }
74
+ ${when(isLastButtonVisible, () =>
75
+ this.renderControl(props, {
76
+ label: "»",
77
+ target: count,
78
+ isDisabled: page >= count || isDisabled,
79
+ }),
80
+ )}
84
81
  </nav>`
85
82
  },
86
83
 
@@ -6,6 +6,7 @@
6
6
 
7
7
  import { html } from "@inglorious/web"
8
8
  import { classMap } from "@inglorious/web/directives/class-map"
9
+ import { when } from "@inglorious/web/directives/when"
9
10
 
10
11
  export const Stepper = {
11
12
  /**
@@ -67,11 +68,10 @@ export const Stepper = {
67
68
  <span class="iw-stepper-indicator">${completed ? "✓" : index + 1}</span>
68
69
  <span class="iw-stepper-content">
69
70
  <span class="iw-stepper-label">${step.label}</span>
70
- ${
71
- step.optional
72
- ? html`<span class="iw-stepper-optional">${step.optional}</span>`
73
- : null
74
- }
71
+ ${when(
72
+ step.optional,
73
+ () => html`<span class="iw-stepper-optional">${step.optional}</span>`,
74
+ )}
75
75
  </span>
76
76
  </li>`
77
77
  },
@@ -6,6 +6,7 @@
6
6
 
7
7
  import { html, nothing } from "@inglorious/web"
8
8
  import { classMap } from "@inglorious/web/directives/class-map"
9
+ import { when } from "@inglorious/web/directives/when"
9
10
 
10
11
  export const Tabs = {
11
12
  /**
@@ -88,7 +89,7 @@ export const Tabs = {
88
89
  props.onChange?.(tabValue)
89
90
  }}
90
91
  >
91
- ${item.icon ? html`<span class="iw-tab-icon">${item.icon}</span>` : null}
92
+ ${when(item.icon, () => html`<span class="iw-tab-icon">${item.icon}</span>`)}
92
93
  <span>${item.label}</span>
93
94
  </button>`
94
95
  },
@@ -12,6 +12,11 @@ export default {
12
12
  control: "object",
13
13
  description: "Accordion sections with title, content, and state flags.",
14
14
  },
15
+ name: {
16
+ control: "text",
17
+ description:
18
+ "Items sharing a name close each other: at most one is open at a time.",
19
+ },
15
20
  onItemToggle: { action: "onItemToggle" },
16
21
  },
17
22
  parameters: {
@@ -44,6 +49,10 @@ export const Default = {
44
49
  },
45
50
  }
46
51
 
52
+ export const Exclusive = {
53
+ args: { ...Default.args, name: "settings" },
54
+ }
55
+
47
56
  export const WithIcons = {
48
57
  args: {
49
58
  items: [
@@ -7,6 +7,7 @@
7
7
  import { html, nothing } from "@inglorious/web"
8
8
  import { classMap } from "@inglorious/web/directives/class-map"
9
9
  import { ref } from "@inglorious/web/directives/ref"
10
+ import { when } from "@inglorious/web/directives/when"
10
11
 
11
12
  import { applyElementProps } from "../../shared/apply-element-props.js"
12
13
 
@@ -85,7 +86,11 @@ export const Accordion = {
85
86
  name=${props.name || nothing}
86
87
  .open=${isExpanded}
87
88
  @toggle=${(event) => {
88
- const isOpen = event.newState === "open"
89
+ // newState is what the browser says; an environment whose toggle
90
+ // event lacks it, such as jsdom, still has the element's own state.
91
+ const isOpen = event.newState
92
+ ? event.newState === "open"
93
+ : event.currentTarget.open
89
94
  // Also fires when isExpanded opened it, which needs no report.
90
95
  if (isOpen === isExpanded) return
91
96
  onToggle?.(item, index, isOpen)
@@ -100,7 +105,7 @@ export const Accordion = {
100
105
  if (isDisabled) event.preventDefault()
101
106
  }}
102
107
  >
103
- ${icon ? html`<span class="iw-accordion-icon">${icon}</span>` : null}
108
+ ${when(icon, () => html`<span class="iw-accordion-icon">${icon}</span>`)}
104
109
  <span class="iw-accordion-title">${title}</span>
105
110
  <span class="iw-accordion-caret" aria-hidden="true">▾</span>
106
111
  </summary>
@@ -53,6 +53,12 @@ export default {
53
53
  options: ["none", "sm", "md", "lg", "xl"],
54
54
  description: "Footer padding preset.",
55
55
  },
56
+ variant: {
57
+ control: "select",
58
+ options: ["default", "sunk", "clear"],
59
+ description:
60
+ "The surface: raised, sunk into the page for content inside another surface, or clear.",
61
+ },
56
62
  isHoverable: {
57
63
  control: "boolean",
58
64
  description: "Enable hover visual feedback.",
@@ -97,6 +103,24 @@ export const WithFooter = {
97
103
  },
98
104
  }
99
105
 
106
+ export const Sunk = {
107
+ args: {
108
+ title: "Sunk card",
109
+ subtitle: "Content inside another surface",
110
+ children: "Body content",
111
+ variant: "sunk",
112
+ },
113
+ }
114
+
115
+ export const Clear = {
116
+ args: {
117
+ title: "Clear card",
118
+ subtitle: "Groups content with no surface of its own",
119
+ children: "Body content",
120
+ variant: "clear",
121
+ },
122
+ }
123
+
100
124
  export const Interactive = {
101
125
  args: {
102
126
  title: "Interactive Card",