@usefragments/ui 1.3.2 → 1.4.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 (145) hide show
  1. package/README.md +3 -1
  2. package/dist/assets/ui.css +1261 -849
  3. package/dist/chart.cjs +60 -24
  4. package/dist/chart.js +38 -24
  5. package/dist/codeblock.cjs +14 -6
  6. package/dist/codeblock.js +14 -6
  7. package/dist/colorpicker.cjs +56 -9
  8. package/dist/colorpicker.js +34 -9
  9. package/dist/components/Badge/Badge.module.scss.cjs +23 -16
  10. package/dist/components/Badge/Badge.module.scss.js +23 -16
  11. package/dist/components/Badge/index.cjs +8 -1
  12. package/dist/components/Badge/index.d.ts +3 -0
  13. package/dist/components/Badge/index.d.ts.map +1 -1
  14. package/dist/components/Badge/index.js +8 -1
  15. package/dist/components/Button/Button.module.scss.cjs +16 -13
  16. package/dist/components/Button/Button.module.scss.js +16 -13
  17. package/dist/components/Button/index.d.ts +2 -1
  18. package/dist/components/Button/index.d.ts.map +1 -1
  19. package/dist/components/Chart/index.d.ts +3 -0
  20. package/dist/components/Chart/index.d.ts.map +1 -1
  21. package/dist/components/CodeBlock/CodeBlock.module.scss.cjs +21 -21
  22. package/dist/components/CodeBlock/CodeBlock.module.scss.js +21 -21
  23. package/dist/components/CodeBlock/index.d.ts.map +1 -1
  24. package/dist/components/ColorPicker/index.d.ts +4 -0
  25. package/dist/components/ColorPicker/index.d.ts.map +1 -1
  26. package/dist/components/Combobox/Combobox.module.scss.cjs +23 -23
  27. package/dist/components/Combobox/Combobox.module.scss.js +23 -23
  28. package/dist/components/Command/Command.module.scss.cjs +11 -11
  29. package/dist/components/Command/Command.module.scss.js +11 -11
  30. package/dist/components/ConversationList/ConversationList.module.scss.cjs +13 -10
  31. package/dist/components/ConversationList/ConversationList.module.scss.js +14 -11
  32. package/dist/components/ConversationList/index.cjs +26 -22
  33. package/dist/components/ConversationList/index.d.ts +5 -1
  34. package/dist/components/ConversationList/index.d.ts.map +1 -1
  35. package/dist/components/ConversationList/index.js +27 -23
  36. package/dist/components/DataTable/DataTable.module.scss.cjs +35 -35
  37. package/dist/components/DataTable/DataTable.module.scss.js +35 -35
  38. package/dist/components/DataTable/index.d.ts +4 -1
  39. package/dist/components/DataTable/index.d.ts.map +1 -1
  40. package/dist/components/DatePicker/index.d.ts +3 -0
  41. package/dist/components/DatePicker/index.d.ts.map +1 -1
  42. package/dist/components/Editor/index.d.ts +4 -1
  43. package/dist/components/Editor/index.d.ts.map +1 -1
  44. package/dist/components/Listbox/Listbox.module.scss.cjs +8 -8
  45. package/dist/components/Listbox/Listbox.module.scss.js +8 -8
  46. package/dist/components/Markdown/Markdown.module.scss.cjs +1 -1
  47. package/dist/components/Markdown/Markdown.module.scss.js +1 -1
  48. package/dist/components/Markdown/index.d.ts +16 -0
  49. package/dist/components/Markdown/index.d.ts.map +1 -1
  50. package/dist/components/Menu/Menu.module.scss.cjs +14 -14
  51. package/dist/components/Menu/Menu.module.scss.js +14 -14
  52. package/dist/components/Message/Message.module.scss.cjs +23 -20
  53. package/dist/components/Message/Message.module.scss.js +24 -21
  54. package/dist/components/Message/index.cjs +5 -1
  55. package/dist/components/Message/index.d.ts.map +1 -1
  56. package/dist/components/Message/index.js +5 -1
  57. package/dist/components/NavigationMenu/NavigationMenu.module.scss.cjs +28 -28
  58. package/dist/components/NavigationMenu/NavigationMenu.module.scss.js +28 -28
  59. package/dist/components/Pagination/Pagination.module.scss.cjs +7 -7
  60. package/dist/components/Pagination/Pagination.module.scss.js +7 -7
  61. package/dist/components/Prompt/Prompt.module.scss.cjs +52 -22
  62. package/dist/components/Prompt/Prompt.module.scss.js +52 -22
  63. package/dist/components/Prompt/index.cjs +197 -2
  64. package/dist/components/Prompt/index.d.ts +98 -3
  65. package/dist/components/Prompt/index.d.ts.map +1 -1
  66. package/dist/components/Prompt/index.js +198 -3
  67. package/dist/components/Select/Select.module.scss.cjs +29 -17
  68. package/dist/components/Select/Select.module.scss.js +30 -18
  69. package/dist/components/Select/index.cjs +45 -8
  70. package/dist/components/Select/index.d.ts +17 -2
  71. package/dist/components/Select/index.d.ts.map +1 -1
  72. package/dist/components/Select/index.js +45 -8
  73. package/dist/components/Sidebar/Sidebar.module.scss.cjs +45 -42
  74. package/dist/components/Sidebar/Sidebar.module.scss.js +45 -42
  75. package/dist/components/Sidebar/index.cjs +2 -0
  76. package/dist/components/Sidebar/index.d.ts +6 -1
  77. package/dist/components/Sidebar/index.d.ts.map +1 -1
  78. package/dist/components/Sidebar/index.js +2 -0
  79. package/dist/components/Text/Text.module.scss.cjs +63 -51
  80. package/dist/components/Text/Text.module.scss.js +63 -51
  81. package/dist/components/Text/index.d.ts +4 -2
  82. package/dist/components/Text/index.d.ts.map +1 -1
  83. package/dist/data-table.cjs +210 -28
  84. package/dist/data-table.js +189 -29
  85. package/dist/datepicker.cjs +63 -14
  86. package/dist/datepicker.js +41 -14
  87. package/dist/editor.cjs +67 -16
  88. package/dist/editor.js +45 -16
  89. package/dist/index.cjs +1 -0
  90. package/dist/index.d.ts +1 -1
  91. package/dist/index.d.ts.map +1 -1
  92. package/dist/index.js +2 -1
  93. package/dist/markdown.cjs +63 -19
  94. package/dist/markdown.js +41 -19
  95. package/fragments.json +1 -1
  96. package/package.json +2 -2
  97. package/src/components/Badge/Badge.contract.json +28 -5
  98. package/src/components/Badge/Badge.module.scss +21 -0
  99. package/src/components/Badge/index.tsx +10 -1
  100. package/src/components/Button/Button.contract.json +9 -3
  101. package/src/components/Button/Button.module.scss +34 -0
  102. package/src/components/Button/index.tsx +11 -1
  103. package/src/components/Chart/Chart.contract.json +7 -0
  104. package/src/components/Chart/index.tsx +44 -27
  105. package/src/components/CodeBlock/CodeBlock.contract.json +7 -0
  106. package/src/components/CodeBlock/CodeBlock.module.scss +7 -2
  107. package/src/components/CodeBlock/index.tsx +38 -6
  108. package/src/components/ColorPicker/ColorPicker.contract.json +9 -1
  109. package/src/components/ColorPicker/index.tsx +42 -12
  110. package/src/components/Combobox/Combobox.module.scss +4 -13
  111. package/src/components/ConversationList/ConversationList.contract.json +16 -1
  112. package/src/components/ConversationList/ConversationList.module.scss +12 -0
  113. package/src/components/ConversationList/index.tsx +40 -26
  114. package/src/components/DataTable/DataTable.contract.json +8 -1
  115. package/src/components/DataTable/DataTable.module.scss +5 -0
  116. package/src/components/DataTable/index.tsx +282 -51
  117. package/src/components/DatePicker/DatePicker.contract.json +8 -1
  118. package/src/components/DatePicker/index.tsx +51 -22
  119. package/src/components/Editor/Editor.contract.json +19 -1
  120. package/src/components/Editor/index.tsx +67 -23
  121. package/src/components/Listbox/Listbox.module.scss +3 -11
  122. package/src/components/Markdown/Markdown.contract.json +15 -1
  123. package/src/components/Markdown/Markdown.module.scss +18 -1
  124. package/src/components/Markdown/index.tsx +58 -24
  125. package/src/components/Menu/Menu.module.scss +12 -2
  126. package/src/components/Message/Message.contract.json +3 -0
  127. package/src/components/Message/Message.module.scss +33 -4
  128. package/src/components/Message/index.tsx +7 -1
  129. package/src/components/Pagination/Pagination.contract.json +1 -0
  130. package/src/components/Pagination/Pagination.module.scss +5 -4
  131. package/src/components/Prompt/Prompt.contract.json +40 -1
  132. package/src/components/Prompt/Prompt.module.scss +351 -4
  133. package/src/components/Prompt/index.tsx +324 -0
  134. package/src/components/Select/Select.contract.json +11 -1
  135. package/src/components/Select/Select.module.scss +80 -13
  136. package/src/components/Select/index.tsx +73 -7
  137. package/src/components/Sidebar/Sidebar.contract.json +1 -0
  138. package/src/components/Sidebar/Sidebar.module.scss +10 -0
  139. package/src/components/Sidebar/index.tsx +7 -0
  140. package/src/components/Text/Text.contract.json +7 -4
  141. package/src/components/Text/Text.module.scss +18 -0
  142. package/src/components/Text/index.tsx +4 -2
  143. package/src/index.ts +1 -0
  144. package/src/tokens/_mixins.scss +39 -4
  145. package/src/tokens/_variables.scss +30 -0
