@widgetic/design-system 0.5.22 → 0.5.23

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 (246) hide show
  1. package/README.md +2 -0
  2. package/dist/components/CopyableHeader.svelte +260 -448
  3. package/dist/components/CopyableHeader.svelte.d.ts +8 -5
  4. package/dist/components/EmptyState.svelte +100 -128
  5. package/dist/components/EmptyState.svelte.d.ts +16 -19
  6. package/dist/components/GenerateLoader.svelte +17 -10
  7. package/dist/components/PasswordInput.svelte +41 -44
  8. package/dist/components/PasswordInput.svelte.d.ts +2 -0
  9. package/dist/components/ThumbHashImage.svelte +117 -119
  10. package/dist/components/WScrollArea.svelte +47 -41
  11. package/dist/components/chat/ActivityRow.svelte +115 -0
  12. package/dist/components/chat/ActivityRow.svelte.d.ts +18 -0
  13. package/dist/components/chat/ChatHeader.svelte +114 -0
  14. package/dist/components/chat/ChatHeader.svelte.d.ts +26 -0
  15. package/dist/components/chat/Composer.svelte +172 -0
  16. package/dist/components/chat/Composer.svelte.d.ts +27 -0
  17. package/dist/components/chat/Message.svelte +158 -0
  18. package/dist/components/chat/Message.svelte.d.ts +28 -0
  19. package/dist/components/chat/OptionRow.svelte +59 -0
  20. package/dist/components/chat/OptionRow.svelte.d.ts +14 -0
  21. package/dist/components/chat/PlanCard.svelte +220 -0
  22. package/dist/components/chat/PlanCard.svelte.d.ts +34 -0
  23. package/dist/components/chat/QuestionCard.svelte +265 -0
  24. package/dist/components/chat/QuestionCard.svelte.d.ts +38 -0
  25. package/dist/components/chat/ScrollToBottom.svelte +51 -0
  26. package/dist/components/chat/ScrollToBottom.svelte.d.ts +11 -0
  27. package/dist/components/chat/SuggestionCard.svelte +79 -0
  28. package/dist/components/chat/SuggestionCard.svelte.d.ts +20 -0
  29. package/dist/components/chat/TaskRow.svelte +59 -0
  30. package/dist/components/chat/TaskRow.svelte.d.ts +11 -0
  31. package/dist/components/chat/composerLogic.d.ts +16 -0
  32. package/dist/components/chat/composerLogic.js +29 -0
  33. package/dist/components/chat/index.d.ts +16 -0
  34. package/dist/components/chat/index.js +16 -0
  35. package/dist/components/chat/questionCardLogic.d.ts +11 -0
  36. package/dist/components/chat/questionCardLogic.js +39 -0
  37. package/dist/components/index.d.ts +10 -1
  38. package/dist/components/index.js +10 -0
  39. package/dist/components/modals/ActionModal.svelte +186 -180
  40. package/dist/components/modals/BaseModal.svelte +402 -399
  41. package/dist/components/modals/BaseModal.svelte.d.ts +1 -0
  42. package/dist/components/modals/NewItemDialog.svelte +200 -243
  43. package/dist/components/modals/Portal.svelte +107 -105
  44. package/dist/components/modals/README.md +16 -2
  45. package/dist/components/modals/RenameDialog.svelte +234 -292
  46. package/dist/components/navigation/Breadcrumbs.svelte +415 -377
  47. package/dist/components/navigation/Breadcrumbs.svelte.d.ts +3 -2
  48. package/dist/components/navigation/types.d.ts +2 -0
  49. package/dist/components/pdf/PDFViewer.svelte +1 -1
  50. package/dist/components/search-input/SearchInput.svelte +51 -104
  51. package/dist/components/search-input/SearchInput.svelte.d.ts +9 -27
  52. package/dist/components/shad-editor/editor.css +1 -1
  53. package/dist/components/shad-editor/i18n/messages.d.ts +1 -1
  54. package/dist/components/shad-editor/i18n/messages.js +1 -1
  55. package/dist/components/shell/AppShell.svelte +52 -0
  56. package/dist/components/shell/AppShell.svelte.d.ts +13 -0
  57. package/dist/components/shell/Banner.svelte +90 -0
  58. package/dist/components/shell/Banner.svelte.d.ts +61 -0
  59. package/dist/components/shell/ErrorState.svelte +60 -0
  60. package/dist/components/shell/ErrorState.svelte.d.ts +13 -0
  61. package/dist/components/shell/Link.svelte +63 -0
  62. package/dist/components/shell/Link.svelte.d.ts +53 -0
  63. package/dist/components/shell/NavDrawer.svelte +117 -0
  64. package/dist/components/shell/NavDrawer.svelte.d.ts +10 -0
  65. package/dist/components/shell/NavRow.svelte +128 -0
  66. package/dist/components/shell/NavRow.svelte.d.ts +65 -0
  67. package/dist/components/shell/NavSectionLabel.svelte +27 -0
  68. package/dist/components/shell/NavSectionLabel.svelte.d.ts +9 -0
  69. package/dist/components/shell/NotificationRow.svelte +76 -0
  70. package/dist/components/shell/NotificationRow.svelte.d.ts +16 -0
  71. package/dist/components/shell/PageHeader.svelte +42 -0
  72. package/dist/components/shell/PageHeader.svelte.d.ts +12 -0
  73. package/dist/components/shell/ProfileRow.svelte +95 -0
  74. package/dist/components/shell/ProfileRow.svelte.d.ts +54 -0
  75. package/dist/components/shell/SettingsDialog.svelte +118 -0
  76. package/dist/components/shell/SettingsDialog.svelte.d.ts +19 -0
  77. package/dist/components/shell/SettingsNav.svelte +46 -0
  78. package/dist/components/shell/SettingsNav.svelte.d.ts +11 -0
  79. package/dist/components/shell/Sidebar.svelte +82 -0
  80. package/dist/components/shell/Sidebar.svelte.d.ts +25 -0
  81. package/dist/components/shell/Stat.svelte +63 -0
  82. package/dist/components/shell/Stat.svelte.d.ts +58 -0
  83. package/dist/components/shell/TopBar.svelte +37 -0
  84. package/dist/components/shell/TopBar.svelte.d.ts +11 -0
  85. package/dist/components/shell/WidgetRow.svelte +126 -0
  86. package/dist/components/shell/WidgetRow.svelte.d.ts +78 -0
  87. package/dist/components/shell/error-state-messages.d.ts +7 -0
  88. package/dist/components/shell/error-state-messages.js +9 -0
  89. package/dist/components/shell/index.d.ts +22 -0
  90. package/dist/components/shell/index.js +25 -0
  91. package/dist/components/shell/link-external.d.ts +2 -0
  92. package/dist/components/shell/link-external.js +7 -0
  93. package/dist/components/shell/messages.d.ts +7 -0
  94. package/dist/components/shell/messages.js +5 -0
  95. package/dist/components/shell/settings-nav-types.d.ts +12 -0
  96. package/dist/components/shell/settings-nav-types.js +3 -0
  97. package/dist/components/shell/shell-utils.d.ts +7 -0
  98. package/dist/components/shell/shell-utils.js +27 -0
  99. package/dist/components/shell/stat-tone.d.ts +3 -0
  100. package/dist/components/shell/stat-tone.js +11 -0
  101. package/dist/components/toast/ToasterModal.svelte +102 -168
  102. package/dist/components/toast/ToasterModal.svelte.d.ts +2 -12
  103. package/dist/components/ui/accordion/accordion-content.svelte +3 -2
  104. package/dist/components/ui/accordion/accordion-item.svelte +10 -1
  105. package/dist/components/ui/accordion/accordion-trigger.svelte +4 -3
  106. package/dist/components/ui/alert/alert-description.svelte +5 -1
  107. package/dist/components/ui/alert/alert-title.svelte +2 -1
  108. package/dist/components/ui/alert/index.d.ts +13 -11
  109. package/dist/components/ui/alert/index.js +4 -4
  110. package/dist/components/ui/avatar/avatar-utils.d.ts +2 -0
  111. package/dist/components/ui/avatar/avatar-utils.js +9 -0
  112. package/dist/components/ui/avatar/avatar.svelte +63 -0
  113. package/dist/components/ui/avatar/avatar.svelte.d.ts +80 -0
  114. package/dist/components/ui/avatar/index.d.ts +4 -0
  115. package/dist/components/ui/avatar/index.js +4 -0
  116. package/dist/components/ui/badge/badge.svelte +22 -10
  117. package/dist/components/ui/badge/badge.svelte.d.ts +14 -15
  118. package/dist/components/ui/breadcrumb/breadcrumb-ellipsis.svelte +2 -2
  119. package/dist/components/ui/breadcrumb/breadcrumb-link.svelte +1 -1
  120. package/dist/components/ui/breadcrumb/breadcrumb-page.svelte +1 -1
  121. package/dist/components/ui/breadcrumb/breadcrumb-separator.svelte +2 -2
  122. package/dist/components/ui/button/button-padding.d.ts +9 -0
  123. package/dist/components/ui/button/button-padding.js +15 -0
  124. package/dist/components/ui/button/button.svelte +81 -31
  125. package/dist/components/ui/button/button.svelte.d.ts +48 -29
  126. package/dist/components/ui/calendar/calendar-cell.svelte +1 -1
  127. package/dist/components/ui/calendar/calendar-head-cell.svelte +1 -1
  128. package/dist/components/ui/calendar/calendar-next-button.svelte +2 -2
  129. package/dist/components/ui/calendar/calendar-prev-button.svelte +2 -2
  130. package/dist/components/ui/card/card-description.svelte +1 -1
  131. package/dist/components/ui/card/card-title.svelte +1 -1
  132. package/dist/components/ui/card/card.svelte +1 -1
  133. package/dist/components/ui/checkbox/checkbox.svelte +9 -5
  134. package/dist/components/ui/chip/chip.svelte +48 -0
  135. package/dist/components/ui/chip/chip.svelte.d.ts +13 -0
  136. package/dist/components/ui/chip/index.d.ts +2 -0
  137. package/dist/components/ui/chip/index.js +2 -0
  138. package/dist/components/ui/command/command-dialog.svelte +1 -1
  139. package/dist/components/ui/command/command-input.svelte +4 -4
  140. package/dist/components/ui/command/command-item.svelte +1 -1
  141. package/dist/components/ui/command/command.svelte +1 -1
  142. package/dist/components/ui/dialog/dialog-content.svelte +4 -4
  143. package/dist/components/ui/dialog/dialog-description.svelte +1 -1
  144. package/dist/components/ui/dialog/dialog-overlay.svelte +1 -1
  145. package/dist/components/ui/dialog/dialog-title.svelte +1 -1
  146. package/dist/components/ui/drawer/drawer-content.svelte +1 -1
  147. package/dist/components/ui/drawer/drawer-overlay.svelte +1 -1
  148. package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +5 -5
  149. package/dist/components/ui/dropdown-menu/dropdown-menu-content.svelte +1 -1
  150. package/dist/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +1 -1
  151. package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte +20 -2
  152. package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte.d.ts +4 -0
  153. package/dist/components/ui/dropdown-menu/dropdown-menu-label.svelte +5 -1
  154. package/dist/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +3 -3
  155. package/dist/components/ui/dropdown-menu/dropdown-menu-separator.svelte +2 -1
  156. package/dist/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +1 -1
  157. package/dist/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +3 -3
  158. package/dist/components/ui/field/field-ids.d.ts +7 -0
  159. package/dist/components/ui/field/field-ids.js +15 -0
  160. package/dist/components/ui/field/field.svelte +62 -0
  161. package/dist/components/ui/field/field.svelte.d.ts +21 -0
  162. package/dist/components/ui/field/index.d.ts +3 -0
  163. package/dist/components/ui/field/index.js +3 -0
  164. package/dist/components/ui/hover-card/hover-card-content.svelte +1 -1
  165. package/dist/components/ui/icon-button/icon-button.svelte +72 -0
  166. package/dist/components/ui/icon-button/icon-button.svelte.d.ts +90 -0
  167. package/dist/components/ui/icon-button/index.d.ts +2 -0
  168. package/dist/components/ui/icon-button/index.js +4 -0
  169. package/dist/components/ui/input/index.d.ts +1 -0
  170. package/dist/components/ui/input/input.svelte +11 -4
  171. package/dist/components/ui/input/input.svelte.d.ts +4 -1
  172. package/dist/components/ui/input-otp/input-otp-separator.svelte +2 -2
  173. package/dist/components/ui/input-otp/input-otp-slot.svelte +5 -4
  174. package/dist/components/ui/input-otp/input-otp-slot.svelte.d.ts +1 -1
  175. package/dist/components/ui/input-otp/input-otp.svelte +1 -1
  176. package/dist/components/ui/label/label.svelte +3 -2
  177. package/dist/components/ui/popover/popover-content.svelte +1 -1
  178. package/dist/components/ui/progress/progress.svelte +19 -5
  179. package/dist/components/ui/progress/progress.svelte.d.ts +4 -2
  180. package/dist/components/ui/radio-group/radio-group-item.svelte +4 -4
  181. package/dist/components/ui/range-calendar/range-calendar-head-cell.svelte +1 -1
  182. package/dist/components/ui/range-calendar/range-calendar-next-button.svelte +2 -2
  183. package/dist/components/ui/range-calendar/range-calendar-prev-button.svelte +2 -2
  184. package/dist/components/ui/scroll-area/scroll-area-scrollbar.svelte +4 -1
  185. package/dist/components/ui/scroll-area/scroll-area.svelte +1 -1
  186. package/dist/components/ui/select/select-content.svelte +1 -3
  187. package/dist/components/ui/select/select-item.svelte +5 -4
  188. package/dist/components/ui/select/select-scroll-down-button.svelte +2 -2
  189. package/dist/components/ui/select/select-scroll-up-button.svelte +2 -2
  190. package/dist/components/ui/select/select-trigger.svelte +4 -3
  191. package/dist/components/ui/skeleton/index.d.ts +2 -0
  192. package/dist/components/ui/skeleton/index.js +2 -0
  193. package/dist/components/ui/skeleton/skeleton.svelte +24 -0
  194. package/dist/components/ui/skeleton/skeleton.svelte.d.ts +8 -0
  195. package/dist/components/ui/slider/slider.svelte +5 -10
  196. package/dist/components/ui/sonner/sonner.svelte +19 -13
  197. package/dist/components/ui/stepper-button/index.d.ts +3 -0
  198. package/dist/components/ui/stepper-button/index.js +2 -0
  199. package/dist/components/ui/stepper-button/stepper-button.svelte +37 -0
  200. package/dist/components/ui/stepper-button/stepper-button.svelte.d.ts +9 -0
  201. package/dist/components/ui/switch/switch.svelte +8 -3
  202. package/dist/components/ui/table/table-cell.svelte +1 -1
  203. package/dist/components/ui/table/table-head.svelte +1 -1
  204. package/dist/components/ui/table/table-row.svelte +1 -1
  205. package/dist/components/ui/tabs/tabs-list.svelte +1 -1
  206. package/dist/components/ui/tabs/tabs-trigger.svelte +1 -1
  207. package/dist/components/ui/tag/index.d.ts +2 -0
  208. package/dist/components/ui/tag/index.js +2 -0
  209. package/dist/components/ui/tag/tag.svelte +42 -0
  210. package/dist/components/ui/tag/tag.svelte.d.ts +11 -0
  211. package/dist/components/ui/textarea/textarea.svelte +4 -3
  212. package/dist/components/ui/textarea/textarea.svelte.d.ts +2 -2
  213. package/dist/components/ui/toggle/toggle.svelte +9 -9
  214. package/dist/components/ui/toggle/toggle.svelte.d.ts +14 -21
  215. package/dist/components/ui/tooltip/tooltip-content.svelte +1 -1
  216. package/dist/fonts/Inter/Inter-Black.woff2 +0 -0
  217. package/dist/fonts/Inter/Inter-BlackItalic.woff2 +0 -0
  218. package/dist/fonts/Inter/Inter-Bold.woff2 +0 -0
  219. package/dist/fonts/Inter/Inter-BoldItalic.woff2 +0 -0
  220. package/dist/fonts/Inter/Inter-ExtraBold.woff2 +0 -0
  221. package/dist/fonts/Inter/Inter-ExtraBoldItalic.woff2 +0 -0
  222. package/dist/fonts/Inter/Inter-ExtraLight-BETA.woff2 +0 -0
  223. package/dist/fonts/Inter/Inter-ExtraLightItalic-BETA.woff2 +0 -0
  224. package/dist/fonts/Inter/Inter-Italic.woff2 +0 -0
  225. package/dist/fonts/Inter/Inter-Light-BETA.woff2 +0 -0
  226. package/dist/fonts/Inter/Inter-LightItalic-BETA.woff2 +0 -0
  227. package/dist/fonts/Inter/Inter-Medium.woff2 +0 -0
  228. package/dist/fonts/Inter/Inter-MediumItalic.woff2 +0 -0
  229. package/dist/fonts/Inter/Inter-Regular.woff2 +0 -0
  230. package/dist/fonts/Inter/Inter-SemiBold.woff2 +0 -0
  231. package/dist/fonts/Inter/Inter-SemiBoldItalic.woff2 +0 -0
  232. package/dist/fonts/Inter/Inter-Thin-BETA.woff2 +0 -0
  233. package/dist/fonts/Inter/Inter-ThinItalic-BETA.woff2 +0 -0
  234. package/dist/fonts/Inter/Inter-italic.var.woff2 +0 -0
  235. package/dist/fonts/Inter/Inter-upright.var.woff2 +0 -0
  236. package/dist/fonts/Inter/Inter.var.woff2 +0 -0
  237. package/dist/fonts/JetBrainsMono/JetBrainsMono-Regular.woff2 +0 -0
  238. package/dist/i18n/audit/designSystemWords.d.ts +1 -1
  239. package/dist/i18n/audit/designSystemWords.js +1 -1
  240. package/dist/styles/fonts.css +18 -14
  241. package/dist/styles/global.css +7 -19
  242. package/dist/styles/tokens.css +332 -0
  243. package/dist/utils.d.ts +27 -0
  244. package/dist/utils.js +33 -2
  245. package/package.json +2 -1
  246. package/tailwind.config.ts +7 -47
