@brandsync/wc 0.0.4 → 0.0.5

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 (49) hide show
  1. package/custom-elements.json +129 -2
  2. package/dist/brandsync-wc/brandsync-wc.esm.js +1 -1
  3. package/dist/brandsync-wc/{p-ea705742.entry.js → p-0ded3be8.entry.js} +1 -1
  4. package/dist/brandsync-wc/p-1dc6d7f5.entry.js +1 -0
  5. package/dist/brandsync-wc/p-300eea85.entry.js +1 -0
  6. package/dist/brandsync-wc/p-cdbad27a.entry.js +1 -0
  7. package/dist/cjs/brandsync-wc.cjs.js +1 -1
  8. package/dist/cjs/bs-chatbot-response-action.cjs.entry.js +30 -4
  9. package/dist/cjs/bs-input.cjs.entry.js +2 -2
  10. package/dist/cjs/bs-menu-item.cjs.entry.js +29 -0
  11. package/dist/cjs/bs-menu.cjs.entry.js +17 -0
  12. package/dist/cjs/loader.cjs.js +1 -1
  13. package/dist/collection/collection-manifest.json +3 -1
  14. package/dist/collection/components/bs-chatbot-response-action/bs-chatbot-response-action.cmp.test.js +43 -2
  15. package/dist/collection/components/bs-chatbot-response-action/bs-chatbot-response-action.css +13 -0
  16. package/dist/collection/components/bs-chatbot-response-action/bs-chatbot-response-action.js +70 -6
  17. package/dist/collection/components/bs-chatbot-response-action/bs-chatbot-response-action.stories.js +96 -0
  18. package/dist/collection/components/bs-input/bs-input.js +2 -2
  19. package/dist/collection/components/bs-menu/bs-menu.cmp.test.js +18 -0
  20. package/dist/collection/components/bs-menu/bs-menu.css +20 -0
  21. package/dist/collection/components/bs-menu/bs-menu.js +39 -0
  22. package/dist/collection/components/bs-menu/bs-menu.stories.js +15 -0
  23. package/dist/collection/components/bs-menu-item/bs-menu-item.cmp.test.js +33 -0
  24. package/dist/collection/components/bs-menu-item/bs-menu-item.css +68 -0
  25. package/dist/collection/components/bs-menu-item/bs-menu-item.js +79 -0
  26. package/dist/collection/components/bs-menu-item/bs-menu-item.stories.js +37 -0
  27. package/dist/components/bs-chatbot-response-action.js +1 -1
  28. package/dist/components/bs-input.js +1 -1
  29. package/dist/components/bs-menu-item.d.ts +11 -0
  30. package/dist/components/bs-menu-item.js +1 -0
  31. package/dist/components/bs-menu.d.ts +11 -0
  32. package/dist/components/bs-menu.js +1 -0
  33. package/dist/esm/brandsync-wc.js +1 -1
  34. package/dist/esm/bs-chatbot-response-action.entry.js +31 -5
  35. package/dist/esm/bs-input.entry.js +2 -2
  36. package/dist/esm/bs-menu-item.entry.js +27 -0
  37. package/dist/esm/bs-menu.entry.js +15 -0
  38. package/dist/esm/loader.js +1 -1
  39. package/dist/types/components/bs-chatbot-response-action/bs-chatbot-response-action.d.ts +16 -2
  40. package/dist/types/components/bs-chatbot-response-action/bs-chatbot-response-action.stories.d.ts +1 -0
  41. package/dist/types/components/bs-menu/bs-menu.cmp.test.d.ts +1 -0
  42. package/dist/types/components/bs-menu/bs-menu.d.ts +24 -0
  43. package/dist/types/components/bs-menu/bs-menu.stories.d.ts +5 -0
  44. package/dist/types/components/bs-menu-item/bs-menu-item.cmp.test.d.ts +1 -0
  45. package/dist/types/components/bs-menu-item/bs-menu-item.d.ts +38 -0
  46. package/dist/types/components/bs-menu-item/bs-menu-item.stories.d.ts +7 -0
  47. package/dist/types/components.d.ts +195 -3
  48. package/package.json +1 -1
  49. package/dist/brandsync-wc/p-4ce458bf.entry.js +0 -1
@@ -16,6 +16,8 @@ import { h } from "@stencil/core";
16
16
  * @slot copy-icon - Overrides the default "Copy" button icon.
17
17
  * @slot regenerate-icon - Overrides the default "Regenerate" button icon.
18
18
  * @slot menu-icon - Overrides the default "More options" button icon.
19
+ * @slot menu - A `bs-menu` (or other content) rendered in a popup positioned below the "More
20
+ * options" button, shown only while `menuOpen` is `true`.
19
21
  * @slot sources-icon - Overrides the default icon in the "N sources" button.
20
22
  * @part like - The "Like" icon button.
21
23
  * @part dislike - The "Dislike" icon button.
@@ -39,12 +41,20 @@ import { h } from "@stencil/core";
39
41
  * @prop --bs-chatbot-response-action-sources-line-height - Aliased to `--bs-line-height-body-sm`.
40
42
  */
