@revikornmann/muka-ui 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/build/manifest.json +20 -0
  2. package/dist/cjs/components/AgentTaskList/AgentTaskList.css +22 -3
  3. package/dist/cjs/components/BottomBar/BottomBar.css +8 -5
  4. package/dist/cjs/components/Button/Button.css +13 -6
  5. package/dist/cjs/components/Button/Button.js +1 -1
  6. package/dist/cjs/components/ChatBubble/ChatBubble.css +251 -31
  7. package/dist/cjs/components/ChatBubble/ChatBubble.js +17 -4
  8. package/dist/cjs/components/ChatBubble/ChatBubble.js.map +1 -1
  9. package/dist/cjs/components/ChatComposer/ChatComposer.css +13 -1
  10. package/dist/cjs/components/ChatMessageList/ChatMessageList.css +17 -1
  11. package/dist/cjs/components/ChatPhotoGrid/ChatPhotoGrid.css +113 -0
  12. package/dist/cjs/components/ChatPhotoGrid/ChatPhotoGrid.js +47 -0
  13. package/dist/cjs/components/ChatPhotoGrid/ChatPhotoGrid.js.map +1 -0
  14. package/dist/cjs/components/ChatPhotoGrid/index.js +11 -0
  15. package/dist/cjs/components/ChatPhotoGrid/index.js.map +1 -0
  16. package/dist/cjs/components/index.js +3 -1
  17. package/dist/cjs/components/index.js.map +1 -1
  18. package/dist/esm/components/AgentTaskList/AgentTaskList.css +22 -3
  19. package/dist/esm/components/BottomBar/BottomBar.css +8 -5
  20. package/dist/esm/components/Button/Button.css +13 -6
  21. package/dist/esm/components/Button/Button.js +1 -1
  22. package/dist/esm/components/ChatBubble/ChatBubble.css +251 -31
  23. package/dist/esm/components/ChatBubble/ChatBubble.js +18 -5
  24. package/dist/esm/components/ChatBubble/ChatBubble.js.map +1 -1
  25. package/dist/esm/components/ChatComposer/ChatComposer.css +13 -1
  26. package/dist/esm/components/ChatMessageList/ChatMessageList.css +17 -1
  27. package/dist/esm/components/ChatPhotoGrid/ChatPhotoGrid.css +113 -0
  28. package/dist/esm/components/ChatPhotoGrid/ChatPhotoGrid.js +43 -0
  29. package/dist/esm/components/ChatPhotoGrid/ChatPhotoGrid.js.map +1 -0
  30. package/dist/esm/components/ChatPhotoGrid/index.js +3 -0
  31. package/dist/esm/components/ChatPhotoGrid/index.js.map +1 -0
  32. package/dist/esm/components/index.js +1 -0
  33. package/dist/esm/components/index.js.map +1 -1
  34. package/dist/styles/components/AgentTaskList.css +22 -3
  35. package/dist/styles/components/BottomBar.css +8 -5
  36. package/dist/styles/components/Button.css +13 -6
  37. package/dist/styles/components/ChatBubble.css +251 -31
  38. package/dist/styles/components/ChatComposer.css +13 -1
  39. package/dist/styles/components/ChatMessageList.css +17 -1
  40. package/dist/styles/components/ChatPhotoGrid.css +113 -0
  41. package/dist/styles/index.css +1025 -574
  42. package/dist/styles/muka-dark.css +1025 -574
  43. package/dist/styles/muka-light.css +1025 -574
  44. package/dist/styles/tokens-bouwplan-dark.css +58 -0
  45. package/dist/styles/tokens-bouwplan-light.css +58 -0
  46. package/dist/styles/tokens-fscl-dark.css +58 -0
  47. package/dist/styles/tokens-fscl-light.css +58 -0
  48. package/dist/styles/tokens-grip-dark.css +58 -0
  49. package/dist/styles/tokens-grip-light.css +58 -0
  50. package/dist/styles/tokens-muka-dark.css +58 -0
  51. package/dist/styles/tokens-muka-light.css +58 -0
  52. package/dist/styles/tokens-wireframe-dark.css +58 -0
  53. package/dist/styles/tokens-wireframe-light.css +58 -0
  54. package/dist/styles/wireframe-dark.css +1025 -574
  55. package/dist/styles/wireframe-light.css +1025 -574
  56. package/dist/types/components/Button/Button.d.ts +1 -1
  57. package/dist/types/components/ChatBubble/ChatBubble.d.ts +63 -8
  58. package/dist/types/components/ChatBubble/ChatBubble.d.ts.map +1 -1
  59. package/dist/types/components/ChatPhotoGrid/ChatPhotoGrid.d.ts +44 -0
  60. package/dist/types/components/ChatPhotoGrid/ChatPhotoGrid.d.ts.map +1 -0
  61. package/dist/types/components/ChatPhotoGrid/index.d.ts +3 -0
  62. package/dist/types/components/ChatPhotoGrid/index.d.ts.map +1 -0
  63. package/dist/types/components/index.d.ts +1 -0
  64. package/dist/types/components/index.d.ts.map +1 -1
  65. package/package.json +1 -1
  66. package/tokens/README.md +14 -0
  67. package/tokens/t2-alias/brand/bouwplan/dark.json +24 -0
  68. package/tokens/t2-alias/brand/bouwplan/light.json +24 -0
  69. package/tokens/t2-alias/brand/fscl/dark.json +24 -0
  70. package/tokens/t2-alias/brand/fscl/light.json +24 -0
  71. package/tokens/t2-alias/brand/grip/dark.json +54 -0
  72. package/tokens/t2-alias/brand/grip/light.json +54 -0
  73. package/tokens/t2-alias/brand/muka/dark.json +54 -0
  74. package/tokens/t2-alias/brand/muka/light.json +54 -0
  75. package/tokens/t2-alias/brand/wireframe/dark.json +54 -0
  76. package/tokens/t2-alias/brand/wireframe/light.json +54 -0
  77. package/tokens/t3-semantics/ui.json +190 -0
  78. package/tokens/t4-components/chatbubble.json +154 -0
  79. package/tokens/t4-components/chatphotogrid.json +32 -0