@@ -27,7 +27,10 @@
27
27
  "maxRows: number (default: 8)",
28
28
  "autoResize: boolean (default: true)",
29
29
  "submitOnEnter: boolean (default: true)",
30
- "variant: default|fixed|sticky (default: default)"
30
+ "variant: default|fixed|sticky (default: default)",
31
+ "appearance: panel|seamless (default: panel)",
32
+ "onFiles: function",
33
+ "accept: string"
31
34
  ],
32
35
  "props": {
33
36
  "children": {
@@ -108,6 +111,26 @@
108
111
  "fixed",
109
112
  "sticky"
110
113
  ]
114
+ },
115
+ "onFiles": {
116
+ "type": "function",
117
+ "description": "Called with files added by the attach button, a paste, or a drop anywhere on the card. Providing it enables all three.",
118
+ "required": false
119
+ },
120
+ "accept": {
121
+ "type": "string",
122
+ "description": "accept filter for the attach button's picker, e.g. \"image/*\"",
123
+ "required": false
124
+ },
125
+ "appearance": {
126
+ "type": "enum",
127
+ "description": "How the card is divided up. \"panel\" keeps the toolbar as a filled footer under a rule; \"seamless\" makes the whole card one writing surface with the controls floating on it.",
128
+ "default": "panel",
129
+ "required": false,
130
+ "values": [
131
+ "panel",
132
+ "seamless"
133
+ ]
111
134
  }
