@inglorious/ui 8.6.0 → 9.1.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 (76) 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 +4 -4
  5. package/src/controls/checkbox/checkbox.stories.js +12 -0
  6. package/src/controls/checkbox/style.css +11 -0
  7. package/src/controls/checkbox/template.js +9 -1
  8. package/src/controls/combobox/style.css +14 -7
  9. package/src/controls/combobox/template.js +12 -2
  10. package/src/controls/radio-group/radio-group.stories.js +21 -1
  11. package/src/controls/radio-group/style.css +11 -0
  12. package/src/controls/radio-group/template.js +11 -1
  13. package/src/controls/rating/rating.stories.js +8 -0
  14. package/src/controls/rating/style.css +26 -4
  15. package/src/controls/rating/template.js +29 -12
  16. package/src/controls/slider/slider.stories.js +28 -1
  17. package/src/controls/slider/style.css +102 -0
  18. package/src/controls/slider/template.js +114 -15
  19. package/src/controls/switch/style.css +11 -0
  20. package/src/controls/switch/switch.stories.js +10 -0
  21. package/src/controls/switch/template.js +14 -2
  22. package/src/data-display/chip/chip.stories.js +17 -0
  23. package/src/data-display/chip/style.css +5 -0
  24. package/src/data-display/chip/template.js +3 -1
  25. package/src/data-display/divider/divider.stories.js +9 -0
  26. package/src/data-display/divider/template.js +12 -4
  27. package/src/data-display/tooltip/style.css +22 -14
  28. package/src/examples/dashboard/primitive-content.js +6 -6
  29. package/src/examples/dashboard/style.css +11 -5
  30. package/src/feedback/alert/alert.stories.js +9 -1
  31. package/src/feedback/alert/style.css +5 -0
  32. package/src/feedback/alert/template.js +2 -1
  33. package/src/feedback/dialog/dialog.stories.js +31 -0
  34. package/src/feedback/dialog/style.css +27 -13
  35. package/src/feedback/dialog/template.js +68 -18
  36. package/src/feedback/progress/progress.stories.js +5 -0
  37. package/src/feedback/progress/style.css +47 -23
  38. package/src/feedback/progress/template.js +18 -11
  39. package/src/feedback/snackbar/style.css +13 -0
  40. package/src/feedback/snackbar/template.js +7 -0
  41. package/src/layout/flex/flex.stories.js +1 -1
  42. package/src/layout/flex/style.css +8 -0
  43. package/src/navigation/drawer/drawer.stories.js +3 -7
  44. package/src/navigation/drawer/style.css +59 -18
  45. package/src/navigation/drawer/template.js +137 -80
  46. package/src/navigation/menu/menu.stories.js +15 -0
  47. package/src/navigation/menu/style.css +19 -0
  48. package/src/navigation/menu/template.js +59 -6
  49. package/src/navigation/speed-dial/style.css +32 -5
  50. package/src/navigation/speed-dial/template.js +41 -12
  51. package/src/navigation/tabs/template.js +52 -4
  52. package/src/shared/anchor.js +11 -0
  53. package/src/shared/top-layer.js +83 -0
  54. package/src/surfaces/accordion/accordion.stories.js +9 -0
  55. package/src/surfaces/accordion/style.css +14 -7
  56. package/src/surfaces/accordion/template.js +31 -20
  57. package/src/surfaces/card/card.stories.js +24 -0
  58. package/src/surfaces/card/style.css +15 -0
  59. package/src/surfaces/card/template.js +2 -0
  60. package/types/controls/button.d.ts +1 -1
  61. package/types/controls/checkbox.d.ts +2 -0
  62. package/types/controls/radio-group.d.ts +2 -0
  63. package/types/controls/rating.d.ts +4 -0
  64. package/types/controls/slider.d.ts +6 -2
  65. package/types/controls/switch.d.ts +2 -0
  66. package/types/data-display/chip.d.ts +2 -0
  67. package/types/data-display/divider.d.ts +2 -0
  68. package/types/feedback/alert.d.ts +1 -1
  69. package/types/feedback/dialog.d.ts +5 -1
  70. package/types/feedback/progress.d.ts +2 -0
  71. package/types/layout/flex.d.ts +1 -1
  72. package/types/navigation/drawer.d.ts +8 -3
  73. package/types/navigation/menu.d.ts +17 -0
  74. package/types/navigation/speed-dial.d.ts +5 -0
  75. package/types/surfaces/accordion.d.ts +2 -0
  76. package/types/surfaces/card.d.ts +5 -0
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The CSS anchor name that ties a popover to the element it opens from.
3
+ * Set as `anchor-name` on the trigger and `position-anchor` on the popover,
4
+ * so the browser positions the popover against it, flipping when it runs out
5
+ * of room. Built from the popover's id, which is unique on the page.
6
+ * @param {string} id
7
+ * @returns {string}
8
+ */
9
+ export function anchorName(id) {
10
+ return `--iw-anchor-${String(id).replace(/[^\w-]/g, "-")}`
11
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Native `<dialog>` and `popover` elements keep their own open state, while the
3
+ * components that use them are driven by an `isOpen` prop from the store. These
4
+ * helpers run from a `ref` on every render and keep the two in step: the prop
5
+ * opens and closes the element, and a change the browser makes on its own
6
+ * (Esc, a light dismiss, a `method="dialog"` form, a `commandfor` invoker) is
7
+ * reported through `onClose` or `onOpen` so the store can catch up.
8
+ *
9
+ * The browser fires `close` and `toggle` after the fact, so the handler tells
10
+ * the two apart by the latest props: a change the props asked for finds
11
+ * `isOpen` already matching, and only one they did not ask for is reported.
12
+ */
13
+
14
+ /**
15
+ * Latest props per element. The listener is added once, and reads the props
16
+ * from here so it never calls an `onClose` captured by an earlier render.
17
+ * @type {WeakMap<Element, { isOpen: boolean, isModal?: boolean, onOpen?: () => void, onClose?: () => void }>}
18
+ */
19
+ const latestProps = new WeakMap()
20
+
21
+ /**
22
+ * Opens or closes a `<dialog>` to match `isOpen`.
23
+ * @param {HTMLDialogElement | undefined} element
24
+ * @param {{ isOpen: boolean, isModal?: boolean, onClose?: () => void }} props
25
+ */
26
+ export function syncDialog(element, props) {
27
+ if (!element) return
28
+ track(element, props, "close", () => false)
29
+ whenConnected(element, () => {
30
+ const { isOpen, isModal = true } = latestProps.get(element)
31
+ if (isOpen && !element.open) {
32
+ if (isModal) element.showModal()
33
+ else element.show()
34
+ }
35
+ if (!isOpen && element.open) element.close()
36
+ })
37
+ }
38
+
39
+ /**
40
+ * Shows or hides a `popover` element to match `isOpen`. Showing an open
41
+ * popover and hiding a closed one are no-ops, so no state check is needed.
42
+ * @param {HTMLElement | undefined} element
43
+ * @param {{ isOpen: boolean, onOpen?: () => void, onClose?: () => void }} props
44
+ */
45
+ export function syncPopover(element, props) {
46
+ if (!element) return
47
+ track(element, props, "toggle", (event) => event.newState === "open")
48
+ whenConnected(element, () => {
49
+ if (!element.popover) return
50
+ if (latestProps.get(element).isOpen) element.showPopover()
51
+ else element.hidePopover()
52
+ })
53
+ }
54
+
55
+ /**
56
+ * @param {Element} element
57
+ * @param {object} props
58
+ * @param {string} eventName
59
+ * @param {(event: Event) => boolean} isOpenAfter whether the event left it open
60
+ */
61
+ function track(element, props, eventName, isOpenAfter) {
62
+ const isTracked = latestProps.has(element)
63
+ latestProps.set(element, props)
64
+ if (isTracked) return
65
+
66
+ element.addEventListener(eventName, (event) => {
67
+ const latest = latestProps.get(element)
68
+ const isOpen = isOpenAfter(event)
69
+ if (isOpen === latest.isOpen) return
70
+ if (isOpen) latest.onOpen?.()
71
+ else latest.onClose?.()
72
+ })
73
+ }
74
+
75
+ /**
76
+ * A ref runs while a new template is still in its fragment, and neither
77
+ * `showModal()` nor `showPopover()` works on an element outside the document.
78
+ * The fragment is inserted synchronously after, so a microtask is late enough.
79
+ */
80
+ function whenConnected(element, callback) {
81
+ if (element.isConnected) callback()
82
+ else queueMicrotask(() => element.isConnected && callback())
83
+ }
@@ -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: [
@@ -11,20 +11,27 @@
11
11
  border-top: 1px solid var(--iw-color-border);
12
12
  }
13
13
 
14
+ /* A <summary>: flex drops the browser's disclosure marker, and the caret
15
+ stands in for it. */
14
16
  .iw-accordion-trigger {
15
- width: 100%;
16
- border: 0 none;
17
- background: transparent;
18
- color: inherit;
19
17
  padding: var(--iw-space-3) var(--iw-space-4);
20
18
  display: flex;
21
19
  align-items: center;
22
20
  gap: var(--iw-space-2);
23
- text-align: left;
21
+ text-align: start;
24
22
  cursor: pointer;
23
+
24
+ &::-webkit-details-marker {
25
+ display: none;
26
+ }
27
+ }
28
+
29
+ .iw-accordion-item-disabled .iw-accordion-trigger {
30
+ cursor: default;
25
31
  }
26
32
 
27
- .iw-accordion-trigger:hover:enabled {
33
+ .iw-accordion-item:not(.iw-accordion-item-disabled)
34
+ .iw-accordion-trigger:hover {
28
35
  background: color-mix(
29
36
  in srgb,
30
37
  var(--iw-color-surface-hover) 75%,
@@ -50,7 +57,7 @@
50
57
  color: var(--iw-color-text-muted, var(--iw-color-text));
51
58
  }
52
59
 
53
- .iw-accordion-item-expanded .iw-accordion-caret {
60
+ .iw-accordion-item[open] .iw-accordion-caret {
54
61
  transform: rotate(180deg);
55
62
  }
56
63
 
@@ -4,7 +4,7 @@
4
4
  * @typedef {import('@inglorious/web').TemplateResult} TemplateResult
5
5
  */
6
6
 
7
- import { html } from "@inglorious/web"
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
10
 
@@ -30,8 +30,10 @@ export const Accordion = {
30
30
  renderAccordion(props) {
31
31
  const {
32
32
  type, // eslint-disable-line no-unused-vars
33
+ name, // eslint-disable-line no-unused-vars
33
34
  items = [],
34
35
  className = "",
36
+ onItemToggle, // eslint-disable-line no-unused-vars
35
37
  ...rest
36
38
  } = props
37
39
 
@@ -54,8 +56,11 @@ export const Accordion = {
54
56
  },
55
57
 
56
58
  /**
57
- * Renders a single accordion item with trigger and panel content.
58
- * Handles expand/collapse behavior and item-level callbacks.
59
+ * Renders a single accordion item as a native `<details>`, whose `<summary>`
60
+ * is the trigger. The browser opens and closes it, keeps the panel's
61
+ * content findable (find-in-page opens the item that holds a match), and,
62
+ * when the accordion has a `name`, closes the other items as one opens.
63
+ * Each change is reported so the store can follow.
59
64
  * @param {AccordionItem} item
60
65
  * @param {number} index
61
66
  * @param {AccordionProps} props
@@ -71,33 +76,39 @@ export const Accordion = {
71
76
  icon,
72
77
  } = item
73
78
 
74
- return html`<section
79
+ return html`<details
75
80
  class=${classMap({
76
81
  "iw-accordion-item": true,
77
82
  "iw-accordion-item-expanded": isExpanded,
78
83
  "iw-accordion-item-disabled": isDisabled,
79
84
  })}
85
+ name=${props.name || nothing}
86
+ .open=${isExpanded}
87
+ @toggle=${(event) => {
88
+ // newState is what the browser says; an environment whose toggle
89
+ // event lacks it, such as jsdom, still has the element's own state.
90
+ const isOpen = event.newState
91
+ ? event.newState === "open"
92
+ : event.currentTarget.open
93
+ // Also fires when isExpanded opened it, which needs no report.
94
+ if (isOpen === isExpanded) return
95
+ onToggle?.(item, index, isOpen)
96
+ props.onItemToggle?.(item, index, isOpen)
97
+ }}
80
98
  >
81
- <button
99
+ <summary
82
100
  class="iw-accordion-trigger"
83
- type="button"
84
- aria-expanded=${isExpanded}
85
- ?disabled=${isDisabled}
86
- @click=${() => {
87
- if (isDisabled) return
88
- onToggle?.(item, index, !isExpanded)
89
- props.onItemToggle?.(item, index, !isExpanded)
101
+ aria-disabled=${isDisabled ? "true" : nothing}
102
+ @click=${(event) => {
103
+ // A summary cannot be disabled, so its toggle is refused instead.
104
+ if (isDisabled) event.preventDefault()
90
105
  }}
91
106
  >
92
107
  ${icon ? html`<span class="iw-accordion-icon">${icon}</span>` : null}
93
108
  <span class="iw-accordion-title">${title}</span>
94
- <span class="iw-accordion-caret">▾</span>
95
- </button>
96
- ${
97
- isExpanded
98
- ? html`<div class="iw-accordion-panel">${content}</div>`
99
- : null
100
- }
101
- </section>`
109
+ <span class="iw-accordion-caret" aria-hidden="true">▾</span>
110
+ </summary>
111
+ <div class="iw-accordion-panel">${content}</div>
112
+ </details>`
102
113
  },
103
114
  }
@@ -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",
@@ -11,6 +11,21 @@
11
11
  transform var(--iw-transition-normal);
12
12
  }
13
13
 
14
+ /* Set into the page rather than raised from it, for content grouped inside
15
+ another surface. */
16
+ .iw-card-sunk {
17
+ background-color: var(--iw-color-bg-secondary, var(--iw-card-bg));
18
+ border-color: transparent;
19
+ box-shadow: none;
20
+ }
21
+
22
+ /* Only the grouping: no surface at all. */
23
+ .iw-card-clear {
24
+ background-color: transparent;
25
+ border-color: transparent;
26
+ box-shadow: none;
27
+ }
28
+
14
29
  .iw-card-hoverable:hover {
15
30
  box-shadow: var(--iw-card-shadow-hover);
16
31
  }
@@ -30,6 +30,7 @@ export const Card = {
30
30
  const {
31
31
  type, // eslint-disable-line no-unused-vars
32
32
  element = "article",
33
+ variant = "default",
33
34
  isHoverable = false,
34
35
  isClickable = false,
35
36
  isFullWidth = false,
@@ -47,6 +48,7 @@ export const Card = {
47
48
 
48
49
  const classes = {
49
50
  "iw-card": true,
51
+ [`iw-card-${variant}`]: variant !== "default",
50
52
  "iw-card-hoverable": isHoverable || isClickable,
51
53
  "iw-card-clickable": isClickable,
52
54
  "iw-card-full-width": isFullWidth,
@@ -13,7 +13,7 @@ export interface ButtonProps {
13
13
  | "warning"
14
14
  | "error"
15
15
  | "info"
16
- size?: "sm" | "md" | "lg"
16
+ size?: "xs" | "sm" | "md" | "lg"
17
17
  shape?: "rectangle" | "pill" | "round" | "square"
18
18
  isDisabled?: boolean
19
19
  isPressed?: boolean
@@ -4,6 +4,8 @@ export interface CheckboxProps {
4
4
  id?: string
5
5
  name?: string
6
6
  label?: string
7
+ /** A line of detail under the label. */
8
+ description?: string
7
9
  isChecked?: boolean
8
10
  isDisabled?: boolean
9
11
  isRequired?: boolean
@@ -2,6 +2,8 @@ import type { TemplateResult } from "@inglorious/web"
2
2
 
3
3
  export interface RadioOption {
4
4
  label: string
5
+ /** A line of detail under the label. */
6
+ description?: string
5
7
  value: string
6
8
  isDisabled?: boolean
7
9
  [key: string]: unknown
@@ -2,6 +2,8 @@ import type { TemplateResult } from "@inglorious/web"
2
2
 
3
3
  export interface RatingProps {
4
4
  id?: string
5
+ /** Groups the radios and names the value in a form. Falls back to `id`. */
6
+ name?: string
5
7
  value?: number
6
8
  max?: number
7
9
  isDisabled?: boolean
@@ -10,6 +12,8 @@ export interface RatingProps {
10
12
  emptySymbol?: string
11
13
  onChange?: (value: number) => void
12
14
  size?: "sm" | "md" | "lg"
15
+ /** Names the group for assistive tech. Defaults to "Rating". */
16
+ ariaLabel?: string
13
17
  }
14
18
 
15
19
  export type RatingType = {
@@ -4,7 +4,8 @@ export interface SliderProps {
4
4
  id?: string
5
5
  name?: string
6
6
  label?: string
7
- value?: number
7
+ /** One number, or `[low, high]` for a range with a thumb at each end. */
8
+ value?: number | [number, number]
8
9
  min?: number
9
10
  max?: number
10
11
  step?: number
@@ -12,7 +13,10 @@ export interface SliderProps {
12
13
  isDisabled?: boolean
13
14
  isValueVisible?: boolean
14
15
  isFullWidth?: boolean
15
- onChange?: (value: number) => void
16
+ /** Draws a tick at every step, for a slider that moves in clear steps. */
17
+ hasMarks?: boolean
18
+ /** The new value, in the shape `value` has: a number, or `[low, high]`. */
19
+ onChange?: (value: number | [number, number]) => void
16
20
  [key: string]: unknown
17
21
  }
18
22
 
@@ -4,6 +4,8 @@ export interface SwitchProps {
4
4
  id?: string
5
5
  name?: string
6
6
  label?: string
7
+ /** A line of detail under the label. */
8
+ description?: string
7
9
  isChecked?: boolean
8
10
  isDisabled?: boolean
9
11
  color?: "primary" | "secondary" | "success" | "warning" | "error" | "info"
@@ -3,6 +3,8 @@ import type { TemplateResult } from "@inglorious/web"
3
3
  export interface ChipProps {
4
4
  id?: string
5
5
  children?: TemplateResult | string | number
6
+ /** Shown before the label, such as an `Icon.render(...)`. Decorative. */
7
+ icon?: TemplateResult
6
8
  isRemovable?: boolean
7
9
  color?:
8
10
  | "default"
@@ -4,6 +4,8 @@ export interface DividerProps {
4
4
  id?: string
5
5
  orientation?: "horizontal" | "vertical"
6
6
  isInset?: boolean
7
+ /** Only visual: hidden from assistive tech, which otherwise announces a separator. */
8
+ isDecorative?: boolean
7
9
  [key: string]: unknown
8
10
  }
9
11
 
@@ -6,7 +6,7 @@ export interface AlertProps {
6
6
  title?: TemplateResult | string | number
7
7
  description?: TemplateResult | string | number
8
8
  children?: TemplateResult | string | number
9
- severity?: "info" | "success" | "warning" | "error"
9
+ severity?: "info" | "success" | "warning" | "error" | "neutral"
10
10
  variant?: "filled" | "outlined"
11
11
  icon?: TemplateResult | string | number
12
12
  action?: TemplateResult | string | number
@@ -4,6 +4,10 @@ export interface DialogProps {
4
4
  id?: string
5
5
  type?: string
6
6
  isOpen?: boolean
7
+ /** Modal by default; `false` shows the dialog in place, with the page still usable. */
8
+ isModal?: boolean
9
+ /** Its width: `"sm"`, `"md"` (default) or `"lg"`. */
10
+ size?: "sm" | "md" | "lg"
7
11
  title?: TemplateResult | string | number
8
12
  description?: TemplateResult | string | number
9
13
  children?: TemplateResult | string | number
@@ -15,7 +19,7 @@ export interface DialogProps {
15
19
  }
16
20
 
17
21
  export type DialogType = {
18
- render(props: DialogProps): TemplateResult | null
22
+ render(props: DialogProps): TemplateResult
19
23
  }
20
24
 
21
25
  export declare const Dialog: DialogType
@@ -15,6 +15,8 @@ export interface ProgressProps {
15
15
  | "info"
16
16
  size?: number
17
17
  thickness?: number
18
+ /** Names the indicator for assistive tech, e.g. "Uploading photo". */
19
+ ariaLabel?: string
18
20
  className?: string
19
21
  [key: string]: unknown
20
22
  }
@@ -16,7 +16,7 @@ export interface FlexProps {
16
16
  wrap?: "nowrap" | "wrap" | "wrap-reverse"
17
17
  justify?: "start" | "center" | "end" | "between" | "around" | "evenly"
18
18
  align?: "stretch" | "start" | "center" | "end" | "baseline"
19
- gap?: "none" | "sm" | "md" | "lg" | "xl"
19
+ gap?: "none" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl"
20
20
  padding?: "none" | "sm" | "md" | "lg" | "xl"
21
21
  overflow?: "visible" | "hidden" | "clip" | "scroll" | "auto"
22
22
  isInline?: boolean
@@ -5,19 +5,24 @@ export interface DrawerProps {
5
5
  type?: string
6
6
  isOpen?: boolean
7
7
  anchor?: "left" | "right" | "top" | "bottom"
8
- variant?: "temporary" | "persistent" | "permanent" | "responsive"
9
- breakpoint?: "sm" | "md" | "lg" | "xl"
8
+ /**
9
+ * When the drawer overlays the page as a popover: `responsive` (default)
10
+ * below 64rem and docked above, `temporary` at every width, `permanent` never.
11
+ */
12
+ variant?: "responsive" | "temporary" | "permanent"
13
+ isHidden?: boolean
10
14
  isCollapsed?: boolean
11
15
  title?: TemplateResult | string | number
12
16
  children?: TemplateResult | string | number
13
17
  hasBackdrop?: boolean
14
18
  className?: string
15
19
  onClose?: () => void
20
+ onCollapseToggle?: () => void
16
21
  [key: string]: unknown
17
22
  }
18
23
 
19
24
  export type DrawerType = {
20
- render(props: DrawerProps): TemplateResult | null
25
+ render(props: DrawerProps): TemplateResult
21
26
  }
22
27
 
23
28
  export declare const Drawer: DrawerType
@@ -1,5 +1,7 @@
1
1
  import type { TemplateResult } from "@inglorious/web"
2
2
 
3
+ import type { ButtonProps } from "../controls/button"
4
+
3
5
  export interface MenuItem {
4
6
  id?: string | number
5
7
  value?: string | number
@@ -14,9 +16,23 @@ export interface MenuItem {
14
16
  }
15
17
 
16
18
  export interface MenuProps {
19
+ /** Required with `trigger`: it links the button to the menu. */
17
20
  id?: string
18
21
  type?: string
19
22
  isOpen?: boolean
23
+ /**
24
+ * Content of a button that opens the menu. With it, the menu is a popover
25
+ * anchored to the button; without it, the menu renders in place while open.
26
+ */
27
+ trigger?: TemplateResult | string | number
28
+ /** Extra props for the trigger button, such as `variant` or `ariaLabel`. */
29
+ triggerProps?: ButtonProps
30
+ /**
31
+ * Called with the new state when the browser opens or closes an anchored
32
+ * menu. Without it, the browser alone keeps the open state and `isOpen` is
33
+ * not applied.
34
+ */
35
+ onToggle?: (isOpen: boolean) => void
20
36
  items?: MenuItem[]
21
37
  isDense?: boolean
22
38
  className?: string
@@ -26,6 +42,7 @@ export interface MenuProps {
26
42
 
27
43
  export type MenuType = {
28
44
  render(props: MenuProps): TemplateResult | null
45
+ renderTrigger(props: MenuProps): TemplateResult
29
46
  renderMenu(props: MenuProps): TemplateResult | null
30
47
  renderItem(item: MenuItem, index: number, props: MenuProps): TemplateResult
31
48
  }
@@ -18,6 +18,11 @@ export interface SpeedDialProps {
18
18
  ariaLabel?: string
19
19
  direction?: "up" | "down" | "left" | "right"
20
20
  actions?: SpeedDialAction[]
21
+ /**
22
+ * Called with the new state when the browser opens or closes the actions.
23
+ * Without it, the browser alone keeps the open state and `isOpen` is not
24
+ * applied.
25
+ */
21
26
  onToggle?: (isOpen: boolean) => void
22
27
  onAction?: (value: string | number, action: SpeedDialAction) => void
23
28
  [key: string]: unknown
@@ -15,6 +15,8 @@ export interface AccordionProps {
15
15
  id?: string
16
16
  type?: string
17
17
  className?: string
18
+ /** Items sharing a name close each other: at most one is open at a time. */
19
+ name?: string
18
20
  items?: AccordionItem[]
19
21
  onItemToggle?: (item: AccordionItem, index: number, expanded: boolean) => void
20
22
  [key: string]: unknown
@@ -12,6 +12,11 @@ export interface CardProps {
12
12
  | "footer"
13
13
  | "nav"
14
14
  | "aside"
15
+ /**
16
+ * The surface: raised (`"default"`), `"sunk"` into the page for content
17
+ * inside another surface, or `"clear"` with none.
18
+ */
19
+ variant?: "default" | "sunk" | "clear"
15
20
  title?: TemplateResult | string | number
16
21
  subtitle?: TemplateResult | string | number
17
22
  header?: TemplateResult | string | number