@@ -11,6 +11,8 @@
11
11
  "tokens/t3-semantics/ui.json",
12
12
  "tokens/t4-components/button.json",
13
13
  "tokens/t4-components/card.json",
14
+ "tokens/t4-components/chatbubble.json",
15
+ "tokens/t4-components/chatphotogrid.json",
14
16
  "tokens/t4-components/container.json",
15
17
  "tokens/t4-components/input.json",
16
18
  "tokens/t4-components/chip.json",
@@ -71,6 +73,8 @@
71
73
  "tokens/t3-semantics/ui.json",
72
74
  "tokens/t4-components/button.json",
73
75
  "tokens/t4-components/card.json",
76
+ "tokens/t4-components/chatbubble.json",
77
+ "tokens/t4-components/chatphotogrid.json",
74
78
  "tokens/t4-components/container.json",
75
79
  "tokens/t4-components/input.json",
76
80
  "tokens/t4-components/chip.json",
@@ -130,6 +134,8 @@
130
134
  "tokens/t3-semantics/ui.json",
131
135
  "tokens/t4-components/button.json",
132
136
  "tokens/t4-components/card.json",
137
+ "tokens/t4-components/chatbubble.json",
138
+ "tokens/t4-components/chatphotogrid.json",
133
139
  "tokens/t4-components/container.json",
134
140
  "tokens/t4-components/input.json",
135
141
  "tokens/t4-components/chip.json",
@@ -189,6 +195,8 @@
189
195
  "tokens/t3-semantics/ui.json",
190
196
  "tokens/t4-components/button.json",
191
197
  "tokens/t4-components/card.json",
198
+ "tokens/t4-components/chatbubble.json",
199
+ "tokens/t4-components/chatphotogrid.json",
192
200
  "tokens/t4-components/container.json",
193
201
  "tokens/t4-components/input.json",
194
202
  "tokens/t4-components/chip.json",
@@ -248,6 +256,8 @@
248
256
  "tokens/t3-semantics/ui.json",
249
257
  "tokens/t4-components/button.json",
250
258
  "tokens/t4-components/card.json",
259
+ "tokens/t4-components/chatbubble.json",
260
+ "tokens/t4-components/chatphotogrid.json",
251
261
  "tokens/t4-components/container.json",
252
262
  "tokens/t4-components/input.json",
253
263
  "tokens/t4-components/chip.json",
@@ -307,6 +317,8 @@
307
317
  "tokens/t3-semantics/ui.json",
308
318
  "tokens/t4-components/button.json",
309
319
  "tokens/t4-components/card.json",
320
+ "tokens/t4-components/chatbubble.json",
321
+ "tokens/t4-components/chatphotogrid.json",
310
322
  "tokens/t4-components/container.json",
311
323
  "tokens/t4-components/input.json",
312
324
  "tokens/t4-components/chip.json",
@@ -366,6 +378,8 @@
366
378
  "tokens/t3-semantics/ui.json",
367
379
  "tokens/t4-components/button.json",
368
380
  "tokens/t4-components/card.json",
381
+ "tokens/t4-components/chatbubble.json",
382
+ "tokens/t4-components/chatphotogrid.json",
369
383
  "tokens/t4-components/container.json",
370
384
  "tokens/t4-components/input.json",
371
385
  "tokens/t4-components/chip.json",
@@ -425,6 +439,8 @@
425
439
  "tokens/t3-semantics/ui.json",
426
440
  "tokens/t4-components/button.json",
427
441
  "tokens/t4-components/card.json",
442
+ "tokens/t4-components/chatbubble.json",
443
+ "tokens/t4-components/chatphotogrid.json",
428
444
  "tokens/t4-components/container.json",
429
445
  "tokens/t4-components/input.json",
430
446
  "tokens/t4-components/chip.json",
@@ -484,6 +500,8 @@
484
500
  "tokens/t3-semantics/ui.json",
485
501
  "tokens/t4-components/button.json",
486
502
  "tokens/t4-components/card.json",
503
+ "tokens/t4-components/chatbubble.json",
504
+ "tokens/t4-components/chatphotogrid.json",
487
505
  "tokens/t4-components/container.json",
488
506
  "tokens/t4-components/input.json",
489
507
  "tokens/t4-components/chip.json",
