@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,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 { applyElementProps } from "../../shared/apply-element-props.js"
11
12
  import { fallbackLabel } from "../../shared/fallback-label.js"
@@ -14,10 +15,17 @@ const DEFAULT_VALUE = 0
14
15
  const DEFAULT_MIN = 0
15
16
  const DEFAULT_MAX = 100
16
17
  const DEFAULT_STEP = 1
18
+ const PERCENT = 100
19
+ /** Past this many steps, marks would be a smear rather than a scale. */
20
+ const MAX_MARKS = 100
21
+ const RANGE_LENGTH = 2
17
22
 
18
23
  /**
19
24
  * Renders a range slider with optional label and current value display.
20
25
  * Supports min/max/step, color, disabled state, and full-width layout.
26
+ * With a `[low, high]` value it has two thumbs, one for each end of a range:
27
+ * two native range inputs on one track, so each keeps the keyboard and
28
+ * assistive tech support of the platform. `hasMarks` draws a tick per step.
21
29
  * @param {SliderProps} props
22
30
  * @returns {TemplateResult}
23
31
  */
@@ -35,49 +43,143 @@ export function render(props) {
35
43
  color = "primary",
36
44
  isValueVisible = true,
37
45
  isFullWidth = false,
46
+ hasMarks = false,
38
47
  onChange,
39
48
  ...rest
40
49
  } = props
41
50
 
42
51
  const inputId = id || name
52
+ const isRange = Array.isArray(value) && value.length === RANGE_LENGTH
53
+ const shown = isRange ? `${value[0]} – ${value[1]}` : value
43
54
 
44
55
  const classes = {
45
56
  "iw-slider": true,
57
+ "iw-slider-range": isRange,
46
58
  [`iw-slider-${color}`]: color !== "primary",
47
59
  "iw-slider-full-width": isFullWidth,
48
60
  }
49
61
 
50
62
  return html`
51
63
  <div class=${classMap(classes)}>
52
- ${
53
- (label || isValueVisible) &&
54
- html`<div class="iw-slider-header">
55
- ${
56
- label
57
- ? html`<label for=${inputId || nothing}>${label}</label>`
58
- : html`<span></span>`
59
- }
60
- ${
61
- isValueVisible
62
- ? html`<output class="iw-slider-value">${value}</output>`
63
- : null
64
- }
65
- </div>`
66
- }
67
- <input
68
- id=${inputId || nothing}
69
- class="iw-slider-input"
70
- aria-label=${fallbackLabel(inputId, label)}
71
- type="range"
72
- name=${name}
73
- min=${min}
74
- max=${max}
75
- step=${step}
76
- .value=${String(value)}
77
- ?disabled=${isDisabled}
78
- @input=${(event) => onChange?.(Number(event.target.value))}
79
- ${ref((element) => applyElementProps(element, rest))}
80
- />
64
+ ${when(
65
+ label || isValueVisible,
66
+ () =>
67
+ html`<div class="iw-slider-header">
68
+ ${when(
69
+ label,
70
+ () => html`<label for=${inputId || nothing}>${label}</label>`,
71
+ () => html`<span></span>`,
72
+ )}
73
+ ${when(
74
+ isValueVisible,
75
+ () => html`<output class="iw-slider-value">${shown}</output>`,
76
+ )}
77
+ </div>`,
78
+ )}
79
+ ${when(
80
+ isRange,
81
+ () =>
82
+ renderRange({
83
+ inputId,
84
+ name,
85
+ label,
86
+ value,
87
+ min,
88
+ max,
89
+ step,
90
+ isDisabled,
91
+ onChange,
92
+ rest,
93
+ }),
94
+ () =>
95
+ html`<input
96
+ id=${inputId || nothing}
97
+ class="iw-slider-input"
98
+ aria-label=${fallbackLabel(inputId, label)}
99
+ type="range"
100
+ name=${name}
101
+ min=${min}
102
+ max=${max}
103
+ step=${step}
104
+ .value=${String(value)}
105
+ ?disabled=${isDisabled}
106
+ @input=${(event) => onChange?.(Number(event.target.value))}
107
+ ${ref((element) => applyElementProps(element, rest))}
108
+ />`,
109
+ )}
110
+ ${when(hasMarks, () => renderMarks({ min, max, step }))}
81
111
  </div>
82
112
  `
83
113
  }
