@inglorious/ui 9.1.0 → 9.2.0

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 (37) hide show
  1. package/package.json +2 -2
  2. package/src/controls/button/template.js +13 -12
  3. package/src/controls/button-group/template.js +6 -6
  4. package/src/controls/checkbox/checkbox.stories.js +16 -0
  5. package/src/controls/checkbox/style.css +24 -0
  6. package/src/controls/checkbox/template.js +23 -8
  7. package/src/controls/file-upload/template.js +27 -26
  8. package/src/controls/icon-button/template.js +6 -6
  9. package/src/controls/image-cropper/template.js +3 -1
  10. package/src/controls/radio-group/radio-group.stories.js +19 -0
  11. package/src/controls/radio-group/style.css +30 -0
  12. package/src/controls/radio-group/template.js +27 -15
  13. package/src/controls/select/template.js +5 -6
  14. package/src/controls/slider/template.js +47 -44
  15. package/src/controls/switch/template.js +14 -10
  16. package/src/controls/textarea/template.js +29 -26
  17. package/src/data-display/avatar/template.js +6 -5
  18. package/src/data-display/chip/chip.stories.js +13 -0
  19. package/src/data-display/chip/style.css +22 -0
  20. package/src/data-display/chip/template.js +33 -9
  21. package/src/data-display/data-grid/template.js +1 -1
  22. package/src/data-display/list/template.js +4 -5
  23. package/src/examples/dashboard/primitive-section.js +18 -18
  24. package/src/feedback/alert/template.js +26 -26
  25. package/src/feedback/dialog/template.js +31 -29
  26. package/src/feedback/snackbar/template.js +14 -13
  27. package/src/navigation/bottom-navigation/template.js +11 -12
  28. package/src/navigation/breadcrumbs/template.js +28 -25
  29. package/src/navigation/menu/template.js +9 -10
  30. package/src/navigation/pagination/template.js +15 -18
  31. package/src/navigation/stepper/template.js +5 -5
  32. package/src/navigation/tabs/template.js +2 -1
  33. package/src/surfaces/accordion/template.js +2 -1
  34. package/src/surfaces/card/template.js +3 -2
  35. package/types/controls/checkbox.d.ts +4 -0
  36. package/types/controls/radio-group.d.ts +4 -0
  37. package/types/data-display/chip.d.ts +7 -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"
@@ -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
 
@@ -10,6 +10,10 @@ export default {
10
10
  render: createRender(Chip),
11
11
  argTypes: {
12
12
  children: { control: "text", description: "Chip label content." },
13
+ isSelected: {
14
+ control: "boolean",
15
+ description: "Makes the chip a toggle button, pressed while true.",
16
+ },
13
17
  icon: {
14
18
  control: false,
15
19
  description: "Shown before the label, such as an `Icon.render(...)`.",
@@ -72,3 +76,12 @@ export const WithIcon = {
72
76
  color: "success",
73
77
  },
74
78
  }
79
+
80
+ export const Selectable = {
81
+ args: {
82
+ ...Default.args,
83
+ children: "Vegan",
84
+ isRemovable: false,
85
+ isSelected: true,
86
+ },
87
+ }
@@ -64,6 +64,28 @@
64
64
  border-radius: var(--iw-radius-none);
65
65
  }
66
66
 
67
+ &.iw-chip-selectable {
68
+ border: 1px solid var(--iw-color-border);
69
+ font: inherit;
70
+ font-size: var(--iw-font-size-sm);
71
+ cursor: pointer;
72
+
73
+ &:focus-visible {
74
+ outline: 2px solid var(--iw-color-primary);
75
+ outline-offset: 2px;
76
+ }
77
+
78
+ &:disabled {
79
+ opacity: 0.6;
80
+ cursor: not-allowed;
81
+ }
82
+ }
83
+
84
+ &.iw-chip-selected {
85
+ border-color: var(--iw-color-primary);
86
+ background: color-mix(in srgb, var(--iw-color-primary) 20%, transparent);
87
+ }
88
+
67
89
  &.iw-chip-primary {
68
90
  background: color-mix(in srgb, var(--iw-color-primary) 20%, transparent);
69
91
  }
@@ -5,6 +5,7 @@
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
11
  * Renders a compact chip label with an optional leading icon and remove action.
@@ -20,6 +21,8 @@ export function render(props) {
20
21
  color = "default",
21
22
  size = "md",
22
23
  shape = "pill",
24
+ isSelected,
25
+ isDisabled = false,
23
26
  onClick,
24
27
  } = props
25
28
 
@@ -30,15 +33,36 @@ export function render(props) {
30
33
  [`iw-chip-shape-${shape}`]: shape !== "pill",
31
34
  }
32
35
 
36
+ const content = html`${when(
37
+ icon,
38
+ () => html`<span class="iw-chip-icon" aria-hidden="true">${icon}</span>`,
39
+ )} <span class="iw-chip-label">${children}</span>`
40
+
41
+ // A chip that can be picked is a toggle button, pressed while selected.
42
+ if (isSelected !== undefined) {
43
+ return html`<button
44
+ type="button"
45
+ class=${classMap({
46
+ ...classes,
47
+ "iw-chip-selectable": true,
48
+ "iw-chip-selected": isSelected,
49
+ })}
50
+ aria-pressed=${String(isSelected)}
51
+ ?disabled=${isDisabled}
52
+ @click=${onClick}
53
+ >
54
+ ${content}
55
+ </button>`
56
+ }
57
+
33
58
  return html`<span class=${classMap(classes)}>
34
- ${icon ? html`<span class="iw-chip-icon" aria-hidden="true">${icon}</span>` : null}
35
- <span class="iw-chip-label">${children}</span>
36
- ${
37
- isRemovable
38
- ? html`<button type="button" class="iw-chip-remove" @click=${onClick}>
39
- ×
40
- </button>`
41
- : null
42
- }
59
+ ${content}
60
+ ${when(
61
+ isRemovable,
62
+ () =>
63
+ html`<button type="button" class="iw-chip-remove" @click=${onClick}>
64
+ ×
65
+ </button>`,
66
+ )}
43
67
  </span>`
44
68
  }
