@redvars/peacock 3.4.0 → 3.5.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 (258) hide show
  1. package/dist/BaseButton-DuASuVth.js +219 -0
  2. package/dist/BaseButton-DuASuVth.js.map +1 -0
  3. package/dist/BaseHyperlinkMixin-BNuwbiEf.js +65 -0
  4. package/dist/BaseHyperlinkMixin-BNuwbiEf.js.map +1 -0
  5. package/dist/assets/components.css +1 -1
  6. package/dist/assets/components.css.map +1 -1
  7. package/dist/assets/styles.css +1 -1
  8. package/dist/assets/styles.css.map +1 -1
  9. package/dist/banner.js +187 -0
  10. package/dist/banner.js.map +1 -0
  11. package/dist/bottom-sheet.js +2 -2
  12. package/dist/{button-COYCtuA8.js → button-DouvOfEU.js} +92 -283
  13. package/dist/button-DouvOfEU.js.map +1 -0
  14. package/dist/{button-group-DsXquZQn.js → button-group-CEdMwvJJ.js} +72 -48
  15. package/dist/button-group-CEdMwvJJ.js.map +1 -0
  16. package/dist/button-group.js +8 -5
  17. package/dist/button-group.js.map +1 -1
  18. package/dist/button.js +7 -4
  19. package/dist/button.js.map +1 -1
  20. package/dist/card.js +29 -74
  21. package/dist/card.js.map +1 -1
  22. package/dist/chart-bar.js +2 -2
  23. package/dist/chart-bar.js.map +1 -1
  24. package/dist/chart-doughnut.js +2 -2
  25. package/dist/chart-doughnut.js.map +1 -1
  26. package/dist/chart-pie.js +2 -2
  27. package/dist/chart-pie.js.map +1 -1
  28. package/dist/chart-stacked-bar.js +2 -2
  29. package/dist/chart-stacked-bar.js.map +1 -1
  30. package/dist/{class-map-3TAnCMAX.js → class-map-YU7g0o3B.js} +2 -2
  31. package/dist/{class-map-3TAnCMAX.js.map → class-map-YU7g0o3B.js.map} +1 -1
  32. package/dist/clock.js.map +1 -1
  33. package/dist/code-editor.js +4 -4
  34. package/dist/code-editor.js.map +1 -1
  35. package/dist/code-highlighter.js +5 -4
  36. package/dist/code-highlighter.js.map +1 -1
  37. package/dist/custom-elements-jsdocs.json +6627 -3477
  38. package/dist/custom-elements.json +10954 -7810
  39. package/dist/directive-ZPhl09Yt.js +9 -0
  40. package/dist/directive-ZPhl09Yt.js.map +1 -0
  41. package/dist/dispatch-event-utils-CuEqjlPT.js +127 -0
  42. package/dist/dispatch-event-utils-CuEqjlPT.js.map +1 -0
  43. package/dist/fab.js +423 -0
  44. package/dist/fab.js.map +1 -0
  45. package/dist/index.js +17 -9
  46. package/dist/index.js.map +1 -1
  47. package/dist/{observe-theme-change-DKAIv5BB.js → is-dark-mode-DicqGkCJ.js} +6 -2
  48. package/dist/is-dark-mode-DicqGkCJ.js.map +1 -0
  49. package/dist/{select-C3XAzenC.js → navigation-rail-Lxetd5-Z.js} +2426 -898
  50. package/dist/navigation-rail-Lxetd5-Z.js.map +1 -0
  51. package/dist/notification.js +418 -0
  52. package/dist/notification.js.map +1 -0
  53. package/dist/number-counter.js +2 -2
  54. package/dist/number-counter.js.map +1 -1
  55. package/dist/observe-slot-change-BGJfgg2E.js +31 -0
  56. package/dist/observe-slot-change-BGJfgg2E.js.map +1 -0
  57. package/dist/peacock-loader.js +48 -13
  58. package/dist/peacock-loader.js.map +1 -1
  59. package/dist/search.js +456 -0
  60. package/dist/search.js.map +1 -0
  61. package/dist/side-sheet.js +2 -2
  62. package/dist/src/__mixins/BaseButtonMixin.d.ts +20 -0
  63. package/dist/src/__mixins/BaseHyperlinkMixin.d.ts +18 -0
  64. package/dist/src/__mixins/MixinConstructor.d.ts +1 -0
  65. package/dist/src/__utils/cache-fetch.d.ts +1 -0
  66. package/dist/src/__utils/is-dark-mode.d.ts +1 -0
  67. package/dist/src/__utils/is-in-viewport.d.ts +1 -0
  68. package/dist/src/__utils/observe-slot-change.d.ts +1 -0
  69. package/dist/src/__utils/sanitize-svg.d.ts +1 -0
  70. package/dist/src/__utils/throttle.d.ts +4 -0
  71. package/dist/src/accordion/accordion-item.d.ts +33 -9
  72. package/dist/src/accordion/accordion.d.ts +21 -5
  73. package/dist/src/banner/banner.d.ts +43 -0
  74. package/dist/src/banner/index.d.ts +1 -0
  75. package/dist/src/button/BaseButton.d.ts +7 -57
  76. package/dist/src/button/button/button.d.ts +32 -3
  77. package/dist/src/button/button-group/button-group.d.ts +4 -4
  78. package/dist/src/button/icon-button/icon-button.d.ts +33 -8
  79. package/dist/src/card/card.d.ts +4 -15
  80. package/dist/src/empty-state/empty-state.d.ts +1 -1
  81. package/dist/src/fab/fab.d.ts +80 -0
  82. package/dist/src/fab/index.d.ts +1 -0
  83. package/dist/src/focus-ring/focus-ring.d.ts +11 -5
  84. package/dist/src/index.d.ts +8 -1
  85. package/dist/src/link/link.d.ts +3 -10
  86. package/dist/src/menu/menu/menu.d.ts +3 -2
  87. package/dist/src/menu/sub-menu/sub-menu.d.ts +1 -0
  88. package/dist/src/navigation-rail/index.d.ts +2 -0
  89. package/dist/src/navigation-rail/navigation-rail-item.d.ts +55 -0
  90. package/dist/src/navigation-rail/navigation-rail.d.ts +71 -0
  91. package/dist/src/notification/index.d.ts +1 -0
  92. package/dist/src/notification/notification.d.ts +69 -0
  93. package/dist/src/pagination/pagination.d.ts +8 -1
  94. package/dist/src/search/index.d.ts +1 -0
  95. package/dist/src/search/search.d.ts +76 -0
  96. package/dist/src/select/select.d.ts +3 -5
  97. package/dist/src/sidebar-menu/index.d.ts +3 -0
  98. package/dist/src/sidebar-menu/sidebar-menu-item.d.ts +58 -0
  99. package/dist/src/sidebar-menu/sidebar-menu.d.ts +38 -0
  100. package/dist/src/sidebar-menu/sidebar-sub-menu.d.ts +35 -0
  101. package/dist/src/slider/slider.d.ts +4 -0
  102. package/dist/src/snackbar/snackbar.d.ts +14 -1
  103. package/dist/src/toolbar/index.d.ts +1 -0
  104. package/dist/src/toolbar/toolbar.d.ts +86 -0
  105. package/dist/src/tooltip/tooltip.d.ts +3 -0
  106. package/dist/src/url-field/index.d.ts +1 -0
  107. package/dist/src/url-field/url-field.d.ts +48 -0
  108. package/dist/{style-map-CRFEoCEg.js → style-map-DVmWOuYy.js} +2 -2
  109. package/dist/{style-map-CRFEoCEg.js.map → style-map-DVmWOuYy.js.map} +1 -1
  110. package/dist/test/banner.test.d.ts +1 -0
  111. package/dist/test/search.test.d.ts +1 -0
  112. package/dist/test/sidebar-menu.test.d.ts +1 -0
  113. package/dist/test/toolbar.test.d.ts +1 -0
  114. package/dist/toolbar.js +306 -0
  115. package/dist/toolbar.js.map +1 -0
  116. package/dist/tsconfig.tsbuildinfo +1 -1
  117. package/dist/{unsafe-html-D3GHRaGQ.js → unsafe-html-BsGUjx94.js} +2 -2
  118. package/dist/{unsafe-html-D3GHRaGQ.js.map → unsafe-html-BsGUjx94.js.map} +1 -1
  119. package/package.json +1 -1
  120. package/readme.md +73 -65
  121. package/scss/mixin.scss +16 -0
  122. package/scss/styles.scss +4 -0
  123. package/src/__mixins/BaseButtonMixin.ts +83 -0
  124. package/src/__mixins/BaseHyperlinkMixin.ts +68 -0
  125. package/src/__mixins/MixinConstructor.ts +1 -0
  126. package/src/__mixins/README.md +19 -0
  127. package/src/__utils/cache-fetch.ts +65 -0
  128. package/src/__utils/is-dark-mode.ts +3 -0
  129. package/src/__utils/is-in-viewport.ts +6 -0
  130. package/src/__utils/observe-slot-change.ts +38 -0
  131. package/src/__utils/sanitize-svg.ts +27 -0
  132. package/src/__utils/throttle.ts +27 -0
  133. package/src/accordion/accordion-item.scss +136 -65
  134. package/src/accordion/accordion-item.ts +117 -44
  135. package/src/accordion/accordion.scss +24 -5
  136. package/src/accordion/accordion.ts +29 -23
  137. package/src/accordion/demo/index.html +74 -35
  138. package/src/banner/banner.scss +83 -0
  139. package/src/banner/banner.ts +101 -0
  140. package/src/banner/index.ts +1 -0
  141. package/src/button/BaseButton.ts +13 -115
  142. package/src/button/button/button-colors.scss +14 -14
  143. package/src/button/button/button-sizes.scss +4 -2
  144. package/src/button/button/button.ts +80 -26
  145. package/src/button/button-group/button-group.ts +5 -5
  146. package/src/button/icon-button/icon-button.ts +79 -44
  147. package/src/card/card.ts +50 -100
  148. package/src/chart-bar/chart-bar.ts +10 -15
  149. package/src/chart-bar/chart-stacked-bar.ts +15 -19
  150. package/src/chart-doughnut/chart-doughnut.ts +24 -28
  151. package/src/chart-pie/chart-pie.ts +20 -24
  152. package/src/checkbox/checkbox.scss +17 -34
  153. package/src/checkbox/checkbox.ts +4 -2
  154. package/src/clock/clock.ts +1 -1
  155. package/src/code-editor/code-editor.ts +4 -4
  156. package/src/code-highlighter/code-highlighter.scss +1 -0
  157. package/src/code-highlighter/code-highlighter.ts +3 -3
  158. package/src/date-picker/date-picker.ts +6 -3
  159. package/src/divider/divider.ts +3 -1
  160. package/src/elevation/elevation.scss +5 -5
  161. package/src/empty-state/empty-state.scss +7 -9
  162. package/src/empty-state/empty-state.ts +1 -1
  163. package/src/fab/fab-colors.scss +49 -0
  164. package/src/fab/fab-sizes.scss +47 -0
  165. package/src/fab/fab.scss +137 -0
  166. package/src/fab/fab.ts +214 -0
  167. package/src/fab/index.ts +1 -0
  168. package/src/field/field.ts +3 -1
  169. package/src/focus-ring/focus-ring.ts +47 -40
  170. package/src/icon/datasource.ts +1 -1
  171. package/src/icon/icon.ts +3 -1
  172. package/src/image/image.ts +3 -2
  173. package/src/index.ts +8 -1
  174. package/src/input/input.ts +8 -3
  175. package/src/link/link.ts +2 -15
  176. package/src/menu/menu/menu.scss +7 -0
  177. package/src/menu/menu/menu.ts +7 -4
  178. package/src/menu/menu-item/menu-item.ts +3 -1
  179. package/src/menu/sub-menu/sub-menu.ts +1 -0
  180. package/src/navigation-rail/index.ts +2 -0
  181. package/src/navigation-rail/navigation-rail-item.scss +216 -0
  182. package/src/navigation-rail/navigation-rail-item.ts +223 -0
  183. package/src/navigation-rail/navigation-rail.scss +72 -0
  184. package/src/navigation-rail/navigation-rail.ts +149 -0
  185. package/src/notification/index.ts +1 -0
  186. package/src/notification/notification.scss +201 -0
  187. package/src/notification/notification.ts +207 -0
  188. package/src/number-counter/number-counter.ts +3 -1
  189. package/src/number-field/number-field.ts +10 -6
  190. package/src/pagination/pagination.scss +33 -24
  191. package/src/pagination/pagination.ts +115 -60
  192. package/src/peacock-loader.ts +42 -5
  193. package/src/radio/radio.ts +3 -1
  194. package/src/search/index.ts +1 -0
  195. package/src/search/search-colors.scss +14 -0
  196. package/src/search/search.scss +204 -0
  197. package/src/search/search.ts +244 -0
  198. package/src/select/option.ts +1 -1
  199. package/src/select/select.scss +5 -0
  200. package/src/select/select.ts +71 -37
  201. package/src/sidebar-menu/demo/index.html +68 -0
  202. package/src/sidebar-menu/index.ts +3 -0
  203. package/src/sidebar-menu/sidebar-menu-item.scss +102 -0
  204. package/src/sidebar-menu/sidebar-menu-item.ts +151 -0
  205. package/src/{tree-view/tree-view.scss → sidebar-menu/sidebar-menu.scss} +1 -1
  206. package/src/sidebar-menu/sidebar-menu.ts +182 -0
  207. package/src/sidebar-menu/sidebar-sub-menu.scss +130 -0
  208. package/src/sidebar-menu/sidebar-sub-menu.ts +160 -0
  209. package/src/skeleton/skeleton.scss +18 -24
  210. package/src/slider/slider.scss +19 -0
  211. package/src/slider/slider.ts +30 -19
  212. package/src/snackbar/snackbar.scss +62 -31
  213. package/src/snackbar/snackbar.ts +91 -11
  214. package/src/switch/switch.ts +3 -1
  215. package/src/table/table.ts +3 -1
  216. package/src/tabs/tab.ts +10 -6
  217. package/src/text/text.css-component.scss +7 -1
  218. package/src/textarea/textarea.ts +4 -2
  219. package/src/time-picker/time-picker.ts +5 -3
  220. package/src/toolbar/index.ts +1 -0
  221. package/src/toolbar/toolbar-colors.scss +16 -0
  222. package/src/toolbar/toolbar.scss +165 -0
  223. package/src/toolbar/toolbar.ts +137 -0
  224. package/src/tooltip/tooltip.ts +24 -0
  225. package/src/url-field/index.ts +1 -0
  226. package/src/url-field/url-field.scss +50 -0
  227. package/src/url-field/url-field.ts +239 -0
  228. package/dist/button-COYCtuA8.js.map +0 -1
  229. package/dist/button-group-DsXquZQn.js.map +0 -1
  230. package/dist/directive-Cuw6h7YA.js +0 -9
  231. package/dist/directive-Cuw6h7YA.js.map +0 -1
  232. package/dist/dispatch-event-utils-B4odODQf.js +0 -277
  233. package/dist/dispatch-event-utils-B4odODQf.js.map +0 -1
  234. package/dist/observe-theme-change-DKAIv5BB.js.map +0 -1
  235. package/dist/select-C3XAzenC.js.map +0 -1
  236. package/dist/src/styleMixins.css.d.ts +0 -9
  237. package/dist/src/tree-view/index.d.ts +0 -2
  238. package/dist/src/tree-view/tree-node.d.ts +0 -69
  239. package/dist/src/tree-view/tree-view.d.ts +0 -40
  240. package/dist/src/tree-view/wc-tree-view.d.ts +0 -6
  241. package/dist/src/utils.d.ts +0 -9
  242. package/dist/test/tree-view.test.d.ts +0 -1
  243. package/src/styleMixins.css.ts +0 -55
  244. package/src/tree-view/demo/index.html +0 -57
  245. package/src/tree-view/index.ts +0 -2
  246. package/src/tree-view/tree-node.scss +0 -101
  247. package/src/tree-view/tree-node.ts +0 -268
  248. package/src/tree-view/tree-view.ts +0 -182
  249. package/src/tree-view/wc-tree-view.ts +0 -9
  250. package/src/utils.ts +0 -193
  251. /package/dist/src/{spread.d.ts → __directive/spread.d.ts} +0 -0
  252. /package/dist/src/{utils → __utils}/copy-to-clipboard.d.ts +0 -0
  253. /package/dist/src/{utils → __utils}/dispatch-event-utils.d.ts +0 -0
  254. /package/dist/src/{utils → __utils}/observe-theme-change.d.ts +0 -0
  255. /package/src/{spread.ts → __directive/spread.ts} +0 -0
  256. /package/src/{utils → __utils}/copy-to-clipboard.ts +0 -0
  257. /package/src/{utils → __utils}/dispatch-event-utils.ts +0 -0
  258. /package/src/{utils → __utils}/observe-theme-change.ts +0 -0