114
+
115
+ /**
116
+ * Two thumbs on one track. Each input stops at the other's value, so the
117
+ * range never turns inside out, and the track is filled between them.
118
+ */
119
+ function renderRange({
120
+ inputId,
121
+ name,
122
+ label,
123
+ value,
124
+ min,
125
+ max,
126
+ step,
127
+ isDisabled,
128
+ onChange,
129
+ rest,
130
+ }) {
131
+ const [low, high] = value
132
+ const span = max - min || DEFAULT_STEP
133
+ const fill = `--iw-slider-low: ${((low - min) / span) * PERCENT}%; --iw-slider-high: ${((high - min) / span) * PERCENT}%`
134
+ const endName = (end) => (name ? `${name}-${end}` : nothing)
135
+ // Each thumb is named for its end of the range, after the slider's label.
136
+ const endLabel = (end) =>
137
+ typeof label === "string" && label ? `${label} ${end}` : end
138
+
139
+ return html`<div class="iw-slider-track" style=${fill}>
140
+ <input
141
+ id=${inputId || nothing}
142
+ class="iw-slider-input"
143
+ aria-label=${endLabel("minimum")}
144
+ type="range"
145
+ name=${endName("min")}
146
+ min=${min}
147
+ max=${max}
148
+ step=${step}
149
+ .value=${String(low)}
150
+ ?disabled=${isDisabled}
151
+ @input=${(event) => {
152
+ const next = Math.min(Number(event.target.value), high)
153
+ event.target.value = String(next)
154
+ onChange?.([next, high])
155
+ }}
156
+ ${ref((element) => applyElementProps(element, rest))}
157
+ />
158
+ <input
159
+ class="iw-slider-input"
160
+ aria-label=${endLabel("maximum")}
161
+ type="range"
162
+ name=${endName("max")}
163
+ min=${min}
164
+ max=${max}
165
+ step=${step}
166
+ .value=${String(high)}
167
+ ?disabled=${isDisabled}
168
+ @input=${(event) => {
169
+ const next = Math.max(Number(event.target.value), low)
170
+ event.target.value = String(next)
171
+ onChange?.([low, next])
172
+ }}
173
+ />
174
+ </div>`
175
+ }
176
+
177
+ /** A tick per step, spread along the track. Decorative. */
178
+ function renderMarks({ min, max, step }) {
179
+ const count = Math.floor((max - min) / step) + 1
180
+ if (!(count > 1) || count > MAX_MARKS) return nothing
181
+
182
+ return html`<div class="iw-slider-marks" aria-hidden="true">
183
+ ${Array.from({ length: count }, () => html`<span></span>`)}
184
+ </div>`
185
+ }
@@ -43,6 +43,17 @@
43
43
  transform: translateX(calc(1rem * var(--iw-dir-sign, 1)));
44
44
  }
45
45
 