41
43
  export class BsChatbotResponseAction {
44
+ host;
42
45
  /**
43
46
  * Number of sources backing the response. When set to a positive number, renders the "N
44
47
  * sources" button (singular "1 source" / plural "N sources"). When unset or `0`, the button is
45
48
  * not rendered at all.
46
49
  */
47
50
  sourcesCount;
51
+ /**
52
+ * Whether the "more options" popup menu (the `menu` slot) is currently open. Mutable + reflected
53
+ * so the component can track/close itself (kebab click, click outside, Escape) the same way
54
+ * `bs-chatbot-header`'s `expanded` prop tracks its own toggle state, while still emitting
55
+ * `bsMenuOpen` for the consumer to react to.
56
+ */
57
+ menuOpen = false;
48
58
  /** Fires when the "Like" button is clicked. */
49
59
  bsLike;
50
60
  /** Fires when the "Dislike" button is clicked. */
@@ -53,7 +63,8 @@ export class BsChatbotResponseAction {
53
63
  bsCopy;
54
64
  /** Fires when the "Regenerate" button is clicked. */
55
65
  bsRegenerate;
56
- /** Fires when the "More options" button is clicked. */
66
+ /** Fires when the "More options" button is clicked or the menu is closed (click outside,
67
+ * Escape), with the new `menuOpen` value. */
57
68
  bsMenuOpen;
58
69
  /** Fires when the "N sources" button is clicked. */
59
70
  bsSourcesClick;
@@ -70,14 +81,31 @@ export class BsChatbotResponseAction {
70
81
  this.bsRegenerate.emit();
71
82
  };
72
83
  onMenuClick = () => {
73
- this.bsMenuOpen.emit();
84
+ this.menuOpen = !this.menuOpen;
85
+ this.bsMenuOpen.emit(this.menuOpen);
86
+ };
87
+ closeMenu = () => {
88
+ if (!this.menuOpen)
89
+ return;
90
+ this.menuOpen = false;
91
+ this.bsMenuOpen.emit(this.menuOpen);
74
92
  };
93
+ onDocumentClick(ev) {
94
+ if (this.menuOpen && !ev.composedPath().includes(this.host)) {
95
+ this.closeMenu();
96
+ }
97
+ }
98
+ onKeydown(ev) {
99
+ if (this.menuOpen && ev.key === 'Escape') {
100
+ this.closeMenu();
101
+ }
102
+ }
75
103
  onSourcesClick = () => {
76
104
  this.bsSourcesClick.emit();
77
105
  };
78
106
  render() {
79
107
  const hasSources = !!this.sourcesCount && this.sourcesCount > 0;
80
- return (h("div", { key: '61a46c51ffba5e1256a94cbab87ce6e06e45941f', class: "bs-chatbot-response-action" }, h("button", { key: '33e8e29235191bc2a4128074d5df08cc304dbefb', type: "button", part: "like", class: "bs-chatbot-response-action__button", "aria-label": "Like", onClick: this.onLikeClick }, h("slot", { key: '2337aa07fec93d12e6706ded394248f0353b91e6', name: "like-icon" }, h(ThumbsUpIcon, { key: '9be56ca48cbb7027ba608ea9f8e89c2c14f98eee' }))), h("button", { key: 'fcdd8f3d4028a0e91cd5e10525ac97febf2261b6', type: "button", part: "dislike", class: "bs-chatbot-response-action__button", "aria-label": "Dislike", onClick: this.onDislikeClick }, h("slot", { key: 'bc219702b5e935659aa7824596511e8b01d2014f', name: "dislike-icon" }, h(ThumbsDownIcon, { key: '5ec9da81d5a89d1d2a35dbfe88682214178204cb' }))), h("button", { key: '59d6b3032d141092537d9e56ced8e3045b40384f', type: "button", part: "copy", class: "bs-chatbot-response-action__button", "aria-label": "Copy", onClick: this.onCopyClick }, h("slot", { key: 'e5bd9fbc4774ae9d67707803819697b56c516984', name: "copy-icon" }, h(CopyIcon, { key: 'e7f8f643920bad3f88c597bb183cfc71f8319c46' }))), h("button", { key: 'a6bd1be8be7f0f70157a5a5e37bc18d2c94462fc', type: "button", part: "regenerate", class: "bs-chatbot-response-action__button", "aria-label": "Regenerate", onClick: this.onRegenerateClick }, h("slot", { key: '7d35981706ea38ae1e613047945dde9d67a94c62', name: "regenerate-icon" }, h(RegenerateIcon, { key: '1d2dacd4bba68fcd120317bb207e7aa25bc0f62f' }))), h("button", { key: '32f3f790f4024cd7b58ba6f291d32b3f5f0dbbc8', type: "button", part: "menu", class: "bs-chatbot-response-action__button", "aria-label": "More options", onClick: this.onMenuClick }, h("slot", { key: '0aaf67bbc34bdf5d12247f9ab264a75dad63f996', name: "menu-icon" }, h(MenuIcon, { key: 'c32d76d1e6f73cd9a82bbf4371a4d047b807b80c' }))), hasSources && (h("button", { key: '240f26ed6ec5faded1efe1f171da10e4a292011e', type: "button", part: "sources", class: "bs-chatbot-response-action__sources", onClick: this.onSourcesClick }, h("slot", { key: 'e97e26840df27d2ff281882823bae7556d53bdc6', name: "sources-icon" }, h(SourcesIcon, { key: '5dd3538a28ee3cf22b2801258024386827b9c774' })), h("span", { key: 'acf07f2bba262a08c3f2d9650b9fd09fab51d172', class: "bs-chatbot-response-action__sources-label" }, this.sourcesCount, " ", this.sourcesCount === 1 ? 'source' : 'sources')))));
108
+ return (h("div", { key: '6595c290930eacb48dc9d52db12a29e2942c2d8f', class: "bs-chatbot-response-action" }, h("button", { key: 'e3acdbb150047bce0718a168c048ee9db6c829f2', type: "button", part: "like", class: "bs-chatbot-response-action__button", "aria-label": "Like", onClick: this.onLikeClick }, h("slot", { key: 'bc9339dafb25f7529a097eaa578ef9ff5d61cafe', name: "like-icon" }, h(ThumbsUpIcon, { key: '49bf4ef39238d9763838ee72ca1b1799e2a284ff' }))), h("button", { key: '1a9900dbb7b1892233477cc4140b690de9d87cf3', type: "button", part: "dislike", class: "bs-chatbot-response-action__button", "aria-label": "Dislike", onClick: this.onDislikeClick }, h("slot", { key: 'db4555adfb4a8cc83cb73ff13d537806e5918f25', name: "dislike-icon" }, h(ThumbsDownIcon, { key: 'a741f40e3f0ea3a11e839277ad5659e0689fb13a' }))), h("button", { key: '19fbad2e8ae607368b78dc076188ec2b30d21325', type: "button", part: "copy", class: "bs-chatbot-response-action__button", "aria-label": "Copy", onClick: this.onCopyClick }, h("slot", { key: '8789336bec82b3335be38628d00664246d0f4015', name: "copy-icon" }, h(CopyIcon, { key: 'e65198dff09540ce0d9af3fa2ed1e5ece12396cf' }))), h("button", { key: '09c16cca24ab255299d9f892e3d2c5334fcaf7f8', type: "button", part: "regenerate", class: "bs-chatbot-response-action__button", "aria-label": "Regenerate", onClick: this.onRegenerateClick }, h("slot", { key: '9758f958967908ce1a25805d9d3bea968f3590f4', name: "regenerate-icon" }, h(RegenerateIcon, { key: '608515ede5570b2482cc46c3f2910d56e086f0b3' }))), h("div", { key: '81c609157f7bb8024a2d0b1a3da30ae3d93f46e8', class: "bs-chatbot-response-action__menu-wrapper" }, h("button", { key: '588d3eef27b342c784fd925e7ff4478370ef8b48', type: "button", part: "menu", class: "bs-chatbot-response-action__button", "aria-label": "More options", "aria-haspopup": "menu", "aria-expanded": this.menuOpen ? 'true' : 'false', onClick: this.onMenuClick }, h("slot", { key: 'f14e53e800cc2976306824a6b5131a98cf22ee49', name: "menu-icon" }, h(MenuIcon, { key: '7ce9bc2ee29ab3f7d56b4a5cbead2a014f586841' }))), this.menuOpen && (h("div", { key: '80e0e9688f639c51601f891e46c82059ee1fd921', class: "bs-chatbot-response-action__menu-popup" }, h("slot", { key: '9b1a2652e3460a1d97b4de856269516eaf1273e1', name: "menu" })))), hasSources && (h("button", { key: '15febee771c334afddbbdedc21add700ed44ceb1', type: "button", part: "sources", class: "bs-chatbot-response-action__sources", onClick: this.onSourcesClick }, h("slot", { key: '39d2cf88bda64d2f2b0a6d767e6ba5fcb1acc8bd', name: "sources-icon" }, h(SourcesIcon, { key: '358a3b2c1e8479c7ee92e89401edc5e68ff93e1c' })), h("span", { key: '96b364bdfc9f8ae1ba605f0fd751e19e77959720', class: "bs-chatbot-response-action__sources-label" }, this.sourcesCount, " ", this.sourcesCount === 1 ? 'source' : 'sources')))));
81
109
  }
82
110
  static get is() { return "bs-chatbot-response-action"; }
83
111
  static get encapsulation() { return "shadow"; }
@@ -111,6 +139,26 @@ export class BsChatbotResponseAction {
111
139
  "setter": false,
112
140
  "reflect": false,
113
141
  "attribute": "sources-count"
142
+ },
143
+ "menuOpen": {
144
+ "type": "boolean",
145
+ "mutable": true,
146
+ "complexType": {
147
+ "original": "boolean",
148
+ "resolved": "boolean",
149
+ "references": {}
150
+ },
151
+ "required": false,
152
+ "optional": false,
153
+ "docs": {
154
+ "tags": [],
155
+ "text": "Whether the \"more options\" popup menu (the `menu` slot) is currently open. Mutable + reflected\nso the component can track/close itself (kebab click, click outside, Escape) the same way\n`bs-chatbot-header`'s `expanded` prop tracks its own toggle state, while still emitting\n`bsMenuOpen` for the consumer to react to."
156
+ },
157
+ "getter": false,
158
+ "setter": false,
159
+ "reflect": true,
160
+ "attribute": "menu-open",
161
+ "defaultValue": "false"
114
162
  }
115
163
  };
116
164
  }