112
135
  },
113
136
  "usage": {
@@ -126,6 +149,14 @@
126
149
  "Use loading state during API calls",
127
150
  "Prompt.Textarea forwards native textarea props (autoComplete, inputMode, aria-*, data-*, handlers)",
128
151
  "Prompt.Textarea composes your onChange/onKeyDown handlers with built-in submit and auto-resize behavior",
152
+ "Use appearance=\"seamless\" for agent composers where the controls should float on the writing surface rather than sit in a footer",
153
+ "Seamless controls share the dense 32px token scale with body-sized labels and standard glyphs so mixed Prompt.Select, Prompt.Attach and Prompt.Submit rows remain optically aligned",
154
+ "Seamless secondary controls keep transparent hover and open states so they read as inline tools; only Prompt.Submit carries a persistent filled shape",
155
+ "Seamless Prompt.Submit mirrors the trailing ink gutter established by Prompt.Attach at the leading edge",
156
+ "Prompt.Submit uses a neutral inverse surface by default so the primary action stays visible without consuming semantic brand color",
157
+ "Use Prompt.ModeButton for a setting you toggle and Prompt.Select for one you pick from a list",
158
+ "Wire onFiles once: the attach button, pasting a screenshot and dropping on the card all go through it",
159
+ "Render Prompt.Attachments above Prompt.Textarea — attachments are part of the message, not a setting on it",
129
160
  "Consider showing usage/token limits for AI contexts"
130
161
  ],
