@brandsync/wc 0.0.7 → 0.0.9

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 (195) hide show
  1. package/custom-elements.json +1810 -355
  2. package/dist/brandsync-wc/brandsync-wc.esm.js +1 -1
  3. package/dist/brandsync-wc/p-0d64ea40.entry.js +1 -0
  4. package/dist/brandsync-wc/{p-c63d16e1.entry.js → p-1515498e.entry.js} +1 -1
  5. package/dist/brandsync-wc/p-16a54578.entry.js +1 -0
  6. package/dist/brandsync-wc/p-18af02df.entry.js +1 -0
  7. package/dist/brandsync-wc/p-30a0e168.entry.js +1 -0
  8. package/dist/brandsync-wc/p-3dacae70.entry.js +1 -0
  9. package/dist/brandsync-wc/p-5333bb5a.entry.js +1 -0
  10. package/dist/brandsync-wc/p-575ce905.entry.js +1 -0
  11. package/dist/brandsync-wc/{p-dd0fea0e.entry.js → p-7776b57b.entry.js} +1 -1
  12. package/dist/brandsync-wc/{p-bfd09c98.entry.js → p-805644e4.entry.js} +1 -1
  13. package/dist/brandsync-wc/{p-f88d9a6e.entry.js → p-8a78d6ea.entry.js} +1 -1
  14. package/dist/brandsync-wc/p-8d12ed96.entry.js +1 -0
  15. package/dist/brandsync-wc/p-94ed898a.entry.js +1 -0
  16. package/dist/brandsync-wc/{p-9e5d2211.entry.js → p-96f374be.entry.js} +1 -1
  17. package/dist/brandsync-wc/{p-ab5cc7af.entry.js → p-a34f460d.entry.js} +1 -1
  18. package/dist/brandsync-wc/p-bc72439a.entry.js +1 -0
  19. package/dist/brandsync-wc/{p-76eca100.entry.js → p-ce0914a7.entry.js} +1 -1
  20. package/dist/brandsync-wc/{p-5a0fe72e.entry.js → p-d682675e.entry.js} +1 -1
  21. package/dist/brandsync-wc/p-dad4a92c.entry.js +1 -0
  22. package/dist/brandsync-wc/{p-cfc3421b.entry.js → p-dd747a12.entry.js} +1 -1
  23. package/dist/brandsync-wc/{p-2742ecef.entry.js → p-eb4d87f0.entry.js} +1 -1
  24. package/dist/cjs/brandsync-wc.cjs.js +1 -1
  25. package/dist/cjs/bs-ai-disclaimer.cjs.entry.js +17 -0
  26. package/dist/cjs/bs-ai-greeting.cjs.entry.js +24 -0
  27. package/dist/cjs/bs-ai-thinking.cjs.entry.js +2 -2
  28. package/dist/cjs/bs-attachment-list.cjs.entry.js +47 -0
  29. package/dist/cjs/bs-attachment.cjs.entry.js +46 -0
  30. package/dist/cjs/bs-badge.cjs.entry.js +1 -1
  31. package/dist/cjs/bs-button-skeleton.cjs.entry.js +1 -1
  32. package/dist/cjs/bs-button.cjs.entry.js +1 -1
  33. package/dist/cjs/bs-card.cjs.entry.js +1 -1
  34. package/dist/cjs/bs-chatbot-feedback.cjs.entry.js +94 -0
  35. package/dist/cjs/bs-chatbot-header.cjs.entry.js +1 -1
  36. package/dist/cjs/bs-chatbot-response-action.cjs.entry.js +1 -1
  37. package/dist/cjs/bs-chatbot-sources-drawer.cjs.entry.js +38 -0
  38. package/dist/cjs/bs-chatbot-suggestion-button.cjs.entry.js +1 -1
  39. package/dist/cjs/bs-composer-status-banner.cjs.entry.js +43 -0
  40. package/dist/cjs/bs-composer.cjs.entry.js +16 -2
  41. package/dist/cjs/bs-data-table.cjs.entry.js +2 -2
  42. package/dist/cjs/bs-input.cjs.entry.js +270 -4
  43. package/dist/cjs/{bs-menu-item.cjs.entry.js → bs-menu_2.cjs.entry.js} +14 -1
  44. package/dist/cjs/bs-source-link.cjs.entry.js +39 -0
  45. package/dist/cjs/bs-tooltip.cjs.entry.js +1 -1
  46. package/dist/cjs/loader.cjs.js +1 -1
  47. package/dist/collection/collection-manifest.json +8 -0
  48. package/dist/collection/components/bs-ai-chatbot/bs-ai-chatbot.stories.js +180 -0
  49. package/dist/collection/components/bs-ai-disclaimer/bs-ai-disclaimer.cmp.test.js +14 -0
  50. package/dist/collection/components/bs-ai-disclaimer/bs-ai-disclaimer.css +27 -0
  51. package/dist/collection/components/bs-ai-disclaimer/bs-ai-disclaimer.js +41 -0
  52. package/dist/collection/components/bs-ai-disclaimer/bs-ai-disclaimer.stories.js +15 -0
  53. package/dist/collection/components/bs-ai-greeting/bs-ai-greeting.cmp.test.js +22 -0
  54. package/dist/collection/components/bs-ai-greeting/bs-ai-greeting.css +53 -0
  55. package/dist/collection/components/bs-ai-greeting/bs-ai-greeting.js +103 -0
  56. package/dist/collection/components/bs-ai-greeting/bs-ai-greeting.stories.js +23 -0
  57. package/dist/collection/components/bs-ai-thinking/bs-ai-thinking.css +31 -17
  58. package/dist/collection/components/bs-ai-thinking/bs-ai-thinking.js +9 -11
  59. package/dist/collection/components/bs-attachment/bs-attachment.cmp.test.js +41 -0
  60. package/dist/collection/components/bs-attachment/bs-attachment.css +184 -0
  61. package/dist/collection/components/bs-attachment/bs-attachment.js +230 -0
  62. package/dist/collection/components/bs-attachment/bs-attachment.stories.js +58 -0
  63. package/dist/collection/components/bs-attachment-list/bs-attachment-list.cmp.test.js +28 -0
  64. package/dist/collection/components/bs-attachment-list/bs-attachment-list.css +56 -0
  65. package/dist/collection/components/bs-attachment-list/bs-attachment-list.js +105 -0
  66. package/dist/collection/components/bs-attachment-list/bs-attachment-list.stories.js +47 -0
  67. package/dist/collection/components/bs-badge/bs-badge.js +1 -1
  68. package/dist/collection/components/bs-button/bs-button.js +1 -1
  69. package/dist/collection/components/bs-button-skeleton/bs-button-skeleton.js +1 -1
  70. package/dist/collection/components/bs-card/bs-card.js +1 -1
  71. package/dist/collection/components/bs-chatbot-feedback/bs-chatbot-feedback.cmp.test.js +73 -0
  72. package/dist/collection/components/bs-chatbot-feedback/bs-chatbot-feedback.css +253 -0
  73. package/dist/collection/components/bs-chatbot-feedback/bs-chatbot-feedback.js +324 -0
  74. package/dist/collection/components/bs-chatbot-feedback/bs-chatbot-feedback.stories.js +54 -0
  75. package/dist/collection/components/bs-chatbot-header/bs-chatbot-header.js +1 -1
  76. package/dist/collection/components/bs-chatbot-response-action/bs-chatbot-response-action.js +1 -1
  77. package/dist/collection/components/bs-chatbot-sources-drawer/bs-chatbot-sources-drawer.cmp.test.js +34 -0
  78. package/dist/collection/components/bs-chatbot-sources-drawer/bs-chatbot-sources-drawer.css +106 -0
  79. package/dist/collection/components/bs-chatbot-sources-drawer/bs-chatbot-sources-drawer.js +142 -0
  80. package/dist/collection/components/bs-chatbot-sources-drawer/bs-chatbot-sources-drawer.stories.js +44 -0
  81. package/dist/collection/components/bs-chatbot-suggestion-button/bs-chatbot-suggestion-button.js +1 -1
  82. package/dist/collection/components/bs-composer/bs-composer.cmp.test.js +12 -0
  83. package/dist/collection/components/bs-composer/bs-composer.css +10 -0
  84. package/dist/collection/components/bs-composer/bs-composer.js +25 -1
  85. package/dist/collection/components/bs-composer/bs-composer.stories.js +13 -0
  86. package/dist/collection/components/bs-composer-status-banner/bs-composer-status-banner.cmp.test.js +42 -0
  87. package/dist/collection/components/bs-composer-status-banner/bs-composer-status-banner.css +173 -0
  88. package/dist/collection/components/bs-composer-status-banner/bs-composer-status-banner.js +270 -0
  89. package/dist/collection/components/bs-composer-status-banner/bs-composer-status-banner.stories.js +75 -0
  90. package/dist/collection/components/bs-data-table/bs-data-table.js +2 -2
  91. package/dist/collection/components/bs-input/bs-input.cmp.test.js +296 -0
  92. package/dist/collection/components/bs-input/bs-input.css +333 -23
  93. package/dist/collection/components/bs-input/bs-input.js +647 -11
  94. package/dist/collection/components/bs-input/bs-input.stories.js +128 -1
  95. package/dist/collection/components/bs-menu/bs-menu.js +1 -1
  96. package/dist/collection/components/bs-menu-item/bs-menu-item.js +1 -1
  97. package/dist/collection/components/bs-source-link/bs-source-link.cmp.test.js +42 -0
  98. package/dist/collection/components/bs-source-link/bs-source-link.css +114 -0
  99. package/dist/collection/components/bs-source-link/bs-source-link.js +192 -0
  100. package/dist/collection/components/bs-source-link/bs-source-link.stories.js +33 -0
  101. package/dist/collection/components/bs-tooltip/bs-tooltip.js +1 -1
  102. package/dist/components/bs-ai-disclaimer.d.ts +11 -0
  103. package/dist/components/bs-ai-disclaimer.js +1 -0
  104. package/dist/components/bs-ai-greeting.d.ts +11 -0
  105. package/dist/components/bs-ai-greeting.js +1 -0
  106. package/dist/components/bs-ai-thinking.js +1 -1
  107. package/dist/components/bs-attachment-list.d.ts +11 -0
  108. package/dist/components/bs-attachment-list.js +1 -0
  109. package/dist/components/bs-attachment.d.ts +11 -0
  110. package/dist/components/bs-attachment.js +1 -0
  111. package/dist/components/bs-badge.js +1 -1
  112. package/dist/components/bs-button-skeleton.js +1 -1
  113. package/dist/components/bs-button.js +1 -1
  114. package/dist/components/bs-card.js +1 -1
  115. package/dist/components/bs-chatbot-feedback.d.ts +11 -0
  116. package/dist/components/bs-chatbot-feedback.js +1 -0
  117. package/dist/components/bs-chatbot-header.js +1 -1
  118. package/dist/components/bs-chatbot-response-action.js +1 -1
  119. package/dist/components/bs-chatbot-sources-drawer.d.ts +11 -0
  120. package/dist/components/bs-chatbot-sources-drawer.js +1 -0
  121. package/dist/components/bs-chatbot-suggestion-button.js +1 -1
  122. package/dist/components/bs-composer-status-banner.d.ts +11 -0
  123. package/dist/components/bs-composer-status-banner.js +1 -0
  124. package/dist/components/bs-composer.js +1 -1
  125. package/dist/components/bs-data-table.js +1 -1
  126. package/dist/components/bs-input.js +1 -1
  127. package/dist/components/bs-menu-item.js +1 -1
  128. package/dist/components/bs-menu.js +1 -1
  129. package/dist/components/bs-source-link.d.ts +11 -0
  130. package/dist/components/bs-source-link.js +1 -0
  131. package/dist/components/bs-tooltip.js +1 -1
  132. package/dist/components/{p-CoC4LQCI.js → p-Cz2nfz2j.js} +1 -1
  133. package/dist/components/p-DndQiCMP.js +1 -0
  134. package/dist/components/p-_vXDfB3V.js +1 -0
  135. package/dist/esm/brandsync-wc.js +1 -1
  136. package/dist/esm/bs-ai-disclaimer.entry.js +15 -0
  137. package/dist/esm/bs-ai-greeting.entry.js +22 -0
  138. package/dist/esm/bs-ai-thinking.entry.js +2 -2
  139. package/dist/esm/bs-attachment-list.entry.js +45 -0
  140. package/dist/esm/bs-attachment.entry.js +44 -0
  141. package/dist/esm/bs-badge.entry.js +1 -1
  142. package/dist/esm/bs-button-skeleton.entry.js +1 -1
  143. package/dist/esm/bs-button.entry.js +1 -1
  144. package/dist/esm/bs-card.entry.js +1 -1
  145. package/dist/esm/bs-chatbot-feedback.entry.js +92 -0
  146. package/dist/esm/bs-chatbot-header.entry.js +1 -1
  147. package/dist/esm/bs-chatbot-response-action.entry.js +1 -1
  148. package/dist/esm/bs-chatbot-sources-drawer.entry.js +36 -0
  149. package/dist/esm/bs-chatbot-suggestion-button.entry.js +1 -1
  150. package/dist/esm/bs-composer-status-banner.entry.js +41 -0
  151. package/dist/esm/bs-composer.entry.js +17 -3
  152. package/dist/esm/bs-data-table.entry.js +2 -2
  153. package/dist/esm/bs-input.entry.js +271 -5
  154. package/dist/esm/{bs-menu-item.entry.js → bs-menu_2.entry.js} +15 -3
  155. package/dist/esm/bs-source-link.entry.js +37 -0
  156. package/dist/esm/bs-tooltip.entry.js +1 -1
  157. package/dist/esm/loader.js +1 -1
  158. package/dist/types/components/bs-ai-chatbot/bs-ai-chatbot.stories.d.ts +18 -0
  159. package/dist/types/components/bs-ai-disclaimer/bs-ai-disclaimer.cmp.test.d.ts +1 -0
  160. package/dist/types/components/bs-ai-disclaimer/bs-ai-disclaimer.d.ts +26 -0
  161. package/dist/types/components/bs-ai-disclaimer/bs-ai-disclaimer.stories.d.ts +8 -0
  162. package/dist/types/components/bs-ai-greeting/bs-ai-greeting.cmp.test.d.ts +1 -0
  163. package/dist/types/components/bs-ai-greeting/bs-ai-greeting.d.ts +42 -0
  164. package/dist/types/components/bs-ai-greeting/bs-ai-greeting.stories.d.ts +10 -0
  165. package/dist/types/components/bs-ai-thinking/bs-ai-thinking.d.ts +8 -10
  166. package/dist/types/components/bs-attachment/bs-attachment.cmp.test.d.ts +1 -0
  167. package/dist/types/components/bs-attachment/bs-attachment.d.ts +59 -0
  168. package/dist/types/components/bs-attachment/bs-attachment.stories.d.ts +17 -0
  169. package/dist/types/components/bs-attachment-list/bs-attachment-list.cmp.test.d.ts +1 -0
  170. package/dist/types/components/bs-attachment-list/bs-attachment-list.d.ts +42 -0
  171. package/dist/types/components/bs-attachment-list/bs-attachment-list.stories.d.ts +10 -0
  172. package/dist/types/components/bs-chatbot-feedback/bs-chatbot-feedback.cmp.test.d.ts +1 -0
  173. package/dist/types/components/bs-chatbot-feedback/bs-chatbot-feedback.d.ts +90 -0
  174. package/dist/types/components/bs-chatbot-feedback/bs-chatbot-feedback.stories.d.ts +16 -0
  175. package/dist/types/components/bs-chatbot-sources-drawer/bs-chatbot-sources-drawer.cmp.test.d.ts +1 -0
  176. package/dist/types/components/bs-chatbot-sources-drawer/bs-chatbot-sources-drawer.d.ts +54 -0
  177. package/dist/types/components/bs-chatbot-sources-drawer/bs-chatbot-sources-drawer.stories.d.ts +10 -0
  178. package/dist/types/components/bs-composer/bs-composer.d.ts +14 -0
  179. package/dist/types/components/bs-composer/bs-composer.stories.d.ts +1 -0
  180. package/dist/types/components/bs-composer-status-banner/bs-composer-status-banner.cmp.test.d.ts +1 -0
  181. package/dist/types/components/bs-composer-status-banner/bs-composer-status-banner.d.ts +88 -0
  182. package/dist/types/components/bs-composer-status-banner/bs-composer-status-banner.stories.d.ts +20 -0
  183. package/dist/types/components/bs-input/bs-input.d.ts +139 -6
  184. package/dist/types/components/bs-input/bs-input.stories.d.ts +19 -0
  185. package/dist/types/components/bs-source-link/bs-source-link.cmp.test.d.ts +1 -0
  186. package/dist/types/components/bs-source-link/bs-source-link.d.ts +51 -0
  187. package/dist/types/components/bs-source-link/bs-source-link.stories.d.ts +13 -0
  188. package/dist/types/components.d.ts +1778 -55
  189. package/package.json +1 -1
  190. package/dist/brandsync-wc/p-6abdfaf0.entry.js +0 -1
  191. package/dist/brandsync-wc/p-6c1fa4da.entry.js +0 -1
  192. package/dist/brandsync-wc/p-a12d0a60.entry.js +0 -1
  193. package/dist/brandsync-wc/p-f5f443e2.entry.js +0 -1
  194. package/dist/cjs/bs-menu.cjs.entry.js +0 -17
  195. package/dist/esm/bs-menu.entry.js +0 -15