@@ -1,111 +1,182 @@
1
1
  @use '../../scss/mixin';
2
2
 
3
-
4
3
  @include mixin.base-styles;
5
4
 
5
+ // ─── Host ────────────────────────────────────────────────────────────────────
6
+
6
7
  :host {
7
8
  display: block;
8
-
9
- --accordion-item-title-align: start;
10
9
  }
11
10
 
12
- .accordion-item {
11
+ // ─── Expansion Panel ─────────────────────────────────────────────────────────
13
12
 
14
- .accordion-heading {
15
- cursor: pointer;
16
- width: 100%;
17
- border-radius: 0;
18
- border: 0;
19
- padding: 0 var(--spacing-200);
20
- background: var(--accordion-item-heading-background, transparent);
13
+ .expansion-panel {
14
+ border: var(--_accordion-item-border, 1px solid var(--color-outline-variant));
15
+ border-radius: var(--shape-corner-medium); // 12dp — M3 medium shape
16
+ background-color: var(--_accordion-item-background, var(--color-surface-container-low));
17
+ overflow: hidden; // clip children to border-radius
18
+ transition:
19
+ background-color var(--duration-medium1) var(--easing-standard),
20
+ border-color var(--duration-medium1) var(--easing-standard);
21
+
22
+ // ─── Header Button ─────────────────────────────────────────────────────────
21
23
 
24
+ .header-button {
25
+ position: relative;
22
26
  display: flex;
23
- flex-direction: row-reverse;
24
27
  align-items: center;
28
+ width: 100%;
29
+ min-height: 3rem; // 48dp — grows with description
30
+ padding: 0 var(--spacing-300); // 0 24dp
31
+ gap: var(--spacing-200); // 16dp between label and icon
32
+ border: none;
33
+ border-radius: 0;
34
+ background: transparent;
35
+ cursor: pointer;
36
+ text-align: start;
25
37
  color: var(--color-on-surface);
26
- justify-content: flex-start;
27
- gap: 0.5rem;
28
- @include mixin.get-typography(title-medium);
38
+ overflow: hidden;
39
+
40
+ // M3 state layer
41
+ &::before {
42
+ content: '';
43
+ position: absolute;
44
+ inset: 0;
45
+ background-color: var(--color-on-surface);
46
+ opacity: 0;
47
+ pointer-events: none;
48
+ transition: opacity var(--duration-short4) var(--easing-standard);
49
+ }
29
50
 
30
- .accordion-title {
31
- width: 100%;
32
- text-align: var(--accordion-item-title-align);
51
+ &:not(:disabled):hover::before {
52
+ opacity: 0.08;
33
53
  }
34
54
 
35
55
  &:focus-visible {
56
+ outline: none;
36
57
  @include mixin.focus-ring();
37
58
  }
38
59
 
39
- .accordion-icon {
40
- --icon-size: 1.5rem;
41
- --icon-color: currentColor;
42
- transition: transform var(--duration-short2) var(--easing-standard);
60
+ &:focus-visible::before {
61
+ opacity: 0.12;
43
62
  }
63
+ }
64
+
65
+ // ─── Header Label (title + description column) ──────────────────────────────
44
66
 
67
+ .header-label {
68
+ flex: 1;
69
+ display: flex;
70
+ flex-direction: column;
71
+ justify-content: center;
72
+ gap: 2px;
73
+ padding: var(--spacing-150) 0; // 12dp vertical padding
74
+ min-width: 0;
45
75
  }
46
76
 
47
- .item-section {
48
- height: 0;
49
- opacity: 0;
50
- pointer-events: none;
51
- text-align: start;
52
- transition: all var(--duration-short2) var(--easing-standard);
77
+ .panel-title {
78
+ @include mixin.get-typography(body-large);
79
+ color: var(--color-on-surface);
80
+ white-space: nowrap;
81
+ overflow: hidden;
82
+ text-overflow: ellipsis;
53
83
  }
54
84
 
55
- &:not(.disabled) .accordion-heading:hover {
56
- --accordion-item-heading-background: var(--color-inverse-primary);
85
+ .panel-description {
86
+ @include mixin.get-typography(body-small);
87
+ color: var(--color-on-surface-variant);
88
+ white-space: nowrap;
89
+ overflow: hidden;
90
+ text-overflow: ellipsis;
91
+
92
+ &[hidden] {
93
+ display: none;
94
+ }
57
95
  }
58
96
 
59
- &.disabled .accordion-heading {
60
- cursor: not-allowed;
61
- opacity: 0.38;
97
+ // ─── Header Actions slot ────────────────────────────────────────────────────
98
+
99
+ ::slotted([slot='header-actions']) {
100
+ display: flex;
101
+ align-items: center;
102
+ flex-shrink: 0;
103
+ gap: var(--spacing-100);
62
104
  }
63
105
 
64
- &.open {
65
- .item-section {
66
- height: 100%;
67
- pointer-events: auto;
68
- opacity: 1;
69
- padding: var(--spacing-100) var(--spacing-800) var(--spacing-300) var(--spacing-200);
70
- }
106
+ // ─── Toggle Icon ────────────────────────────────────────────────────────────
71
107
 
72
- .accordion-icon {
73
- transform: rotate(180deg);
74
- }
108
+ .toggle-icon {
109
+ --icon-size: 1.5rem;
110
+ --icon-color: var(--color-on-surface-variant);
111
+ flex-shrink: 0;
112
+ transition: transform var(--duration-medium1) var(--easing-standard);
75
113
  }
76
114
 
77
- }
115
+ // ─── Expandable Content ─────────────────────────────────────────────────────
78
116
 
79
- /*
80
- * Sizes
81
- */
82
- .accordion-item {
83
- .accordion-heading {
84
- height: 2.5rem;
117
+ .panel-content {
118
+ display: grid;
119
+ grid-template-rows: 0fr;
120
+ transition: grid-template-rows var(--duration-medium1) var(--easing-standard);
121
+ }
122
+
123
+ .content-inner {
124
+ overflow: hidden;
125
+ min-height: 0;
126
+ @include mixin.get-typography(body-medium);
127
+ color: var(--color-on-surface-variant);
85
128
  }
86
- }
87
129
 
88
- :host-context([size="sm"]) {
89
- .accordion-item {
90
- .accordion-heading {
91
- height: 2rem;
130
+ // ─── Disabled State ─────────────────────────────────────────────────────────
131
+
132
+ &.disabled {
133
+ background-color: color-mix(in srgb, var(--color-surface-container-low) 38%, transparent);
134
+ border-color: color-mix(in srgb, var(--color-outline-variant) 38%, transparent);
135
+
136
+ .header-button {
137
+ cursor: not-allowed;
138
+ pointer-events: none;
139
+ opacity: 0.38;
92
140
  }
93
141
  }
94
- }
95
142
 
96
- :host-context([size="lg"]) {
97
- .accordion-item {
98
- .accordion-heading {
99
- height: 3rem;
143
+ // ─── Expanded / Open State (must come after disabled so .open wins) ─────────
144
+
145
+ &.open {
146
+ background-color: var(--color-surface-container);
147
+ border-color: transparent; // border fades out when expanded, matching M3
148
+
149
+ .panel-content {
150
+ grid-template-rows: 1fr;
151
+ border-block-start: 1px solid var(--color-outline-variant); // header–content divider
100
152
  }
153
+
154
+ .content-inner {
155
+ padding: 0 var(--spacing-300) var(--spacing-200); // 0 24dp 16dp
156
+ }
157
+
158
+ .toggle-icon {
159
+ transform: rotate(180deg);
160
+ }
161
+ }
162
+
163
+ // ─── Disabled State (border + bg dimmed) ────────────────────────────────────
164
+
165
+ &.disabled {
166
+ background-color: color-mix(in srgb, var(--color-surface-container-low) 38%, transparent);
167
+ border-color: color-mix(in srgb, var(--color-outline-variant) 38%, transparent);
101
168
  }
102
169
  }
103
170
 
171
+ // ─── Toggle position: before ─────────────────────────────────────────────────
104
172
 
105
- :host-context(p-accordion[align="start"]) {
106
- .accordion-item {
107
- .accordion-heading {
108
- flex-direction: row;
109
- }
173
+ :host([toggle-position='before']) {
174
+ .header-button {
175
+ flex-direction: row-reverse;
176
+ justify-content: flex-end;
177
+ }
178
+
179
+ .header-label {
180
+ flex: 1;
110
181
  }
111
182
  }
@@ -1,5 +1,5 @@
1
- import { html, LitElement } from 'lit';
2
- import { property, query } from 'lit/decorators.js';
1
+ import { html, LitElement, nothing } from 'lit';
2
+ import { property, query, state } from 'lit/decorators.js';
3
3
  import { classMap } from 'lit/directives/class-map.js';
4
4
  import styles from './accordion-item.scss';
5
5
 
@@ -7,13 +7,27 @@ import styles from './accordion-item.scss';
7
7
  * @label Accordion Item
8
8
  * @tag wc-accordion-item
9
9
  * @rawTag accordion-item
10
- * @summary An accordion item is single item in an accordion list. It contains a header and a content section that can be expanded or collapsed by the user.
10
+ * @summary An expansion panel with a header that reveals or hides associated content. Follows Material Design 3 expansion panel guidelines.
11
11
  * @parentRawTag accordion
12
- *
12
+ *
13
+ * @slot - The body content revealed when the panel is expanded.
14
+ * @slot heading - The panel title. Renders as `body-large` text.
15
+ * @slot description - Optional subtitle rendered below the title. Renders as `body-small` text.
16
+ * @slot header-actions - Actions (e.g. icon buttons) placed at the trailing end of the header, before the toggle icon.
17
+ *
18
+ * @part header - The header `<button>` element.
19
+ * @part title - The title text container.
20
+ * @part description - The description text container.
21
+ * @part content - The expandable content region wrapper.
22
+ *
23
+ * @fires {CustomEvent<{ open: boolean }>} accordion-item:toggle - Fired when the panel is expanded or collapsed.
24
+ *
13
25
  * @example
14
26
  * ```html
15
27
  * <wc-accordion-item>
16
- * Testing
28
+ * <span slot="heading">Personal information</span>
29
+ * <span slot="description">Fill in your details</span>
30
+ * <p>Content goes here.</p>
17
31
  * </wc-accordion-item>
18
32
  * ```
19
33
  * @tags display
@@ -24,24 +38,38 @@ export class AccordionItem extends LitElement {
24
38
  #id = crypto.randomUUID();
25
39
 
26
40
  /**
27
- * The menu item value.
28
- */
29
- @property({ type: String, reflect: true })
30
- heading: string = '';
31
-
32
- /**
33
- * If true, the user cannot interact with the button. Defaults to `false`.
41
+ * Whether the user cannot interact with the panel.
34
42
  */
35
43
  @property({ type: Boolean, reflect: true })
36
44
  disabled: boolean = false;
37
45
 
38
46
  /**
39
- * Menu item selection state.
47
+ * Whether the panel is expanded.
40
48
  */
41
49
  @property({ type: Boolean, reflect: true })
42
50
  open: boolean = false;
43
51
 
44
- @query('.accordion-heading')
52
+ /**
53
+ * Whether to hide the expand/collapse toggle indicator icon.
54
+ */
55
+ @property({ type: Boolean, reflect: true, attribute: 'hide-toggle' })
56
+ hideToggle: boolean = false;
57
+
58
+ /**
59
+ * Position of the toggle icon relative to the panel title.
60
+ * `'after'` places it at the trailing end (default, matches M3).
61
+ * `'before'` places it at the leading start.
62
+ */
63
+ @property({ type: String, reflect: true, attribute: 'toggle-position' })
64
+ togglePosition: 'before' | 'after' = 'after';
65
+
66
+ @state()
67
+ private _hasDescriptionSlot = false;
68
+
69
+ @state()
70
+ private _hasHeadingSlot = false;
71
+
72
+ @query('.header-button')
45
73
  private readonly buttonElement!: HTMLElement | null;
46
74
 
47
75
  override focus() {
@@ -52,48 +80,93 @@ export class AccordionItem extends LitElement {
52
80
  this.buttonElement?.blur();
53
81
  }
54
82
 
55
- private __handleToggle() {
83
+ private _handleToggle() {
56
84
  if (this.disabled) return;
57
85
  this.open = !this.open;
58
86
  this.dispatchEvent(
59
- new CustomEvent('accordion-item--toggle', {
87
+ new CustomEvent('accordion-item:toggle', {
60
88
  bubbles: true,
61
89
  composed: true,
62
- detail: { open: this.open, id: this.#id },
90
+ detail: { open: this.open },
63
91
  }),
64
92
  );
65
93
  }
66
94
 
95
+ private static _onSlotChange(e: Event, setter: (v: boolean) => void) {
96
+ const slot = e.target as HTMLSlotElement;
97
+ const nodes = slot.assignedNodes({ flatten: true });
98
+ setter(
99
+ nodes.some(n =>
100
+ n.nodeType === Node.TEXT_NODE
101
+ ? (n.textContent?.trim() ?? '') !== ''
102
+ : true,
103
+ ),
104
+ );
105
+ }
106
+
107
+ private _renderToggleIcon() {
108
+ if (this.hideToggle) return nothing;
109
+ return html`<wc-icon
110
+ class="toggle-icon"
111
+ name="keyboard_arrow_down"
112
+ aria-hidden="true"
113
+ ></wc-icon>`;
114
+ }
115
+
67
116
  render() {
68
- return html`<div
69
- class=${classMap({
70
- 'accordion-item': true,
71
- open: this.open,
72
- disabled: this.disabled,
73
- })}
74
- >
75
- <button
76
- id=${`accordion-heading-${this.#id}`}
77
- tabindex="0"
78
- aria-controls=${`accordion-control-${this.#id}`}
79
- class="accordion-heading"
80
- aria-disabled=${this.disabled}
81
- @click=${this.__handleToggle}
82
- aria-expanded=${this.open}
83
- >
84
- <wc-icon class="accordion-icon" name="keyboard_arrow_down"></wc-icon>
85
- <div part="title" class="accordion-title">
86
- <slot name="heading">${this.heading}</slot>
87
- </div>
88
- </button>
117
+ return html`
89
118
  <div
90
- class="item-section slot-main"
91
- id=${`accordion-control-${this.#id}`}
92
- aria-labelledby=${`accordion-heading-${this.#id}`}
93
- role="region"
119
+ class=${classMap({
120
+ 'expansion-panel': true,
121
+ open: this.open,
122
+ disabled: this.disabled,
123
+ })}
94
124
  >
95
- <slot></slot>
125
+ <button
126
+ id=${`panel-header-${this.#id}`}
127
+ part="header"
128
+ class="header-button"
129
+ tabindex=${this.disabled ? '-1' : '0'}
130
+ aria-controls=${`panel-content-${this.#id}`}
131
+ aria-disabled=${this.disabled}
132
+ aria-expanded=${this.open}
133
+ ?disabled=${this.disabled}
134
+ @click=${this._handleToggle}
135
+ >
136
+ ${this.togglePosition === 'before' ? this._renderToggleIcon() : nothing}
137
+ <span class="header-label">
138
+ <span part="title" class="panel-title">
139
+ <slot
140
+ name="heading"
141
+ @slotchange=${(e: Event) => AccordionItem._onSlotChange(e, v => { this._hasHeadingSlot = v; })}
142
+ ></slot>
143
+ </span>
144
+ <span
145
+ part="description"
146
+ class="panel-description"
147
+ ?hidden=${!this._hasDescriptionSlot}
148
+ >
149
+ <slot
150
+ name="description"
151
+ @slotchange=${(e: Event) => AccordionItem._onSlotChange(e, v => { this._hasDescriptionSlot = v; })}
152
+ ></slot>
153
+ </span>
154
+ </span>
155
+ <slot name="header-actions" class="header-actions"></slot>
156
+ ${this.togglePosition === 'after' ? this._renderToggleIcon() : nothing}
157
+ </button>
158
+ <div
159
+ id=${`panel-content-${this.#id}`}
160
+ part="content"
161
+ class="panel-content"
162
+ role="region"
163
+ aria-labelledby=${`panel-header-${this.#id}`}
164
+ >
165
+ <div class="content-inner">
166
+ <slot></slot>
167
+ </div>
168
+ </div>
96
169
  </div>
97
- </div>`;
170
+ `;
98
171
  }
99
172
  }
@@ -2,15 +2,34 @@
2
2
 
3
3
  @include mixin.base-styles;
4
4
 
5
+ // ─── Host ────────────────────────────────────────────────────────────────────
6
+
5
7
  :host {
6
8
  display: block;
7
9
  }
8
10
 
9
- slot::slotted(:not(:last-child)) {
10
- border-block-start: 1px solid var(--color-outline);
11
+ // ─── Default display mode ────────────────────────────────────────────────────
12
+ // Each item is a fully-bordered rounded container (M3 expansion panel shape).
13
+ // Items are stacked with a gap; no shared dividers.
14
+
15
+ .accordion {
16
+ display: flex;
17
+ flex-direction: column;
18
+ gap: var(--spacing-100); // 8dp gap between panels
11
19
  }
12
20
 
13
- slot::slotted(:last-child) {
14
- border-block-start: 1px solid var(--color-outline);
15
- border-block-end: 1px solid var(--color-outline);
21
+ // ─── Flat display mode ───────────────────────────────────────────────────────
22
+ // Removes the gap and the per-item border/background for use inside cards.
23
+
24
+ :host([display-mode='flat']) {
25
+ .accordion {
26
+ gap: 0;
27
+ }
28
+
29
+ // Push overrides into each item via inherited CSS custom properties.
30
+ // wc-accordion-item reads --_accordion-item-border / --_accordion-item-background.
31
+ slot::slotted(wc-accordion-item) {
32
+ --_accordion-item-border: none;
33
+ --_accordion-item-background: transparent;
34
+ }
16
35
  }
@@ -7,14 +7,20 @@ import { AccordionItem } from './accordion-item.js';
7
7
  * @label Accordion
8
8
  * @tag wc-accordion
9
9
  * @rawTag accordion
10
- * @summary An accordion is a vertically stacked list of headers that reveal or hide associated sections of content.
10
+ * @summary A vertically stacked set of expansion panels. Follows Material Design 3 expansion panel guidelines.
11
11
  *
12
12
  * @example
13
13
  * ```html
14
14
  * <wc-accordion>
15
- * <wc-accordion-item heading="Accordion">
16
- * Content
17
- * </wc-accordion-item>
15
+ * <wc-accordion-item>
16
+ * <span slot="heading">Panel 1</span>
17
+ * <span slot="description">Summary text</span>
18
+ * Content
19
+ * </wc-accordion-item>
20
+ * <wc-accordion-item>
21
+ * <span slot="heading">Panel 2</span>
22
+ * Content
23
+ * </wc-accordion-item>
18
24
  * </wc-accordion>
19
25
  * ```
20
26
  * @tags display
@@ -22,10 +28,22 @@ import { AccordionItem } from './accordion-item.js';
22
28
  export class Accordion extends LitElement {
23
29
  static styles = [styles];
24
30
 
25
- @property({ type: Boolean, attribute: 'allow-multiple' })
26
- allowMultiple = false;
31
+ /**
32
+ * Whether multiple panels can be expanded simultaneously.
33
+ * When `false` (default), expanding one panel collapses all others.
34
+ */
35
+ @property({ type: Boolean, reflect: true })
36
+ multi = false;
27
37
 
28
- @queryAssignedElements({ selector: 'p-accordion-item' })
38
+ /**
39
+ * Display mode for the accordion.
40
+ * `'default'` renders panels with a subtle background on expand and dividers between items.
41
+ * `'flat'` renders panels without borders or background changes — suitable for use inside cards.
42
+ */
43
+ @property({ type: String, reflect: true, attribute: 'display-mode' })
44
+ displayMode: 'default' | 'flat' = 'default';
45
+
46
+ @queryAssignedElements({ selector: 'wc-accordion-item' })
29
47
  items!: Array<AccordionItem>;
30
48
 
31
49
  connectedCallback() {
@@ -39,7 +57,6 @@ export class Accordion extends LitElement {
39
57
  disconnectedCallback() {
40
58
  super.disconnectedCallback();
41
59
  // @ts-ignore
42
- // eslint-disable-next-line no-undef
43
60
  this.removeEventListener('accordion-item:toggle', this._onItemToggle);
44
61
  this.removeEventListener('keydown', this._onKeyDown);
45
62
  }
@@ -47,11 +64,10 @@ export class Accordion extends LitElement {
47
64
  private _onItemToggle(e: CustomEvent) {
48
65
  const targetItem = e.target as AccordionItem;
49
66
 
50
- // Stop event bubbling if it came from a nested accordion
51
- // We check if the target item is a direct child of *this* accordion
67
+ // Ignore events from nested accordions only handle direct children
52
68
  if (targetItem.parentElement !== this) return;
53
69
 
54
- if (!this.allowMultiple && targetItem.open) {
70
+ if (!this.multi && targetItem.open) {
55
71
  this.items.forEach(item => {
56
72
  if (item !== targetItem && item.open) {
57
73
  // eslint-disable-next-line no-param-reassign
@@ -62,17 +78,11 @@ export class Accordion extends LitElement {
62
78
  }
63
79
 
64
80
  private _onKeyDown(e: KeyboardEvent) {
65
- // 1. Find which item currently has its HEADER focused.
66
- // We check the shadowRoot of each item to see if the internal <button> is the active element.
67
81
  const focusedItemIndex = this.items.findIndex(item => {
68
- // Access the Shadow DOM of the item
69
82
  const root = item.shadowRoot;
70
- // Check if the focused element inside that shadow DOM is the toggle button
71
- return root?.activeElement?.classList.contains('accordion-heading');
83
+ return root?.activeElement?.classList.contains('header-button');
72
84
  });
73
85
 
74
- // 2. If no header is focused (e.g., focus is on body content or outside), do nothing.
75
- // This prevents stealing focus when the user is typing in a form inside the accordion.
76
86
  if (focusedItemIndex === -1) return;
77
87
 
78
88
  let nextIndex = -1;
@@ -81,12 +91,10 @@ export class Accordion extends LitElement {
81
91
  switch (e.key) {
82
92
  case 'ArrowDown':
83
93
  e.preventDefault();
84
- // Cycle next
85
94
  nextIndex = (focusedItemIndex + 1) % this.items.length;
86
95
  break;
87
96
  case 'ArrowUp':
88
97
  e.preventDefault();
89
- // Cycle previous
90
98
  nextIndex =
91
99
  (focusedItemIndex - 1 + this.items.length) % this.items.length;
92
100
  break;
@@ -100,12 +108,10 @@ export class Accordion extends LitElement {
100
108
  break;
101
109
  }
102
110
 
103
- // 3. Apply Focus
104
111
  if (nextIndex !== -1) {
105
112
  const itemToFocus = this.items[nextIndex];
106
- // Select the button inside the Shadow DOM of the target item
107
113
  const button = itemToFocus.shadowRoot?.querySelector(
108
- '.accordion-heading',
114
+ '.header-button',
109
115
  ) as HTMLElement;
110
116
  button?.focus();
111
117
  }