@@ -0,0 +1,172 @@
1
+ <!--
2
+ Figma: Composer (component set 65:31, node 65:31)
3
+ -->
4
+ <script lang="ts" module>
5
+ import type { Snippet } from 'svelte';
6
+ import type { HTMLAttributes } from 'svelte/elements';
7
+
8
+ export type ComposerMessages = {
9
+ send: string;
10
+ stop: string;
11
+ attach: string;
12
+ characterLimit: string;
13
+ };
14
+
15
+ export type ComposerProps = HTMLAttributes<HTMLDivElement> & {
16
+ ref?: HTMLDivElement | null;
17
+ class?: string;
18
+ value?: string;
19
+ placeholder?: string;
20
+ busy?: boolean;
21
+ disabled?: boolean;
22
+ maxLength?: number;
23
+ allowEmpty?: boolean;
24
+ messages?: Partial<ComposerMessages>;
25
+ onSend?: (text: string) => void;
26
+ onStop?: () => void;
27
+ leading?: Snippet;
28
+ trailing?: Snippet;
29
+ attachments?: Snippet;
30
+ };
31
+ </script>
32
+
33
+ <script lang="ts">
34
+ import IconPlayerStop from '@tabler/icons-svelte/icons/player-stop';
35
+ import IconSend from '@tabler/icons-svelte/icons/send';
36
+ import { cn } from '../../utils.js';
37
+ import { IconButton } from '../ui/icon-button/index.js';
38
+ import { canSendText, clampTextareaHeight, shouldSendOnKeyDown } from './composerLogic.js';
39
+
40
+ const defaultMessages: ComposerMessages = {
41
+ send: 'Send message',
42
+ stop: 'Stop generating',
43
+ attach: 'Add attachment',
44
+ characterLimit: 'Message is too long'
45
+ };
46
+
47
+ let {
48
+ ref = $bindable(null),
49
+ class: className,
50
+ value = $bindable(''),
51
+ placeholder = 'Type your message…',
52
+ busy = $bindable(false),
53
+ disabled = false,
54
+ maxLength,
55
+ allowEmpty = false,
56
+ messages: messagesProp,
57
+ onSend,
58
+ onStop,
59
+ leading,
60
+ trailing,
61
+ attachments,
62
+ ...restProps
63
+ }: ComposerProps = $props();
64
+
65
+ const messages = $derived({ ...defaultMessages, ...messagesProp });
66
+
67
+ let textareaRef = $state<HTMLTextAreaElement | null>(null);
68
+ let isComposing = $state(false);
69
+
70
+ const sendEnabled = $derived(
71
+ busy ? !disabled : canSendText(value, { disabled, allowEmpty, maxLength })
72
+ );
73
+
74
+ const overLimit = $derived(maxLength != null && value.trim().length > maxLength);
75
+
76
+ function autosize() {
77
+ const el = textareaRef;
78
+ if (!el) return;
79
+ el.style.height = 'auto';
80
+ el.style.height = `${clampTextareaHeight(el.scrollHeight)}px`;
81
+ }
82
+
83
+ function handleSend() {
84
+ if (busy) {
85
+ if (!disabled) onStop?.();
86
+ return;
87
+ }
88
+ if (!sendEnabled || overLimit) return;
89
+ const text = value.trim();
90
+ if (!allowEmpty && text.length === 0) return;
91
+ try {
92
+ onSend?.(text);
93
+ } catch (error) {
94
+ console.error(error);
95
+ }
96
+ }
97
+
98
+ function handleKeyDown(event: KeyboardEvent) {
99
+ if (event.isComposing || isComposing) {
100
+ if (event.key === 'Enter') event.preventDefault();
101
+ return;
102
+ }
103
+ if (!shouldSendOnKeyDown(event.key, event.shiftKey, false)) return;
104
+ event.preventDefault();
105
+ handleSend();
106
+ }
107
+
108
+ $effect(() => {
109
+ value;
110
+ queueMicrotask(autosize);
111
+ });
112
+ </script>
113
+
114
+ <div
115
+ bind:this={ref}
116
+ class={cn(
117
+ 'max-w-content rounded-card border-input bg-background flex w-full flex-col gap-2 border p-3',
118
+ disabled && 'pointer-events-none opacity-50',
119
+ className
120
+ )}
121
+ {...restProps}
122
+ >
123
+ {#if attachments}
124
+ <div class="flex flex-wrap gap-1.5 overflow-hidden">{@render attachments()}</div>
125
+ {/if}
126
+
127
+ <div class="min-h-12 w-full px-1 pt-1">
128
+ <textarea
129
+ bind:this={textareaRef}
130
+ bind:value
131
+ class="text-body text-foreground placeholder:text-muted-foreground block w-full resize-none overflow-y-auto border-0 bg-transparent outline-hidden focus-visible:outline-hidden disabled:cursor-not-allowed"
132
+ style="min-height: 3rem; max-height: 7.5rem;"
133
+ {placeholder}
134
+ {disabled}
135
+ aria-invalid={overLimit ? true : undefined}
136
+ oninput={autosize}
137
+ onkeydown={handleKeyDown}
138
+ oncompositionstart={() => {
139
+ isComposing = true;
140
+ }}
141
+ oncompositionend={() => {
142
+ isComposing = false;
143
+ }}
144
+ ></textarea>
145
+ {#if overLimit}
146
+ <p class="text-caption text-destructive mt-1" role="status">{messages.characterLimit}</p>
147
+ {/if}
148
+ </div>
149
+
150
+ <div class="flex items-center gap-2">
151
+ <div class="flex min-w-0 flex-1 items-center gap-1">
152
+ {#if leading}
153
+ {@render leading()}
154
+ {/if}
155
+ </div>
156
+ {#if trailing}
157
+ {@render trailing()}
158
+ {/if}
159
+ <IconButton
160
+ variant="primary"
161
+ label={busy ? messages.stop : messages.send}
162
+ disabled={!sendEnabled}
163
+ onclick={handleSend}
164
+ >
165
+ {#if busy}
166
+ <IconPlayerStop class="size-4 stroke-[1.333]" aria-hidden="true" />
167
+ {:else}
168
+ <IconSend class="size-4 stroke-[1.333]" aria-hidden="true" />
169
+ {/if}
170
+ </IconButton>
171
+ </div>
172
+ </div>
@@ -0,0 +1,27 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ export type ComposerMessages = {
4
+ send: string;
5
+ stop: string;
6
+ attach: string;
7
+ characterLimit: string;
8
+ };
9
+ export type ComposerProps = HTMLAttributes<HTMLDivElement> & {
10
+ ref?: HTMLDivElement | null;
11
+ class?: string;
12
+ value?: string;
13
+ placeholder?: string;
14
+ busy?: boolean;
15
+ disabled?: boolean;
16
+ maxLength?: number;
17
+ allowEmpty?: boolean;
18
+ messages?: Partial<ComposerMessages>;
19
+ onSend?: (text: string) => void;
20
+ onStop?: () => void;
21
+ leading?: Snippet;
22
+ trailing?: Snippet;
23
+ attachments?: Snippet;
24
+ };
25
+ declare const Composer: import("svelte").Component<ComposerProps, {}, "ref" | "value" | "busy">;
26
+ type Composer = ReturnType<typeof Composer>;
27
+ export default Composer;
@@ -0,0 +1,158 @@
1
+ <!--
2
+ Figma: Message (component set 67:4, node 67:4)
3
+ -->
4
+ <script lang="ts" module>
5
+ import type { Snippet } from 'svelte';
6
+ import type { HTMLAttributes } from 'svelte/elements';
7
+
8
+ export type MessageRole = 'user' | 'assistant';
9
+
10
+ export type MessageMessages = {
11
+ copy: string;
12
+ regenerate: string;
13
+ restoreCheckpoint: string;
14
+ streaming: string;
15
+ error: string;
16
+ };
17
+
18
+ export type MessageProps = HTMLAttributes<HTMLDivElement> & {
19
+ ref?: HTMLDivElement | null;
20
+ class?: string;
21
+ role?: MessageRole;
22
+ streaming?: boolean;
23
+ error?: boolean;
24
+ errorText?: string;
25
+ showActions?: boolean;
26
+ messages?: Partial<MessageMessages>;
27
+ onCopy?: () => void;
28
+ onRegenerate?: () => void;
29
+ onRestoreCheckpoint?: () => void;
30
+ actions?: Snippet;
31
+ children?: Snippet;
32
+ };
33
+ </script>
34
+
35
+ <script lang="ts">
36
+ import IconCopy from '@tabler/icons-svelte/icons/copy';
37
+ import IconRefresh from '@tabler/icons-svelte/icons/refresh';
38
+ import IconRestore from '@tabler/icons-svelte/icons/history';
39
+ import { cn } from '../../utils.js';
40
+ import { Button } from '../ui/button/index.js';
41
+ import { IconButton } from '../ui/icon-button/index.js';
42
+
43
+ const defaultMessages: MessageMessages = {
44
+ copy: 'Copy message',
45
+ regenerate: 'Regenerate response',
46
+ restoreCheckpoint: 'Restore checkpoint',
47
+ streaming: 'Assistant is typing',
48
+ error: 'Something went wrong'
49
+ };
50
+
51
+ let {
52
+ ref = $bindable(null),
53
+ class: className,
54
+ role = 'user',
55
+ streaming = false,
56
+ error = false,
57
+ errorText,
58
+ showActions = false,
59
+ messages: messagesProp,
60
+ onCopy,
61
+ onRegenerate,
62
+ onRestoreCheckpoint,
63
+ actions,
64
+ children,
65
+ ...restProps
66
+ }: MessageProps = $props();
67
+
68
+ const messages = $derived({ ...defaultMessages, ...messagesProp });
69
+ const isUser = $derived(role === 'user');
70
+ </script>
71
+
72
+ <div
73
+ bind:this={ref}
74
+ class={cn(
75
+ 'group flex w-full max-w-[25.5rem] flex-col gap-1',
76
+ isUser ? 'ms-auto items-end' : 'items-start',
77
+ className
78
+ )}
79
+ {...restProps}
80
+ >
81
+ {#if isUser}
82
+ <div
83
+ class={cn(
84
+ 'rounded-card bg-muted text-body text-foreground max-w-full px-3 py-2 text-start',
85
+ error && 'border-destructive border'
86
+ )}
87
+ >
88
+ {#if children}
89
+ {@render children()}
90
+ {/if}
91
+ </div>
92
+ <div
93
+ class={cn(
94
+ 'duration-fast flex min-h-8 items-center gap-1 opacity-0 transition-opacity group-focus-within:opacity-100 group-hover:opacity-100 motion-reduce:transition-none',
95
+ showActions && 'opacity-100'
96
+ )}
97
+ >
98
+ {#if onRestoreCheckpoint}
99
+ <Button variant="ghost" size="sm" onclick={onRestoreCheckpoint}>
100
+ <IconRestore class="size-4 stroke-[1.333]" aria-hidden="true" />
101
+ {messages.restoreCheckpoint}
102
+ </Button>
103
+ {/if}
104
+ {#if onCopy}
105
+ <IconButton variant="ghost" label={messages.copy} onclick={onCopy}>
106
+ <IconCopy class="size-4 stroke-[1.333]" aria-hidden="true" />
107
+ </IconButton>
108
+ {/if}
109
+ {#if actions}
110
+ {@render actions()}
111
+ {/if}
112
+ </div>
113
+ {:else}
114
+ <div
115
+ class={cn('text-body text-foreground w-full', error && 'text-destructive')}
116
+ aria-live={streaming ? 'polite' : undefined}
117
+ aria-busy={streaming ? true : undefined}
118
+ >
119
+ {#if error}
120
+ <p class="text-body-medium">{errorText ?? messages.error}</p>
121
+ {:else if children}
122
+ {@render children()}
123
+ {/if}
124
+ {#if streaming}
125
+ <span class="sr-only">{messages.streaming}</span>
126
+ <span class="text-muted-foreground mt-2 inline-flex items-center gap-1" aria-hidden="true">
127
+ <span class="bg-muted-foreground size-1.5 rounded-full motion-safe:animate-pulse"></span>
128
+ <span
129
+ class="bg-muted-foreground size-1.5 rounded-full motion-safe:animate-pulse motion-safe:[animation-delay:120ms]"
130
+ ></span>
131
+ <span
132
+ class="bg-muted-foreground size-1.5 rounded-full motion-safe:animate-pulse motion-safe:[animation-delay:240ms]"
133
+ ></span>
134
+ </span>
135
+ {/if}
136
+ </div>
137
+ <div
138
+ class={cn(
139
+ 'duration-fast flex min-h-8 items-center gap-1 opacity-0 transition-opacity group-focus-within:opacity-100 group-hover:opacity-100 motion-reduce:transition-none',
140
+ showActions && 'opacity-100'
141
+ )}
142
+ >
143
+ {#if onCopy}
144
+ <IconButton variant="ghost" label={messages.copy} onclick={onCopy}>
145
+ <IconCopy class="size-4 stroke-[1.333]" aria-hidden="true" />
146
+ </IconButton>
147
+ {/if}
148
+ {#if onRegenerate}
149
+ <IconButton variant="ghost" label={messages.regenerate} onclick={onRegenerate}>
150
+ <IconRefresh class="size-4 stroke-[1.333]" aria-hidden="true" />
151
+ </IconButton>
152
+ {/if}
153
+ {#if actions}
154
+ {@render actions()}
155
+ {/if}
156
+ </div>
157
+ {/if}
158
+ </div>
@@ -0,0 +1,28 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ export type MessageRole = 'user' | 'assistant';
4
+ export type MessageMessages = {
5
+ copy: string;
6
+ regenerate: string;
7
+ restoreCheckpoint: string;
8
+ streaming: string;
9
+ error: string;
10
+ };
11
+ export type MessageProps = HTMLAttributes<HTMLDivElement> & {
12
+ ref?: HTMLDivElement | null;
13
+ class?: string;
14
+ role?: MessageRole;
15
+ streaming?: boolean;
16
+ error?: boolean;
17
+ errorText?: string;
18
+ showActions?: boolean;
19
+ messages?: Partial<MessageMessages>;
20
+ onCopy?: () => void;
21
+ onRegenerate?: () => void;
22
+ onRestoreCheckpoint?: () => void;
23
+ actions?: Snippet;
24
+ children?: Snippet;
25
+ };
26
+ declare const Message: import("svelte").Component<MessageProps, {}, "ref">;
27
+ type Message = ReturnType<typeof Message>;
28
+ export default Message;
@@ -0,0 +1,59 @@
1
+ <!--
2
+ Figma: Option Row (component set 68:4, node 68:4)
3
+ -->
4
+ <script lang="ts" module>
5
+ import type { HTMLAttributes } from 'svelte/elements';
6
+
7
+ export type OptionRowSelectionMode = 'radio' | 'checkbox';
8
+
9
+ export type OptionRowProps = HTMLAttributes<HTMLButtonElement> & {
10
+ ref?: HTMLButtonElement | null;
11
+ class?: string;
12
+ label: string;
13
+ selected?: boolean;
14
+ disabled?: boolean;
15
+ selectionMode?: OptionRowSelectionMode;
16
+ onSelect?: () => void;
17
+ };
18
+ </script>
19
+
20
+ <script lang="ts">
21
+ import IconCheck from '@tabler/icons-svelte/icons/check';
22
+ import { cn } from '../../utils.js';
23
+
24
+ let {
25
+ ref = $bindable(null),
26
+ class: className,
27
+ label,
28
+ selected = false,
29
+ disabled = false,
30
+ selectionMode = 'radio',
31
+ onSelect,
32
+ ...restProps
33
+ }: OptionRowProps = $props();
34
+ </script>
35
+
36
+ <button
37
+ bind:this={ref}
38
+ type="button"
39
+ role={selectionMode === 'radio' ? 'radio' : 'checkbox'}
40
+ aria-checked={selected}
41
+ {disabled}
42
+ class={cn(
43
+ 'border-border duration-fast hover:bg-accent focus-visible:ring-ring focus-visible:ring-offset-background flex h-9 w-full items-center gap-2 rounded-lg border px-3 py-2 text-start transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50',
44
+ selected && 'border-primary bg-accent',
45
+ className
46
+ )}
47
+ onclick={() => onSelect?.()}
48
+ {...restProps}
49
+ >
50
+ <!-- Figma: the label comes first and fills the row; the check sits after it and exists only when selected -->
51
+ <span class={cn('text-body min-w-0 flex-1 truncate', selected && 'text-body-medium')}>
52
+ {label}
53
+ </span>
54
+ {#if selected}
55
+ <span class="text-foreground flex size-4 shrink-0 items-center justify-center" aria-hidden="true">
56
+ <IconCheck class="size-4 stroke-[1.333]" />
57
+ </span>
58
+ {/if}
59
+ </button>
@@ -0,0 +1,14 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ export type OptionRowSelectionMode = 'radio' | 'checkbox';
3
+ export type OptionRowProps = HTMLAttributes<HTMLButtonElement> & {
4
+ ref?: HTMLButtonElement | null;
5
+ class?: string;
6
+ label: string;
7
+ selected?: boolean;
8
+ disabled?: boolean;
9
+ selectionMode?: OptionRowSelectionMode;
10
+ onSelect?: () => void;
11
+ };
12
+ declare const OptionRow: import("svelte").Component<OptionRowProps, {}, "ref">;
13
+ type OptionRow = ReturnType<typeof OptionRow>;
14
+ export default OptionRow;
@@ -0,0 +1,220 @@
1
+ <!--
2
+ Figma: Plan Card (component set 68:107, page Plan Card)
3
+ -->
4
+ <script lang="ts">
5
+ import type { HTMLAttributes } from 'svelte/elements';
6
+ import type { Snippet } from 'svelte';
7
+ import IconChevronDown from '@tabler/icons-svelte/icons/chevron-down';
8
+ import IconCircle from '@tabler/icons-svelte/icons/circle';
9
+ import IconCircleCheck from '@tabler/icons-svelte/icons/circle-check';
10
+ import IconListCheck from '@tabler/icons-svelte/icons/list-check';
11
+ import IconLoader2 from '@tabler/icons-svelte/icons/loader-2';
12
+ import { Badge } from '../ui/badge';
13
+ import { Button } from '../ui/button';
14
+ import * as Collapsible from '../ui/collapsible';
15
+ import { cn } from '../../utils.js';
16
+
17
+ export type PlanTask = {
18
+ id: string;
19
+ label: string;
20
+ status: 'pending' | 'active' | 'done';
21
+ };
22
+
23
+ export type PlanCardMessages = {
24
+ progress: string;
25
+ showMoreTasks: string;
26
+ showFewerTasks: string;
27
+ primaryAction: string;
28
+ secondaryAction: string;
29
+ };
30
+
31
+ const defaultMessages: PlanCardMessages = {
32
+ progress: '{current} of {total}',
33
+ showMoreTasks: 'Show {count} more tasks',
34
+ showFewerTasks: 'Show fewer tasks',
35
+ primaryAction: 'Build',
36
+ secondaryAction: 'Edit plan'
37
+ };
38
+
39
+ type Props = HTMLAttributes<HTMLDivElement> & {
40
+ ref?: HTMLDivElement | null;
41
+ class?: string;
42
+ title: string;
43
+ tasks: PlanTask[];
44
+ /** Shown in the header badge; defaults from active task index and task count. */
45
+ progressCurrent?: number;
46
+ progressTotal?: number;
47
+ /** Tasks shown before the collapsible section. */
48
+ visibleTaskCount?: number;
49
+ messages?: Partial<PlanCardMessages>;
50
+ onPrimary?: () => void;
51
+ onSecondary?: () => void;
52
+ headerIcon?: Snippet;
53
+ primaryAction?: Snippet;
54
+ secondaryAction?: Snippet;
55
+ };
56
+
57
+ let {
58
+ ref = $bindable(null),
59
+ class: className,
60
+ title,
61
+ tasks,
62
+ progressCurrent,
63
+ progressTotal,
64
+ visibleTaskCount = 3,
65
+ messages: messagesProp,
66
+ onPrimary,
67
+ onSecondary,
68
+ headerIcon,
69
+ primaryAction,
70
+ secondaryAction,
71
+ ...restProps
72
+ }: Props = $props();
73
+
74
+ const messages = $derived({ ...defaultMessages, ...messagesProp });
75
+
76
+ let expanded = $state(false);
77
+
78
+ const activeIndex = $derived(tasks.findIndex((t) => t.status === 'active'));
79
+ const resolvedProgressCurrent = $derived(
80
+ progressCurrent ?? (activeIndex >= 0 ? activeIndex + 1 : tasks.length > 0 ? 1 : 0)
81
+ );
82
+ const resolvedProgressTotal = $derived(progressTotal ?? tasks.length);
83
+ const progressLabel = $derived(
84
+ messages.progress
85
+ .replace('{current}', String(resolvedProgressCurrent))
86
+ .replace('{total}', String(resolvedProgressTotal))
87
+ .replace('{completed}', String(resolvedProgressCurrent))
88
+ );
89
+
90
+ const showFooter = $derived(
91
+ Boolean(onPrimary || onSecondary || primaryAction || secondaryAction)
92
+ );
93
+
94
+ const visibleTasks = $derived(tasks.slice(0, visibleTaskCount));
95
+ const hiddenTasks = $derived(tasks.slice(visibleTaskCount));
96
+ const hiddenCount = $derived(hiddenTasks.length);
97
+
98
+ const showMoreLabel = $derived(
99
+ expanded
100
+ ? messages.showFewerTasks
101
+ : messages.showMoreTasks.replace('{count}', String(hiddenCount))
102
+ );
103
+
104
+ function taskIcon(status: PlanTask['status']) {
105
+ if (status === 'done') return IconCircleCheck;
106
+ if (status === 'active') return IconLoader2;
107
+ return IconCircle;
108
+ }
109
+
110
+ function handlePrimary() {
111
+ if (!onPrimary) return;
112
+ try {
113
+ onPrimary();
114
+ } catch (error) {
115
+ console.error(error);
116
+ }
117
+ }
118
+
119
+ function handleSecondary() {
120
+ if (!onSecondary) return;
121
+ try {
122
+ onSecondary();
123
+ } catch (error) {
124
+ console.error(error);
125
+ }
126
+ }
127
+ </script>
128
+
129
+ {#snippet taskRow(task: PlanTask)}
130
+ {@const Icon = taskIcon(task.status)}
131
+ <div class="flex min-h-7 items-center gap-2 px-0 py-1">
132
+ <Icon
133
+ class={cn(
134
+ 'size-4 shrink-0 stroke-[1.333]',
135
+ task.status === 'active' && 'animate-spin motion-reduce:animate-none',
136
+ task.status === 'done' ? 'text-muted-foreground' : 'text-foreground'
137
+ )}
138
+ aria-hidden="true"
139
+ />
140
+ <span
141
+ class={cn(
142
+ 'text-body min-w-0 flex-1 truncate',
143
+ task.status === 'pending' && 'text-muted-foreground',
144
+ task.status === 'active' && 'text-body-medium text-foreground',
145
+ task.status === 'done' && 'text-muted-foreground line-through'
146
+ )}
147
+ >
148
+ {task.label}
149
+ </span>
150
+ </div>
151
+ {/snippet}
152
+
153
+ <div
154
+ bind:this={ref}
155
+ class={cn(
156
+ 'rounded-card border-border bg-background flex w-full max-w-full flex-col gap-3 border p-4',
157
+ className
158
+ )}
159
+ {...restProps}
160
+ >
161
+ <header class="flex min-w-0 items-center gap-2">
162
+ <span class="text-foreground flex size-4 shrink-0 items-center justify-center">
163
+ {#if headerIcon}
164
+ {@render headerIcon()}
165
+ {:else}
166
+ <IconListCheck class="size-4 stroke-[1.333]" aria-hidden="true" />
167
+ {/if}
168
+ </span>
169
+ <h3 class="text-body-medium text-foreground min-w-0 flex-1 truncate">{title}</h3>
170
+ <Badge variant="default" class="shrink-0">{progressLabel}</Badge>
171
+ </header>
172
+
173
+ <div class="flex flex-col">
174
+ {#each visibleTasks as task (task.id)}
175
+ {@render taskRow(task)}
176
+ {/each}
177
+
178
+ {#if hiddenCount > 0}
179
+ <Collapsible.Root bind:open={expanded}>
180
+ <Collapsible.Trigger
181
+ class="text-label text-foreground duration-fast hover:bg-accent focus-visible:ring-ring focus-visible:ring-offset-background mt-1 inline-flex h-8 items-center gap-1 rounded-lg px-2 transition-colors focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden"
182
+ >
183
+ {showMoreLabel}
184
+ <IconChevronDown
185
+ class={cn(
186
+ 'duration-base size-4 stroke-[1.333] transition-transform motion-reduce:transition-none',
187
+ expanded && 'rotate-180'
188
+ )}
189
+ aria-hidden="true"
190
+ />
191
+ </Collapsible.Trigger>
192
+ <Collapsible.Content class="flex flex-col overflow-hidden motion-reduce:transition-none">
193
+ {#each hiddenTasks as task (task.id)}
194
+ {@render taskRow(task)}
195
+ {/each}
196
+ </Collapsible.Content>
197
+ </Collapsible.Root>
198
+ {/if}
199
+ </div>
200
+
201
+ {#if showFooter}
202
+ <footer class="flex flex-wrap items-center gap-2 pt-1">
203
+ {#if secondaryAction}
204
+ {@render secondaryAction()}
205
+ {:else if onSecondary}
206
+ <Button variant="outline" size="sm" class="shrink-0" onclick={handleSecondary}>
207
+ {messages.secondaryAction}
208
+ </Button>
209
+ {/if}
210
+ <span class="min-w-0 flex-1" aria-hidden="true"></span>
211
+ {#if primaryAction}
212
+ {@render primaryAction()}
213
+ {:else if onPrimary}
214
+ <Button variant="default" size="sm" class="shrink-0" onclick={handlePrimary}>
215
+ {messages.primaryAction}
216
+ </Button>
217
+ {/if}
218
+ </footer>
219
+ {/if}
220
+ </div>