@@ -183,11 +231,11 @@ export class BsChatbotResponseAction {
183
231
  "composed": true,
184
232
  "docs": {
185
233
  "tags": [],
186
- "text": "Fires when the \"More options\" button is clicked."
234
+ "text": "Fires when the \"More options\" button is clicked or the menu is closed (click outside,\nEscape), with the new `menuOpen` value."
187
235
  },
188
236
  "complexType": {
189
- "original": "void",
190
- "resolved": "void",
237
+ "original": "boolean",
238
+ "resolved": "boolean",
191
239
  "references": {}
192
240
  }
193
241
  }, {
@@ -207,6 +255,22 @@ export class BsChatbotResponseAction {
207
255
  }
208
256
  }];
209
257
  }
258
+ static get elementRef() { return "host"; }
259
+ static get listeners() {
260
+ return [{
261
+ "name": "click",
262
+ "method": "onDocumentClick",
263
+ "target": "document",
264
+ "capture": false,
265
+ "passive": false
266
+ }, {
267
+ "name": "keydown",
268
+ "method": "onKeydown",
269
+ "target": "document",
270
+ "capture": false,
271
+ "passive": false
272
+ }];
273
+ }
210
274
  }
211
275
  const ThumbsUpIcon = () => (h("svg", { viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true" }, h("path", { d: "M2.5 8.125H6.25V16.25H2.5C2.33424 16.25 2.17527 16.1842 2.05806 16.0669C1.94085 15.9497 1.875 15.7908 1.875 15.625V8.75C1.875 8.58424 1.94085 8.42527 2.05806 8.30806C2.17527 8.19085 2.33424 8.125 2.5 8.125Z", stroke: "currentColor", "stroke-width": "1.66667", "stroke-linecap": "round", "stroke-linejoin": "round" }), h("path", { d: "M6.25 8.125L9.375 1.875C10.038 1.875 10.6739 2.13839 11.1428 2.60723C11.6116 3.07607 11.875 3.71196 11.875 4.375V6.25H16.875C17.0523 6.25005 17.2276 6.28782 17.3892 6.36081C17.5508 6.4338 17.695 6.54033 17.8123 6.67333C17.9295 6.80634 18.0172 6.96277 18.0693 7.13223C18.1215 7.3017 18.137 7.48033 18.1148 7.65625L17.1773 15.1562C17.1393 15.4583 16.9923 15.736 16.7641 15.9374C16.5358 16.1388 16.2419 16.2499 15.9375 16.25H6.25", stroke: "currentColor", "stroke-width": "1.66667", "stroke-linecap": "round", "stroke-linejoin": "round" })));
212
276
  const ThumbsDownIcon = () => (h("svg", { viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true" }, h("path", { d: "M2.5 3.75H6.25V11.875H2.5C2.33424 11.875 2.17527 11.8092 2.05806 11.6919C1.94085 11.5747 1.875 11.4158 1.875 11.25V4.375C1.875 4.20924 1.94085 4.05027 2.05806 3.93306C2.17527 3.81585 2.33424 3.75 2.5 3.75Z", stroke: "currentColor", "stroke-width": "1.66667", "stroke-linecap": "round", "stroke-linejoin": "round" }), h("path", { d: "M6.25 11.875L9.375 18.125C10.038 18.125 10.6739 17.8616 11.1428 17.3928C11.6116 16.9239 11.875 16.288 11.875 15.625V13.75H16.875C17.0523 13.75 17.2276 13.7122 17.3892 13.6392C17.5508 13.5662 17.695 13.4597 17.8123 13.3267C17.9295 13.1937 18.0172 13.0372 18.0693 12.8678C18.1215 12.6983 18.137 12.5197 18.1148 12.3438L17.1773 4.84375C17.1393 4.54174 16.9923 4.26399 16.7641 4.06262C16.5358 3.86124 16.2419 3.75009 15.9375 3.75H6.25", stroke: "currentColor", "stroke-width": "1.66667", "stroke-linecap": "round", "stroke-linejoin": "round" })));
@@ -31,3 +31,99 @@ export const InResponseMessage = {
31
31
  </div>
32
32
  `,
33
33
  };
34
+ const ReadAloudIcon = () => html `
35
+ <svg slot="icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
36
+ <path
37
+ d="M7.5 15.75H3C2.80109 15.75 2.61032 15.671 2.46967 15.5303C2.32902 15.3897 2.25 15.1989 2.25 15V9C2.25 8.80109 2.32902 8.61032 2.46967 8.46967C2.61032 8.32902 2.80109 8.25 3 8.25H7.5L14.25 3V21L7.5 15.75Z"
38
+ stroke="currentColor"
39
+ stroke-width="2"
40
+ stroke-linecap="round"
41
+ stroke-linejoin="round"
42
+ />
43
+ <path d="M7.5 8.25V15.75" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
44
+ <path
45
+ d="M18 10.0172C18.4825 10.565 18.7487 11.27 18.7487 12C18.7487 12.73 18.4825 13.435 18 13.9828"
46
+ stroke="currentColor"
47
+ stroke-width="2"
48
+ stroke-linecap="round"
49
+ stroke-linejoin="round"
50
+ />
51
+ <path
52
+ d="M20.7816 7.5C21.8885 8.73755 22.5004 10.3397 22.5004 12C22.5004 13.6603 21.8885 15.2624 20.7816 16.5"
53
+ stroke="currentColor"
54
+ stroke-width="2"
55
+ stroke-linecap="round"
56
+ stroke-linejoin="round"
57
+ />
58
+ </svg>
59
+ `;
60
+ const ChatWithHumanIcon = () => html `
61
+ <svg slot="icon" viewBox="0 0 24.5 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
62
+ <path
63
+ d="M12.25 15C14.3211 15 16 13.3211 16 11.25C16 9.17893 14.3211 7.5 12.25 7.5C10.1789 7.5 8.5 9.17893 8.5 11.25C8.5 13.3211 10.1789 15 12.25 15Z"
64
+ stroke="currentColor"
65
+ stroke-width="2"
66
+ stroke-linecap="round"
67
+ stroke-linejoin="round"
68
+ />
69
+ <path
70
+ d="M6.23125 18.6909C6.79554 17.5795 7.65659 16.6459 8.71895 15.9939C9.7813 15.3418 11.0035 14.9966 12.25 14.9966C13.4965 14.9966 14.7187 15.3418 15.7811 15.9939C16.8434 16.6459 17.7045 17.5795 18.2687 18.6909"
71
+ stroke="currentColor"
72
+ stroke-width="2"
73
+ stroke-linecap="round"
74
+ stroke-linejoin="round"
75
+ />
76
+ <path d="M19 12L21.25 14.25L23.5 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
77
+ <path d="M1 12L3.25 9.75L5.5 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
78
+ <path
79
+ d="M3.25 9.75V12C3.24929 13.9055 3.85337 15.762 4.97524 17.3022C6.09712 18.8424 7.67888 19.9868 9.49273 20.5706C11.3066 21.1543 13.2589 21.1474 15.0685 20.5506C16.8782 19.9539 18.4517 18.7982 19.5625 17.25"
80
+ stroke="currentColor"
81
+ stroke-width="2"
82
+ stroke-linecap="round"
83
+ stroke-linejoin="round"
84
+ />
85
+ <path
86
+ d="M21.25 14.25V12C21.2507 10.0945 20.6466 8.238 19.5248 6.69779C18.4029 5.15758 16.8211 4.01319 15.0073 3.42942C13.1934 2.84565 11.2411 2.85264 9.43147 3.44938C7.62185 4.04611 6.04832 5.2018 4.9375 6.75"
87
+ stroke="currentColor"
88
+ stroke-width="2"
89
+ stroke-linecap="round"
90
+ stroke-linejoin="round"
91
+ />
92
+ </svg>
93
+ `;
94
+ const RaiseTicketIcon = () => html `
95
+ <svg slot="icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
96
+ <path d="M9 5.25V18.75" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
97
+ <path
98
+ d="M2.25 15C3.04565 15 3.80871 14.6839 4.37132 14.1213C4.93393 13.5587 5.25 12.7956 5.25 12C5.25 11.2044 4.93393 10.4413 4.37132 9.87868C3.80871 9.31607 3.04565 9 2.25 9V6C2.25 5.80109 2.32902 5.61032 2.46967 5.46967C2.61032 5.32902 2.80109 5.25 3 5.25H21C21.1989 5.25 21.3897 5.32902 21.5303 5.46967C21.671 5.61032 21.75 5.80109 21.75 6V9C20.9544 9 20.1913 9.31607 19.6287 9.87868C19.0661 10.4413 18.75 11.2044 18.75 12C18.75 12.7956 19.0661 13.5587 19.6287 14.1213C20.1913 14.6839 20.9544 15 21.75 15V18C21.75 18.1989 21.671 18.3897 21.5303 18.5303C21.3897 18.671 21.1989 18.75 21 18.75H3C2.80109 18.75 2.61032 18.671 2.46967 18.5303C2.32902 18.3897 2.25 18.1989 2.25 18V15Z"
99
+ stroke="currentColor"
100
+ stroke-width="2"
101
+ stroke-linecap="round"
102
+ stroke-linejoin="round"
103
+ />
104
+ </svg>
105
+ `;
106
+ export const WithMenu = {
107
+ name: 'More options menu',
108
+ render: () => {
109
+ // Demonstrates the intended integration pattern: bs-chatbot-response-action only tracks/emits
110
+ // the menuOpen toggle and provides a `menu` slot positioned below the kebab button -- it has no
111
+ // opinion on what goes in the menu. The consuming app (here, this story) supplies a bs-menu with
112
+ // real bs-menu-items and is responsible for wiring bsMenuOpen to the response-action's menuOpen
113
+ // prop, mirroring how bs-chatbot-header's WithComposer story wires bsExpand.
114
+ const onMenuOpen = (ev) => {
115
+ ev.target.menuOpen = ev.detail;
116
+ };
117
+ return html `
118
+ <div style="max-width: 480px; font-family: sans-serif;">
119
+ <bs-chatbot-response-action sources-count="3" @bsMenuOpen=${onMenuOpen}>
120
+ <bs-menu slot="menu" style="width: 210px;">
121
+ <bs-menu-item>${ReadAloudIcon()}Read aloud</bs-menu-item>
122
+ <bs-menu-item>${ChatWithHumanIcon()}Chat with a human</bs-menu-item>
123
+ <bs-menu-item>${RaiseTicketIcon()}Raise a ticket</bs-menu-item>
124
+ </bs-menu>
125
+ </bs-chatbot-response-action>
126
+ </div>
127
+ `;
128
+ },
129
+ };
@@ -36,10 +36,10 @@ export class BsInput {
36
36
  this.bsChange.emit(value);
37
37
  };
38
38
  render() {
39
- return (h("div", { key: '53b4d56fb2c955f061ac113b7dc2dbe8548a85d1', class: `bs-input ${this.error ? 'bs-input--error' : ''}` }, this.label && (
39
+ return (h("div", { key: 'df12d3d7af1bdc0e0e536e6c635ee406e4043b4f', class: `bs-input ${this.error ? 'bs-input--error' : ''}` }, this.label && (
40
40
  // "control" is a fixed id, not a generated one -- safe because Shadow DOM scopes ids
41
41
  // to this instance's own shadow root, so it can never collide with another bs-input.
42
- h("label", { key: '60ac25b59a78410843dbe9416398d4e0ec15c976', part: "label", class: "bs-input__label", htmlFor: "control" }, this.label)), h("input", { key: 'a9f801b2a7e16550c5d22c1301435e7a7082b362', id: "control", part: "control", class: "bs-input__control", type: this.type, value: this.value, placeholder: this.placeholder, disabled: this.disabled, "aria-describedby": this.error || this.description ? 'description-or-error' : undefined, "aria-invalid": this.error ? 'true' : undefined, onInput: this.onInput, onChange: this.onChange }), this.error ? (h("span", { id: "description-or-error", part: "error", class: "bs-input__error" }, this.error)) : this.description ? (h("span", { id: "description-or-error", part: "description", class: "bs-input__description" }, this.description)) : null));
42
+ h("label", { key: '2e4f867d17ab691a1df1b0b5b2964c0e1e986a91', part: "label", class: "bs-input__label", htmlFor: "control" }, this.label)), h("input", { key: 'a078574a5ffad9eafea791cf5866ac123c4d5f56', id: "control", part: "control", class: "bs-input__control", type: this.type, value: this.value, placeholder: this.placeholder, disabled: this.disabled, "aria-describedby": this.error || this.description ? 'description-or-error' : undefined, "aria-invalid": this.error ? 'true' : undefined, onInput: this.onInput, onChange: this.onChange }), this.error ? (h("span", { id: "description-or-error", part: "error", class: "bs-input__error" }, this.error)) : this.description ? (h("span", { id: "description-or-error", part: "description", class: "bs-input__description" }, this.description)) : null));
43
43
  }
44
44
  static get is() { return "bs-input"; }
45
45
  static get encapsulation() { return "shadow"; }
@@ -0,0 +1,18 @@
1
+ import { render, h, describe, it, expect } from "@stencil/vitest";
2
+ describe('bs-menu', () => {
3
+ it('renders with role="menu" on the host', async () => {
4
+ const { root } = await render(h("bs-menu", null));
5
+ expect(root.shadowRoot.querySelector('[part="list"]').getAttribute('role')).toBe('menu');
6
+ });
7
+ it('exposes the list part', async () => {
8
+ const { root } = await render(h("bs-menu", null));
9
+ expect(root.shadowRoot.querySelector('[part="list"]')).not.toBeNull();
10
+ });
11
+ it('renders slotted content in the default slot', async () => {
12
+ const { root } = await render(h("bs-menu", null, h("bs-menu-item", null, "Item one")));
13
+ expect(root).toHaveTextContent('Item one');
14
+ const slot = root.shadowRoot.querySelector('[part="list"] slot:not([name])');
15
+ expect(slot).not.toBeNull();
16
+ expect(slot.assignedElements()).toHaveLength(1);
17
+ });
18
+ });
@@ -0,0 +1,20 @@
1
+ :host {
2
+ display: block;
3
+ --bs-menu-bg: var(--bs-surface-raised);
4
+ --bs-menu-radius: var(--bs-border-radius-100);
5
+ --bs-menu-padding-y: var(--bs-spacing-50);
6
+ --bs-menu-gap: var(--bs-spacing-50);
7
+ --bs-menu-shadow: var(--bs-shadow-sm);
8
+ }
9
+
10
+ .bs-menu {
11
+ box-sizing: border-box;
12
+ display: flex;
13
+ flex-direction: column;
14
+ gap: var(--bs-menu-gap);
15
+ background: var(--bs-menu-bg);
16
+ border-radius: var(--bs-menu-radius);
17
+ padding: var(--bs-menu-padding-y) 0;
18
+ box-shadow: var(--bs-menu-shadow);
19
+ font-family: inherit;
20
+ }
@@ -0,0 +1,39 @@
1
+ import { h } from "@stencil/core";
2
+ /**
3
+ * A generic dropdown/popup menu container: a rounded, elevated list of items (typically
4
+ * `bs-menu-item` elements).
5
+ *
6
+ * ## When to use
7
+ * - A popup list of actions/options triggered by another control (e.g. a "more options" kebab
8
+ * button), such as `bs-chatbot-response-action`'s `menu` slot.
9
+ *
10
+ * ## When not to use
11
+ * - A persistent, always-visible list of options -- this component is purpose-built as a popup
12
+ * surface (rounded corners, elevation shadow), not a plain list.
13
+ *
14
+ * @slot - Default slot: the menu's items (typically `bs-menu-item` elements, though anything can
15
+ * be slotted in -- this component doesn't restrict slot content via JS).
16
+ * @part list - The rounded, elevated box wrapping the slotted items.
17
+ * @prop --bs-menu-bg - Background of the menu box. Aliased to `--bs-surface-raised`.
18
+ * @prop --bs-menu-radius - Corner radius of the menu box. Aliased to `--bs-border-radius-100`.
19
+ * @prop --bs-menu-padding-y - Top/bottom padding of the menu box. Aliased to `--bs-spacing-50`.
20
+ * @prop --bs-menu-gap - Gap between slotted items. Aliased to `--bs-spacing-50`.
21
+ * @prop --bs-menu-shadow - Elevation shadow of the menu box. Aliased to `--bs-shadow-sm`.
22
+ */
23
+ export class BsMenu {
24
+ render() {
25
+ return (h("div", { key: 'd4eee73bfffd1fcfd8f057e394a53fea4688748b', part: "list", class: "bs-menu", role: "menu" }, h("slot", { key: '866189bfabc2798ce37b8f0f6141143d4c49081e' })));
26
+ }
27
+ static get is() { return "bs-menu"; }
28
+ static get encapsulation() { return "shadow"; }
29
+ static get originalStyleUrls() {
30
+ return {
31
+ "$": ["bs-menu.css"]
32
+ };
33
+ }
34
+ static get styleUrls() {
35
+ return {
36
+ "$": ["bs-menu.css"]
37
+ };
38
+ }
39
+ }
@@ -0,0 +1,15 @@
1
+ import { html } from "lit";
2
+ import { componentDescription } from "../../stories-utils";
3
+ const meta = {
4
+ title: 'Components/bs-menu',
5
+ parameters: { docs: { description: { component: componentDescription('bs-menu') } } },
6
+ render: () => html `
7
+ <bs-menu style="width: 210px;">
8
+ <bs-menu-item>Read aloud</bs-menu-item>
9
+ <bs-menu-item>Chat with a human</bs-menu-item>
10
+ <bs-menu-item>Raise a ticket</bs-menu-item>
11
+ </bs-menu>
12
+ `,
13
+ };
14
+ export default meta;
15
+ export const Default = {};
@@ -0,0 +1,33 @@
1
+ import { render, h, describe, it, expect } from "@stencil/vitest";
2
+ describe('bs-menu-item', () => {
3
+ it('renders a button with role="menuitem" and part="item"', async () => {
4
+ const { root } = await render(h("bs-menu-item", null, "Read aloud"));
5
+ const button = root.shadowRoot.querySelector('[part="item"]');
6
+ expect(button).not.toBeNull();
7
+ expect(button.tagName).toBe('BUTTON');
8
+ expect(button.getAttribute('role')).toBe('menuitem');
9
+ expect(button.getAttribute('type')).toBe('button');
10
+ });
11
+ it('renders the label slot content', async () => {
12
+ const { root } = await render(h("bs-menu-item", null, "Read aloud"));
13
+ expect(root).toHaveTextContent('Read aloud');
14
+ });
15
+ it('does not reserve icon space when no icon is slotted', async () => {
16
+ const { root } = await render(h("bs-menu-item", null, "Read aloud"));
17
+ const icon = root.shadowRoot.querySelector('[part="icon"]');
18
+ expect(icon).not.toBeNull();
19
+ expect(icon).not.toHaveClass('bs-menu-item__icon--has-content');
20
+ expect(getComputedStyle(icon).display).toBe('none');
21
+ });
22
+ it('shows the icon wrapper when an icon is slotted', async () => {
23
+ const { root } = await render(h("bs-menu-item", null, h("span", { slot: "icon" }, "ICON"), "Read aloud"));
24
+ const icon = root.shadowRoot.querySelector('[part="icon"]');
25
+ expect(icon).toHaveClass('bs-menu-item__icon--has-content');
26
+ });
27
+ it('emits bsSelect when clicked', async () => {
28
+ const { root, spyOnEvent } = await render(h("bs-menu-item", null, "Read aloud"));
29
+ const selectSpy = spyOnEvent('bsSelect');
30
+ root.shadowRoot.querySelector('[part="item"]').click();
31
+ expect(selectSpy).toHaveReceivedEventTimes(1);
32
+ });
33
+ });
@@ -0,0 +1,68 @@
1
+ :host {
2
+ display: block;
3
+ --bs-menu-item-radius: var(--bs-border-radius-100);
4
+ --bs-menu-item-padding-y: var(--bs-spacing-100);
5
+ --bs-menu-item-padding-x: var(--bs-spacing-150);
6
+ --bs-menu-item-gap: var(--bs-spacing-100);
7
+ --bs-menu-item-hover: var(--bs-color-neutral-container);
8
+ --bs-menu-item-pressed: var(--bs-color-neutral-container-pressed);
9
+ --bs-menu-item-icon-color: var(--bs-icon-default);
10
+ --bs-menu-item-color: var(--bs-text-muted);
11
+ --bs-menu-item-font-size: var(--bs-font-size-md);
12
+ --bs-menu-item-line-height: var(--bs-line-height-body-md);
13
+ }
14
+
15
+ .bs-menu-item {
16
+ box-sizing: border-box;
17
+ display: flex;
18
+ align-items: center;
19
+ width: 100%;
20
+ height: 48px;
21
+ border: none;
22
+ cursor: pointer;
23
+ background: transparent;
24
+ color: var(--bs-menu-item-color);
25
+ font-family: inherit;
26
+ border-radius: var(--bs-menu-item-radius);
27
+ padding: var(--bs-menu-item-padding-y) var(--bs-menu-item-padding-x);
28
+ gap: var(--bs-menu-item-gap);
29
+ transition: background-color var(--bs-duration-fast) var(--bs-easing-standard);
30
+ }
31
+
32
+ .bs-menu-item:focus-visible {
33
+ outline: 2px solid var(--bs-border-neutral-focus);
34
+ outline-offset: 2px;
35
+ }
36
+
37
+ .bs-menu-item:hover {
38
+ background: var(--bs-menu-item-hover);
39
+ }
40
+
41
+ .bs-menu-item:active {
42
+ background: var(--bs-menu-item-pressed);
43
+ }
44
+
45
+ .bs-menu-item__icon {
46
+ display: none;
47
+ width: 24px;
48
+ height: 24px;
49
+ flex-shrink: 0;
50
+ color: var(--bs-menu-item-icon-color);
51
+ }
52
+
53
+ .bs-menu-item__icon--has-content {
54
+ display: inline-flex;
55
+ align-items: center;
56
+ justify-content: center;
57
+ }
58
+
59
+ .bs-menu-item__icon ::slotted(svg) {
60
+ width: 24px;
61
+ height: 24px;
62
+ }
63
+
64
+ .bs-menu-item__label {
65
+ font-size: var(--bs-menu-item-font-size);
66
+ line-height: calc(var(--bs-menu-item-line-height) * 1px);
67
+ white-space: nowrap;
68
+ }
@@ -0,0 +1,79 @@
1
+ import { h } from "@stencil/core";
2
+ /**
3
+ * A single selectable row inside a `bs-menu`: an optional icon plus a text label, rendered as a
4
+ * real `<button>` for correct keyboard/click semantics.
5
+ *
6
+ * ## When to use
7
+ * - As a child of `bs-menu`, one per selectable action/option.
8
+ *
9
+ * ## When not to use
10
+ * - Outside of `bs-menu` -- this component's sizing/hover treatment is designed to sit inside the
11
+ * menu's rounded, padded list box.
12
+ *
13
+ * @slot icon - Optional leading icon (24x24). When left empty, no icon space is reserved.
14
+ * @slot - Default slot: the item's text label.
15
+ * @part item - The button element.
16
+ * @part icon - The icon wrapper.
17
+ * @part label - The label wrapper.
18
+ * @prop --bs-menu-item-radius - Corner radius of the row. Aliased to `--bs-border-radius-100`.
19
+ * @prop --bs-menu-item-padding-y - Top/bottom padding. Aliased to `--bs-spacing-100`.
20
+ * @prop --bs-menu-item-padding-x - Left/right padding. Aliased to `--bs-spacing-150`.
21
+ * @prop --bs-menu-item-gap - Gap between icon and label. Aliased to `--bs-spacing-100`.
22
+ * @prop --bs-menu-item-hover - Background on hover. Aliased to `--bs-color-neutral-container`.
23
+ * @prop --bs-menu-item-pressed - Background when pressed. Aliased to `--bs-color-neutral-container-pressed`.
24
+ * @prop --bs-menu-item-icon-color - Icon color. Aliased to `--bs-icon-default`.
25
+ * @prop --bs-menu-item-color - Label text color. Aliased to `--bs-text-muted`.
26
+ * @prop --bs-menu-item-font-size - Aliased to `--bs-font-size-md`.
27
+ * @prop --bs-menu-item-line-height - Aliased to `--bs-line-height-body-md`.
28
+ */
29
+ export class BsMenuItem {
30
+ /** Tracks whether the `icon` slot has assigned content, so the icon wrapper (and its gap) only
31
+ * renders when there's actually an icon to show. */
32
+ hasIcon = false;
33
+ /** Fires when the item is clicked. */
34
+ bsSelect;
35
+ onIconSlotchange = (ev) => {
36
+ this.hasIcon = ev.target.assignedNodes().length > 0;
37
+ };
38
+ onClick = () => {
39
+ this.bsSelect.emit();
40
+ };
41
+ render() {
42
+ return (h("button", { key: '4d0e94afadd487e2ec44c0c6b416c27eb8f1a5f4', type: "button", part: "item", role: "menuitem", class: "bs-menu-item", onClick: this.onClick }, h("span", { key: 'a436154aaddddadc3944049597a87a2b52eb61cb', part: "icon", class: `bs-menu-item__icon ${this.hasIcon ? 'bs-menu-item__icon--has-content' : ''}` }, h("slot", { key: '50ad9c1b78bc8ea55219ad7692d7ef7fa7e48189', name: "icon", onSlotchange: this.onIconSlotchange })), h("span", { key: 'b3d1ee83556f10de5ad08d6f212ebfd9531dd54e', part: "label", class: "bs-menu-item__label" }, h("slot", { key: '5fe02e9b2407c4fd9eaf699caec9ecc9c0e25575' }))));
43
+ }
44
+ static get is() { return "bs-menu-item"; }
45
+ static get encapsulation() { return "shadow"; }
46
+ static get originalStyleUrls() {
47
+ return {
48
+ "$": ["bs-menu-item.css"]
49
+ };
50
+ }
51
+ static get styleUrls() {
52
+ return {
53
+ "$": ["bs-menu-item.css"]
54
+ };
55
+ }
56
+ static get states() {
57
+ return {
58
+ "hasIcon": {}
59
+ };
60
+ }
61
+ static get events() {
62
+ return [{
63
+ "method": "bsSelect",
64
+ "name": "bsSelect",
65
+ "bubbles": true,
66
+ "cancelable": true,
67
+ "composed": true,
68
+ "docs": {
69
+ "tags": [],
70
+ "text": "Fires when the item is clicked."
71
+ },
72
+ "complexType": {
73
+ "original": "void",
74
+ "resolved": "void",
75
+ "references": {}
76
+ }
77
+ }];
78
+ }
79
+ }
@@ -0,0 +1,37 @@
1
+ import { html } from "lit";
2
+ import { componentDescription } from "../../stories-utils";
3
+ const meta = {
4
+ title: 'Components/bs-menu-item',
5
+ parameters: { docs: { description: { component: componentDescription('bs-menu-item') } } },
6
+ render: () => html ` <bs-menu-item style="width: 210px;">Read aloud</bs-menu-item> `,
7
+ };
8
+ export default meta;
9
+ export const Default = {};
10
+ export const WithIcon = {
11
+ name: 'With an icon',
12
+ render: () => html `
13
+ <bs-menu-item style="width: 210px;">
14
+ <svg slot="icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
15
+ <path
16
+ d="M7.5 15.75H3C2.80109 15.75 2.61032 15.671 2.46967 15.5303C2.32902 15.3897 2.25 15.1989 2.25 15V9C2.25 8.80109 2.32902 8.61032 2.46967 8.46967C2.61032 8.32902 2.80109 8.25 3 8.25H7.5L14.25 3V21L7.5 15.75Z"
17
+ stroke="currentColor"
18
+ stroke-width="2"
19
+ stroke-linecap="round"
20
+ stroke-linejoin="round"
21
+ />
22
+ <path d="M7.5 8.25V15.75" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
23
+ </svg>
24
+ Read aloud
25
+ </bs-menu-item>
26
+ `,
27
+ };
28
+ export const InAMenu = {
29
+ name: 'In a bs-menu',
30
+ render: () => html `
31
+ <bs-menu style="width: 210px;">
32
+ <bs-menu-item>Read aloud</bs-menu-item>
33
+ <bs-menu-item>Chat with a human</bs-menu-item>
34
+ <bs-menu-item>Raise a ticket</bs-menu-item>
35
+ </bs-menu>
36
+ `,
37
+ };