46
+ /* With a description, the label and it stack beside the track. */
47
+ .iw-switch-text {
48
+ display: flex;
49
+ flex-direction: column;
50
+ }
51
+
52
+ .iw-switch-description {
53
+ font-size: var(--iw-font-size-sm);
54
+ color: var(--iw-color-text-secondary);
55
+ }
56
+
46
57
  .iw-switch-label {
47
58
  color: var(--iw-color-text);
48
59
  font-size: var(--iw-font-size-base);
@@ -7,6 +7,10 @@ export default {
7
7
  render: createRender(Switch),
8
8
  argTypes: {
9
9
  label: { control: "text", description: "Label text." },
10
+ description: {
11
+ control: "text",
12
+ description: "A line of detail under the label.",
13
+ },
10
14
  name: { control: "text", description: "Native input name." },
11
15
  isChecked: { control: "boolean", description: "Current checked state." },
12
16
  isDisabled: { control: "boolean", description: "Disables interaction." },
@@ -46,3 +50,9 @@ On.args = {
46
50
  ...Default.args,
47
51
  isChecked: true,
48
52
  }
53
+
54
+ export const WithDescription = {}
55
+ WithDescription.args = {
56
+ ...Default.args,
57
+ description: "Alerts when a payment is due or a budget runs out.",
58
+ }
@@ -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 { applyElementProps } from "../../shared/apply-element-props.js"
11
12
 
@@ -22,6 +23,7 @@ export function render(props) {
22
23
  type, // eslint-disable-line no-unused-vars
23
24
  name = "",
24
25
  label = "",
26
+ description,
25
27
  isChecked = false,
26
28
  isDisabled = false,
27
29
  color = "primary",
@@ -57,7 +59,19 @@ export function render(props) {
57
59
  <span class="iw-switch-track" aria-hidden="true">
58
60
  <span class="iw-switch-thumb"></span>
59
61
  </span>
60
- ${label ? html`<span class="iw-switch-label">${label}</span>` : null}
62
+ ${when(
63
+ description,
64
+ () =>
65
+ html`<span class="iw-switch-text">
66
+ ${when(label, () => html`<span class="iw-switch-label">${label}</span>`)}
67
+ <span class="iw-switch-description">${description}</span>
68
+ </span>`,
69
+ () =>
70
+ when(
71
+ label,
72
+ () => html`<span class="iw-switch-label">${label}</span>`,
73
+ ),
74
+ )}
61
75
  </label>
62
76
  `
63
77
  }
@@ -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 { applyElementProps } from "../../shared/apply-element-props.js"
11
12
  import { fallbackLabel } from "../../shared/fallback-label.js"
@@ -59,20 +60,18 @@ export function render(props) {
59
60
 
60
61
  return html`
61
62
  <div class=${classMap(wrapperClasses)}>
62
- ${
63
- label
64
- ? html`
65
- <label for=${textareaId || nothing} class="iw-textarea-label">
66
- ${label}
67
- ${
68
- isRequired
69
- ? html`<span class="iw-textarea-required">*</span>`
70
- : null
71
- }
72
- </label>
73
- `
74
- : null
75
- }
63
+ ${when(
64
+ label,
65
+ () => html`
66
+ <label for=${textareaId || nothing} class="iw-textarea-label">
67
+ ${label}
68
+ ${when(
69
+ isRequired,
70
+ () => html`<span class="iw-textarea-required">*</span>`,
71
+ )}
72
+ </label>
73
+ `,
74
+ )}
76
75
 
77
76
  <textarea
78
77
  id=${textareaId || nothing}
@@ -93,19 +92,23 @@ export function render(props) {
93
92
  ${ref((element) => applyElementProps(element, rest))}
94
93
  ></textarea>
95
94
 
96
- ${
97
- error
98
- ? html`<span
99
- id=${messageId || nothing}
100
- class="iw-textarea-error-message"
101
- >${error}</span
102
- >`
103
- : hint
104
- ? html`<span id=${messageId || nothing} class="iw-textarea-hint"
95
+ ${when(
96
+ error,
97
+ () =>
98
+ html`<span
99
+ id=${messageId || nothing}
100
+ class="iw-textarea-error-message"
101
+ >${error}</span
102
+ >`,
103
+ () =>
104
+ when(
105
+ hint,
106
+ () =>
107
+ html`<span id=${messageId || nothing} class="iw-textarea-hint"
105
108
  >${hint}</span
106
- >`
107
- : null
108
- }
109
+ >`,
110
+ ),
111
+ )}
109
112
  </div>
110
113
  `
111
114
  }
@@ -6,6 +6,7 @@
6
6
  import { html } from "@inglorious/web"
7
7
  import { classMap } from "@inglorious/web/directives/class-map"
8
8
  import { styleMap } from "@inglorious/web/directives/style-map"
9
+ import { when } from "@inglorious/web/directives/when"
9
10
 
10
11
  /**
11
12
  * Renders an avatar with image, initials, or fallback content.
@@ -57,11 +58,11 @@ export function render(props) {
57
58
  style=${styleMap(styles)}
58
59
  @click=${onClick}
59
60
  >
60
- ${
61
- src
62
- ? html`<img class="iw-avatar-img" src=${src} alt=${alt} />`
63
- : (children ?? initials ?? "?")
64
- }
61
+ ${when(
62
+ src,
63
+ () => html`<img class="iw-avatar-img" src=${src} alt=${alt} />`,
64
+ () => children ?? initials ?? "?",
65
+ )}
65
66
  </span>`
66
67
  }
67
68
 
@@ -1,4 +1,7 @@
1
+ import sell from "@iconify-icons/material-symbols/sell"
2
+
1
3
  import { createRender } from "../../stories/notify-story.js"
4
+ import { Icon } from "../icon"
2
5
  import { Chip } from "."
3
6
 
4
7
  export default {
@@ -7,6 +10,10 @@ export default {
7
10
  render: createRender(Chip),
8
11
  argTypes: {
9
12
  children: { control: "text", description: "Chip label content." },
13
+ icon: {
14
+ control: false,
15
+ description: "Shown before the label, such as an `Icon.render(...)`.",
16
+ },
10
17
  isRemovable: {
11
18
  control: "boolean",
12
19
  description: "Shows a remove affordance and emits `#id:remove`.",
@@ -55,3 +62,13 @@ export const Default = {
55
62
  shape: "pill",
56
63
  },
57
64
  }
65
+
66
+ export const WithIcon = {
67
+ args: {
68
+ ...Default.args,
69
+ children: "Paid",
70
+ icon: Icon.render({ icon: sell, size: "sm" }),
71
+ isRemovable: false,
72
+ color: "success",
73
+ },
74
+ }
@@ -10,6 +10,11 @@
10
10
  line-height: 1;
11
11
  white-space: nowrap;
12
12
 
13
+ .iw-chip-icon {
14
+ display: inline-flex;
15
+ align-items: center;
16
+ }
17
+
13
18
  .iw-chip-label {
14
19
  display: inline-flex;
15
20
  align-items: center;
@@ -5,9 +5,10 @@
5
5
 
6
6
  import { html } from "@inglorious/web"
7
7
  import { classMap } from "@inglorious/web/directives/class-map"
8
+ import { when } from "@inglorious/web/directives/when"
8
9
 
9
10
  /**
10
- * Renders a compact chip label with optional remove action.
11
+ * Renders a compact chip label with an optional leading icon and remove action.
11
12
  * Use chips for tags, filters, or selected values.
12
13
  * @param {ChipProps} props
13
14
  * @returns {TemplateResult}
@@ -15,6 +16,7 @@ import { classMap } from "@inglorious/web/directives/class-map"
15
16
  export function render(props) {
16
17
  const {
17
18
  children,
19
+ icon,
18
20
  isRemovable = false,
19
21
  color = "default",
20
22
  size = "md",
@@ -30,13 +32,14 @@ export function render(props) {
30
32
  }
31
33
 
32
34
  return html`<span class=${classMap(classes)}>
35
+ ${when(icon, () => html`<span class="iw-chip-icon" aria-hidden="true">${icon}</span>`)}
33
36
  <span class="iw-chip-label">${children}</span>
34
- ${
35
- isRemovable
36
- ? html`<button type="button" class="iw-chip-remove" @click=${onClick}>
37
- ×
38
- </button>`
39
- : null
40
- }
37
+ ${when(
38
+ isRemovable,
39
+ () =>
40
+ html`<button type="button" class="iw-chip-remove" @click=${onClick}>
41
+ ×
42
+ </button>`,
43
+ )}
41
44
  </span>`
42
45
  }
@@ -111,7 +111,7 @@ export const DataGrid = {
111
111
  ${column.title} ${getSortIcon(getSortDirection(props, column.id))}
112
112
  </div>
113
113
 
114
- ${column.isFilterable && Filters.render(props, column)}
114
+ ${when(column.isFilterable, () => Filters.render(props, column))}
115
115
 
116
116
  <div
117
117
  class="iw-data-grid-column-resizer"
@@ -17,6 +17,11 @@ export default {
17
17
  control: "boolean",
18
18
  description: "Applies start inset for sectional separators.",
19
19
  },
20
+ isDecorative: {
21
+ control: "boolean",
22
+ description:
23
+ "Only visual: hidden from assistive tech, which otherwise announces a separator.",
24
+ },
20
25
  },
21
26
  parameters: {
22
27
  docs: {
@@ -31,6 +36,10 @@ export const Default = {
31
36
  args: {},
32
37
  }
33
38
 
39
+ export const Decorative = {
40
+ args: { isDecorative: true },
41
+ }
42
+
34
43
  export const Vertical = {
35
44
  args: {
36
45
  ...Default.args,
@@ -3,23 +3,31 @@
3
3
  * @typedef {import('@inglorious/web').TemplateResult} TemplateResult
4
4
  */
5
5
 
6
- import { html } from "@inglorious/web"
6
+ import { html, nothing } from "@inglorious/web"
7
7
  import { classMap } from "@inglorious/web/directives/class-map"
8
8
 
9
9
  /**
10
- * Renders a horizontal or vertical divider line.
10
+ * Renders a horizontal or vertical divider line: a native `<hr>`, which
11
+ * assistive tech announces as a separator, unless it is only decorative.
11
12
  * Supports inset styling for aligned content blocks.
12
13
  * @param {DividerProps} props
13
14
  * @returns {TemplateResult}
14
15
  */
15
16
  export function render(props) {
16
- const { orientation = "horizontal", isInset = false } = props
17
+ const {
18
+ orientation = "horizontal",
19
+ isInset = false,
20
+ isDecorative = false,
21
+ } = props
22
+ const isVertical = orientation === "vertical"
17
23
 
18
24
  return html`<hr
19
25
  class=${classMap({
20
26
  "iw-divider": true,
21
- "iw-divider-vertical": orientation === "vertical",
27
+ "iw-divider-vertical": isVertical,
22
28
  "iw-divider-inset": isInset,
23
29
  })}
30
+ role=${isDecorative ? "none" : nothing}
31
+ aria-orientation=${isVertical && !isDecorative ? "vertical" : nothing}
24
32
  />`
25
33
  }
@@ -96,11 +96,10 @@ export const List = {
96
96
  meta,
97
97
  index,
98
98
  path: [...path, index],
99
- })}${
100
- shouldDivide
101
- ? html`<li class="iw-list-divider" role="separator"></li>`
102
- : null
103
- }`
99
+ })}${when(
100
+ shouldDivide,
101
+ () => html`<li class="iw-list-divider" role="separator"></li>`,
102
+ )}`
104
103
  },
105
104
  )
106
105
 
@@ -1,4 +1,5 @@
1
1
  import { html } from "@inglorious/web"
2
+ import { when } from "@inglorious/web/directives/when"
2
3
 
3
4
  import { Typography } from "../../data-display/typography/index.js"
4
5
  import { Container } from "../../layout/container/index.js"
@@ -77,24 +78,23 @@ export const PrimitiveSection = {
77
78
  description ??
78
79
  "This section is wired for per-primitive docs and examples.",
79
80
  })}
80
- ${
81
- useCases.length
82
- ? html`
83
- <div style="margin-top: 16px; text-align: left;">
84
- ${Typography.render({
85
- variant: "body2",
86
- color: "secondary",
87
- children: "Common use cases",
88
- })}
89
- <ul style="margin: 8px 0 0; padding-left: 20px;">
90
- ${useCases.map(
91
- (useCase) => html`<li>${useCase}</li>`,
92
- )}
93
- </ul>
94
- </div>
95
- `
96
- : ""
97
- }
81
+ ${when(
82
+ useCases.length,
83
+ () => html`
84
+ <div style="margin-top: 16px; text-align: left;">
85
+ ${Typography.render({
86
+ variant: "body2",
87
+ color: "secondary",
88
+ children: "Common use cases",
89
+ })}
90
+ <ul style="margin: 8px 0 0; padding-left: 20px;">
91
+ ${useCases.map(
92
+ (useCase) => html`<li>${useCase}</li>`,
93
+ )}
94
+ </ul>
95
+ </div>
96
+ `,
97
+ )}
98
98
  </div>
99
99
  `,
100
100
  }),
@@ -10,7 +10,7 @@ export default {
10
10
  description: { control: "text" },
11
11
  severity: {
12
12
  control: "select",
13
- options: ["info", "success", "warning", "error"],
13
+ options: ["info", "success", "warning", "error", "neutral"],
14
14
  },
15
15
  variant: { control: "select", options: ["filled", "outlined"] },
16
16
  icon: { control: "text" },
@@ -50,6 +50,14 @@ export const Error = {
50
50
  },
51
51
  }
52
52
 
53
+ export const Neutral = {
54
+ args: {
55
+ title: "Note",
56
+ description: "A plain remark, with no tone of its own.",
57
+ severity: "neutral",
58
+ },
59
+ }
60
+
53
61
  export const Outlined = {
54
62
  args: {
55
63
  title: "Outlined Alert",
@@ -81,6 +81,11 @@
81
81
  --iw-alert-color: var(--iw-color-error, #d14343);
82
82
  }
83
83
 
84
+ /* A plain note: no tone of its own, just the text colour. */
85
+ .iw-alert-neutral {
86
+ --iw-alert-color: var(--iw-color-text-secondary, var(--iw-color-text));
87
+ }
88
+
84
89
  .iw-alert-outlined {
85
90
  background: transparent;
86
91
  border-color: color-mix(in srgb, var(--iw-alert-color) 55%, transparent);