@happyvertical/smrt-messages 0.34.5 → 0.34.7

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 (32) hide show
  1. package/dist/manifest.json +2 -2
  2. package/dist/smrt-knowledge.json +4 -4
  3. package/dist/svelte/components/AccountCard.svelte +22 -13
  4. package/dist/svelte/components/AttachmentChip.svelte +26 -7
  5. package/dist/svelte/components/AttachmentChip.svelte.d.ts +0 -3
  6. package/dist/svelte/components/AttachmentChip.svelte.d.ts.map +1 -1
  7. package/dist/svelte/components/AttachmentUpload.svelte +19 -7
  8. package/dist/svelte/components/AttachmentUpload.svelte.d.ts.map +1 -1
  9. package/dist/svelte/components/ComposeForm.svelte +66 -75
  10. package/dist/svelte/components/ComposeForm.svelte.d.ts.map +1 -1
  11. package/dist/svelte/components/EmailAccountManager.svelte +81 -115
  12. package/dist/svelte/components/EmailAccountManager.svelte.d.ts.map +1 -1
  13. package/dist/svelte/components/EmailFilterManager.svelte +94 -127
  14. package/dist/svelte/components/EmailFilterManager.svelte.d.ts.map +1 -1
  15. package/dist/svelte/components/FolderNav.svelte +22 -15
  16. package/dist/svelte/components/FolderNav.svelte.d.ts.map +1 -1
  17. package/dist/svelte/components/ForwardForm.svelte +16 -48
  18. package/dist/svelte/components/ForwardForm.svelte.d.ts.map +1 -1
  19. package/dist/svelte/components/MessageCard.svelte +14 -6
  20. package/dist/svelte/components/MessageCard.svelte.d.ts.map +1 -1
  21. package/dist/svelte/components/MessageDetail.svelte +19 -12
  22. package/dist/svelte/components/MessageFilters.svelte +47 -43
  23. package/dist/svelte/components/MessageFilters.svelte.d.ts.map +1 -1
  24. package/dist/svelte/components/MessageToolbar.svelte +28 -20
  25. package/dist/svelte/components/MessageToolbar.svelte.d.ts.map +1 -1
  26. package/dist/svelte/components/RecipientInput.svelte +32 -9
  27. package/dist/svelte/components/RecipientInput.svelte.d.ts.map +1 -1
  28. package/dist/svelte/components/ReplyForm.svelte +16 -48
  29. package/dist/svelte/components/ReplyForm.svelte.d.ts.map +1 -1
  30. package/dist/svelte/components/ThreadView.svelte +19 -12
  31. package/dist/svelte/components/ThreadView.svelte.d.ts.map +1 -1
  32. package/package.json +8 -7
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import { useI18n } from '@happyvertical/smrt-ui/i18n';
7
+ import { Button } from '@happyvertical/smrt-ui/ui';
7
8
  import type { Snippet } from 'svelte';
8
9
  import { M } from '../i18n.messages.js';
9
10
  import type { AccountData, MessageData } from '../types.js';