@@ -543,6 +561,8 @@
543
561
  "tokens/t3-semantics/ui.json",
544
562
  "tokens/t4-components/button.json",
545
563
  "tokens/t4-components/card.json",
564
+ "tokens/t4-components/chatbubble.json",
565
+ "tokens/t4-components/chatphotogrid.json",
546
566
  "tokens/t4-components/container.json",
547
567
  "tokens/t4-components/input.json",
548
568
  "tokens/t4-components/chip.json",
@@ -20,12 +20,19 @@
20
20
  margin: 0;
21
21
  cursor: pointer;
22
22
  color: var(--color-text-subtle-default);
23
+ font-family: var(--text-body-sm-regular-fontFamily);
24
+ font-weight: var(--text-body-sm-regular-fontWeight);
25
+ font-size: var(--text-body-sm-regular-fontSize);
26
+ line-height: var(--text-body-sm-regular-lineHeight);
27
+ text-align: left;
28
+ -webkit-tap-highlight-color: transparent;
29
+ }
30
+
31
+ .muka-agent-task-list--open .muka-agent-task-list__header {
23
32
  font-family: var(--text-body-sm-bold-fontFamily);
24
33
  font-weight: var(--text-body-sm-bold-fontWeight);
25
34
  font-size: var(--text-body-sm-bold-fontSize);
26
35
  line-height: var(--text-body-sm-bold-lineHeight);
27
- text-align: left;
28
- -webkit-tap-highlight-color: transparent;
29
36
  }
30
37
 
31
38
  .muka-agent-task-list__header:focus-visible {
@@ -83,7 +90,7 @@
83
90
  position: absolute;
84
91
  left: 7px;
85
92
  width: 1px;
86
- background-color: var(--color-border-default, #d9d9d9);
93
+ background-color: var(--color-border-default);
87
94
  }
88
95
 
89
96
  .muka-agent-task-list__line--top {
@@ -145,3 +152,15 @@
145
152
  line-height: var(--text-body-sm-regular-lineHeight);
146
153
  word-break: break-word;
147
154
  }
155
+
156
+ /* ─── Print ────────────────────────────────────────────── */
157
+
158
+ @media print {
159
+ .muka-agent-task-list {
160
+ break-inside: avoid;
161
+ }
162
+
163
+ .muka-agent-task-list__chevron {
164
+ display: none;
165
+ }
166
+ }
@@ -81,11 +81,6 @@
81
81
  max-width: var(--bottombar-chat-max-width);
82
82
  }
83
83
 
84
- /* Let the composer fill the wider bar instead of its standalone 768px cap. */
85
- .muka-bottombar--chat .muka-chat-composer {
86
- max-width: none;
87
- }
88
-
89
84
  /* ─── Tab item ────────────────────────────────────────── */
90
85
 
91
86
  .muka-bottombar__tab {
@@ -296,3 +291,11 @@
296
291
  box-shadow: none;
297
292
  }
298
293
  }
294
+
295
+ /* ─── Print ────────────────────────────────────────────── */
296
+
297
+ @media print {
298
+ .muka-bottombar {
299
+ display: none;
300
+ }
301
+ }
@@ -96,10 +96,13 @@
96
96
  }
97
97
 
98
98
  /* `[data-state="open"]` keeps a Button pressed while it's an open disclosure
99
- trigger (Radix Menu/Popover set this on the trigger). */
99
+ trigger (Radix Menu/Popover set this on the trigger). `[aria-pressed="true"]`
100
+ gives toggle buttons a visible selected state, so the toggle is not conveyed
101
+ to sighted users by the accessibility tree alone. */
100
102
  .muka-button--primary:active:not(:disabled),
101
103
  .muka-button--primary.muka-button--pressed:not(:disabled),