@@ -0,0 +1,180 @@
1
+ import { html } from "lit";
2
+ /**
3
+ * This is not a real component -- `bs-ai-chatbot` isn't a registered custom element. It's a
4
+ * showcase composing the existing Genie AI components (bs-chatbot-header, bs-ai-greeting,
5
+ * bs-chatbot-suggestion-button, bs-composer, bs-composer-status-banner, bs-attachment-list,
6
+ * bs-ai-thinking, bs-chatbot-response-action, bs-chatbot-sources-drawer, bs-ai-disclaimer) into
7
+ * realistic full-panel use cases, since no single component's own story shows how they fit
8
+ * together end to end.
9
+ */
10
+ const meta = {
11
+ title: 'Genie AI Components/bs-ai-chatbot',
12
+ parameters: { docs: { description: { component: 'A composed showcase of Genie AI components in realistic full-chat-panel use cases. Not a real custom element.' } } },
13
+ };
14
+ export default meta;
15
+ const PANEL_STYLE = `
16
+ display: flex;
17
+ flex-direction: column;
18
+ width: 420px;
19
+ height: 640px;
20
+ border: 1px solid var(--bs-border-default);
21
+ border-radius: var(--bs-border-radius-200);
22
+ overflow: hidden;
23
+ background: var(--bs-surface-base);
24
+ font-family: var(--bs-typography-font-family-body), sans-serif;
25
+ `;
26
+ const TRANSCRIPT_STYLE = `
27
+ flex: 1;
28
+ min-height: 0;
29
+ overflow-y: auto;
30
+ display: flex;
31
+ flex-direction: column;
32
+ gap: var(--bs-spacing-200);
33
+ padding: var(--bs-spacing-200);
34
+ `;
35
+ const USER_BUBBLE_STYLE = `
36
+ align-self: flex-end;
37
+ max-width: 80%;
38
+ padding: var(--bs-spacing-100) var(--bs-spacing-150);
39
+ border-radius: var(--bs-border-radius-150);
40
+ background: var(--bs-color-primary-container);
41
+ color: var(--bs-text-default);
42
+ font-size: var(--bs-font-size-md);
43
+ line-height: 1.5;
44
+ `;
45
+ const AI_RESPONSE_STYLE = `
46
+ max-width: 100%;
47
+ color: var(--bs-text-default);
48
+ font-size: var(--bs-font-size-md);
49
+ line-height: 1.5;
50
+ `;
51
+ export const EmptyState = {
52
+ name: 'Empty state',
53
+ render: () => html `
54
+ <div style=${PANEL_STYLE}>
55
+ <bs-chatbot-header heading="Genie"></bs-chatbot-header>
56
+ <div style="${TRANSCRIPT_STYLE} align-items: center; justify-content: center; gap: var(--bs-spacing-400);">
57
+ <bs-ai-greeting product-name="BrandSync"></bs-ai-greeting>
58
+ <div style="display: flex; flex-wrap: wrap; gap: var(--bs-spacing-100); justify-content: center;">
59
+ <bs-chatbot-suggestion-button>How do I reset my password?</bs-chatbot-suggestion-button>
60
+ <bs-chatbot-suggestion-button>Summarize this document</bs-chatbot-suggestion-button>
61
+ <bs-chatbot-suggestion-button>What's my leave balance?</bs-chatbot-suggestion-button>
62
+ </div>
63
+ </div>
64
+ <bs-composer aria-label="Message" style="margin: var(--bs-spacing-100) var(--bs-spacing-200) 0;"></bs-composer>
65
+ <bs-ai-disclaimer style="padding: var(--bs-spacing-100) 0;">AI can make mistakes. Please verify important information.</bs-ai-disclaimer>
66
+ </div>
67
+ `,
68
+ };
69
+ export const ActiveConversation = {
70
+ name: 'Active conversation',
71
+ render: () => html `
72
+ <div style=${PANEL_STYLE}>
73
+ <bs-chatbot-header heading="Genie"></bs-chatbot-header>
74
+ <div style=${TRANSCRIPT_STYLE}>
75
+ <p style=${USER_BUBBLE_STYLE}>How do I submit a ByggSøk application?</p>
76
+ <div style="display: flex; flex-direction: column; gap: var(--bs-spacing-50);">
77
+ <p style=${AI_RESPONSE_STYLE}>
78
+ You can submit a ByggSøk application through the municipal portal. Log in with your ID, select "New
79
+ application", and attach your building plans before submitting for review.
80
+ </p>
81
+ <bs-chatbot-response-action sources-count="3"></bs-chatbot-response-action>
82
+ </div>
83
+ </div>
84
+ <bs-composer aria-label="Message" style="margin: var(--bs-spacing-100) var(--bs-spacing-200) 0;"></bs-composer>
85
+ <bs-ai-disclaimer style="padding: var(--bs-spacing-100) 0;">AI can make mistakes. Please verify important information.</bs-ai-disclaimer>
86
+ </div>
87
+ `,
88
+ };
89
+ export const Thinking = {
90
+ name: 'AI is thinking',
91
+ render: () => html `
92
+ <div style=${PANEL_STYLE}>
93
+ <bs-chatbot-header heading="Genie"></bs-chatbot-header>
94
+ <div style=${TRANSCRIPT_STYLE}>
95
+ <p style=${USER_BUBBLE_STYLE}>How do I submit a ByggSøk application?</p>
96
+ <bs-ai-thinking label="Retrieving sources"></bs-ai-thinking>
97
+ </div>
98
+ <bs-composer aria-label="Message" state="generating" style="margin: var(--bs-spacing-100) var(--bs-spacing-200) 0;"></bs-composer>
99
+ <bs-ai-disclaimer style="padding: var(--bs-spacing-100) 0;">AI can make mistakes. Please verify important information.</bs-ai-disclaimer>
100
+ </div>
101
+ `,
102
+ };
103
+ export const WithSourcesDrawerOpen = {
104
+ name: 'Sources drawer open',
105
+ render: () => html `
106
+ <div style="${PANEL_STYLE} position: relative;">
107
+ <bs-chatbot-header heading="Genie"></bs-chatbot-header>
108
+ <div style=${TRANSCRIPT_STYLE}>
109
+ <p style=${USER_BUBBLE_STYLE}>How do I submit a ByggSøk application?</p>
110
+ <div style="display: flex; flex-direction: column; gap: var(--bs-spacing-50);">
111
+ <p style=${AI_RESPONSE_STYLE}>
112
+ You can submit a ByggSøk application through the municipal portal. Log in with your ID, select "New
113
+ application", and attach your building plans before submitting for review.
114
+ </p>
115
+ <bs-chatbot-response-action sources-count="3"></bs-chatbot-response-action>
116
+ </div>
117
+ </div>
118
+ <bs-composer aria-label="Message" style="margin: var(--bs-spacing-100) var(--bs-spacing-200) 0;"></bs-composer>
119
+ <bs-ai-disclaimer style="padding: var(--bs-spacing-100) 0;">AI can make mistakes. Please verify important information.</bs-ai-disclaimer>
120
+
121
+ <!-- Backdrop -->
122
+ <div style="position: absolute; inset: 0; background: rgba(33, 38, 46, 0.32);"></div>
123
+
124
+ <!-- Slide-in drawer, docked to the panel's own right edge -- not a separate side-by-side
125
+ column, so it stays within the chat panel's own bounds like a real in-app drawer. -->
126
+ <div style="position: absolute; top: 0; right: 0; bottom: 0; width: 85%; box-shadow: var(--bs-shadow-lg); background: var(--bs-surface-raised);">
127
+ <bs-chatbot-sources-drawer heading="Sources">
128
+ <bs-source-link file-name="Employee Handbook 2026" source-name="HoltePortalen" version="v3.2"></bs-source-link>
129
+ <bs-source-link file-name="Payroll Processing Guide" source-name="WorkdayHQ" version="v2.4"></bs-source-link>
130
+ <bs-source-link file-name="Benefits & Leave Policy" source-name="PeopleOps Wiki" version="v1.8"></bs-source-link>
131
+ </bs-chatbot-sources-drawer>
132
+ </div>
133
+ </div>
134
+ `,
135
+ };
136
+ export const UploadingAttachments = {
137
+ name: 'Uploading attachments',
138
+ render: () => html `
139
+ <div style=${PANEL_STYLE}>
140
+ <bs-chatbot-header heading="Genie"></bs-chatbot-header>
141
+ <div style=${TRANSCRIPT_STYLE}></div>
142
+ <div style="margin: 0 var(--bs-spacing-200);">
143
+ <bs-composer-status-banner
144
+ type="info"
145
+ message="Uploading files, please wait."
146
+ style="margin-bottom: calc(-1 * var(--bs-spacing-150));"
147
+ ></bs-composer-status-banner>
148
+ <bs-composer aria-label="Message">
149
+ <bs-attachment-list slot="attachments">
150
+ <bs-attachment type="image" image-src="https://images.unsplash.com/photo-1512917774080-9991f1c4c750?w=200&h=200&fit=crop" loading></bs-attachment>
151
+ <bs-attachment type="pdf" file-name="Dummy-pdf-in-here" loading></bs-attachment>
152
+ </bs-attachment-list>
153
+ </bs-composer>
154
+ </div>
155
+ <bs-ai-disclaimer style="padding: var(--bs-spacing-100) 0;">AI can make mistakes. Please verify important information.</bs-ai-disclaimer>
156
+ </div>
157
+ `,
158
+ };
159
+ export const SendFailed = {
160
+ name: 'Send failed',
161
+ render: () => html `
162
+ <div style=${PANEL_STYLE}>
163
+ <bs-chatbot-header heading="Genie"></bs-chatbot-header>
164
+ <div style=${TRANSCRIPT_STYLE}>
165
+ <p style=${USER_BUBBLE_STYLE}>How do I submit a ByggSøk application?</p>
166
+ </div>
167
+ <div style="margin: 0 var(--bs-spacing-200);">
168
+ <bs-composer-status-banner
169
+ type="error"
170
+ message="Message failed to send"
171
+ show-button
172
+ action-label="Retry"
173
+ style="margin-bottom: calc(-1 * var(--bs-spacing-150));"
174
+ ></bs-composer-status-banner>
175
+ <bs-composer aria-label="Message"></bs-composer>
176
+ </div>
177
+ <bs-ai-disclaimer style="padding: var(--bs-spacing-100) 0;">AI can make mistakes. Please verify important information.</bs-ai-disclaimer>
178
+ </div>
179
+ `,
180
+ };
@@ -0,0 +1,14 @@
1
+ import { render, h, describe, it, expect } from "@stencil/vitest";
2
+ describe('bs-ai-disclaimer', () => {
3
+ it('renders the default slot content inside the text part', async () => {
4
+ const { root } = await render(h("bs-ai-disclaimer", null, "AI can make mistakes. Please verify important information."));
5
+ expect(root).toHaveTextContent('AI can make mistakes. Please verify important information.');
6
+ const text = root.shadowRoot.querySelector('[part="text"]');
7
+ expect(text).not.toBeNull();
8
+ expect(text.tagName).toBe('P');
9
+ });
10
+ it('renders arbitrary slotted content, e.g. a link', async () => {
11
+ const { root } = await render(h("bs-ai-disclaimer", null, "AI can make mistakes. ", h("a", { href: "/policy" }, "Learn more")));
12
+ expect(root.querySelector('a')).not.toBeNull();
13
+ });
14
+ });
@@ -0,0 +1,27 @@
1
+ :host {
2
+ display: block;
3
+ --bs-ai-disclaimer-padding-x: var(--bs-spacing-200);
4
+ --bs-ai-disclaimer-text-color: var(--bs-text-secondary);
5
+ --bs-ai-disclaimer-font-size: var(--bs-font-size-sm);
6
+ --bs-ai-disclaimer-line-height: var(--bs-line-height-body-sm);
7
+ }
8
+
9
+ .bs-ai-disclaimer {
10
+ box-sizing: border-box;
11
+ display: flex;
12
+ align-items: center;
13
+ justify-content: center;
14
+ padding: 0 var(--bs-ai-disclaimer-padding-x);
15
+ font-family: inherit;
16
+ }
17
+
18
+ .bs-ai-disclaimer__text {
19
+ margin: 0;
20
+ flex: 1 0 0;
21
+ min-width: 0;
22
+ color: var(--bs-ai-disclaimer-text-color);
23
+ font-size: var(--bs-ai-disclaimer-font-size);
24
+ line-height: calc(var(--bs-ai-disclaimer-line-height) * 1px);
25
+ text-align: center;
26
+ word-break: break-word;
27
+ }
@@ -0,0 +1,41 @@
1
+ import { h } from "@stencil/core";
2
+ /**
3
+ * A centered caption disclaimer for Genie AI surfaces, e.g. "AI can make mistakes. Please verify
4
+ * important information."
5
+ *
6
+ * ## When to use
7
+ * - Below or above `bs-composer` in a Genie AI chat panel, to remind users AI output can be wrong.
8
+ *
9
+ * ## When not to use
10
+ * - A general-purpose caption/helper text — use plain text or `bs-input`'s description slot for
11
+ * non-AI-related helper copy.
12
+ *
13
+ * The default slot (rather than a fixed prop) is intentional: Figma only specifies plain text, but
14
+ * some consuming apps link out to a policy/help page from this copy, and a slot supports that
15
+ * without inventing an unconfirmed `href`/link prop.
16
+ *
17
+ * @slot - The disclaimer text/content, centered. Defaults to no content -- the consumer supplies
18
+ * the copy, e.g. "AI can make mistakes. Please verify important information."
19
+ * @part text - The disclaimer text container.
20
+ * @prop --bs-ai-disclaimer-padding-x - Horizontal padding. Aliased to `--bs-spacing-200`.
21
+ * @prop --bs-ai-disclaimer-text-color - Text color. Aliased to `--bs-text-secondary`.
22
+ * @prop --bs-ai-disclaimer-font-size - Text font size. Aliased to `--bs-font-size-sm`.
23
+ * @prop --bs-ai-disclaimer-line-height - Text line height. Aliased to `--bs-line-height-body-sm`.
24
+ */
25
+ export class BsAiDisclaimer {
26
+ render() {
27
+ return (h("div", { key: 'c306fa7e8f7f9dca07642e1038708847847e2d7e', class: "bs-ai-disclaimer" }, h("p", { key: '7f42e2094e7baf287c886ce966a3813657a99a61', part: "text", class: "bs-ai-disclaimer__text" }, h("slot", { key: 'c5a66f6093b413274c20de59e7f82d4f7372b56c' }))));
28
+ }
29
+ static get is() { return "bs-ai-disclaimer"; }
30
+ static get encapsulation() { return "shadow"; }
31
+ static get originalStyleUrls() {
32
+ return {
33
+ "$": ["bs-ai-disclaimer.css"]
34
+ };
35
+ }
36
+ static get styleUrls() {
37
+ return {
38
+ "$": ["bs-ai-disclaimer.css"]
39
+ };
40
+ }
41
+ }
@@ -0,0 +1,15 @@
1
+ import { html } from "lit";
2
+ import { componentDescription } from "../../stories-utils";
3
+ const meta = {
4
+ title: 'Genie AI Components/bs-ai-disclaimer',
5
+ parameters: { docs: { description: { component: componentDescription('bs-ai-disclaimer') } } },
6
+ render: args => html ` <bs-ai-disclaimer>${args.text}</bs-ai-disclaimer> `,
7
+ argTypes: {
8
+ text: { control: 'text', description: 'Default slot: the disclaimer text/content.' },
9
+ },
10
+ args: {
11
+ text: 'AI can make mistakes. Please verify important information.',
12
+ },
13
+ };
14
+ export default meta;
15
+ export const Default = {};
@@ -0,0 +1,22 @@
1
+ import { render, h, describe, it, expect } from "@stencil/vitest";
2
+ describe('bs-ai-greeting', () => {
3
+ it('renders the default heading and a product-agnostic subtext', async () => {
4
+ const { root } = await render(h("bs-ai-greeting", null));
5
+ expect(root).toHaveTextContent("Hi. I'm Genie,");
6
+ expect(root).toHaveTextContent('your AI assistant.');
7
+ expect(root).toHaveTextContent('How can I help you today?');
8
+ });
9
+ it('uses a custom assistant name in the heading', async () => {
10
+ const { root } = await render(h("bs-ai-greeting", { "assistant-name": "Sasha" }));
11
+ expect(root).toHaveTextContent("Hi. I'm Sasha,");
12
+ });
13
+ it('mentions the product name in the subtext when provided', async () => {
14
+ const { root } = await render(h("bs-ai-greeting", { "product-name": "Brandsync" }));
15
+ expect(root).toHaveTextContent('How can I help you with Brandsync today?');
16
+ });
17
+ it('exposes heading and subtext parts', async () => {
18
+ const { root } = await render(h("bs-ai-greeting", null));
19
+ expect(root.shadowRoot.querySelector('[part="heading"]')).not.toBeNull();
20
+ expect(root.shadowRoot.querySelector('[part="subtext"]')).not.toBeNull();
21
+ });
22
+ });
@@ -0,0 +1,53 @@
1
+ :host {
2
+ display: block;
3
+ --bs-ai-greeting-gap: var(--bs-spacing-200);
4
+ --bs-ai-greeting-max-width: 490px;
5
+ --bs-ai-greeting-heading-color: var(--bs-text-default);
6
+ --bs-ai-greeting-heading-font-size: var(--bs-font-size-4xl);
7
+ /* Figma specifies a 28px line-height for this heading, which doesn't match any
8
+ * --bs-line-height-heading-* token (h4 is 32) -- aliased directly to --bs-line-height-body-lg,
9
+ * the token that happens to carry the same numeric value. */
10
+ --bs-ai-greeting-heading-line-height: var(--bs-line-height-body-lg);
11
+ --bs-ai-greeting-heading-weight: var(--bs-font-weight-bold);
12
+ --bs-ai-greeting-heading-gap: var(--bs-spacing-200);
13
+ --bs-ai-greeting-subtext-color: var(--bs-text-secondary);
14
+ /* Figma specifies 20px, which doesn't match the semantic --bs-text-style-body-lg-size (18px) --
15
+ * aliased directly to the raw --bs-font-size-xl primitive instead. */
16
+ --bs-ai-greeting-subtext-font-size: var(--bs-font-size-xl);
17
+ --bs-ai-greeting-subtext-line-height: var(--bs-line-height-body-lg);
18
+ }
19
+
20
+ .bs-ai-greeting {
21
+ box-sizing: border-box;
22
+ display: flex;
23
+ flex-direction: column;
24
+ align-items: center;
25
+ justify-content: center;
26
+ gap: var(--bs-ai-greeting-gap);
27
+ max-width: var(--bs-ai-greeting-max-width);
28
+ margin: 0 auto;
29
+ text-align: center;
30
+ font-family: inherit;
31
+ }
32
+
33
+ .bs-ai-greeting__heading {
34
+ display: flex;
35
+ flex-direction: column;
36
+ gap: var(--bs-ai-greeting-heading-gap);
37
+ color: var(--bs-ai-greeting-heading-color);
38
+ font-size: var(--bs-ai-greeting-heading-font-size);
39
+ font-weight: var(--bs-ai-greeting-heading-weight);
40
+ line-height: calc(var(--bs-ai-greeting-heading-line-height) * 1px);
41
+ }
42
+
43
+ .bs-ai-greeting__heading-line {
44
+ margin: 0;
45
+ }
46
+
47
+ .bs-ai-greeting__subtext {
48
+ margin: 0;
49
+ color: var(--bs-ai-greeting-subtext-color);
50
+ font-size: var(--bs-ai-greeting-subtext-font-size);
51
+ font-weight: var(--bs-font-weight-regular);
52
+ line-height: calc(var(--bs-ai-greeting-subtext-line-height) * 1px);
53
+ }
@@ -0,0 +1,103 @@
1
+ import { h } from "@stencil/core";
2
+ /**
3
+ * The centered welcome heading shown at the top of an empty Genie AI chat panel, e.g.
4
+ * "Hi. I'm Genie, your AI assistant." + "How can I help you with [product] today?"
5
+ *
6
+ * ## When to use
7
+ * - The empty/initial state of a Genie AI chat panel, before the user has sent a message.
8
+ *
9
+ * ## When not to use
10
+ * - Once a conversation has started -- this is a one-time empty-state greeting, not a persistent
11
+ * header (see `bs-chatbot-header` for that).
12
+ *
13
+ * `productName` is optional: Figma's copy has a `[product_name]` placeholder the consuming app is
14
+ * expected to fill in, but a component shouldn't force a product name to exist, so leaving it
15
+ * unset falls back to a product-agnostic "How can I help you today?".
16
+ *
17
+ * @part heading - The bold "Hi. I'm {assistantName}, your AI assistant." heading.
18
+ * @part subtext - The "How can I help you..." subtext.
19
+ * @prop --bs-ai-greeting-gap - Gap between heading and subtext. Aliased to `--bs-spacing-200`.
20
+ * @prop --bs-ai-greeting-max-width - Max width of the centered content block. Matches Figma's
21
+ * 490px.
22
+ * @prop --bs-ai-greeting-heading-color - Heading text color. Aliased to `--bs-text-default`.
23
+ * @prop --bs-ai-greeting-heading-font-size - Heading font size. Aliased to `--bs-font-size-4xl`.
24
+ * @prop --bs-ai-greeting-heading-line-height - Heading line height. Figma specifies 28px, which
25
+ * doesn't match any `--bs-line-height-heading-*` token (h4 is 32) -- aliased directly to
26
+ * `--bs-line-height-body-lg`, the token that happens to share the same numeric value.
27
+ * @prop --bs-ai-greeting-heading-weight - Heading font weight. Aliased to `--bs-font-weight-bold`.
28
+ * @prop --bs-ai-greeting-heading-gap - Gap between the two heading lines. Aliased to
29
+ * `--bs-spacing-200`.
30
+ * @prop --bs-ai-greeting-subtext-color - Subtext color. Aliased to `--bs-text-secondary`.
31
+ * @prop --bs-ai-greeting-subtext-font-size - Subtext font size. Figma specifies 20px, which
32
+ * doesn't match the semantic `--bs-text-style-body-lg-size` (18px) -- aliased directly to the
33
+ * raw `--bs-font-size-xl` primitive instead.
34
+ * @prop --bs-ai-greeting-subtext-line-height - Subtext line height. Aliased to
35
+ * `--bs-line-height-body-lg`.
36
+ */
37
+ export class BsAiGreeting {
38
+ /** The assistant's name, used in the heading ("Hi. I'm {assistantName},"). */
39
+ assistantName = 'Genie';
40
+ /** The product name mentioned in the subtext. Omit to use a product-agnostic subtext. */
41
+ productName;
42
+ render() {
43
+ const subtext = this.productName
44
+ ? `How can I help you with ${this.productName} today?`
45
+ : 'How can I help you today?';
46
+ return (h("div", { key: '7931e7404b70476a5488e31f07981c97617c1e5b', class: "bs-ai-greeting" }, h("div", { key: '1ba75538642eebb9fe7c24eae2aae9a4eed176cf', part: "heading", class: "bs-ai-greeting__heading" }, h("p", { key: '6cf5d97d81249298d02d3983a039e664a75fbb40', class: "bs-ai-greeting__heading-line" }, "Hi. I'm ", this.assistantName, ","), h("p", { key: 'f7a1b0235b5b32b1319bc7748e0073fae0defef0', class: "bs-ai-greeting__heading-line" }, "your AI assistant.")), h("p", { key: '5f07b44ca8d000c39204374f99c6608ecbfc22d3', part: "subtext", class: "bs-ai-greeting__subtext" }, subtext)));
47
+ }
48
+ static get is() { return "bs-ai-greeting"; }
49
+ static get encapsulation() { return "shadow"; }
50
+ static get originalStyleUrls() {
51
+ return {
52
+ "$": ["bs-ai-greeting.css"]
53
+ };
54
+ }
55
+ static get styleUrls() {
56
+ return {
57
+ "$": ["bs-ai-greeting.css"]
58
+ };
59
+ }
60
+ static get properties() {
61
+ return {
62
+ "assistantName": {
63
+ "type": "string",
64
+ "mutable": false,
65
+ "complexType": {
66
+ "original": "string",
67
+ "resolved": "string",
68
+ "references": {}
69
+ },
70
+ "required": false,
71
+ "optional": false,
72
+ "docs": {
73
+ "tags": [],
74
+ "text": "The assistant's name, used in the heading (\"Hi. I'm {assistantName},\")."
75
+ },
76
+ "getter": false,
77
+ "setter": false,
78
+ "reflect": false,
79
+ "attribute": "assistant-name",
80
+ "defaultValue": "'Genie'"
81
+ },
82
+ "productName": {
83
+ "type": "string",
84
+ "mutable": false,
85
+ "complexType": {
86
+ "original": "string",
87
+ "resolved": "string",
88
+ "references": {}
89
+ },
90
+ "required": false,
91
+ "optional": true,
92
+ "docs": {
93
+ "tags": [],
94
+ "text": "The product name mentioned in the subtext. Omit to use a product-agnostic subtext."
95
+ },
96
+ "getter": false,
97
+ "setter": false,
98
+ "reflect": false,
99
+ "attribute": "product-name"
100
+ }
101
+ };
102
+ }
103
+ }
@@ -0,0 +1,23 @@
1
+ import { html } from "lit";
2
+ import { componentDescription, propDescription } from "../../stories-utils";
3
+ const meta = {
4
+ title: 'Genie AI Components/bs-ai-greeting',
5
+ parameters: { docs: { description: { component: componentDescription('bs-ai-greeting') } } },
6
+ render: args => html `
7
+ <bs-ai-greeting assistant-name=${args.assistantName} product-name=${args.productName || undefined}></bs-ai-greeting>
8
+ `,
9
+ argTypes: {
10
+ assistantName: { control: 'text', description: propDescription('bs-ai-greeting', 'assistantName') },
11
+ productName: { control: 'text', description: propDescription('bs-ai-greeting', 'productName') },
12
+ },
13
+ args: {
14
+ assistantName: 'Genie',
15
+ productName: 'Brandsync',
16
+ },
17
+ };
18
+ export default meta;
19
+ export const Default = {};
20
+ export const WithoutProductName = {
21
+ name: 'Without product name',
22
+ args: { productName: '' },
23
+ };
@@ -3,9 +3,15 @@
3
3
  --bs-ai-thinking-gap: var(--bs-spacing-100);