@@ -97,6 +98,7 @@ const _slackMeta = $derived.by(() => {
97
98
  >
98
99
  {#if onselect}
99
100
  <div class="select-col">
101
+ <!-- raw-primitive-allow: native checkbox; no Provider-free checkbox primitive (Toggle is a switch with different semantics, CheckboxInput requires a Provider) -->
100
102
  <input
101
103
  type="checkbox"
102
104
  checked={selected}
@@ -106,6 +108,7 @@ const _slackMeta = $derived.by(() => {
106
108
  </div>
107
109
  {/if}
108
110
 
111
+ <!-- raw-primitive-allow: large pressable message row wrapping rich header/subject/preview content (a structural selection surface with descendant compact-mode layout rules), not a standard action button -->
109
112
  <button
110
113
  class="message-content"
111
114
  type="button"
@@ -162,15 +165,15 @@ const _slackMeta = $derived.by(() => {
162
165
  </button>
163
166
 
164
167
  {#if onflag}
165
- <button
168
+ <Button
169
+ variant="ghost"
166
170
  class="flag-btn"
167
- type="button"
168
171
  onclick={() => onflag?.(message)}
169
172
  title={message.isFlagged ? 'Unflag' : 'Flag'}
170
173
  aria-label={message.isFlagged ? 'Unflag' : 'Flag'}
171
174
  >
172
175
  {message.isFlagged ? '⚑' : '⚐'}
173
- </button>
176
+ </Button>
174
177
  {/if}
175
178
  </div>
176
179
 
@@ -308,21 +311,26 @@ const _slackMeta = $derived.by(() => {
308
311
  color: var(--smrt-color-on-surface-variant, #43474e);
309
312
  }
310
313
 
311
- .flag-btn {
314
+ /*
315
+ * The flag toggle now renders through smrt-ui's <Button variant="ghost">.
316
+ * `.message-card :global(.flag-btn)` anchors on the real `.message-card`
317
+ * element and pierces the Button child scope to keep the dimmed icon-toggle
318
+ * styling (issue #1589).
319
+ */
320
+ .message-card :global(.flag-btn) {
312
321
  display: flex;
313
322
  align-items: center;
314
323
  justify-content: center;
315
324
  width: 2rem;
316
325
  border: none;
317
326
  background: none;
318
- cursor: pointer;
319
327
  font-size: var(--smrt-typography-body-large-size, 1rem);
320
328
  color: var(--smrt-color-on-surface-variant, #43474e);
321
329
  opacity: 0.5;
322
330
  transition: opacity var(--smrt-duration-short2, 150ms);
323
331
  }
324
332
 
325
- .flag-btn:hover {
333
+ .message-card :global(.flag-btn):hover {
326
334
  opacity: 1;
327
335
  }
328
336
 
@@ -1 +1 @@
1
- {"version":3,"file":"MessageCard.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/MessageCard.svelte.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAK5D,MAAM,WAAW,KAAK;IACpB,OAAO,EAAE,WAAW,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IACzC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IAC1C,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IACxC,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC,CAAC;QAAE,OAAO,EAAE,WAAW,CAAA;KAAE,CAAC,CAAC,CAAC;CACnD;AA6ID,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"MessageCard.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/MessageCard.svelte.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAK5D,MAAM,WAAW,KAAK;IACpB,OAAO,EAAE,WAAW,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IACzC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IAC1C,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IACxC,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,WAAW,CAAC,EAAE,OAAO,CAAC,CAAC;QAAE,OAAO,EAAE,WAAW,CAAA;KAAE,CAAC,CAAC,CAAC;CACnD;AAgJD,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -3,7 +3,7 @@
3
3
  * MessageDetail - Full message view with type-adaptive sections
4
4
  */
5
5
 
6
- import { Card } from '@happyvertical/smrt-ui/ui';
6
+ import { Button, Card } from '@happyvertical/smrt-ui/ui';
7
7
  import type { Snippet } from 'svelte';
8
8
  import type { AccountData, AttachmentData, MessageData } from '../types.js';
9
9
  import AttachmentChip from './AttachmentChip.svelte';
@@ -162,19 +162,19 @@ const _displayBody = $derived.by(() => {
162
162
  {#if onreply || onforward || ondelete}
163
163
  <div class="actions">
164
164
  {#if onreply}
165
- <button class="action-btn" type="button" onclick={() => onreply?.(message)}>
165
+ <Button variant="ghost" class="action-btn" onclick={() => onreply?.(message)}>
166
166
  ↩ Reply
167
- </button>
167
+ </Button>
168
168
  {/if}
169
169
  {#if onforward}
170
- <button class="action-btn" type="button" onclick={() => onforward?.(message)}>
170
+ <Button variant="ghost" class="action-btn" onclick={() => onforward?.(message)}>
171
171
  ↪ Forward
172
- </button>
172
+ </Button>
173
173
  {/if}
174
174
  {#if ondelete}
175
- <button class="action-btn action-btn--danger" type="button" onclick={() => ondelete?.(message)}>
175
+ <Button variant="ghost" class="action-btn action-btn--danger" onclick={() => ondelete?.(message)}>
176
176
  🗑 Delete
177
- </button>
177
+ </Button>
178
178
  {/if}
179
179
  </div>
180
180
  {/if}
@@ -280,27 +280,34 @@ const _displayBody = $derived.by(() => {
280
280
  border-top: 1px solid var(--smrt-color-outline-variant, #c4c6d0);
281
281
  }
282
282
 
283
- .action-btn {
283
+ /*
284
+ * Action buttons now render through smrt-ui's <Button variant="ghost">. The
285
+ * <button> is emitted inside the Button child, so `.actions :global(.action-btn)`
286
+ * anchors on the real `.actions` element and pierces the child scope to keep
287
+ * the original outlined-surface styling (issue #1589). The destructive button's
288
+ * modifier is `action-btn--danger`, NOT `danger`, to avoid colliding with
289
+ * Button's own `.danger` variant class.
290
+ */
291
+ .actions :global(.action-btn) {
284
292
  padding: 0.5rem 1rem;
285
293
  border: 1px solid var(--smrt-color-outline, #72787e);
286
294
  border-radius: var(--smrt-radius-small, 0.25rem);
287
295
  background: var(--smrt-color-surface, #fefbff);
288
296
  color: var(--smrt-color-on-surface, #1a1c1e);
289
297
  font: var(--smrt-typography-label-large-font, 500 0.875rem / 1.25 sans-serif);
290
- cursor: pointer;
291
298
  transition: background var(--smrt-duration-short2, 150ms);
292
299
  }
293
300
 
294
- .action-btn:hover {
301
+ .actions :global(.action-btn):hover {
295
302
  background: var(--smrt-color-surface-variant, #e1e2ec);
296
303
  }
297
304
 
298
- .action-btn--danger {
305
+ .actions :global(.action-btn--danger) {
299
306
  color: var(--smrt-color-error, #ba1a1a);
300
307
  border-color: var(--smrt-color-error, #ba1a1a);
301
308
  }
302
309
 
303
- .action-btn--danger:hover {
310
+ .actions :global(.action-btn--danger):hover {
304
311
  background: var(--smrt-color-error-container, #ffdad6);
305
312
  }
306
313
  </style>
@@ -2,7 +2,9 @@
2
2
  /**
3
3
  * MessageFilters - Filter/sort controls bar
4
4
  */
5
+ import { Input, Select } from '@happyvertical/smrt-ui/forms';
5
6
  import { useI18n } from '@happyvertical/smrt-ui/i18n';
7
+ import { Button } from '@happyvertical/smrt-ui/ui';
6
8
  import { M } from '../i18n.messages.js';
7
9
  import type {
8
10
  AccountData,
@@ -77,7 +79,7 @@ const _hasActiveFilters = $derived(
77
79
 
78
80
  <div class="message-filters" role="search" aria-label={t(M['messages.message_filters.filters_label'])}>
79
81
  <div class="search-row">
80
- <input
82
+ <Input
81
83
  type="search"
82
84
  class="search-input"
83
85
  placeholder={t(M['messages.message_filters.search_placeholder'])}
@@ -85,41 +87,41 @@ const _hasActiveFilters = $derived(
85
87
  onkeydown={(e) => { if (e.key === 'Enter') handleSearch(); }}
86
88
  aria-label={t(M['messages.message_filters.search_label'])}
87
89
  />
88
- <button class="search-btn" type="button" onclick={handleSearch}>Search</button>
90
+ <Button variant="primary" class="search-btn" onclick={handleSearch}>Search</Button>
89
91
  </div>
90
92
 
91
93
  <div class="filter-row">
92
- <select
94
+ <Select
93
95
  class="filter-select"
94
96
  value={filters.type || ''}
95
- onchange={(e) => updateFilter('type', (e.target as HTMLSelectElement).value)}
97
+ onchange={(e) => updateFilter('type', (e.currentTarget as HTMLSelectElement).value)}
96
98
  aria-label={t(M['messages.message_filters.filter_by_type'])}
97
99
  >
98
100
  <option value="">{t(M['messages.message_filters.all_types'])}</option>
99
101
  {#each availableTypes as type}
100
102
  <option value={type}>{type === 'email' ? 'Email' : type === 'tweet' ? 'Tweet' : type === 'slack' ? 'Slack' : type}</option>
101
103
  {/each}
102
- </select>
104
+ </Select>
103
105
 
104
106
  {#if accounts.length > 0}
105
- <select
107
+ <Select
106
108
  class="filter-select"
107
109
  value={filters.accountId || ''}
108
- onchange={(e) => updateFilter('accountId', (e.target as HTMLSelectElement).value)}
110
+ onchange={(e) => updateFilter('accountId', (e.currentTarget as HTMLSelectElement).value)}
109
111
  aria-label={t(M['messages.message_filters.filter_by_account'])}
110
112
  >
111
113
  <option value="">{t(M['messages.message_filters.all_accounts'])}</option>
112
114
  {#each accounts as account}
113
115
  <option value={account.id}>{account.name}</option>
114
116
  {/each}
115
- </select>
117
+ </Select>
116
118
  {/if}
117
119
 
118
- <select
120
+ <Select
119
121
  class="filter-select"
120
122
  value={filters.isRead === undefined ? '' : filters.isRead ? 'read' : 'unread'}
121
123
  onchange={(e) => {
122
- const val = (e.target as HTMLSelectElement).value;
124
+ const val = (e.currentTarget as HTMLSelectElement).value;
123
125
  updateFilter('isRead', val === '' ? undefined : val === 'read');
124
126
  }}
125
127
  aria-label={t(M['messages.message_filters.filter_by_read_status'])}
@@ -127,21 +129,22 @@ const _hasActiveFilters = $derived(
127
129
  <option value="">Read & unread</option>
128
130
  <option value="unread">{t(M['messages.message_filters.unread_only'])}</option>
129
131
  <option value="read">{t(M['messages.message_filters.read_only'])}</option>
130
- </select>
132
+ </Select>
131
133
 
132
134
  <label class="filter-checkbox">
135
+ <!-- raw-primitive-allow: native checkbox; no Provider-free checkbox primitive (Toggle is a switch with different semantics, CheckboxInput requires a Provider) -->
133
136
  <input
134
137
  type="checkbox"
135
138
  checked={filters.isFlagged === true}
136
- onchange={(e) => updateFilter('isFlagged', (e.target as HTMLInputElement).checked ? true : undefined)}
139
+ onchange={(e) => updateFilter('isFlagged', (e.currentTarget as HTMLInputElement).checked ? true : undefined)}
137
140
  />
138
141
  Flagged
139
142
  </label>
140
143
 
141
144
  {#if _hasActiveFilters}
142
- <button class="clear-btn" type="button" onclick={clearFilters}>
145
+ <Button variant="ghost" size="sm" class="clear-btn" onclick={clearFilters}>
143
146
  {t(M['messages.message_filters.clear_filters'])}
144
- </button>
147
+ </Button>
145
148
  {/if}
146
149
  </div>
147
150
  </div>
@@ -162,33 +165,26 @@ const _hasActiveFilters = $derived(
162
165
  gap: 0.5rem;
163
166
  }
164
167
 
165
- .search-input {
168
+ /*
169
+ * The search field now renders through smrt-ui's <Input>. The primitive owns
170
+ * border / background / focus; `.search-row :global(.search-input)` only
171
+ * re-asserts `flex: 1` so it shares the row with the Search button, piercing
172
+ * the Input child scope (issue #1589).
173
+ */
174
+ .search-row :global(.search-input) {
166
175
  flex: 1;
167
- padding: 0.5rem 0.75rem;
168
- border: 1px solid var(--smrt-color-outline, #72787e);
169
- border-radius: var(--smrt-radius-small, 0.25rem);
170
- font: var(--smrt-typography-body-medium-font, 0.875rem / 1.25 sans-serif);
171
- background: var(--smrt-color-surface, #fefbff);
172
- color: var(--smrt-color-on-surface, #1a1c1e);
173
176
  }
174
177
 
175
- .search-input:focus {
176
- outline: 2px solid var(--smrt-color-primary, #005ac1);
177
- outline-offset: -1px;
178
- }
179
-
180
- .search-btn {
178
+ /*
179
+ * Search now renders through smrt-ui's <Button variant="primary">. The variant
180
+ * owns the filled-primary background + hover; `.search-row :global(.search-btn)`
181
+ * only re-asserts the original small radius and label font/padding by piercing
182
+ * the Button child scope (issue #1589).
183
+ */
184
+ .search-row :global(.search-btn) {
181
185
  padding: 0.5rem 1rem;
182
- border: none;
183
186
  border-radius: var(--smrt-radius-small, 0.25rem);
184
- background: var(--smrt-color-primary, #005ac1);
185
- color: var(--smrt-color-on-primary, #fff);
186
187
  font: var(--smrt-typography-label-large-font, 500 0.875rem / 1.25 sans-serif);
187
- cursor: pointer;
188
- }
189
-
190
- .search-btn:hover {
191
- opacity: 0.9;
192
188
  }
193
189
 
194
190
  .filter-row {
@@ -198,13 +194,16 @@ const _hasActiveFilters = $derived(
198
194
  gap: 0.5rem;
199
195
  }
200
196
 
201
- .filter-select {
202
- padding: 0.375rem 0.5rem;
203
- border: 1px solid var(--smrt-color-outline, #72787e);
204
- border-radius: var(--smrt-radius-small, 0.25rem);
197
+ /*
198
+ * The filter dropdowns now render through smrt-ui's <Select>. The primitive
199
+ * owns border / background / focus / chevron; `.filter-row :global(.filter-select)`
200
+ * re-asserts the compact filter-bar sizing (auto width, tighter padding, small
201
+ * font) by piercing the Select child scope (issue #1589).
202
+ */
203
+ .filter-row :global(.filter-select) {
204
+ width: auto;
205
+ padding: 0.375rem 2rem 0.375rem 0.5rem;
205
206
  font: var(--smrt-typography-body-small-font, 0.75rem / 1.33 sans-serif);
206
- background: var(--smrt-color-surface, #fefbff);
207
- color: var(--smrt-color-on-surface, #1a1c1e);
208
207
  }
209
208
 
210
209
  .filter-checkbox {
@@ -216,13 +215,18 @@ const _hasActiveFilters = $derived(
216
215
  cursor: pointer;
217
216
  }
218
217
 
219
- .clear-btn {
218
+ /*
219
+ * Clear filters now renders through smrt-ui's <Button variant="ghost">.
220
+ * `.filter-row :global(.clear-btn)` anchors on the real `.filter-row` element
221
+ * and pierces the Button child scope to keep the underlined text-link look
222
+ * (issue #1589).
223
+ */
224
+ .filter-row :global(.clear-btn) {
220
225
  padding: 0.25rem 0.5rem;
221
226
  border: none;
222
227
  background: none;
223
228
  font: var(--smrt-typography-label-medium-font, 500 0.75rem / 1.33 sans-serif);
224
229
  color: var(--smrt-color-primary, #005ac1);
225
- cursor: pointer;
226
230
  text-decoration: underline;
227
231
  }
228
232
  </style>
@@ -1 +1 @@
1
- {"version":3,"file":"MessageFilters.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/MessageFilters.svelte.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,WAAW,EACX,kBAAkB,EAClB,WAAW,EACX,WAAW,EACZ,MAAM,aAAa,CAAC;AAGrB,MAAM,WAAW,KAAK;IACpB,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;IACzB,cAAc,CAAC,EAAE,WAAW,EAAE,CAAC;IAC/B,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACvD,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAgHD,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"MessageFilters.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/MessageFilters.svelte.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EACV,WAAW,EACX,kBAAkB,EAClB,WAAW,EACX,WAAW,EACZ,MAAM,aAAa,CAAC;AAGrB,MAAM,WAAW,KAAK;IACpB,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;IACzB,cAAc,CAAC,EAAE,WAAW,EAAE,CAAC;IAC/B,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACvD,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAmHD,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
@@ -3,6 +3,7 @@
3
3
  * MessageToolbar - Bulk action toolbar
4
4
  */
5
5
  import { useI18n } from '@happyvertical/smrt-ui/i18n';
6
+ import { Button } from '@happyvertical/smrt-ui/ui';
6
7
  import type { Snippet } from 'svelte';
7
8
  import { M } from '../i18n.messages.js';
8
9
  import type { BulkAction } from '../types.js';
@@ -33,37 +34,37 @@ const {
33
34
  {#if selectedCount > 0}
34
35
  <span class="count">{t(M['messages.message_toolbar.count_selected'], { selectedCount, totalCount })}</span>
35
36
  {#if onclearselection}
36
- <button class="link-btn" type="button" onclick={onclearselection}>
37
+ <Button variant="ghost" size="sm" class="link-btn" onclick={onclearselection}>
37
38
  Clear
38
- </button>
39
+ </Button>
39
40
  {/if}
40
41
  {:else}
41
42
  <span class="count">{totalCount} messages</span>
42
43
  {/if}
43
44
  {#if onselectall && selectedCount < totalCount}
44
- <button class="link-btn" type="button" onclick={onselectall}>
45
+ <Button variant="ghost" size="sm" class="link-btn" onclick={onselectall}>
45
46
  {t(M['messages.message_toolbar.select_all'])}
46
- </button>
47
+ </Button>
47
48
  {/if}
48
49
  </div>
49
50
 
50
51
  {#if selectedCount > 0 && onaction}
51
52
  <div class="actions">
52
- <button class="action-btn" type="button" onclick={() => onaction?.('markRead')}>
53
+ <Button variant="ghost" size="sm" class="action-btn" onclick={() => onaction?.('markRead')}>
53
54
  {t(M['messages.message_toolbar.mark_read'])}
54
- </button>
55
- <button class="action-btn" type="button" onclick={() => onaction?.('markUnread')}>
55
+ </Button>
56
+ <Button variant="ghost" size="sm" class="action-btn" onclick={() => onaction?.('markUnread')}>
56
57
  {t(M['messages.message_toolbar.mark_unread'])}
57
- </button>
58
- <button class="action-btn" type="button" onclick={() => onaction?.('flag')}>
58
+ </Button>
59
+ <Button variant="ghost" size="sm" class="action-btn" onclick={() => onaction?.('flag')}>
59
60
  Flag
60
- </button>
61
- <button class="action-btn" type="button" onclick={() => onaction?.('unflag')}>
61
+ </Button>
62
+ <Button variant="ghost" size="sm" class="action-btn" onclick={() => onaction?.('unflag')}>
62
63
  Unflag
63
- </button>
64
- <button class="action-btn action-btn--danger" type="button" onclick={() => onaction?.('delete')}>
64
+ </Button>
65
+ <Button variant="ghost" size="sm" class="action-btn action-btn--danger" onclick={() => onaction?.('delete')}>
65
66
  Delete
66
- </button>
67
+ </Button>
67
68
  {#if extraActions}
68
69
  {@render extraActions()}
69
70
  {/if}
@@ -94,12 +95,20 @@ const {
94
95
  color: var(--smrt-color-on-surface-variant, #43474e);
95
96
  }
96
97
 
97
- .link-btn {
98
+ /*
99
+ * The link and action buttons now render through smrt-ui's <Button
100
+ * variant="ghost">. The <button> is emitted inside the Button child, so a
101
+ * plain scoped rule would not match — anchoring on the real `.selection-info`
102
+ * / `.actions` elements and piercing with `:global(...)` keeps the original
103
+ * text-link / outlined-surface styling (issue #1589). The destructive button's
104
+ * modifier is `action-btn--danger`, NOT `danger`, so it never collides with
105
+ * Button's own `.danger` variant class.
106
+ */
107
+ .selection-info :global(.link-btn) {
98
108
  border: none;
99
109
  background: none;
100
110
  font: var(--smrt-typography-label-medium-font, 500 0.75rem / 1.33 sans-serif);
101
111
  color: var(--smrt-color-primary, #005ac1);
102
- cursor: pointer;
103
112
  text-decoration: underline;
104
113
  padding: 0;
105
114
  }
@@ -110,21 +119,20 @@ const {
110
119
  flex-wrap: wrap;
111
120
  }
112
121
 
113
- .action-btn {
122
+ .actions :global(.action-btn) {
114
123
  padding: 0.25rem 0.625rem;
115
124
  border: 1px solid var(--smrt-color-outline, #72787e);
116
125
  border-radius: var(--smrt-radius-small, 0.25rem);
117
126
  background: var(--smrt-color-surface, #fefbff);
118
127
  color: var(--smrt-color-on-surface, #1a1c1e);
119
128
  font: var(--smrt-typography-label-small-font, 500 0.6875rem / 1 sans-serif);
120
- cursor: pointer;
121
129
  }
122
130
 
123
- .action-btn:hover {
131
+ .actions :global(.action-btn):hover {
124
132
  background: var(--smrt-color-surface-variant, #e1e2ec);
125
133
  }
126
134
 
127
- .action-btn--danger {
135
+ .actions :global(.action-btn--danger) {
128
136
  color: var(--smrt-color-error, #ba1a1a);
129
137
  border-color: var(--smrt-color-error, #ba1a1a);
130
138
  }
@@ -1 +1 @@
1
- {"version":3,"file":"MessageToolbar.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/MessageToolbar.svelte.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,MAAM,WAAW,KAAK;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;IACxC,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAoED,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"MessageToolbar.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/MessageToolbar.svelte.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,MAAM,WAAW,KAAK;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,IAAI,CAAC;IACxC,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAqED,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
@@ -10,6 +10,9 @@ export interface Props {
10
10
  </script>
11
11
 
12
12
  <script lang="ts">
13
+ import { Input } from '@happyvertical/smrt-ui/forms';
14
+ import { Button } from '@happyvertical/smrt-ui/ui';
15
+
13
16
  let {
14
17
  label = 'To',
15
18
  recipients = [],
@@ -61,17 +64,18 @@ export interface Props {
61
64
  {#each recipients as recipient, i}
62
65
  <span class="chip" class:invalid={!recipient.isValid}>
63
66
  <span class="chip-text">{recipient.name || recipient.address}</span>
64
- <button
67
+ <Button
68
+ variant="ghost"
69
+ size="sm"
65
70
  class="chip-remove"
66
71
  onclick={() => removeRecipient(i)}
67
- type="button"
68
- >×</button>
72
+ >×</Button>
69
73
  </span>
70
74
  {/each}
71
- <input
75
+ <Input
72
76
  id={inputId}
73
77
  type="text"
74
- class="input"
78
+ class="recipient-field"
75
79
  bind:value={inputValue}
76
80
  {placeholder}
77
81
  onkeydown={handleKeydown}
@@ -122,29 +126,48 @@ export interface Props {
122
126
  color: var(--smrt-color-on-error-container, #410002);
123
127
  }
124
128
 
125
- .chip-remove {
129
+ /*
130
+ * The chip remove button now renders through smrt-ui's <Button variant="ghost">.
131
+ * `.chips-container :global(.chip-remove)` anchors on the real `.chips-container`
132
+ * element and pierces the Button child scope to keep the compact icon styling.
133
+ * `color: inherit` keeps it matching the chip text color (including the invalid
134
+ * chip's error color) — issue #1589.
135
+ */
136
+ .chips-container :global(.chip-remove) {
126
137
  background: none;
127
138
  border: none;
128
- cursor: pointer;
129
139
  padding: 0 var(--smrt-spacing-1, 4px);
130
140
  font-size: var(--smrt-typography-label-large-size, 14px);
131
141
  color: inherit;
132
142
  opacity: 0.7;
133
143
  }
134
144
 
135
- .chip-remove:hover {
145
+ .chips-container :global(.chip-remove):hover {
136
146
  opacity: 1;
137
147
  }
138
148
 
139
- .input {
149
+ /*
150
+ * The chip-bar field now renders through smrt-ui's <Input>. This is a naked,
151
+ * borderless inline field that sits among the recipient chips, so
152
+ * `.chips-container :global(.recipient-field)` pierces the Input child scope to
153
+ * strip the primitive's border / background / block padding / focus box-shadow
154
+ * and restore the flush inline look (issue #1589).
155
+ */
156
+ .chips-container :global(.recipient-field) {
140
157
  flex: 1;
141
158
  min-width: 120px;
142
159
  border: none;
143
160
  outline: none;
161
+ box-shadow: none;
144
162
  font-family: var(--smrt-font-family, system-ui);
145
163
  font-size: var(--smrt-typography-body-medium-size, 14px);
146
164
  padding: var(--smrt-spacing-1, 4px) 0;
147
165
  background: transparent;
148
166
  color: var(--smrt-color-on-surface, #1c1b1f);
149
167
  }
168
+
169
+ .chips-container :global(.recipient-field):focus {
170
+ border: none;
171
+ box-shadow: none;
172
+ }
150
173
  </style>
@@ -1 +1 @@
1
- {"version":3,"file":"RecipientInput.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/RecipientInput.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,WAAW,KAAK;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,QAAQ,CAAC,EAAE,CAAC,UAAU,EAAE,cAAc,EAAE,KAAK,IAAI,CAAC;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAmED,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"RecipientInput.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/RecipientInput.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,WAAW,KAAK;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,QAAQ,CAAC,EAAE,CAAC,UAAU,EAAE,cAAc,EAAE,KAAK,IAAI,CAAC;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AA0ED,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
@@ -11,6 +11,8 @@ export interface Props {
11
11
 
12
12
  <script lang="ts">
13
13
  import { useI18n } from '@happyvertical/smrt-ui/i18n';
14
+ import { Textarea } from '@happyvertical/smrt-ui/forms';
15
+ import { Button } from '@happyvertical/smrt-ui/ui';
14
16
  import { M } from '../i18n.js';
15
17
 
16
18
  const { t } = useI18n();
@@ -59,12 +61,11 @@ export interface Props {
59
61
  {replyAll ? 'Reply All' : 'Reply'} to {originalMessage.senderName || originalMessage.senderAddress}
60
62
  </div>
61
63
 
62
- <textarea
63
- class="reply-body"
64
+ <Textarea
64
65
  bind:value={body}
65
66
  placeholder={t(M['messages.reply_form.body_placeholder'])}
66
67
  rows={5}
67
- ></textarea>
68
+ />
68
69
 
69
70
  <div class="quoted-original">
70
71
  <pre class="quoted-text">{quotedBody}</pre>
@@ -77,17 +78,16 @@ export interface Props {
77
78
  {/if}
78
79
 
79
80
  <div class="actions">
80
- <button
81
- type="button"
82
- class="btn-primary"
81
+ <Button
82
+ variant="primary"
83
83
  disabled={isSending || !body.trim()}
84
84
  onclick={handleSend}
85
85
  >
86
86
  {isSending ? 'Sending...' : 'Send Reply'}
87
- </button>
88
- <button type="button" class="btn-text" onclick={() => oncancel?.()}>
87
+ </Button>
88
+ <Button variant="ghost" class="btn-text" onclick={() => oncancel?.()}>
89
89
  Cancel
90
- </button>
90
+ </Button>
91
91
  </div>
92
92
  </div>
93
93
 
@@ -109,22 +109,6 @@ export interface Props {
109
109
  font-weight: var(--smrt-typography-weight-medium, 500);
110
110
  }
111
111
 
112
- .reply-body {
113
- width: 100%;
114
- border: 1px solid var(--smrt-color-outline-variant, #cac4d0);
115
- border-radius: var(--smrt-radius-sm, 8px);
116
- padding: var(--smrt-spacing-2, 8px);
117
- font-family: var(--smrt-font-family, system-ui);
118
- font-size: var(--smrt-typography-body-medium-size, 14px);
119
- resize: vertical;
120
- box-sizing: border-box;
121
- }
122
-
123
- .reply-body:focus {
124
- outline: 2px solid var(--smrt-color-primary, #6750a4);
125
- outline-offset: -1px;
126
- }
127
-
128
112
  .quoted-original {
129
113
  padding: var(--smrt-spacing-2, 8px);
130
114
  border-left: 3px solid var(--smrt-color-outline-variant, #cac4d0);
@@ -155,29 +139,13 @@ export interface Props {
155
139
  gap: var(--smrt-spacing-2, 8px);
156
140
  }
157
141
 
158
- .btn-primary {
159
- padding: var(--smrt-spacing-2, 8px) var(--smrt-spacing-6, 24px);
160
- border-radius: var(--smrt-radius-full, 20px);
161
- border: none;
162
- background: var(--smrt-color-primary, #6750a4);
163
- color: var(--smrt-color-on-primary, #fff);
164
- font-family: var(--smrt-font-family, system-ui);
165
- font-size: var(--smrt-typography-label-large-size, 14px);
166
- cursor: pointer;
167
- }
168
-
169
- .btn-primary:disabled {
170
- opacity: 0.6;
171
- cursor: not-allowed;
172
- }
173
-
174
- .btn-text {
175
- padding: var(--smrt-spacing-2, 8px) var(--smrt-spacing-4, 16px);
176
- border: none;
177
- background: transparent;
142
+ /*
143
+ * Send Reply now uses Button's primary variant directly (dead .btn-primary CSS
144
+ * removed). The Cancel button keeps its neutral on-surface-variant text via
145
+ * `.actions :global(.btn-text)` (Button's ghost uses the primary color) —
146
+ * issue #1589.
147
+ */
148
+ .actions :global(.btn-text) {
178
149
  color: var(--smrt-color-on-surface-variant, #49454f);
179
- font-family: var(--smrt-font-family, system-ui);
180
- font-size: var(--smrt-typography-label-large-size, 14px);
181
- cursor: pointer;
182
150
  }
183
151
  </style>