102
- .muka-button--primary[data-state="open"]:not(:disabled) {
104
+ .muka-button--primary[data-state="open"]:not(:disabled),
105
+ .muka-button--primary[aria-pressed="true"]:not(:disabled) {
103
106
  background-color: var(--button-color-primary-background-pressed);
104
107
  color: var(--button-color-primary-foreground-pressed);
105
108
  box-shadow: var(--button-color-primary-shadow-pressed);
@@ -128,7 +131,8 @@
128
131
 
129
132
  .muka-button--secondary:active:not(:disabled),
130
133
  .muka-button--secondary.muka-button--pressed:not(:disabled),
131
- .muka-button--secondary[data-state="open"]:not(:disabled) {
134
+ .muka-button--secondary[data-state="open"]:not(:disabled),
135
+ .muka-button--secondary[aria-pressed="true"]:not(:disabled) {
132
136
  background-color: var(--button-color-secondary-background-pressed);
133
137
  color: var(--button-color-secondary-foreground-pressed);
134
138
  box-shadow: var(--button-color-secondary-shadow-pressed);
@@ -157,7 +161,8 @@
157
161
 
158
162
  .muka-button--tertiary:active:not(:disabled),
159
163
  .muka-button--tertiary.muka-button--pressed:not(:disabled),
160
- .muka-button--tertiary[data-state="open"]:not(:disabled) {
164
+ .muka-button--tertiary[data-state="open"]:not(:disabled),
165
+ .muka-button--tertiary[aria-pressed="true"]:not(:disabled) {
161
166
  background-color: var(--button-color-tertiary-background-pressed);
162
167
  color: var(--button-color-tertiary-foreground-pressed);
163
168
  border-color: var(--button-color-tertiary-border-pressed);
@@ -186,7 +191,8 @@
186
191
 
187
192
  .muka-button--ghost:active:not(:disabled),
188
193
  .muka-button--ghost.muka-button--pressed:not(:disabled),
189
- .muka-button--ghost[data-state="open"]:not(:disabled) {
194
+ .muka-button--ghost[data-state="open"]:not(:disabled),
195
+ .muka-button--ghost[aria-pressed="true"]:not(:disabled) {
190
196
  background-color: var(--button-color-ghost-background-pressed);
191
197
  color: var(--button-color-ghost-foreground-pressed);
192
198
  border-color: var(--button-color-ghost-border-pressed);
@@ -215,7 +221,8 @@
215
221
 
216
222
  .muka-button--muted:active:not(:disabled),
217
223
  .muka-button--muted.muka-button--pressed:not(:disabled),
218
- .muka-button--muted[data-state="open"]:not(:disabled) {
224
+ .muka-button--muted[data-state="open"]:not(:disabled),
225
+ .muka-button--muted[aria-pressed="true"]:not(:disabled) {
219
226
  background-color: var(--button-color-muted-background-pressed);
220
227
  color: var(--button-color-muted-foreground-pressed);
221
228
  border-color: var(--button-color-muted-border-pressed);
@@ -21,7 +21,7 @@ require("./Button.css");
21
21
  * - Uses native <button> element for full keyboard support
22
22
  * - Focus indicator meets WCAG 2.4.7 (visible focus)
23
23
  * - When iconOnly=true, aria-label is required (dev warning if missing)
24
- * - Supports aria-pressed for toggle buttons
24
+ * - Supports aria-pressed for toggle buttons (renders the pressed treatment when true)
25
25
  * - Supports aria-expanded/aria-controls for menu buttons
26
26
  *
27
27
  * @see https://www.w3.org/WAI/ARIA/apg/patterns/button/
@@ -3,69 +3,222 @@
3
3
  .muka-chat-bubble {
4
4
  display: flex;
5
5
  flex-direction: column;
6
- gap: var(--spacing-1);
7
6
  width: 100%;
8
7
  max-width: 100%;
9
8
  }
10
9
 
11
- /* ─── User variant — right-aligned bubble ──────────────── */
10
+ /* ─── Row: avatar rail + bubble + action bar ───────────────
11
+ *
12
+ * A two-row grid rather than a flex column, so the avatar bottom-aligns to the
13
+ * bubble itself and not to the action bar underneath it (Figma 5223:8520).
14
+ */
12
15
 
13
- .muka-chat-bubble--user {
14
- align-items: flex-end;
16
+ .muka-chat-bubble__row {
17
+ display: grid;
18
+ grid-template-columns: minmax(0, 1fr);
19
+ grid-template-areas:
20
+ 'bubble'
21
+ 'actions';
22
+ column-gap: var(--chatbubble-row-gap);
23
+ row-gap: var(--chatbubble-gap);
24
+ width: 100%;
15
25
  }
16
26
 
17
- .muka-chat-bubble--user .muka-chat-bubble__message {
18
- background-color: var(--color-surface-level2);
19
- color: var(--color-text-default-default);
20
- border-radius: var(--radius-lg);
21
- padding: var(--spacing-4);
22
- max-width: min(600px, 100%);
23
- word-break: break-word;
27
+ .muka-chat-bubble--has-avatar .muka-chat-bubble__row {
28
+ grid-template-columns: auto minmax(0, 1fr);
29
+ grid-template-areas:
30
+ 'rail bubble'
31
+ '. actions';
24
32
  }
25
33
 
26
- .muka-chat-bubble--user .muka-chat-bubble__meta {
27
- justify-content: flex-end;
34
+ .muka-chat-bubble--own.muka-chat-bubble--has-avatar .muka-chat-bubble__row {
35
+ grid-template-columns: minmax(0, 1fr) auto;
36
+ grid-template-areas:
37
+ 'bubble rail'
38
+ 'actions .';
28
39
  }
29
40
 
30
- /* ─── Agent variant — no bubble, full width ────────────── */
31
-
32
- .muka-chat-bubble--agent {
33
- align-items: flex-start;
41
+ .muka-chat-bubble__rail {
42
+ grid-area: rail;
43
+ /* Sits on the bubble's baseline, above the action bar */
44
+ align-self: end;
45
+ display: flex;
46
+ justify-content: center;
47
+ min-width: var(--chatbubble-avatar-size);
34
48
  }
35
49
 
36
- .muka-chat-bubble--agent .muka-chat-bubble__message {
37
- color: var(--color-text-default-default);
38
- padding: 0 var(--spacing-2);
50
+ /* ─── Bubble surface ───────────────────────────────────── */
51
+
52
+ .muka-chat-bubble__bubble {
53
+ grid-area: bubble;
54
+ max-width: var(--chatbubble-max-width);
55
+ position: relative;
56
+ display: flex;
57
+ flex-direction: column;
58
+ gap: var(--chatbubble-gap);
39
59
  width: 100%;
60
+ padding: var(--chatbubble-padding);
61
+ border-radius: var(--chatbubble-radius);
62
+ border: var(--border-width-regular) solid transparent;
40
63
  word-break: break-word;
41
64
  }
42
65
 
43
- .muka-chat-bubble--agent .muka-chat-bubble__meta {
44
- justify-content: flex-start;
66
+ .muka-chat-bubble--own .muka-chat-bubble__bubble,
67
+ .muka-chat-bubble--own .muka-chat-bubble__actions {
68
+ justify-self: end;
69
+ }
70
+
71
+ .muka-chat-bubble--own .muka-chat-bubble__bubble {
72
+ background-color: var(--chatbubble-color-own-background);
73
+ border-color: var(--chatbubble-color-own-border);
74
+ color: var(--chatbubble-color-own-foreground);
75
+ /* Room for the inset timestamp in the bottom-right corner */
76
+ padding-right: var(--spacing-6);
77
+ }
78
+
79
+ .muka-chat-bubble--peer .muka-chat-bubble__bubble {
80
+ background-color: var(--chatbubble-color-peer-background);
81
+ border-color: var(--chatbubble-color-peer-border);
82
+ color: var(--chatbubble-color-peer-foreground);
83
+ box-shadow: var(--shadow-level2);
84
+ }
85
+
86
+ .muka-chat-bubble--agent .muka-chat-bubble__bubble {
87
+ background-color: var(--chatbubble-color-agent-background);
88
+ border-color: var(--chatbubble-color-agent-border);
89
+ color: var(--chatbubble-color-agent-foreground);
45
90
  }
46
91
 
47
- /* ─── Message typography ───────────────────────────────── */
92
+ /* ─── Header row (author · meta · time · badge) ────────── */
93
+
94
+ .muka-chat-bubble__header {
95
+ display: flex;
96
+ flex-flow: row wrap;
97
+ align-items: center;
98
+ gap: var(--chatbubble-row-gap);
99
+ }
100
+
101
+ .muka-chat-bubble__author {
102
+ font-family: var(--text-body-base-bold-fontFamily);
103
+ font-weight: var(--text-body-base-bold-fontWeight);
104
+ font-size: var(--text-body-base-bold-fontSize);
105
+ line-height: var(--text-body-base-bold-lineHeight);
106
+ }
107
+
108
+ .muka-chat-bubble__author-meta {
109
+ /* Inherits the variant's body colour from the bubble */
110
+ color: inherit;
111
+ font-family: var(--text-body-base-regular-fontFamily);
112
+ font-weight: var(--text-body-base-regular-fontWeight);
113
+ font-size: var(--text-body-base-regular-fontSize);
114
+ line-height: var(--text-body-base-regular-lineHeight);
115
+ }
116
+
117
+ .muka-chat-bubble__badge {
118
+ display: inline-flex;
119
+ align-items: center;
120
+ }
121
+
122
+ /*
123
+ * Keep the first line of content clear of the absolutely positioned menu
124
+ * button in the bubble's top-right corner.
125
+ */
126
+ .muka-chat-bubble--has-menu .muka-chat-bubble__header,
127
+ .muka-chat-bubble--has-menu.muka-chat-bubble--no-header .muka-chat-bubble__message {
128
+ padding-right: var(--spacing-6);
129
+ }
130
+
131
+ /* ─── Body ─────────────────────────────────────────────── */
132
+
133
+ .muka-chat-bubble__body {
134
+ display: flex;
135
+ flex-direction: column;
136
+ gap: var(--chatbubble-gap);
137
+ min-width: 0;
138
+ }
139
+
140
+ /* The agent balloon spaces its blocks further apart (Figma 5223:8520) */
141
+ .muka-chat-bubble--agent .muka-chat-bubble__body {
142
+ gap: var(--chatbubble-body-gap);
143
+ }
144
+
145
+ .muka-chat-bubble__media {
146
+ min-width: 0;
147
+ }
48
148
 
49
149
  .muka-chat-bubble__message {
50
150
  font-family: var(--text-body-base-regular-fontFamily);
51
151
  font-weight: var(--text-body-base-regular-fontWeight);
52
152
  font-size: var(--text-body-base-regular-fontSize);
53
153
  line-height: var(--text-body-base-regular-lineHeight);
154
+ white-space: pre-wrap;
155
+ word-break: break-word;
54
156
  }
55
157
 
56
- /* ─── Meta row (timestamp + actions) ───────────────────── */
158
+ /* Empty message block should not add a gap of its own */
159
+ .muka-chat-bubble__message:empty {
160
+ display: none;
161
+ }
57
162
 
58
- .muka-chat-bubble__meta {
163
+ /* ─── Embedded card ────────────────────────────────────── */
164
+
165
+ .muka-chat-bubble__embed {
166
+ position: relative;
59
167
  display: flex;
60
- align-items: center;
61
- gap: var(--spacing-1);
168
+ flex-direction: column;
169
+ gap: var(--chatbubble-embed-gap);
170
+ padding: var(--chatbubble-embed-padding);
171
+ border-radius: var(--chatbubble-embed-radius);
172
+ border: var(--border-width-regular) solid transparent;
62
173
  }
63
174
 
175
+ /*
176
+ * The embed sits on the raised surface of its author's role, one step apart
177
+ * from the bubble it is nested in, and reuses that role's border colour.
178
+ */
179
+ .muka-chat-bubble--own .muka-chat-bubble__embed {
180
+ background-color: var(--chatbubble-embed-color-own-background);
181
+ border-color: var(--chatbubble-embed-color-own-border);
182
+ color: var(--chatbubble-embed-color-own-foreground);
183
+ }
184
+
185
+ .muka-chat-bubble--peer .muka-chat-bubble__embed {
186
+ background-color: var(--chatbubble-embed-color-peer-background);
187
+ border-color: var(--chatbubble-embed-color-peer-border);
188
+ color: var(--chatbubble-embed-color-peer-foreground);
189
+ }
190
+
191
+ .muka-chat-bubble--agent .muka-chat-bubble__embed {
192
+ background-color: var(--chatbubble-embed-color-agent-background);
193
+ border-color: var(--chatbubble-embed-color-agent-border);
194
+ color: var(--chatbubble-embed-color-agent-foreground);
195
+ }
196
+
197
+ .muka-chat-bubble__embed-content {
198
+ display: flex;
199
+ flex-direction: column;
200
+ gap: var(--chatbubble-embed-gap);
201
+ min-width: 0;
202
+ }
203
+
204
+ .muka-chat-bubble__embed-copy {
205
+ position: absolute;
206
+ top: var(--chatbubble-embed-padding);
207
+ right: var(--chatbubble-embed-padding);
208
+ }
209
+
210
+ /* ─── Stacked full-width action buttons ────────────────── */
211
+
212
+ .muka-chat-bubble__action-stack {
213
+ display: flex;
214
+ flex-direction: column;
215
+ gap: var(--chatbubble-action-stack-gap);
216
+ }
217
+
218
+ /* ─── Inset timestamp (user variant) ───────────────────── */
219
+
64
220
  .muka-chat-bubble__timestamp {
65
- display: inline-flex;
66
- align-items: center;
67
- padding: 0 var(--spacing-2);
68
- color: var(--color-text-subtle-default);
221
+ color: var(--chatbubble-color-timestamp);
69
222
  font-family: var(--text-body-sm-regular-fontFamily);
70
223
  font-weight: var(--text-body-sm-regular-fontWeight);
71
224
  font-size: var(--text-body-sm-regular-fontSize);
@@ -73,8 +226,75 @@
73
226
  white-space: nowrap;
74
227
  }
75
228
 
229
+ .muka-chat-bubble__timestamp--inset {
230
+ align-self: flex-end;
231
+ }
232
+
233
+ /* ─── Menu affordance ──────────────────────────────────── */
234
+
235
+ .muka-chat-bubble__menu {
236
+ position: absolute;
237
+ top: var(--spacing-1);
238
+ right: var(--spacing-1);
239
+ }
240
+
241
+ /* ─── Meta action row (below the bubble) ───────────────── */
242
+
76
243
  .muka-chat-bubble__actions {
244
+ grid-area: actions;
245
+ max-width: var(--chatbubble-max-width);
77
246
  display: flex;
78
247
  align-items: center;
79
248
  gap: 0;
80
249
  }
250
+
251
+ .muka-chat-bubble--own .muka-chat-bubble__actions,
252
+ .muka-chat-bubble--agent .muka-chat-bubble__actions {
253
+ justify-content: flex-end;
254
+ }
255
+
256
+ /*
257
+ * The meta row uses 32px icon buttons per the design. Expand the pointer/touch
258
+ * target to the 44px WCAG 2.1 AA minimum without changing the visual size.
259
+ */
260
+ .muka-chat-bubble__actions .muka-button--icon-only {
261
+ position: relative;
262
+ }
263
+
264
+ .muka-chat-bubble__actions .muka-button--icon-only::after {
265
+ content: '';
266
+ position: absolute;
267
+ top: 50%;
268
+ left: 50%;
269
+ width: 44px;
270
+ height: 44px;
271
+ transform: translate(-50%, -50%);
272
+ }
273
+
274
+ /* ─── Print ────────────────────────────────────────────── */
275
+
276
+ @media print {
277
+ .muka-chat-bubble__bubble {
278
+ box-shadow: none;
279
+ break-inside: avoid;
280
+ print-color-adjust: exact;
281
+ -webkit-print-color-adjust: exact;
282
+ }
283
+
284
+ .muka-chat-bubble__embed {
285
+ break-inside: avoid;
286
+ print-color-adjust: exact;
287
+ -webkit-print-color-adjust: exact;
288
+ }
289
+
290
+ .muka-chat-bubble__menu,
291
+ .muka-chat-bubble__actions,
292
+ .muka-chat-bubble__embed-copy,
293
+ .muka-chat-bubble__action-stack {
294
+ display: none;
295
+ }
296
+
297
+ .muka-chat-bubble__column {
298
+ max-width: 100%;
299
+ }
300
+ }
@@ -2,22 +2,35 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ChatBubble = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const Button_1 = require("../Button");
6
+ const Icon_1 = require("../Icon");
5
7
  require("./ChatBubble.css");
6
8
  /**
7
9
  * ChatBubble — a single chat message.
8
10
  *
9
- * Composes with `ChatMessageList`. For agent messages, render an `AgentTaskList`
10
- * above the bubble children if you want to show tool/task progress.
11
+ * Composes with `ChatMessageList`. Pass `<ChatPhotoGrid />` to `media`, an
12
+ * `<AgentTaskList />` above the bubble for tool progress, and a `<Badge />` to
13
+ * `badge` for visibility scope.
11
14
  */
12
- const ChatBubble = ({ variant, children, timestamp, actions, className = '', }) => {
15
+ const ChatBubble = ({ variant, children, avatar, author, authorMeta, badge, timestamp, media, embed, onEmbedCopy, embedCopyLabel = 'Copy', actionStack, onCopy, copyLabel = 'Copy', onFeedback, feedback = null, feedbackUpLabel = 'Helpful', feedbackDownLabel = 'Not helpful', onRefresh, refreshLabel = 'Regenerate', actions, onMenuClick, menuLabel = 'Message options', className = '', }) => {
16
+ // The own variant shows its timestamp inside the bubble; the others in the header row.
17
+ const timestampInHeader = variant !== 'own';
18
+ const hasHeader = Boolean(author || badge || (timestampInHeader && timestamp));
13
19
  const classes = [
14
20
  'muka-chat-bubble',
15
21
  `muka-chat-bubble--${variant}`,
22
+ avatar && 'muka-chat-bubble--has-avatar',
23
+ onMenuClick && 'muka-chat-bubble--has-menu',
24
+ !hasHeader && 'muka-chat-bubble--no-header',
16
25
  className,
17
26
  ]
18
27
  .filter(Boolean)
19
28
  .join(' ');
20
- return ((0, jsx_runtime_1.jsxs)("div", { className: classes, "data-variant": variant, children: [(0, jsx_runtime_1.jsx)("div", { className: "muka-chat-bubble__message", children: children }), (timestamp || actions) && ((0, jsx_runtime_1.jsxs)("div", { className: "muka-chat-bubble__meta", children: [timestamp && ((0, jsx_runtime_1.jsx)("span", { className: "muka-chat-bubble__timestamp", children: timestamp })), actions && ((0, jsx_runtime_1.jsx)("div", { className: "muka-chat-bubble__actions", children: actions }))] }))] }));
29
+ // The rail is only reserved when an avatar is supplied. To keep grouped
30
+ // messages aligned, pass a spacer node for the follow-up messages.
31
+ const rail = avatar ? ((0, jsx_runtime_1.jsx)("div", { className: "muka-chat-bubble__rail", children: avatar })) : null;
32
+ const hasActionBar = Boolean(onCopy || onFeedback || onRefresh || actions);
33
+ return ((0, jsx_runtime_1.jsx)("div", { className: classes, "data-variant": variant, children: (0, jsx_runtime_1.jsxs)("div", { className: "muka-chat-bubble__row", children: [variant !== 'own' ? rail : null, (0, jsx_runtime_1.jsxs)("div", { className: "muka-chat-bubble__bubble", children: [hasHeader && ((0, jsx_runtime_1.jsxs)("div", { className: "muka-chat-bubble__header", children: [author && ((0, jsx_runtime_1.jsx)("span", { className: "muka-chat-bubble__author", children: author })), authorMeta && ((0, jsx_runtime_1.jsx)("span", { className: "muka-chat-bubble__author-meta", children: authorMeta })), timestampInHeader && timestamp && ((0, jsx_runtime_1.jsx)("span", { className: "muka-chat-bubble__timestamp", children: timestamp })), badge && (0, jsx_runtime_1.jsx)("span", { className: "muka-chat-bubble__badge", children: badge })] })), (0, jsx_runtime_1.jsxs)("div", { className: "muka-chat-bubble__body", children: [media && (0, jsx_runtime_1.jsx)("div", { className: "muka-chat-bubble__media", children: media }), (0, jsx_runtime_1.jsx)("div", { className: "muka-chat-bubble__message", children: children }), embed && ((0, jsx_runtime_1.jsxs)("div", { className: "muka-chat-bubble__embed", children: [(0, jsx_runtime_1.jsx)("div", { className: "muka-chat-bubble__embed-content", children: embed }), onEmbedCopy && ((0, jsx_runtime_1.jsx)("div", { className: "muka-chat-bubble__embed-copy", children: (0, jsx_runtime_1.jsx)(Button_1.Button, { variant: "muted", size: "md", iconOnly: true, type: "button", "aria-label": embedCopyLabel, onClick: onEmbedCopy, children: (0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: "file-copy", size: "sm" }) }) }))] })), actionStack && ((0, jsx_runtime_1.jsx)("div", { className: "muka-chat-bubble__action-stack", children: actionStack }))] }), !timestampInHeader && timestamp && ((0, jsx_runtime_1.jsx)("span", { className: "muka-chat-bubble__timestamp muka-chat-bubble__timestamp--inset", children: timestamp })), onMenuClick && ((0, jsx_runtime_1.jsx)("div", { className: "muka-chat-bubble__menu", children: (0, jsx_runtime_1.jsx)(Button_1.Button, { variant: "muted", size: "sm", iconOnly: true, type: "button", "aria-label": menuLabel, onClick: onMenuClick, children: (0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: "arrow-down-s", size: "xs" }) }) }))] }), hasActionBar && ((0, jsx_runtime_1.jsxs)("div", { className: "muka-chat-bubble__actions", children: [onCopy && ((0, jsx_runtime_1.jsx)(Button_1.Button, { variant: "muted", size: "sm", iconOnly: true, type: "button", "aria-label": copyLabel, onClick: onCopy, children: (0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: "file-copy", size: "xs" }) })), onFeedback && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(Button_1.Button, { variant: "muted", size: "sm", iconOnly: true, type: "button", "aria-label": feedbackUpLabel, "aria-pressed": feedback === 'up', onClick: () => onFeedback(feedback === 'up' ? null : 'up'), children: (0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: "thumb-up", size: "xs" }) }), (0, jsx_runtime_1.jsx)(Button_1.Button, { variant: "muted", size: "sm", iconOnly: true, type: "button", "aria-label": feedbackDownLabel, "aria-pressed": feedback === 'down', onClick: () => onFeedback(feedback === 'down' ? null : 'down'), children: (0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: "thumb-down", size: "xs" }) })] })), onRefresh && ((0, jsx_runtime_1.jsx)(Button_1.Button, { variant: "muted", size: "sm", iconOnly: true, type: "button", "aria-label": refreshLabel, onClick: onRefresh, children: (0, jsx_runtime_1.jsx)(Icon_1.Icon, { name: "restart", size: "xs" }) })), actions] })), variant === 'own' ? rail : null] }) }));
21
34
  };
22
35
  exports.ChatBubble = ChatBubble;
23
36
  exports.default = exports.ChatBubble;
@@ -1 +1 @@
1
- {"version":3,"file":"ChatBubble.js","sourceRoot":"","sources":["../../../../components/ChatBubble/ChatBubble.tsx"],"names":[],"mappings":";;;;AACA,4BAA0B;AA0B1B;;;;;GAKG;AACI,MAAM,UAAU,GAA8B,CAAC,EACpD,OAAO,EACP,QAAQ,EACR,SAAS,EACT,OAAO,EACP,SAAS,GAAG,EAAE,GACf,EAAE,EAAE;IACH,MAAM,OAAO,GAAG;QACd,kBAAkB;QAClB,qBAAqB,OAAO,EAAE;QAC9B,SAAS;KACV;SACE,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,GAAG,CAAC,CAAC;IAEb,OAAO,CACL,iCAAK,SAAS,EAAE,OAAO,kBAAgB,OAAO,aAC5C,gCAAK,SAAS,EAAC,2BAA2B,YAAE,QAAQ,GAAO,EAC1D,CAAC,SAAS,IAAI,OAAO,CAAC,IAAI,CACzB,iCAAK,SAAS,EAAC,wBAAwB,aACpC,SAAS,IAAI,CACZ,iCAAM,SAAS,EAAC,6BAA6B,YAAE,SAAS,GAAQ,CACjE,EACA,OAAO,IAAI,CACV,gCAAK,SAAS,EAAC,2BAA2B,YAAE,OAAO,GAAO,CAC3D,IACG,CACP,IACG,CACP,CAAC;AACJ,CAAC,CAAC;AA9BW,QAAA,UAAU,cA8BrB;AAEF,kBAAe,kBAAU,CAAC"}
1
+ {"version":3,"file":"ChatBubble.js","sourceRoot":"","sources":["../../../../components/ChatBubble/ChatBubble.tsx"],"names":[],"mappings":"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"}
@@ -1,11 +1,15 @@
1
1
  /* ChatComposer Component Styles */
2
2
 
3
+ /*
4
+ * The composer is a bare, fill-width row: no padding, background, border or
5
+ * max-width of its own (Figma 5243:14141). Whatever hosts it owns the
6
+ * container — see BottomBar's `chat` variant.
7
+ */
3
8
  .muka-chat-composer {
4
9
  display: flex;
5
10
  flex-direction: column;
6
11
  gap: var(--spacing-3);
7
12
  width: 100%;
8
- max-width: 768px;
9
13
  }
10
14
 
11
15
  .muka-chat-composer--disabled {
@@ -111,3 +115,11 @@
111
115
  right: var(--spacing-2);
112
116
  transform: translateY(-50%);
113
117
  }
118
+
119
+ /* ─── Print ────────────────────────────────────────────── */
120
+
121
+ @media print {
122
+ .muka-chat-composer {
123
+ display: none;
124
+ }
125
+ }
@@ -19,6 +19,22 @@
19
19
  align-items: stretch;
20
20
  gap: var(--spacing-8);
21
21
  width: 100%;
22
- max-width: 640px;
22
+ max-width: var(--container-max-width-sm);
23
23
  margin: 0 auto;
24
24
  }
25
+
26
+ /* ─── Print ────────────────────────────────────────────── */
27
+
28
+ @media print {
29
+ .muka-chat-message-list {
30
+ overflow: visible;
31
+ height: auto;
32
+ max-height: none;
33
+ background: none;
34
+ padding: 0;
35
+ }
36
+
37
+ .muka-chat-message-list__inner {
38
+ max-width: 100%;
39
+ }
40
+ }