@widgetic/design-system 0.5.22 → 0.5.24

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 (258) 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 +495 -401
  41. package/dist/components/modals/BaseModal.svelte.d.ts +4 -0
  42. package/dist/components/modals/NewItemDialog.svelte +198 -243
  43. package/dist/components/modals/Portal.svelte +109 -105
  44. package/dist/components/modals/README.md +16 -2
  45. package/dist/components/modals/RenameDialog.svelte +232 -292
  46. package/dist/components/modals/modalStore.d.ts +9 -1
  47. package/dist/components/modals/modalStore.js +44 -11
  48. package/dist/components/navigation/Breadcrumbs.svelte +421 -377
  49. package/dist/components/navigation/Breadcrumbs.svelte.d.ts +3 -2
  50. package/dist/components/navigation/breadcrumbsAvailableWidth.d.ts +10 -0
  51. package/dist/components/navigation/breadcrumbsAvailableWidth.js +10 -0
  52. package/dist/components/navigation/types.d.ts +2 -0
  53. package/dist/components/pdf/PDFViewer.svelte +1 -1
  54. package/dist/components/search-input/SearchInput.svelte +63 -104
  55. package/dist/components/search-input/SearchInput.svelte.d.ts +13 -27
  56. package/dist/components/search-input/index2.d.ts +14 -9
  57. package/dist/components/shad-editor/editor.css +1 -1
  58. package/dist/components/shad-editor/i18n/messages.d.ts +1 -1
  59. package/dist/components/shad-editor/i18n/messages.js +1 -1
  60. package/dist/components/shell/AppShell.svelte +52 -0
  61. package/dist/components/shell/AppShell.svelte.d.ts +13 -0
  62. package/dist/components/shell/Banner.svelte +90 -0
  63. package/dist/components/shell/Banner.svelte.d.ts +61 -0
  64. package/dist/components/shell/ErrorState.svelte +60 -0
  65. package/dist/components/shell/ErrorState.svelte.d.ts +13 -0
  66. package/dist/components/shell/Link.svelte +63 -0
  67. package/dist/components/shell/Link.svelte.d.ts +53 -0
  68. package/dist/components/shell/NavDrawer.svelte +117 -0
  69. package/dist/components/shell/NavDrawer.svelte.d.ts +10 -0
  70. package/dist/components/shell/NavRow.svelte +128 -0
  71. package/dist/components/shell/NavRow.svelte.d.ts +65 -0
  72. package/dist/components/shell/NavSectionLabel.svelte +27 -0
  73. package/dist/components/shell/NavSectionLabel.svelte.d.ts +9 -0
  74. package/dist/components/shell/NotificationRow.svelte +76 -0
  75. package/dist/components/shell/NotificationRow.svelte.d.ts +16 -0
  76. package/dist/components/shell/PageHeader.svelte +42 -0
  77. package/dist/components/shell/PageHeader.svelte.d.ts +12 -0
  78. package/dist/components/shell/ProfileRow.svelte +95 -0
  79. package/dist/components/shell/ProfileRow.svelte.d.ts +54 -0
  80. package/dist/components/shell/SettingsDialog.svelte +163 -0
  81. package/dist/components/shell/SettingsDialog.svelte.d.ts +19 -0
  82. package/dist/components/shell/SettingsNav.svelte +46 -0
  83. package/dist/components/shell/SettingsNav.svelte.d.ts +11 -0
  84. package/dist/components/shell/Sidebar.svelte +82 -0
  85. package/dist/components/shell/Sidebar.svelte.d.ts +25 -0
  86. package/dist/components/shell/Stat.svelte +63 -0
  87. package/dist/components/shell/Stat.svelte.d.ts +58 -0
  88. package/dist/components/shell/TopBar.svelte +37 -0
  89. package/dist/components/shell/TopBar.svelte.d.ts +11 -0
  90. package/dist/components/shell/WidgetRow.svelte +126 -0
  91. package/dist/components/shell/WidgetRow.svelte.d.ts +78 -0
  92. package/dist/components/shell/error-state-messages.d.ts +7 -0
  93. package/dist/components/shell/error-state-messages.js +9 -0
  94. package/dist/components/shell/index.d.ts +22 -0
  95. package/dist/components/shell/index.js +25 -0
  96. package/dist/components/shell/link-external.d.ts +2 -0
  97. package/dist/components/shell/link-external.js +7 -0
  98. package/dist/components/shell/messages.d.ts +7 -0
  99. package/dist/components/shell/messages.js +5 -0
  100. package/dist/components/shell/nested-modal-guard.d.ts +25 -0
  101. package/dist/components/shell/nested-modal-guard.js +33 -0
  102. package/dist/components/shell/settings-nav-types.d.ts +12 -0
  103. package/dist/components/shell/settings-nav-types.js +3 -0
  104. package/dist/components/shell/shell-utils.d.ts +7 -0
  105. package/dist/components/shell/shell-utils.js +27 -0
  106. package/dist/components/shell/stat-tone.d.ts +3 -0
  107. package/dist/components/shell/stat-tone.js +11 -0
  108. package/dist/components/toast/ToasterModal.svelte +102 -168
  109. package/dist/components/toast/ToasterModal.svelte.d.ts +2 -12
  110. package/dist/components/ui/accordion/accordion-content.svelte +3 -2
  111. package/dist/components/ui/accordion/accordion-item.svelte +10 -1
  112. package/dist/components/ui/accordion/accordion-trigger.svelte +4 -3
  113. package/dist/components/ui/alert/alert-description.svelte +5 -1
  114. package/dist/components/ui/alert/alert-title.svelte +2 -1
  115. package/dist/components/ui/alert/index.d.ts +13 -11
  116. package/dist/components/ui/alert/index.js +4 -4
  117. package/dist/components/ui/avatar/avatar-utils.d.ts +2 -0
  118. package/dist/components/ui/avatar/avatar-utils.js +9 -0
  119. package/dist/components/ui/avatar/avatar.svelte +63 -0
  120. package/dist/components/ui/avatar/avatar.svelte.d.ts +80 -0
  121. package/dist/components/ui/avatar/index.d.ts +4 -0
  122. package/dist/components/ui/avatar/index.js +4 -0
  123. package/dist/components/ui/badge/badge.svelte +22 -10
  124. package/dist/components/ui/badge/badge.svelte.d.ts +14 -15
  125. package/dist/components/ui/breadcrumb/breadcrumb-ellipsis.svelte +2 -2
  126. package/dist/components/ui/breadcrumb/breadcrumb-link.svelte +1 -1
  127. package/dist/components/ui/breadcrumb/breadcrumb-page.svelte +1 -1
  128. package/dist/components/ui/breadcrumb/breadcrumb-separator.svelte +2 -2
  129. package/dist/components/ui/button/button-padding.d.ts +9 -0
  130. package/dist/components/ui/button/button-padding.js +15 -0
  131. package/dist/components/ui/button/button.svelte +81 -31
  132. package/dist/components/ui/button/button.svelte.d.ts +48 -29
  133. package/dist/components/ui/calendar/calendar-cell.svelte +1 -1
  134. package/dist/components/ui/calendar/calendar-head-cell.svelte +1 -1
  135. package/dist/components/ui/calendar/calendar-next-button.svelte +2 -2
  136. package/dist/components/ui/calendar/calendar-prev-button.svelte +2 -2
  137. package/dist/components/ui/calendar/calendar.svelte.d.ts +1 -1
  138. package/dist/components/ui/card/card-description.svelte +1 -1
  139. package/dist/components/ui/card/card-title.svelte +1 -1
  140. package/dist/components/ui/card/card.svelte +1 -1
  141. package/dist/components/ui/checkbox/checkbox.svelte +9 -5
  142. package/dist/components/ui/chip/chip.svelte +48 -0
  143. package/dist/components/ui/chip/chip.svelte.d.ts +13 -0
  144. package/dist/components/ui/chip/index.d.ts +2 -0
  145. package/dist/components/ui/chip/index.js +2 -0
  146. package/dist/components/ui/command/command-dialog.svelte +1 -1
  147. package/dist/components/ui/command/command-input.svelte +4 -4
  148. package/dist/components/ui/command/command-item.svelte +1 -1
  149. package/dist/components/ui/command/command-link-item.svelte +1 -1
  150. package/dist/components/ui/command/command.svelte +1 -1
  151. package/dist/components/ui/dialog/dialog-content.svelte +11 -4
  152. package/dist/components/ui/dialog/dialog-description.svelte +1 -1
  153. package/dist/components/ui/dialog/dialog-overlay.svelte +8 -1
  154. package/dist/components/ui/dialog/dialog-title.svelte +1 -1
  155. package/dist/components/ui/drawer/drawer-content.svelte +1 -1
  156. package/dist/components/ui/drawer/drawer-overlay.svelte +1 -1
  157. package/dist/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +5 -5
  158. package/dist/components/ui/dropdown-menu/dropdown-menu-content.svelte +1 -1
  159. package/dist/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +1 -1
  160. package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte +20 -2
  161. package/dist/components/ui/dropdown-menu/dropdown-menu-item.svelte.d.ts +4 -0
  162. package/dist/components/ui/dropdown-menu/dropdown-menu-label.svelte +5 -1
  163. package/dist/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +3 -3
  164. package/dist/components/ui/dropdown-menu/dropdown-menu-separator.svelte +2 -1
  165. package/dist/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +1 -1
  166. package/dist/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +3 -3
  167. package/dist/components/ui/field/field-ids.d.ts +7 -0
  168. package/dist/components/ui/field/field-ids.js +15 -0
  169. package/dist/components/ui/field/field.svelte +62 -0
  170. package/dist/components/ui/field/field.svelte.d.ts +21 -0
  171. package/dist/components/ui/field/index.d.ts +3 -0
  172. package/dist/components/ui/field/index.js +3 -0
  173. package/dist/components/ui/hover-card/hover-card-content.svelte +1 -1
  174. package/dist/components/ui/icon-button/icon-button.svelte +72 -0
  175. package/dist/components/ui/icon-button/icon-button.svelte.d.ts +90 -0
  176. package/dist/components/ui/icon-button/index.d.ts +2 -0
  177. package/dist/components/ui/icon-button/index.js +4 -0
  178. package/dist/components/ui/input/index.d.ts +1 -0
  179. package/dist/components/ui/input/input.svelte +11 -4
  180. package/dist/components/ui/input/input.svelte.d.ts +4 -1
  181. package/dist/components/ui/input-otp/input-otp-separator.svelte +2 -2
  182. package/dist/components/ui/input-otp/input-otp-slot.svelte +5 -4
  183. package/dist/components/ui/input-otp/input-otp-slot.svelte.d.ts +1 -1
  184. package/dist/components/ui/input-otp/input-otp.svelte +1 -1
  185. package/dist/components/ui/label/label.svelte +3 -2
  186. package/dist/components/ui/popover/popover-content.svelte +1 -1
  187. package/dist/components/ui/progress/progress.svelte +19 -5
  188. package/dist/components/ui/progress/progress.svelte.d.ts +4 -2
  189. package/dist/components/ui/radio-group/radio-group-item.svelte +4 -4
  190. package/dist/components/ui/range-calendar/range-calendar-head-cell.svelte +1 -1
  191. package/dist/components/ui/range-calendar/range-calendar-next-button.svelte +2 -2
  192. package/dist/components/ui/range-calendar/range-calendar-prev-button.svelte +2 -2
  193. package/dist/components/ui/range-calendar/range-calendar.svelte.d.ts +1 -1
  194. package/dist/components/ui/scroll-area/scroll-area-scrollbar.svelte +4 -1
  195. package/dist/components/ui/scroll-area/scroll-area.svelte +1 -1
  196. package/dist/components/ui/select/select-content.svelte +1 -3
  197. package/dist/components/ui/select/select-item.svelte +5 -4
  198. package/dist/components/ui/select/select-scroll-down-button.svelte +2 -2
  199. package/dist/components/ui/select/select-scroll-up-button.svelte +2 -2
  200. package/dist/components/ui/select/select-trigger.svelte +4 -3
  201. package/dist/components/ui/skeleton/index.d.ts +2 -0
  202. package/dist/components/ui/skeleton/index.js +2 -0
  203. package/dist/components/ui/skeleton/skeleton.svelte +24 -0
  204. package/dist/components/ui/skeleton/skeleton.svelte.d.ts +8 -0
  205. package/dist/components/ui/slider/slider.svelte +5 -10
  206. package/dist/components/ui/sonner/sonner.svelte +19 -13
  207. package/dist/components/ui/stepper-button/index.d.ts +3 -0
  208. package/dist/components/ui/stepper-button/index.js +2 -0
  209. package/dist/components/ui/stepper-button/stepper-button.svelte +37 -0
  210. package/dist/components/ui/stepper-button/stepper-button.svelte.d.ts +9 -0
  211. package/dist/components/ui/switch/switch.svelte +8 -3
  212. package/dist/components/ui/table/table-cell.svelte +1 -1
  213. package/dist/components/ui/table/table-head.svelte +1 -1
  214. package/dist/components/ui/table/table-row.svelte +1 -1
  215. package/dist/components/ui/tabs/tabs-list.svelte +1 -1
  216. package/dist/components/ui/tabs/tabs-trigger.svelte +1 -1
  217. package/dist/components/ui/tag/index.d.ts +2 -0
  218. package/dist/components/ui/tag/index.js +2 -0
  219. package/dist/components/ui/tag/tag.svelte +42 -0
  220. package/dist/components/ui/tag/tag.svelte.d.ts +11 -0
  221. package/dist/components/ui/textarea/textarea.svelte +4 -3
  222. package/dist/components/ui/textarea/textarea.svelte.d.ts +2 -2
  223. package/dist/components/ui/toggle/toggle.svelte +9 -9
  224. package/dist/components/ui/toggle/toggle.svelte.d.ts +14 -21
  225. package/dist/components/ui/tooltip/tooltip-content.svelte +1 -1
  226. package/dist/fonts/Inter/Inter-Black.woff2 +0 -0
  227. package/dist/fonts/Inter/Inter-BlackItalic.woff2 +0 -0
  228. package/dist/fonts/Inter/Inter-Bold.woff2 +0 -0
  229. package/dist/fonts/Inter/Inter-BoldItalic.woff2 +0 -0
  230. package/dist/fonts/Inter/Inter-ExtraBold.woff2 +0 -0
  231. package/dist/fonts/Inter/Inter-ExtraBoldItalic.woff2 +0 -0
  232. package/dist/fonts/Inter/Inter-ExtraLight-BETA.woff2 +0 -0
  233. package/dist/fonts/Inter/Inter-ExtraLightItalic-BETA.woff2 +0 -0
  234. package/dist/fonts/Inter/Inter-Italic.woff2 +0 -0
  235. package/dist/fonts/Inter/Inter-Light-BETA.woff2 +0 -0
  236. package/dist/fonts/Inter/Inter-LightItalic-BETA.woff2 +0 -0
  237. package/dist/fonts/Inter/Inter-Medium.woff2 +0 -0
  238. package/dist/fonts/Inter/Inter-MediumItalic.woff2 +0 -0
  239. package/dist/fonts/Inter/Inter-Regular.woff2 +0 -0
  240. package/dist/fonts/Inter/Inter-SemiBold.woff2 +0 -0
  241. package/dist/fonts/Inter/Inter-SemiBoldItalic.woff2 +0 -0
  242. package/dist/fonts/Inter/Inter-Thin-BETA.woff2 +0 -0
  243. package/dist/fonts/Inter/Inter-ThinItalic-BETA.woff2 +0 -0
  244. package/dist/fonts/Inter/Inter-italic.var.woff2 +0 -0
  245. package/dist/fonts/Inter/Inter-upright.var.woff2 +0 -0
  246. package/dist/fonts/Inter/Inter.var.woff2 +0 -0
  247. package/dist/fonts/JetBrainsMono/JetBrainsMono-Regular.woff2 +0 -0
  248. package/dist/i18n/audit/designSystemWords.d.ts +1 -1
  249. package/dist/i18n/audit/designSystemWords.js +1 -1
  250. package/dist/styles/fonts.css +18 -14
  251. package/dist/styles/global.css +10 -21
  252. package/dist/styles/tokens.css +332 -0
  253. package/dist/utils/overlayMotion.d.ts +49 -0
  254. package/dist/utils/overlayMotion.js +71 -0
  255. package/dist/utils.d.ts +27 -0
  256. package/dist/utils.js +33 -2
  257. package/package.json +2 -1
  258. package/tailwind.config.ts +7 -47