131
162
  "accessibility": [
@@ -150,6 +181,11 @@
150
181
  "description": "Shows token usage indicator",
151
182
  "code": "<Prompt onSubmit={(value) => console.log(value)}>\n <Prompt.Textarea />\n <Prompt.Toolbar>\n <Prompt.Actions>\n <Prompt.ActionButton aria-label=\"Add attachment\">\n +\n </Prompt.ActionButton>\n <Prompt.ModeButton active>Auto</Prompt.ModeButton>\n </Prompt.Actions>\n <Prompt.Info>\n <Prompt.Usage>52% used</Prompt.Usage>\n <Prompt.Submit />\n </Prompt.Info>\n </Prompt.Toolbar>\n</Prompt>"
152
183
  },
184
+ {
185
+ "name": "Agent Composer",
186
+ "description": "Seamless surface with the scope of the run chosen on the toolbar",
187
+ "code": "<Prompt\n appearance=\"seamless\"\n submitOnEnter={false}\n minRows={3}\n onSubmit={(value) => console.log(value)}\n>\n <Prompt.Textarea placeholder=\"What should be true when this is done?\" />\n <Prompt.Toolbar>\n <Prompt.Actions>\n <Prompt.Select\n aria-label=\"Model\"\n defaultValue=\"claude-opus-4-8\"\n options={[{ value: 'claude-opus-4-8', label: 'Opus 4.8' }, { value: 'claude-sonnet-5', label: 'Sonnet 5' }]}\n />\n <Prompt.ModeButton>Plan</Prompt.ModeButton>\n </Prompt.Actions>\n <Prompt.Info>\n <Prompt.Submit />\n </Prompt.Info>\n </Prompt.Toolbar>\n</Prompt>"
188
+ },
153
189
  {
154
190
  "name": "Loading State",
155
191
  "description": "During API submission",
@@ -200,6 +236,9 @@
200
236
  "Info",
201
237
  "ActionButton",
202
238
  "ModeButton",
239
+ "Select",
240
+ "Attach",
241
+ "Attachments",
203
242
  "Usage",
204
243
  "Submit"
205
244
  ]
@@ -113,6 +113,198 @@
113
113
  transform: translateX(-50%);
114
114
  }
115
115
 