4
4
  --bs-ai-thinking-icon-size: 24px;
5
5
  --bs-ai-thinking-text: var(--bs-text-muted);
6
+ /* The moving highlight band in the shimmer sweep. Aliased to --bs-text-default rather than
7
+ * Figma's literal gradient stop (#cedeff, tuned for light mode only) -- --bs-text-default is
8
+ * already more prominent than --bs-text-muted in both themes (near-black on white in light,
9
+ * white in dark), so the sweep reads as a highlight in either theme without a separate
10
+ * dark-mode override. */
11
+ --bs-ai-thinking-shimmer-highlight: var(--bs-text-default);
6
12
  --bs-ai-thinking-font-size: var(--bs-font-size-md);
7
13
  --bs-ai-thinking-line-height: var(--bs-line-height-body-md);
8
- /* Figma's keyframe track for this label runs over exactly 2s -- no --bs-duration-* token
14
+ /* Figma's own keyframe track for this label runs over exactly 2s -- no --bs-duration-* token
9
15
  * reaches that high (the longest, --bs-duration-slower, is 500ms), so this is aliased directly
10
16
  * to the literal value, same precedent as bs-tooltip's --bs-tooltip-shadow. */
11
17
  --bs-ai-thinking-cycle-duration: 2s;
@@ -27,34 +33,42 @@
27
33
  }