@@ -0,0 +1,34 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ import type { Snippet } from 'svelte';
3
+ export type PlanTask = {
4
+ id: string;
5
+ label: string;
6
+ status: 'pending' | 'active' | 'done';
7
+ };
8
+ export type PlanCardMessages = {
9
+ progress: string;
10
+ showMoreTasks: string;
11
+ showFewerTasks: string;
12
+ primaryAction: string;
13
+ secondaryAction: string;
14
+ };
15
+ type Props = HTMLAttributes<HTMLDivElement> & {
16
+ ref?: HTMLDivElement | null;
17
+ class?: string;
18
+ title: string;
19
+ tasks: PlanTask[];
20
+ /** Shown in the header badge; defaults from active task index and task count. */
21
+ progressCurrent?: number;
22
+ progressTotal?: number;
23
+ /** Tasks shown before the collapsible section. */
24
+ visibleTaskCount?: number;
25
+ messages?: Partial<PlanCardMessages>;
26
+ onPrimary?: () => void;
27
+ onSecondary?: () => void;
28
+ headerIcon?: Snippet;
29
+ primaryAction?: Snippet;
30
+ secondaryAction?: Snippet;
31
+ };
32
+ declare const PlanCard: import("svelte").Component<Props, {}, "ref">;
33
+ type PlanCard = ReturnType<typeof PlanCard>;
34
+ export default PlanCard;
@@ -0,0 +1,265 @@
1
+ <!--
2
+ Figma: Question Card (component set 68:26, page Question Card)
3
+ -->
4
+ <script lang="ts">
5
+ import type { HTMLAttributes } from 'svelte/elements';
6
+ import { Button } from '../ui/button';
7
+ import { cn } from '../../utils.js';
8
+ import OptionRow from './OptionRow.svelte';
9
+ import {
10
+ canSubmitQuestion,
11
+ formatQuestionStep,
12
+ isFirstQuestion,
13
+ isLastQuestion,
14
+ moveFocusedIndex,
15
+ optionItemRole,
16
+ primaryQuestionLabel,
17
+ selectionRole,
18
+ toggleOption
19
+ } from './questionCardLogic.js';
20
+
21
+ export type QuestionOption = {
22
+ id: string;
23
+ label: string;
24
+ description?: string;
25
+ };
26
+
27
+ export type QuestionItem = {
28
+ question: string;
29
+ options: QuestionOption[];
30
+ multiple?: boolean;
31
+ };
32
+
33
+ export type QuestionCardMessages = {
34
+ step: string;
35
+ back: string;
36
+ next: string;
37
+ submit: string;
38
+ skip: string;
39
+ };
40
+
41
+ const defaultMessages: QuestionCardMessages = {
42
+ step: 'Question {current} of {total}',
43
+ back: 'Back',
44
+ next: 'Next',
45
+ submit: 'Submit',
46
+ skip: 'Skip'
47
+ };
48
+
49
+ type Props = HTMLAttributes<HTMLDivElement> & {
50
+ ref?: HTMLDivElement | null;
51
+ class?: string;
52
+ /** Overrides the formatted step line when set. */
53
+ step?: string;
54
+ question: string;
55
+ options: QuestionOption[];
56
+ /** Multi-question flow; when set, `question` / `options` on the root are ignored. */
57
+ questions?: QuestionItem[];
58
+ multiple?: boolean;
59
+ showSkip?: boolean;
60
+ messages?: Partial<QuestionCardMessages>;
61
+ onSubmit?: (selectedIds: string[]) => void;
62
+ onNext?: (selectedIds: string[], questionIndex: number) => void;
63
+ onBack?: (questionIndex: number) => void;
64
+ onSkip?: () => void;
65
+ };
66
+
67
+ let {
68
+ ref = $bindable(null),
69
+ class: className,
70
+ step,
71
+ question,
72
+ options,
73
+ questions,
74
+ multiple = false,
75
+ showSkip = false,
76
+ messages: messagesProp,
77
+ onSubmit,
78
+ onNext,
79
+ onBack,
80
+ onSkip,
81
+ ...restProps
82
+ }: Props = $props();
83
+
84
+ const messages = $derived({ ...defaultMessages, ...messagesProp });
85
+
86
+ let currentIndex = $state(0);
87
+ let selectedIds = $state<string[]>([]);
88
+ let focusedIndex = $state(0);
89
+ let selectionsByIndex = $state<Record<number, string[]>>({});
90
+
91
+ const flowItems = $derived(questions && questions.length > 0 ? questions : null);
92
+ const questionTotal = $derived(flowItems ? flowItems.length : 1);
93
+ const activeItem = $derived(
94
+ flowItems ? flowItems[Math.min(currentIndex, flowItems.length - 1)] : null
95
+ );
96
+ const activeQuestion = $derived(activeItem?.question ?? question);
97
+ const activeOptions = $derived(activeItem?.options ?? options);
98
+ const activeMultiple = $derived(activeItem?.multiple ?? multiple);
99
+
100
+ const stepLabel = $derived(
101
+ step ?? formatQuestionStep(messages.step, currentIndex + 1, questionTotal)
102
+ );
103
+ const onFirst = $derived(isFirstQuestion(currentIndex));
104
+ const onLast = $derived(isLastQuestion(currentIndex, questionTotal));
105
+ const primaryLabel = $derived(
106
+ primaryQuestionLabel(onLast && questionTotal > 1, messages.next, messages.submit)
107
+ );
108
+
109
+ const groupRole = $derived(selectionRole(activeMultiple));
110
+ const itemRole = $derived(optionItemRole(activeMultiple));
111
+ const submitEnabled = $derived(canSubmitQuestion(selectedIds));
112
+
113
+ function isSelected(id: string): boolean {
114
+ return selectedIds.includes(id);
115
+ }
116
+
117
+ function persistSelection() {
118
+ selectionsByIndex = { ...selectionsByIndex, [currentIndex]: [...selectedIds] };
119
+ }
120
+
121
+ function loadSelectionForIndex(index: number) {
122
+ selectedIds = [...(selectionsByIndex[index] ?? [])];
123
+ focusedIndex = 0;
124
+ }
125
+
126
+ function selectOption(id: string) {
127
+ selectedIds = toggleOption(selectedIds, id, activeMultiple);
128
+ }
129
+
130
+ function handleOptionKeydown(event: KeyboardEvent, index: number, id: string) {
131
+ if (event.key === ' ' || event.key === 'Spacebar') {
132
+ event.preventDefault();
133
+ selectOption(id);
134
+ return;
135
+ }
136
+ if (event.key === 'ArrowDown') {
137
+ event.preventDefault();
138
+ focusedIndex = moveFocusedIndex(index, 'down', activeOptions.length);
139
+ focusOptionAt(focusedIndex);
140
+ return;
141
+ }
142
+ if (event.key === 'ArrowUp') {
143
+ event.preventDefault();
144
+ focusedIndex = moveFocusedIndex(index, 'up', activeOptions.length);
145
+ focusOptionAt(focusedIndex);
146
+ return;
147
+ }
148
+ if (event.key === 'Enter') {
149
+ event.preventDefault();
150
+ handlePrimary();
151
+ }
152
+ }
153
+
154
+ function focusOptionAt(index: number) {
155
+ const el = ref?.querySelector<HTMLButtonElement>(`[data-option-index="${index}"]`);
156
+ el?.focus();
157
+ }
158
+
159
+ function handlePrimary() {
160
+ if (!submitEnabled) return;
161
+ if (onLast) {
162
+ if (!onSubmit) return;
163
+ try {
164
+ onSubmit([...selectedIds]);
165
+ } catch (error) {
166
+ console.error(error);
167
+ }
168
+ return;
169
+ }
170
+ persistSelection();
171
+ try {
172
+ onNext?.([...selectedIds], currentIndex);
173
+ } catch (error) {
174
+ console.error(error);
175
+ }
176
+ currentIndex += 1;
177
+ loadSelectionForIndex(currentIndex);
178
+ }
179
+
180
+ function handleBack() {
181
+ if (onFirst) return;
182
+ persistSelection();
183
+ try {
184
+ onBack?.(currentIndex);
185
+ } catch (error) {
186
+ console.error(error);
187
+ }
188
+ currentIndex -= 1;
189
+ loadSelectionForIndex(currentIndex);
190
+ }
191
+
192
+ function handleSkip() {
193
+ if (!onSkip) return;
194
+ try {
195
+ onSkip();
196
+ } catch (error) {
197
+ console.error(error);
198
+ }
199
+ }
200
+
201
+ function handleGroupKeydown(event: KeyboardEvent) {
202
+ if (event.key !== 'Enter' || event.target !== ref) return;
203
+ event.preventDefault();
204
+ handlePrimary();
205
+ }
206
+ </script>
207
+
208
+ <div
209
+ bind:this={ref}
210
+ class={cn(
211
+ 'rounded-card border-border bg-background flex w-full max-w-full flex-col gap-3 border p-4',
212
+ className
213
+ )}
214
+ {...restProps}
215
+ >
216
+ <header class="flex min-w-0 flex-col gap-0.5">
217
+ <p class="text-caption-medium text-muted-foreground">{stepLabel}</p>
218
+ <p class="text-body-medium text-foreground">{activeQuestion}</p>
219
+ </header>
220
+
221
+ <div
222
+ class="flex flex-col gap-1"
223
+ role={groupRole}
224
+ aria-label={activeQuestion}
225
+ onkeydown={handleGroupKeydown}
226
+ >
227
+ {#each activeOptions as option, index (option.id)}
228
+ {@const selected = isSelected(option.id)}
229
+ <div class="flex flex-col gap-0.5">
230
+ <OptionRow
231
+ label={option.label}
232
+ {selected}
233
+ selectionMode={itemRole === 'checkbox' ? 'checkbox' : 'radio'}
234
+ data-option-index={index}
235
+ tabindex={index === focusedIndex ? 0 : -1}
236
+ onSelect={() => {
237
+ focusedIndex = index;
238
+ selectOption(option.id);
239
+ }}
240
+ onkeydown={(event) => handleOptionKeydown(event, index, option.id)}
241
+ />
242
+ {#if option.description}
243
+ <p class="text-caption text-muted-foreground ps-3">{option.description}</p>
244
+ {/if}
245
+ </div>
246
+ {/each}
247
+ </div>
248
+
249
+ <footer class="flex min-w-0 items-center gap-2">
250
+ {#if showSkip && onSkip}
251
+ <Button variant="ghost" size="sm" class="shrink-0" onclick={handleSkip}>
252
+ {messages.skip}
253
+ </Button>
254
+ {/if}
255
+ {#if !onFirst}
256
+ <Button variant="ghost" size="sm" class="shrink-0" onclick={handleBack}>
257
+ {messages.back}
258
+ </Button>
259
+ {/if}
260
+ <span class="min-w-0 flex-1" aria-hidden="true"></span>
261
+ <Button variant="default" size="sm" disabled={!submitEnabled} onclick={handlePrimary}>
262
+ {primaryLabel}
263
+ </Button>
264
+ </footer>
265
+ </div>
@@ -0,0 +1,38 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ export type QuestionOption = {
3
+ id: string;
4
+ label: string;
5
+ description?: string;
6
+ };
7
+ export type QuestionItem = {
8
+ question: string;
9
+ options: QuestionOption[];
10
+ multiple?: boolean;
11
+ };
12
+ export type QuestionCardMessages = {
13
+ step: string;
14
+ back: string;
15
+ next: string;
16
+ submit: string;
17
+ skip: string;
18
+ };
19
+ type Props = HTMLAttributes<HTMLDivElement> & {
20
+ ref?: HTMLDivElement | null;
21
+ class?: string;
22
+ /** Overrides the formatted step line when set. */
23
+ step?: string;
24
+ question: string;
25
+ options: QuestionOption[];
26
+ /** Multi-question flow; when set, `question` / `options` on the root are ignored. */
27
+ questions?: QuestionItem[];
28
+ multiple?: boolean;
29
+ showSkip?: boolean;
30
+ messages?: Partial<QuestionCardMessages>;
31
+ onSubmit?: (selectedIds: string[]) => void;
32
+ onNext?: (selectedIds: string[], questionIndex: number) => void;
33
+ onBack?: (questionIndex: number) => void;
34
+ onSkip?: () => void;
35
+ };
36
+ declare const QuestionCard: import("svelte").Component<Props, {}, "ref">;
37
+ type QuestionCard = ReturnType<typeof QuestionCard>;
38
+ export default QuestionCard;
@@ -0,0 +1,51 @@
1
+ <!--
2
+ Figma: Scroll to Bottom (component set 69:79, node 69:79)
3
+ -->
4
+ <script lang="ts" module>
5
+ import type { HTMLAttributes } from 'svelte/elements';
6
+
7
+ export type ScrollToBottomProps = HTMLAttributes<HTMLButtonElement> & {
8
+ ref?: HTMLButtonElement | null;
9
+ class?: string;
10
+ visible?: boolean;
11
+ label?: string;
12
+ onScroll?: () => void;
13
+ };
14
+ </script>
15
+
16
+ <script lang="ts">
17
+ import IconArrowDown from '@tabler/icons-svelte/icons/arrow-down';
18
+ import { cn } from '../../utils.js';
19
+ import { IconButton } from '../ui/icon-button/index.js';
20
+
21
+ let {
22
+ ref = $bindable(null),
23
+ class: className,
24
+ visible = false,
25
+ label = 'Scroll to latest messages',
26
+ onScroll,
27
+ ...restProps
28
+ }: ScrollToBottomProps = $props();
29
+ </script>
30
+
31
+ <div
32
+ class={cn(
33
+ 'duration-base pointer-events-none transition-opacity',
34
+ visible ? 'opacity-100' : 'opacity-0',
35
+ className
36
+ )}
37
+ >
38
+ <IconButton
39
+ bind:ref
40
+ variant="outline"
41
+ {label}
42
+ class={cn(
43
+ 'border-border bg-background hover:bg-accent pointer-events-auto rounded-full',
44
+ !visible && 'pointer-events-none'
45
+ )}
46
+ onclick={() => onScroll?.()}
47
+ {...restProps}
48
+ >
49
+ <IconArrowDown class="size-4 stroke-[1.333]" aria-hidden="true" />
50
+ </IconButton>
51
+ </div>
@@ -0,0 +1,11 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ export type ScrollToBottomProps = HTMLAttributes<HTMLButtonElement> & {
3
+ ref?: HTMLButtonElement | null;
4
+ class?: string;
5
+ visible?: boolean;
6
+ label?: string;
7
+ onScroll?: () => void;
8
+ };
9
+ declare const ScrollToBottom: import("svelte").Component<ScrollToBottomProps, {}, "ref">;
10
+ type ScrollToBottom = ReturnType<typeof ScrollToBottom>;
11
+ export default ScrollToBottom;
@@ -0,0 +1,79 @@
1
+ <!--
2
+ Figma: Suggestion Card (component set 48:3, page Suggestion Card)
3
+ -->
4
+ <script lang="ts">
5
+ import type { Snippet } from 'svelte';
6
+ import type { HTMLButtonAttributes } from 'svelte/elements';
7
+ import IconChevronRight from '@tabler/icons-svelte/icons/chevron-right';
8
+ import { cn } from '../../utils.js';
9
+
10
+ export type SuggestionCardMessages = {
11
+ /** Shown when the card is disabled and no custom reason is passed. */
12
+ unavailable: string;
13
+ };
14
+
15
+ const defaultMessages: SuggestionCardMessages = {
16
+ unavailable: 'Unavailable'
17
+ };
18
+
19
+ type Props = HTMLButtonAttributes & {
20
+ ref?: HTMLButtonElement | null;
21
+ class?: string;
22
+ title: string;
23
+ description?: string;
24
+ showDescription?: boolean;
25
+ disabled?: boolean;
26
+ messages?: Partial<SuggestionCardMessages>;
27
+ icon?: Snippet;
28
+ onclick?: (event: MouseEvent) => void;
29
+ };
30
+
31
+ let {
32
+ ref = $bindable(null),
33
+ class: className,
34
+ title,
35
+ description = '',
36
+ showDescription = false,
37
+ disabled = false,
38
+ messages: messagesProp,
39
+ icon,
40
+ onclick,
41
+ type = 'button',
42
+ ...restProps
43
+ }: Props = $props();
44
+
45
+ const messages = $derived({ ...defaultMessages, ...messagesProp });
46
+ </script>
47
+
48
+ <button
49
+ bind:this={ref}
50
+ {type}
51
+ disabled={disabled || undefined}
52
+ title={disabled ? messages.unavailable : undefined}
53
+ class={cn(
54
+ 'group rounded-card border-border bg-card duration-fast flex w-full max-w-full items-center gap-3 border px-4 py-3 text-start transition-colors',
55
+ 'hover:bg-accent focus-visible:ring-ring focus-visible:ring-offset-background focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden',
56
+ 'disabled:pointer-events-none disabled:opacity-50',
57
+ className
58
+ )}
59
+ {onclick}
60
+ {...restProps}
61
+ >
62
+ {#if icon}
63
+ <span
64
+ class="text-foreground flex size-4 shrink-0 items-center justify-center [&_svg]:size-4 [&_svg]:stroke-[1.333]"
65
+ >
66
+ {@render icon()}
67
+ </span>
68
+ {/if}
69
+ <span class="min-w-0 flex-1">
70
+ <span class="text-body-medium text-foreground block truncate">{title}</span>
71
+ {#if showDescription && description}
72
+ <span class="text-caption text-muted-foreground mt-0.5 block truncate">{description}</span>
73
+ {/if}
74
+ </span>
75
+ <IconChevronRight
76
+ class="text-foreground duration-fast size-4 shrink-0 stroke-[1.333] opacity-0 transition-opacity group-hover:opacity-100 group-focus-visible:opacity-100 rtl:-scale-x-100"
77
+ aria-hidden="true"
78
+ />
79
+ </button>
@@ -0,0 +1,20 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLButtonAttributes } from 'svelte/elements';
3
+ export type SuggestionCardMessages = {
4
+ /** Shown when the card is disabled and no custom reason is passed. */
5
+ unavailable: string;
6
+ };
7
+ type Props = HTMLButtonAttributes & {
8
+ ref?: HTMLButtonElement | null;
9
+ class?: string;
10
+ title: string;
11
+ description?: string;
12
+ showDescription?: boolean;
13
+ disabled?: boolean;
14
+ messages?: Partial<SuggestionCardMessages>;
15
+ icon?: Snippet;
16
+ onclick?: (event: MouseEvent) => void;
17
+ };
18
+ declare const SuggestionCard: import("svelte").Component<Props, {}, "ref">;
19
+ type SuggestionCard = ReturnType<typeof SuggestionCard>;
20
+ export default SuggestionCard;
@@ -0,0 +1,59 @@
1
+ <!--
2
+ Figma: Task Row (component set 68:93, node 68:93)
3
+ -->
4
+ <script lang="ts" module>
5
+ import type { HTMLAttributes } from 'svelte/elements';
6
+
7
+ export type TaskRowStatus = 'pending' | 'active' | 'done';
8
+
9
+ export type TaskRowProps = HTMLAttributes<HTMLDivElement> & {
10
+ ref?: HTMLDivElement | null;
11
+ class?: string;
12
+ label: string;
13
+ status?: TaskRowStatus;
14
+ };
15
+ </script>
16
+
17
+ <script lang="ts">
18
+ import IconCircle from '@tabler/icons-svelte/icons/circle';
19
+ import IconCircleCheck from '@tabler/icons-svelte/icons/circle-check';
20
+ import IconLoader2 from '@tabler/icons-svelte/icons/loader-2';
21
+ import { cn } from '../../utils.js';
22
+
23
+ let {
24
+ ref = $bindable(null),
25
+ class: className,
26
+ label,
27
+ status = 'pending',
28
+ ...restProps
29
+ }: TaskRowProps = $props();
30
+ </script>
31
+
32
+ <div
33
+ bind:this={ref}
34
+ class={cn('flex w-full max-w-[23.5rem] items-center gap-2 px-1 py-1', className)}
35
+ {...restProps}
36
+ >
37
+ <span class="flex size-4 shrink-0 items-center justify-center">
38
+ {#if status === 'active'}
39
+ <IconLoader2
40
+ class="text-foreground size-4 animate-spin stroke-[1.333] motion-reduce:animate-none"
41
+ aria-hidden="true"
42
+ />
43
+ {:else if status === 'done'}
44
+ <IconCircleCheck class="text-brand size-4 stroke-[1.333]" aria-hidden="true" />
45
+ {:else}
46
+ <IconCircle class="text-muted-foreground size-4 stroke-[1.333]" aria-hidden="true" />
47
+ {/if}
48
+ </span>
49
+ <span
50
+ class={cn(
51
+ 'text-body min-w-0 flex-1 truncate',
52
+ status === 'active' || status === 'done'
53
+ ? 'text-body-medium text-foreground'
54
+ : 'text-muted-foreground'
55
+ )}
56
+ >
57
+ {label}
58
+ </span>
59
+ </div>
@@ -0,0 +1,11 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ export type TaskRowStatus = 'pending' | 'active' | 'done';
3
+ export type TaskRowProps = HTMLAttributes<HTMLDivElement> & {
4
+ ref?: HTMLDivElement | null;
5
+ class?: string;
6
+ label: string;
7
+ status?: TaskRowStatus;
8
+ };
9
+ declare const TaskRow: import("svelte").Component<TaskRowProps, {}, "ref">;
10
+ type TaskRow = ReturnType<typeof TaskRow>;
11
+ export default TaskRow;
@@ -0,0 +1,16 @@
1
+ /** Max auto-grow height for the composer textarea (matches product chat input). */
2
+ export declare const COMPOSER_TEXTAREA_MAX_HEIGHT_PX = 120;
3
+ /** Initial textarea row area height from the Figma Composer default tree. */
4
+ export declare const COMPOSER_TEXTAREA_MIN_HEIGHT_PX = 48;
5
+ /** Whether Enter should submit (Shift+Enter inserts a newline; IME composition must not send). */
6
+ export declare function shouldSendOnKeyDown(key: string, shiftKey: boolean, isComposing: boolean): boolean;
7
+ /** Clamp measured scroll height between min and max for autosize. */
8
+ export declare function clampTextareaHeight(scrollHeight: number, minHeight?: number, maxHeight?: number): number;
9
+ export type CanSendTextOptions = {
10
+ disabled?: boolean;
11
+ /** When true, empty trimmed text still cannot send (attachments handled by the component). */
12
+ allowEmpty?: boolean;
13
+ maxLength?: number;
14
+ };
15
+ /** Whether the primary send action is allowed (not stop — use `busy` on the component for that). */
16
+ export declare function canSendText(text: string, options?: CanSendTextOptions): boolean;
@@ -0,0 +1,29 @@
1
+ /** Max auto-grow height for the composer textarea (matches product chat input). */
2
+ export const COMPOSER_TEXTAREA_MAX_HEIGHT_PX = 120;
3
+ /** Initial textarea row area height from the Figma Composer default tree. */
4
+ export const COMPOSER_TEXTAREA_MIN_HEIGHT_PX = 48;
5
+ /** Whether Enter should submit (Shift+Enter inserts a newline; IME composition must not send). */
6
+ export function shouldSendOnKeyDown(key, shiftKey, isComposing) {
7
+ if (isComposing)
8
+ return false;
9
+ if (key !== 'Enter')
10
+ return false;
11
+ if (shiftKey)
12
+ return false;
13
+ return true;
14
+ }
15
+ /** Clamp measured scroll height between min and max for autosize. */
16
+ export function clampTextareaHeight(scrollHeight, minHeight = COMPOSER_TEXTAREA_MIN_HEIGHT_PX, maxHeight = COMPOSER_TEXTAREA_MAX_HEIGHT_PX) {
17
+ return Math.min(Math.max(scrollHeight, minHeight), maxHeight);
18
+ }
19
+ /** Whether the primary send action is allowed (not stop — use `busy` on the component for that). */
20
+ export function canSendText(text, options = {}) {
21
+ if (options.disabled)
22
+ return false;
23
+ const trimmed = text.trim();
24
+ if (!options.allowEmpty && trimmed.length === 0)
25
+ return false;
26
+ if (options.maxLength != null && trimmed.length > options.maxLength)
27
+ return false;
28
+ return true;
29
+ }
@@ -0,0 +1,16 @@
1
+ export { default as Composer, type ComposerMessages, type ComposerProps } from './Composer.svelte';
2
+ export { default as Message, type MessageMessages, type MessageProps, type MessageRole } from './Message.svelte';
3
+ export { default as ActivityRow, type ActivityRowProps, type ActivityRowStatus } from './ActivityRow.svelte';
4
+ export { default as OptionRow, type OptionRowProps, type OptionRowSelectionMode } from './OptionRow.svelte';
5
+ export { default as TaskRow, type TaskRowProps, type TaskRowStatus } from './TaskRow.svelte';
6
+ export { default as ScrollToBottom, type ScrollToBottomProps } from './ScrollToBottom.svelte';
7
+ export { canSendText, clampTextareaHeight, COMPOSER_TEXTAREA_MAX_HEIGHT_PX, COMPOSER_TEXTAREA_MIN_HEIGHT_PX, shouldSendOnKeyDown } from './composerLogic.js';
8
+ export { default as ChatHeader } from './ChatHeader.svelte';
9
+ export type { ChatHeaderLayout, ChatHeaderMessages } from './ChatHeader.svelte';
10
+ export { default as PlanCard } from './PlanCard.svelte';
11
+ export type { PlanCardMessages, PlanTask } from './PlanCard.svelte';
12
+ export { default as QuestionCard } from './QuestionCard.svelte';
13
+ export type { QuestionCardMessages, QuestionItem, QuestionOption } from './QuestionCard.svelte';
14
+ export { canSubmitQuestion, formatQuestionStep, isFirstQuestion, isLastQuestion, moveFocusedIndex, optionItemRole, primaryQuestionLabel, selectionRole, toggleOption } from './questionCardLogic.js';
15
+ export { default as SuggestionCard } from './SuggestionCard.svelte';
16
+ export type { SuggestionCardMessages } from './SuggestionCard.svelte';
@@ -0,0 +1,16 @@
1
+ // Chat components (composer, messages, plan and question cards) built from the Figma design system.
2
+ // Each component is exported here by the agent that owns src/lib/components/chat.
3
+ // [C1 exports]
4
+ export { default as Composer } from './Composer.svelte';
5
+ export { default as Message } from './Message.svelte';
6
+ export { default as ActivityRow } from './ActivityRow.svelte';
7
+ export { default as OptionRow } from './OptionRow.svelte';
8
+ export { default as TaskRow } from './TaskRow.svelte';
9
+ export { default as ScrollToBottom } from './ScrollToBottom.svelte';
10
+ export { canSendText, clampTextareaHeight, COMPOSER_TEXTAREA_MAX_HEIGHT_PX, COMPOSER_TEXTAREA_MIN_HEIGHT_PX, shouldSendOnKeyDown } from './composerLogic.js';
11
+ // [C2 exports]
12
+ export { default as ChatHeader } from './ChatHeader.svelte';
13
+ export { default as PlanCard } from './PlanCard.svelte';
14
+ export { default as QuestionCard } from './QuestionCard.svelte';
15
+ export { canSubmitQuestion, formatQuestionStep, isFirstQuestion, isLastQuestion, moveFocusedIndex, optionItemRole, primaryQuestionLabel, selectionRole, toggleOption } from './questionCardLogic.js';
16
+ export { default as SuggestionCard } from './SuggestionCard.svelte';
@@ -0,0 +1,11 @@
1
+ /** Pure selection and keyboard helpers for QuestionCard. */
2
+ export declare function toggleOption(selected: readonly string[], optionId: string, multiple: boolean): string[];
3
+ export declare function moveFocusedIndex(current: number, direction: 'up' | 'down', count: number): number;
4
+ export declare function canSubmitQuestion(selected: readonly string[]): boolean;
5
+ export declare function selectionRole(multiple: boolean): 'radiogroup' | 'group';
6
+ export declare function optionItemRole(multiple: boolean): 'radio' | 'checkbox';
7
+ /** Format the step line (1-based `current`). */
8
+ export declare function formatQuestionStep(template: string, current: number, total: number): string;
9
+ export declare function isFirstQuestion(index: number): boolean;
10
+ export declare function isLastQuestion(index: number, total: number): boolean;
11
+ export declare function primaryQuestionLabel(isLast: boolean, nextLabel: string, submitLabel: string): string;