@@ -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"
@@ -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
  }),
@@ -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 { applyElementProps } from "../../shared/apply-element-props.js"
11
12
 
@@ -53,33 +54,32 @@ export function render(props) {
53
54
  role="alert"
54
55
  ${ref((el) => applyElementProps(el, rest))}
55
56
  >
56
- ${icon ? html`<div class="iw-alert-icon">${icon}</div>` : null}
57
+ ${when(icon, () => html`<div class="iw-alert-icon">${icon}</div>`)}
57
58
  <div class="iw-alert-content">
58
- ${title ? html`<div class="iw-alert-title">${title}</div>` : null}
59
- ${
60
- content
61
- ? html`<div class="iw-alert-description">${content}</div>`
62
- : null
63
- }
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
+ )}
64
64
  </div>
65
- ${
66
- hasTrailing
67
- ? html`<div class="iw-alert-trailing">
68
- ${action ? html`<div class="iw-alert-action">${action}</div>` : null}
69
- ${
70
- onClose
71
- ? html`<button
72
- type="button"
73
- class="iw-alert-close"
74
- aria-label="Dismiss"
75
- @click=${onClose}
76
- >
77
- ×
78
- </button>`
79
- : null
80
- }
81
- </div>`
82
- : null
83
- }
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
+ )}
84
84
  </div>`
85
85
  }
@@ -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
 
@@ -82,42 +83,43 @@ export function render(props) {
82
83
  ${ref((element) => syncDialog(element, { isOpen, isModal, onClose }))}
83
84
  >
84
85
  <div class="iw-dialog-surface">
85
- ${
86
- (title || onClose) &&
87
- html`<div class="iw-dialog-header">
88
- ${
89
- title
90
- ? 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">
91
94
  ${title}
92
- </div>`
93
- : null
94
- }
95
- ${
96
- onClose
97
- ? html`<button
95
+ </div>`,
96
+ )}
97
+ ${when(
98
+ onClose,
99
+ () =>
100
+ html`<button
98
101
  type="button"
99
102
  class="iw-dialog-close"
100
103
  aria-label="Close"
101
104
  @click=${onClose}
102
105
  >
103
106
  ×
104
- </button>`
105
- : null
106
- }
107
- </div>`
108
- }
109
- ${
110
- description
111
- ? html`<div
112
- id=${descriptionId || nothing}
113
- class="iw-dialog-description"
114
- >
115
- ${description}
116
- </div>`
117
- : null
118
- }
119
- ${children ? html`<div class="iw-dialog-body">${children}</div>` : null}
120
- ${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>`)}
121
123
  </div>
122
124
  </dialog>`
123
125
  }
@@ -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
  }
@@ -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
  }