28
34
 
29
35
  .bs-ai-thinking__label {
30
- color: var(--bs-ai-thinking-text);
31
36
  font-size: var(--bs-ai-thinking-font-size);
32
37
  line-height: calc(var(--bs-ai-thinking-line-height) * 1px);
33
- animation: bs-ai-thinking-pulse var(--bs-ai-thinking-cycle-duration) linear infinite;
38
+ background-image: linear-gradient(
39
+ 90deg,
40
+ var(--bs-ai-thinking-text) 0%,
41
+ var(--bs-ai-thinking-text) 44%,
42
+ var(--bs-ai-thinking-shimmer-highlight) 50%,
43
+ var(--bs-ai-thinking-text) 56%,
44
+ var(--bs-ai-thinking-text) 100%
45
+ );
46
+ background-size: 200% 100%;
47
+ background-position: 200% 0;
48
+ -webkit-background-clip: text;
49
+ background-clip: text;
50
+ -webkit-text-fill-color: transparent;
51
+ color: transparent;
52
+ animation: bs-ai-thinking-shimmer var(--bs-ai-thinking-cycle-duration) linear infinite;
34
53
  }
35
54
 
36
- /* Reproduces Figma's exact keyframe track for this node (hold hidden briefly, ease in with the
37
- * source's own cubic-bezier, hold visible, then reset) -- a breathing pulse, not an invented
38
- * shimmer sweep. */
39
- @keyframes bs-ai-thinking-pulse {
55
+ /* Sweeps a brighter highlight band across the label left-to-right, looping -- a standard
56
+ * "AI is working" text shimmer, replacing the literal opacity-pulse keyframe track Figma
57
+ * exported (a plain fade-in/hold/reset), per explicit request. */
58
+ @keyframes bs-ai-thinking-shimmer {
40
59
  0% {
41
- opacity: 0;
42
- }
43
- 8.175% {
44
- animation-timing-function: cubic-bezier(0.5, 0, 0.5, 1);
45
- opacity: 0;
46
- }
47
- 80.546% {
48
- opacity: 1;
60
+ background-position: 200% 0;
49
61
  }
50
62
  100% {
51
- opacity: 1;
63
+ background-position: -200% 0;
52
64
  }
53
65
  }
54
66
 
55
67
  @media (prefers-reduced-motion: reduce) {
56
68
  .bs-ai-thinking__label {
57
69
  animation: none;
58
- opacity: 1;
70
+ background-image: none;
71
+ -webkit-text-fill-color: currentcolor;
72
+ color: var(--bs-ai-thinking-text);
59
73
  }
60
74
  }
@@ -2,8 +2,8 @@ import { h } from "@stencil/core";
2
2
  import { GENIE_MARK_ICON } from "./genie-mark-icon";
3
3
  /**
4
4
  * A small inline status indicator for Genie AI surfaces: the colorful Genie mark next to a label
5
- * (e.g. "Retrieving", "Thinking", "Searching") whose text pulses in a slow breathing loop while
6
- * an operation is in progress.
5
+ * (e.g. "Retrieving", "Thinking", "Searching") whose text shimmers with a moving highlight band
6
+ * while an operation is in progress.
7
7
  *
8
8
  * ## When to use
9
9
  * - Inline in a Genie AI chat transcript to show what the assistant is currently doing while a
@@ -12,26 +12,24 @@ import { GENIE_MARK_ICON } from "./genie-mark-icon";
12
12
  * ## When not to use
13
13
  * - A blocking/full-panel loading state -- this is a small inline indicator, not a spinner overlay.
14
14
  *
15
- * The label's pulse animation reproduces Figma's exact keyframe track (a plain opacity
16
- * fade-in/hold/reset with the source's own cubic-bezier easing) rather than an invented
17
- * shimmer-sweep -- the source design only specifies this one animation.
18
- *
19
15
  * @part icon - The Genie mark icon.
20
16
  * @part label - The status label text.
21
17
  * @prop --bs-ai-thinking-gap - Gap between icon and label. Aliased to `--bs-spacing-100`.
22
18
  * @prop --bs-ai-thinking-icon-size - Width/height of the icon.
23
- * @prop --bs-ai-thinking-text - Label color. Aliased to `--bs-text-muted`.
19
+ * @prop --bs-ai-thinking-text - Label base color. Aliased to `--bs-text-muted`.
20
+ * @prop --bs-ai-thinking-shimmer-highlight - Color of the moving highlight band. Aliased to
21
+ * `--bs-text-default` (more prominent than `--bs-text-muted` in both themes).
24
22
  * @prop --bs-ai-thinking-font-size - Label font size. Aliased to `--bs-font-size-md`.
25
23
  * @prop --bs-ai-thinking-line-height - Label line height. Aliased to `--bs-line-height-body-md`.
26
- * @prop --bs-ai-thinking-cycle-duration - Duration of one pulse loop. Figma specifies exactly 2s,
27
- * longer than any `--bs-duration-*` token (max `--bs-duration-slower` is 500ms), so this is
28
- * aliased directly to the literal value.
24
+ * @prop --bs-ai-thinking-cycle-duration - Duration of one shimmer sweep. Figma's own keyframe
25
+ * track for this label runs over exactly 2s, longer than any `--bs-duration-*` token (max
26
+ * `--bs-duration-slower` is 500ms), so this is aliased directly to the literal value.
29
27
  */
30
28
  export class BsAiThinking {
31
29
  /** The status text shown next to the icon, e.g. "Retrieving", "Thinking", "Searching". */
32
30
  label = 'Thinking';
33
31
  render() {
34
- return (h("div", { key: '5efb57ce1f0d5bcaed2c0ea963566be5f153e983', class: "bs-ai-thinking", role: "status", "aria-live": "polite" }, h("img", { key: '24e00c0d94ad448cd0f9b08f42baf0208e70baa5', part: "icon", class: "bs-ai-thinking__icon", src: GENIE_MARK_ICON, alt: "", "aria-hidden": "true" }), h("span", { key: '8d207d2c9f7752c853fa85991564d4dd0f547b4c', part: "label", class: "bs-ai-thinking__label" }, this.label)));
32
+ return (h("div", { key: 'b72bc5095cc94257821541517f3446bac960df49', class: "bs-ai-thinking", role: "status", "aria-live": "polite" }, h("img", { key: '6c41c71416fc77f7385505754a68786b5f3412fa', part: "icon", class: "bs-ai-thinking__icon", src: GENIE_MARK_ICON, alt: "", "aria-hidden": "true" }), h("span", { key: '8edef57f7b31a03e2add401e42acdb7b6525a432', part: "label", class: "bs-ai-thinking__label" }, this.label)));
35
33
  }
36
34
  static get is() { return "bs-ai-thinking"; }
37
35
  static get encapsulation() { return "shadow"; }
@@ -0,0 +1,41 @@
1
+ import { render, h, describe, it, expect } from "@stencil/vitest";
2
+ describe('bs-attachment', () => {
3
+ it('renders an image thumbnail by default', async () => {
4
+ const { root } = await render(h("bs-attachment", { "image-src": "/photo.jpg", "image-alt": "A photo" }));
5
+ const thumbnail = root.shadowRoot.querySelector('[part="thumbnail"]');
6
+ expect(thumbnail).not.toBeNull();
7
+ const img = root.shadowRoot.querySelector('img');
8
+ expect(img.getAttribute('src')).toBe('/photo.jpg');
9
+ expect(img.getAttribute('alt')).toBe('A photo');
10
+ });
11
+ it('renders a filename card with a PDF badge for type="pdf"', async () => {
12
+ const { root } = await render(h("bs-attachment", { type: "pdf", "file-name": "report.pdf" }));
13
+ expect(root.shadowRoot.querySelector('[part="card"]')).not.toBeNull();
14
+ expect(root).toHaveTextContent('report.pdf');
15
+ expect(root.shadowRoot.querySelector('[part="badge"]').className).toContain('bs-attachment__badge--pdf');
16
+ });
17
+ it('renders a filename card with a document badge for type="document"', async () => {
18
+ const { root } = await render(h("bs-attachment", { type: "document", "file-name": "notes.docx" }));
19
+ expect(root.shadowRoot.querySelector('[part="badge"]').className).toContain('bs-attachment__badge--document');
20
+ });
21
+ it('shows a spinner in the badge instead of the file icon when loading', async () => {
22
+ const { root } = await render(h("bs-attachment", { type: "pdf", loading: true }));
23
+ const badge = root.shadowRoot.querySelector('[part="badge"]');
24
+ expect(badge.querySelector('svg')).toBeNull();
25
+ expect(badge.querySelector('[role="status"]')).not.toBeNull();
26
+ });
27
+ it('marks the host aria-busy when loading', async () => {
28
+ const { root } = await render(h("bs-attachment", { loading: true }));
29
+ expect(root.shadowRoot.querySelector('.bs-attachment').getAttribute('aria-busy')).toBe('true');
30
+ });
31
+ it('emits bsRemove when the remove button is clicked', async () => {
32
+ const { root, spyOnEvent } = await render(h("bs-attachment", null));
33
+ const removeSpy = spyOnEvent('bsRemove');
34
+ root.shadowRoot.querySelector('[part="remove"]').click();
35
+ expect(removeSpy).toHaveReceivedEventTimes(1);
36
+ });
37
+ it('does not render a remove button when removable is false', async () => {
38
+ const { root } = await render(h("bs-attachment", { removable: false }));
39
+ expect(root.shadowRoot.querySelector('[part="remove"]')).toBeNull();
40
+ });
41
+ });