116
+ // ============================================
117
+ // Seamless appearance
118
+ // ============================================
119
+
120
+ // One continuous writing surface instead of a field with a footer.
121
+ //
122
+ // The whole point is that nothing inside the card announces itself: no rule
123
+ // under the toolbar, no stroke around the edge, no animated border. What is
124
+ // left is a single quiet sheet, lighter than the page, with the controls
125
+ // resting on it — which is what makes the text you are writing the only thing
126
+ // with any weight. Each selector below is one class deeper than the rule it
127
+ // replaces, so this wins wherever the stylesheet happens to land.
128
+ //
129
+ // One gutter runs down the card. `--_gutter` is the distance from the card's
130
+ // edge to the ink of everything in it, and every child is padded from it
131
+ // rather than given a number of its own, so the placeholder, the first glyph
132
+ // in the toolbar and any attachment chip all sit on the same line.
133
+ .seamless {
134
+ --_gutter: var(--fui-space-3, #{$fui-space-3});
135
+ // Every band is inset by its own children's padding, so what lands on the
136
+ // gutter is the ink — the first character of the placeholder, the first glyph
137
+ // in the toolbar, the first pixel of a thumbnail — and not the invisible box
138
+ // around it. Controls and chips carry different padding, so each band
139
+ // subtracts its own.
140
+ --_control-padding: var(--fui-space-2, #{$fui-space-2});
141
+ --_chip-padding: var(--fui-space-1, #{$fui-space-1});
142
+
143
+ // Composer controls are compact, but still need enough visual weight to read
144
+ // as one deliberate toolbar. The xs button token is the dense 32px control
145
+ // height in every density preset; scoping all three inputs to it keeps
146
+ // selects, attach, action and submit controls on one optical grid without a
147
+ // one-off dimension.
148
+ --fui-target-size-min: var(--fui-button-height-xs, #{$fui-button-height-xs});
149
+ --fui-button-height-sm: var(--fui-button-height-xs, #{$fui-button-height-xs});
150
+ --fui-input-height-sm: var(--fui-button-height-xs, #{$fui-button-height-xs});
151
+
152
+ border-color: transparent;
153
+ border-radius: var(--fui-radius-xl, $fui-radius-xl);
154
+
155
+ // The beam draws a 1px animated border by painting a gradient and masking
156
+ // out everything but the edge. Seamless has no edge to draw, so both layers
157
+ // go and the card paints its own surface instead of borrowing the mask's.
158
+ &::before,
159
+ &::after {
160
+ display: none;
161
+ }
162
+
163
+ // Focus lives in the caret and in the fact that the surface is the thing you
164
+ // are typing on. A ring here would be the loudest object on the page.
165
+ &:focus-within {
166
+ border-color: transparent;
167
+ }
168
+
169
+ .toolbar {
170
+ background-color: transparent;
171
+ border-top: none;
172
+ padding: 0 calc(var(--_gutter) - var(--_control-padding)) var(--_gutter);
173
+ }
174
+
175
+ // The message is the content of the card, not a field in it. The bottom is
176
+ // tight because the toolbar below is part of the same surface rather than a
177
+ // footer under it, and the top is tight for the same reason when a scope
178
+ // band sits above.
179
+ .textarea {
180
+ padding: var(--_gutter) var(--_gutter) var(--fui-space-1, $fui-space-1);
181
+ }
182
+
183
+ .attachments {
184
+ padding: var(--_gutter) calc(var(--_gutter) - var(--_chip-padding)) 0;
185
+ gap: var(--fui-space-1, $fui-space-1);
186
+ }
187
+
188
+ // Two bands in a row would otherwise stack their gutters into a gap twice
189
+ // the size of the one at the card's edge.
190
+ .attachments + .textarea,
191
+ .tabs + .attachments {
192
+ padding-top: var(--fui-space-1, $fui-space-1);
193
+ }
194
+
195
+ // In a panel the tabs are a strip the card is docked to, so they are centred
196
+ // and bordered like a folder tab. Seamless has no card edge for them to dock
197
+ // against, so they become the top band of the one surface: left-aligned on
198
+ // the gutter with the rest of it, and stripped of the chrome that only made
199
+ // sense as an edge.
200
+ .tabs {
201
+ margin-bottom: 0;
202
+ justify-content: flex-start;
203
+ padding: var(--_gutter) calc(var(--_gutter) - var(--_control-padding)) 0;
204
+ }
205
+
206
+ .tabsInner {
207
+ background-color: transparent;
208
+ border: none;
209
+ border-radius: 0;
210
+ padding: 0;
211
+ gap: var(--fui-space-1, $fui-space-1);
212
+ }
213
+
214
+ // The band above already sets the top of the card, so the message starts
215
+ // from the tighter of the two spacings rather than adding a second gutter.
216
+ .tabs + .textarea {
217
+ padding-top: var(--fui-space-1, $fui-space-1);
218
+ }
219
+
220
+ // A centred glyph in a square box puts its ink half a box in from the
221
+ // gutter, which reads as the one thing in the card that is out of line.
222
+ // Giving it the same inline padding as its neighbours puts the glyph where
223
+ // the placeholder above it starts, and costs only a wider hover.
224
+ .attach {
225
+ width: auto;
226
+ padding-inline: var(--_control-padding);
227
+
228
+ svg {
229
+ width: var(--fui-icon-md, $fui-icon-md);
230
+ height: var(--fui-icon-md, $fui-icon-md);
231
+ }
232
+ }
233
+
234
+ // Labels and glyphs share the body scale inside a seamless composer. The
235
+ // trigger still truncates at its cap, but no longer looks like caption text
236
+ // floating between larger attach and submit affordances.
237
+ .select,
238
+ .modeButton {
239
+ font-size: var(--fui-font-size-sm, $fui-font-size-sm);
240
+ }
241
+
242
+ .select {
243
+ svg {
244
+ width: var(--fui-icon-md, $fui-icon-md);
245
+ height: var(--fui-icon-md, $fui-icon-md);
246
+ }
247
+ }
248
+
249
+ // These are settings on the message, not a row of buttons. Preserve their
250
+ // full target boxes for hit testing, but keep those boxes visually absent.
251
+ // Send remains the only persistent shape in the composer.
252
+ .select,
253
+ .attach,
254
+ .modeButton {
255
+ &:hover:not(:disabled):not([data-disabled]),
256
+ &:active:not(:disabled):not([data-disabled]) {
257
+ background-color: transparent;
258
+ color: var(--fui-text-primary, $fui-text-primary);
259
+ }
260
+
261
+ // Keyboard focus still needs a visible location, but a restrained baseline
262
+ // reads as focus on inline content rather than another rounded button.
263
+ &:focus-visible {
264
+ outline: none;
265
+ box-shadow: inset 0 calc(var(--fui-space-px, #{$fui-space-px}) * -2) 0
266
+ var(--fui-text-primary, #{$fui-text-primary});
267
+ }
268
+ }
269
+
270
+ .select {
271
+ // Opening a menu identifies the active choice through its popup and text
272
+ // color. A held surface behind the trigger is unnecessary visual chrome.
273
+ &[data-popup-open] {
274
+ border-color: transparent;
275
+ background-color: transparent;
276
+ box-shadow: none;
277
+ color: var(--fui-text-primary, $fui-text-primary);
278
+ }
279
+
280
+ // Pointer focus must not inherit the field shell's accent treatment.
281
+ &:focus:not(:focus-visible) {
282
+ border-color: transparent;
283
+ box-shadow: none;
284
+ }
285
+ }
286
+
287
+ // Round affordances track the scoped target size rather than the 2rem they
288
+ // are given for a form.
289
+ .submit,
290
+ .actionButton {
291
+ width: var(--fui-target-size-min);
292
+ height: var(--fui-target-size-min);
293
+
294
+ svg {
295
+ width: var(--fui-icon-md, $fui-icon-md);
296
+ height: var(--fui-icon-md, $fui-icon-md);
297
+ }
298
+ }
299
+
300
+ // Attach's glyph reaches the leading ink gutter through its own control
301
+ // padding. Give the filled Submit shape the same inset at the trailing edge
302
+ // so the two visible affordances balance across the toolbar.
303
+ .submit {
304
+ margin-inline-end: var(--_control-padding);
305
+ }
306
+ }
307
+
116
308
  // ============================================
117
309
  // Textarea
118
310
  // ============================================
@@ -292,6 +484,159 @@
292
484
  background-color: var(--fui-bg-tertiary, $fui-bg-tertiary);
293
485
  }
294
486
 
487
+ // ============================================
488
+ // Attach (bare icon control)
489
+ // ============================================
490
+
491
+ // A `+` with nothing around it. ActionButton is a filled circle, which is the
492
+ // right weight for a primary affordance in the bar; attaching a file is not
493
+ // one — it should be findable and otherwise silent, so this is the mode button
494
+ // with a square footprint and no label.
495
+ .attach {
496
+ @include button-reset;
497
+ @include interactive-base;
498
+
499
+ display: flex;
500
+ align-items: center;
501
+ justify-content: center;
502
+ flex-shrink: 0;
503
+ // The target size rather than a spacing step, so it tracks whatever scale
504
+ // the surface around it has established.
505
+ width: var(--fui-target-size-min, $fui-target-size-min);
506
+ height: var(--fui-target-size-min, $fui-target-size-min);
507
+ border-radius: var(--fui-radius-md, $fui-radius-md);
508
+ background-color: transparent;
509
+ color: var(--fui-text-secondary, $fui-text-secondary);
510
+
511
+ &:hover:not(:disabled) {
512
+ background-color: var(--fui-bg-hover, $fui-bg-hover);
513
+ color: var(--fui-text-primary, $fui-text-primary);
514
+ }
515
+
516
+ &:active:not(:disabled) {
517
+ background-color: var(--fui-bg-active, $fui-bg-active);
518
+ }
519
+
520
+ &:disabled {
521
+ opacity: 0.5;
522
+ cursor: not-allowed;
523
+ }
524
+
525
+ svg {
526
+ width: var(--fui-icon-sm, $fui-icon-sm);
527
+ height: var(--fui-icon-sm, $fui-icon-sm);
528
+ }
529
+ }
530
+
531
+ // ============================================
532
+ // Attachments strip
533
+ // ============================================
534
+
535
+ .attachments {
536
+ display: flex;
537
+ flex-wrap: wrap;
538
+ gap: var(--fui-space-1, $fui-space-1);
539
+ margin: 0;
540
+ padding: var(--fui-space-2, $fui-space-2) var(--fui-space-3, $fui-space-3) 0;
541
+ list-style: none;
542
+ }
543
+
544
+ // What is attached, stated once and removable. Sits above the message rather
545
+ // than below the toolbar, because it is part of what you are sending, not a
546
+ // setting on the sending.
547
+ // Sized off the same target as everything else in the bar, so a chip and a
548
+ // control are the same height and the card has one horizontal rhythm.
549
+ .attachment {
550
+ --_chip-height: var(--fui-target-size-min, #{$fui-target-size-min});
551
+
552
+ display: inline-flex;
553
+ align-items: center;
554
+ gap: var(--fui-space-1, $fui-space-1);
555
+ height: var(--_chip-height);
556
+ max-width: 15rem;
557
+ padding-inline: var(--fui-space-1, $fui-space-1);
558
+ // Outlined rather than filled. A chip sits on whatever surface the prompt is
559
+ // using, and on an elevated card the raised fill is the same value as the
560
+ // card itself — so the object would disappear exactly where it is most
561
+ // likely to be used. An edge reads on any of them.
562
+ border: 1px solid var(--fui-border, $fui-border);
563
+ border-radius: var(--fui-radius-md, $fui-radius-md);
564
+ background-color: var(--fui-bg-tertiary, $fui-bg-tertiary);
565
+ font-size: var(--fui-font-size-xs, $fui-font-size-xs);
566
+ color: var(--fui-text-secondary, $fui-text-secondary);
567
+ }
568
+
569
+ // A screenshot is recognised by its picture, not by "Screenshot 2026-07-24 at
570
+ // 22.06.09.png", so an image attachment leads with a thumbnail. It fills the
571
+ // chip's height less the padding, which keeps it square without a second
572
+ // number to keep in step.
573
+ .attachmentThumb {
574
+ width: calc(var(--_chip-height) - var(--fui-space-1, #{$fui-space-1}));
575
+ height: calc(var(--_chip-height) - var(--fui-space-1, #{$fui-space-1}));
576
+ flex-shrink: 0;
577
+ border-radius: var(--fui-radius-sm, $fui-radius-sm);
578
+ object-fit: cover;
579
+ }
580
+
581
+ // The name is the only part worth the width, so it is the only part that grows
582
+ // — and the only part that gives way when there is none.
583
+ .attachmentName {
584
+ flex: 1;
585
+ min-width: 0;
586
+ overflow: hidden;
587
+ text-overflow: ellipsis;
588
+ white-space: nowrap;
589
+ }
590
+
591
+ .attachmentSize {
592
+ flex-shrink: 0;
593
+ color: var(--fui-text-tertiary, $fui-text-tertiary);
594
+ font-variant-numeric: tabular-nums;
595
+ }
596
+
597
+ .attachmentRemove {
598
+ @include button-reset;
599
+ @include interactive-base;
600
+
601
+ display: flex;
602
+ align-items: center;
603
+ justify-content: center;
604
+ flex-shrink: 0;
605
+ width: calc(var(--_chip-height) - var(--fui-space-1, #{$fui-space-1}));
606
+ height: calc(var(--_chip-height) - var(--fui-space-1, #{$fui-space-1}));
607
+ border-radius: var(--fui-radius-sm, $fui-radius-sm);
608
+ background-color: transparent;
609
+ color: var(--fui-text-tertiary, $fui-text-tertiary);
610
+
611
+ &:hover {
612
+ background-color: var(--fui-bg-hover, $fui-bg-hover);
613
+ color: var(--fui-text-primary, $fui-text-primary);
614
+ }
615
+
616
+ svg {
617
+ width: var(--fui-icon-xs, $fui-icon-xs);
618
+ height: var(--fui-icon-xs, $fui-icon-xs);
619
+ }
620
+ }
621
+
622
+ // Dropping a file anywhere on the card counts. The whole surface lights rather
623
+ // than a dashed rectangle appearing inside it, because the target is the
624
+ // composer, not some region of it.
625
+ .dragging {
626
+ background-color: var(--fui-bg-hover, $fui-bg-hover);
627
+ }
628
+
629
+ // ============================================
630
+ // Select (ghost Select, sized for the toolbar)
631
+ // ============================================
632
+
633
+ // The chosen value can be arbitrarily long — a model name, a file path — and
634
+ // must not push the submit button off the row. Select's ghost trigger already
635
+ // allows shrinking and ellipsises its value; this only caps how far it grows.
636
+ .select {
637
+ max-width: 15rem;
638
+ }
639
+
295
640
  // ============================================
296
641
  // Usage indicator
297
642
  // ============================================
@@ -315,15 +660,17 @@
315
660
  width: 2rem;
316
661
  height: 2rem;
317
662
  border-radius: var(--fui-radius-full, $fui-radius-full);
318
- background-color: var(--fui-color-accent, $fui-color-accent);
319
- color: var(--fui-color-on-accent, $fui-color-on-accent);
663
+ // A neutral inverse keeps send visibly primary without spending the product
664
+ // accent, which may already encode agent state.
665
+ background-color: var(--fui-text-primary, #{$fui-text-primary});
666
+ color: var(--fui-bg-elevated, #{$fui-bg-elevated});
320
667
 
321
668
  &:hover:not(:disabled) {
322
- background-color: var(--fui-color-accent-hover, $fui-color-accent-hover);
669
+ background-color: var(--fui-text-secondary, #{$fui-text-secondary});
323
670
  }
324
671
 
325
672
  &:active:not(:disabled) {
326
- background-color: var(--fui-color-accent-active, $fui-color-accent-active);
673
+ background-color: var(--fui-text-tertiary, #{$fui-text-tertiary});
327
674
  }
328
675
 
329
676
  &:disabled {