@evinvest/uikit 0.32.0 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/README.md +21 -10
  2. package/dist/components/atomic/accordion.js +2 -1
  3. package/dist/components/atomic/accordion.js.map +1 -1
  4. package/dist/components/atomic/alert.js +4 -4
  5. package/dist/components/atomic/alert.js.map +1 -1
  6. package/dist/components/atomic/badge.js +4 -3
  7. package/dist/components/atomic/badge.js.map +1 -1
  8. package/dist/components/atomic/breadcrumb.js +7 -9
  9. package/dist/components/atomic/breadcrumb.js.map +1 -1
  10. package/dist/components/atomic/button.js +4 -3
  11. package/dist/components/atomic/button.js.map +1 -1
  12. package/dist/components/atomic/calendar.js +10 -25
  13. package/dist/components/atomic/calendar.js.map +1 -1
  14. package/dist/components/atomic/checkbox.js +2 -1
  15. package/dist/components/atomic/checkbox.js.map +1 -1
  16. package/dist/components/atomic/command.js +5 -20
  17. package/dist/components/atomic/command.js.map +1 -1
  18. package/dist/components/atomic/context-menu.js +6 -13
  19. package/dist/components/atomic/context-menu.js.map +1 -1
  20. package/dist/components/atomic/dialog.js +4 -2
  21. package/dist/components/atomic/dialog.js.map +1 -1
  22. package/dist/components/atomic/dropdown-menu.js +6 -14
  23. package/dist/components/atomic/dropdown-menu.js.map +1 -1
  24. package/dist/components/atomic/hover-card.js.map +1 -1
  25. package/dist/components/atomic/info-tip.js +8 -9
  26. package/dist/components/atomic/info-tip.js.map +1 -1
  27. package/dist/components/atomic/input-otp.js +2 -1
  28. package/dist/components/atomic/input-otp.js.map +1 -1
  29. package/dist/components/atomic/key-value.js +5 -4
  30. package/dist/components/atomic/key-value.js.map +1 -1
  31. package/dist/components/atomic/menubar.js +7 -18
  32. package/dist/components/atomic/menubar.js.map +1 -1
  33. package/dist/components/atomic/native-select.js +1 -1
  34. package/dist/components/atomic/native-select.js.map +1 -1
  35. package/dist/components/atomic/navigation-menu.js +6 -14
  36. package/dist/components/atomic/navigation-menu.js.map +1 -1
  37. package/dist/components/atomic/pagination.js +18 -19
  38. package/dist/components/atomic/pagination.js.map +1 -1
  39. package/dist/components/atomic/popover.js.map +1 -1
  40. package/dist/components/atomic/radio-group.js +5 -4
  41. package/dist/components/atomic/radio-group.js.map +1 -1
  42. package/dist/components/atomic/section-descriptor.js +5 -8
  43. package/dist/components/atomic/section-descriptor.js.map +1 -1
  44. package/dist/components/atomic/select-listbox.js +2 -2
  45. package/dist/components/atomic/select-listbox.js.map +1 -1
  46. package/dist/components/atomic/select.js +5 -13
  47. package/dist/components/atomic/select.js.map +1 -1
  48. package/dist/components/atomic/sheet.js +4 -2
  49. package/dist/components/atomic/sheet.js.map +1 -1
  50. package/dist/components/atomic/sidebar.js +7 -26
  51. package/dist/components/atomic/sidebar.js.map +1 -1
  52. package/dist/components/atomic/slider.js.map +1 -1
  53. package/dist/components/atomic/sonner.js +20 -18
  54. package/dist/components/atomic/sonner.js.map +1 -1
  55. package/dist/components/atomic/spinner.js +6 -3
  56. package/dist/components/atomic/spinner.js.map +1 -1
  57. package/dist/components/atomic/tabs.js.map +1 -1
  58. package/dist/components/atomic/toggle-group.js +2 -2
  59. package/dist/components/atomic/toggle-group.js.map +1 -1
  60. package/dist/components/atomic/toggle.js +3 -6
  61. package/dist/components/atomic/toggle.js.map +1 -1
  62. package/dist/components/atomic/tooltip.js.map +1 -1
  63. package/dist/components/derived/carousel.js +6 -6
  64. package/dist/components/derived/carousel.js.map +1 -1
  65. package/dist/components/derived/command-palette.js +8 -7
  66. package/dist/components/derived/command-palette.js.map +1 -1
  67. package/dist/components/derived/confirm-dialog.js +8 -13
  68. package/dist/components/derived/confirm-dialog.js.map +1 -1
  69. package/dist/components/derived/date-time-picker.js +10 -14
  70. package/dist/components/derived/date-time-picker.js.map +1 -1
  71. package/dist/components/derived/field.js.map +1 -1
  72. package/dist/components/derived/form.js +0 -1
  73. package/dist/components/derived/form.js.map +1 -1
  74. package/dist/components/derived/icon-button.js.map +1 -1
  75. package/dist/components/derived/item.js +0 -1
  76. package/dist/components/derived/item.js.map +1 -1
  77. package/dist/components/derived/popover-form.js +4 -3
  78. package/dist/components/derived/popover-form.js.map +1 -1
  79. package/dist/components/derived/settings.js +4 -4
  80. package/dist/components/derived/settings.js.map +1 -1
  81. package/dist/components/shell/account-menu-profile.js +1 -1
  82. package/dist/components/shell/account-menu-profile.js.map +1 -1
  83. package/dist/components/shell/account-settings-dialog.js +3 -2
  84. package/dist/components/shell/account-settings-dialog.js.map +1 -1
  85. package/dist/components/shell/aside.js +6 -4
  86. package/dist/components/shell/aside.js.map +1 -1
  87. package/dist/components/shell/auth-wall.js +3 -5
  88. package/dist/components/shell/auth-wall.js.map +1 -1
  89. package/dist/components/shell/bottom-tab-bar.js +3 -3
  90. package/dist/components/shell/bottom-tab-bar.js.map +1 -1
  91. package/dist/components/shell/chat-composer.js +9 -7
  92. package/dist/components/shell/chat-composer.js.map +1 -1
  93. package/dist/components/shell/chat-message.js.map +1 -1
  94. package/dist/components/shell/chat-typing.js +2 -1
  95. package/dist/components/shell/chat-typing.js.map +1 -1
  96. package/dist/components/shell/footer.js +26 -24
  97. package/dist/components/shell/footer.js.map +1 -1
  98. package/dist/components/shell/header-controls.js +17 -15
  99. package/dist/components/shell/header-controls.js.map +1 -1
  100. package/dist/components/shell/hotbar.js +158 -0
  101. package/dist/components/shell/hotbar.js.map +1 -0
  102. package/dist/components/shell/logo.js +3 -2
  103. package/dist/components/shell/logo.js.map +1 -1
  104. package/dist/components/shell/mobile-app-bar.js +4 -4
  105. package/dist/components/shell/mobile-app-bar.js.map +1 -1
  106. package/dist/components/shell/nav-collapse.js +6 -5
  107. package/dist/components/shell/nav-collapse.js.map +1 -1
  108. package/dist/components/shell/resource-error.js +2 -2
  109. package/dist/components/shell/resource-error.js.map +1 -1
  110. package/dist/components/shell/section-nav.js +3 -3
  111. package/dist/components/shell/section-nav.js.map +1 -1
  112. package/dist/components/shell/shell-icons.js +7 -22
  113. package/dist/components/shell/shell-icons.js.map +1 -1
  114. package/dist/components/shell/shell-nav.js +4 -4
  115. package/dist/components/shell/shell-nav.js.map +1 -1
  116. package/dist/components/shell/smart-header.js.map +1 -1
  117. package/dist/components/shell/status-screen.js +35 -35
  118. package/dist/components/shell/status-screen.js.map +1 -1
  119. package/dist/components/shell/system-banner.js +6 -6
  120. package/dist/components/shell/system-banner.js.map +1 -1
  121. package/dist/components/shell/workspace-tab.js +2 -1
  122. package/dist/components/shell/workspace-tab.js.map +1 -1
  123. package/dist/generated/accent.js +10 -10
  124. package/dist/generated/accent.js.map +1 -1
  125. package/dist/generated/accordion.js +6 -2
  126. package/dist/generated/accordion.js.map +1 -1
  127. package/dist/generated/account-list.js +4 -4
  128. package/dist/generated/account-list.js.map +1 -1
  129. package/dist/generated/account-settings-dialog.js +6 -2
  130. package/dist/generated/account-settings-dialog.js.map +1 -1
  131. package/dist/generated/alert-dialog.js +1 -1
  132. package/dist/generated/alert-dialog.js.map +1 -1
  133. package/dist/generated/alert.js +3 -10
  134. package/dist/generated/alert.js.map +1 -1
  135. package/dist/generated/aside.js +7 -0
  136. package/dist/generated/aside.js.map +1 -1
  137. package/dist/generated/badge.js +2 -11
  138. package/dist/generated/badge.js.map +1 -1
  139. package/dist/generated/band.js +10 -10
  140. package/dist/generated/band.js.map +1 -1
  141. package/dist/generated/breadcrumb.js +1 -1
  142. package/dist/generated/breadcrumb.js.map +1 -1
  143. package/dist/generated/button-group.js +3 -3
  144. package/dist/generated/button-group.js.map +1 -1
  145. package/dist/generated/button.js +4 -20
  146. package/dist/generated/button.js.map +1 -1
  147. package/dist/generated/calendar.js +5 -5
  148. package/dist/generated/calendar.js.map +1 -1
  149. package/dist/generated/card.js +1 -1
  150. package/dist/generated/card.js.map +1 -1
  151. package/dist/generated/carousel.js +1 -1
  152. package/dist/generated/carousel.js.map +1 -1
  153. package/dist/generated/chat-composer.js +9 -2
  154. package/dist/generated/chat-composer.js.map +1 -1
  155. package/dist/generated/chat-message.js +25 -19
  156. package/dist/generated/chat-message.js.map +1 -1
  157. package/dist/generated/checkbox.js +1 -1
  158. package/dist/generated/checkbox.js.map +1 -1
  159. package/dist/generated/command-palette.js +10 -1
  160. package/dist/generated/command-palette.js.map +1 -1
  161. package/dist/generated/command.js +8 -6
  162. package/dist/generated/command.js.map +1 -1
  163. package/dist/generated/confirm-dialog.js +8 -8
  164. package/dist/generated/confirm-dialog.js.map +1 -1
  165. package/dist/generated/context-menu.js +7 -5
  166. package/dist/generated/context-menu.js.map +1 -1
  167. package/dist/generated/copy.js +97 -0
  168. package/dist/generated/copy.js.map +1 -0
  169. package/dist/generated/date-time-picker.js +10 -0
  170. package/dist/generated/date-time-picker.js.map +1 -1
  171. package/dist/generated/dialog.js +2 -2
  172. package/dist/generated/dialog.js.map +1 -1
  173. package/dist/generated/drawer.js +6 -6
  174. package/dist/generated/drawer.js.map +1 -1
  175. package/dist/generated/dropdown-menu.js +7 -5
  176. package/dist/generated/dropdown-menu.js.map +1 -1
  177. package/dist/generated/empty.js +5 -5
  178. package/dist/generated/empty.js.map +1 -1
  179. package/dist/generated/field.js +9 -7
  180. package/dist/generated/field.js.map +1 -1
  181. package/dist/generated/footer.js +45 -0
  182. package/dist/generated/footer.js.map +1 -0
  183. package/dist/generated/header-controls.js +5 -10
  184. package/dist/generated/header-controls.js.map +1 -1
  185. package/dist/generated/hover-card.js +1 -1
  186. package/dist/generated/hover-card.js.map +1 -1
  187. package/dist/generated/icon-button.js +4 -2
  188. package/dist/generated/icon-button.js.map +1 -1
  189. package/dist/generated/icons.js +36 -0
  190. package/dist/generated/icons.js.map +1 -0
  191. package/dist/generated/info-tip.js +5 -0
  192. package/dist/generated/info-tip.js.map +1 -0
  193. package/dist/generated/input-group.js +10 -10
  194. package/dist/generated/input-group.js.map +1 -1
  195. package/dist/generated/input-otp.js +2 -2
  196. package/dist/generated/input-otp.js.map +1 -1
  197. package/dist/generated/input.js +4 -4
  198. package/dist/generated/input.js.map +1 -1
  199. package/dist/generated/item.js +11 -11
  200. package/dist/generated/item.js.map +1 -1
  201. package/dist/generated/kbd.js +1 -1
  202. package/dist/generated/kbd.js.map +1 -1
  203. package/dist/generated/key-value.js +7 -1
  204. package/dist/generated/key-value.js.map +1 -1
  205. package/dist/generated/label.js +1 -1
  206. package/dist/generated/label.js.map +1 -1
  207. package/dist/generated/logo.js +9 -0
  208. package/dist/generated/logo.js.map +1 -0
  209. package/dist/generated/menu.js +7 -0
  210. package/dist/generated/menu.js.map +1 -0
  211. package/dist/generated/menubar.js +10 -8
  212. package/dist/generated/menubar.js.map +1 -1
  213. package/dist/generated/navigation-menu.js +11 -5
  214. package/dist/generated/navigation-menu.js.map +1 -1
  215. package/dist/generated/orientation.js +5 -0
  216. package/dist/generated/orientation.js.map +1 -0
  217. package/dist/generated/pagination.js +15 -0
  218. package/dist/generated/pagination.js.map +1 -0
  219. package/dist/generated/popover-form.js +5 -0
  220. package/dist/generated/popover-form.js.map +1 -1
  221. package/dist/generated/popover.js +1 -1
  222. package/dist/generated/popover.js.map +1 -1
  223. package/dist/generated/primitives.js +7 -0
  224. package/dist/generated/primitives.js.map +1 -0
  225. package/dist/generated/radio-group.js +5 -1
  226. package/dist/generated/radio-group.js.map +1 -1
  227. package/dist/generated/section-descriptor.js +7 -0
  228. package/dist/generated/section-descriptor.js.map +1 -0
  229. package/dist/generated/select.js +16 -10
  230. package/dist/generated/select.js.map +1 -1
  231. package/dist/generated/settings.js +2 -7
  232. package/dist/generated/settings.js.map +1 -1
  233. package/dist/generated/sheet.js +1 -1
  234. package/dist/generated/sheet.js.map +1 -1
  235. package/dist/generated/sidebar.js +24 -12
  236. package/dist/generated/sidebar.js.map +1 -1
  237. package/dist/generated/size.js +12 -0
  238. package/dist/generated/size.js.map +1 -0
  239. package/dist/generated/skeleton.js +1 -1
  240. package/dist/generated/skeleton.js.map +1 -1
  241. package/dist/generated/slider.js +2 -2
  242. package/dist/generated/slider.js.map +1 -1
  243. package/dist/generated/slot.js +5 -0
  244. package/dist/generated/slot.js.map +1 -0
  245. package/dist/generated/smart-header.js +2 -0
  246. package/dist/generated/smart-header.js.map +1 -1
  247. package/dist/generated/sonner.js +11 -13
  248. package/dist/generated/sonner.js.map +1 -1
  249. package/dist/generated/spinner.js +5 -0
  250. package/dist/generated/spinner.js.map +1 -0
  251. package/dist/generated/status-screen.js +27 -0
  252. package/dist/generated/status-screen.js.map +1 -0
  253. package/dist/generated/switch.js +2 -2
  254. package/dist/generated/switch.js.map +1 -1
  255. package/dist/generated/system-banner.js +6 -16
  256. package/dist/generated/system-banner.js.map +1 -1
  257. package/dist/generated/table.js +9 -9
  258. package/dist/generated/table.js.map +1 -1
  259. package/dist/generated/tabs.js +2 -2
  260. package/dist/generated/tabs.js.map +1 -1
  261. package/dist/generated/textarea.js +1 -1
  262. package/dist/generated/textarea.js.map +1 -1
  263. package/dist/generated/toggle-group.js +9 -0
  264. package/dist/generated/toggle-group.js.map +1 -0
  265. package/dist/generated/toggle.js +1 -9
  266. package/dist/generated/toggle.js.map +1 -1
  267. package/dist/generated/tone.js +12 -0
  268. package/dist/generated/tone.js.map +1 -0
  269. package/dist/generated/tooltip.js +1 -1
  270. package/dist/generated/tooltip.js.map +1 -1
  271. package/dist/generated/workspace-tab.js +3 -3
  272. package/dist/generated/workspace-tab.js.map +1 -1
  273. package/dist/icons/icon-shapes.js +10 -0
  274. package/dist/icons/icon-shapes.js.map +1 -0
  275. package/dist/icons/select-chevron.js +2 -1
  276. package/dist/icons/select-chevron.js.map +1 -1
  277. package/dist/icons/ui-icons.js +3 -57
  278. package/dist/icons/ui-icons.js.map +1 -1
  279. package/dist/index.d.ts +203 -155
  280. package/dist/index.js +1 -0
  281. package/dist/lib/cn.js +1 -0
  282. package/dist/lib/cn.js.map +1 -1
  283. package/dist/primitives/use-floating.js.map +1 -1
  284. package/dist/primitives/use-roving-focus.js.map +1 -1
  285. package/package.json +1 -1
  286. package/styles/ev.css +13 -76
  287. package/styles/theme.css +133 -66
  288. package/styles/tokens.css +157 -149
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/shell/chat-message.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\nimport {\n CHAT_ATTACHMENT_BASE,\n CHAT_ATTACHMENT_BODY,\n CHAT_ATTACHMENT_ICON,\n CHAT_ATTACHMENT_META,\n CHAT_ATTACHMENT_NAME,\n CHAT_ATTACHMENT_PROGRESS,\n CHAT_ATTACHMENT_PROGRESS_FILL,\n CHAT_ATTACHMENT_ROW,\n CHAT_BUBBLE_BASE,\n CHAT_MARKER_DOT,\n CHAT_MARKER_RULE,\n CHAT_MARKER_SEPARATOR,\n CHAT_MARKER_SEPARATOR_LABEL,\n CHAT_MARKER_STATUS,\n CHAT_MARKER_TOOL,\n CHAT_MARKER_TOOL_BODY,\n CHAT_MARKER_TOOL_BORDER,\n CHAT_MARKER_TOOL_CHECK,\n CHAT_MARKER_TOOL_DETAIL,\n CHAT_MARKER_TOOL_ERROR_BORDER,\n CHAT_MARKER_TOOL_ICON,\n CHAT_MARKER_TOOL_META,\n CHAT_MARKER_TOOL_TITLE,\n CHAT_MESSAGE_AVATAR,\n CHAT_MESSAGE_AVATAR_TILE,\n CHAT_MESSAGE_BASE,\n CHAT_MESSAGE_BODY,\n CHAT_MESSAGE_BODY_ASSISTANT,\n CHAT_MESSAGE_BODY_USER,\n CHAT_MESSAGE_HEADER,\n CHAT_MESSAGE_NAME,\n CHAT_MESSAGE_TIME,\n CHAT_MESSAGE_USER,\n chatBubbleVariantClasses,\n chatStatusDotClasses,\n chatToolMetaClasses,\n} from \"../../generated/chat-message\";\nimport type { ChatBubbleVariant, ChatStatusState, ChatToolState } from \"../../generated/chat-message\";\nimport { ChatStreamText } from \"./chat-stream-text\";\nimport { IconButton } from \"../derived/icon-button\";\nimport { UiIcon } from \"../../icons/ui-icons\";\n\nexport type { ChatBubbleVariant };\n\nexport interface ChatBubbleProps extends React.ComponentProps<\"div\"> {\n variant?: ChatBubbleVariant;\n /**\n * Still receiving text: a soft caret pulses after it, and string children\n * fade in word by word (`ChatStreamText`). Stays set until the text is complete.\n */\n streaming?: boolean;\n}\n\n/** One block of message text; `assistant` has no box so long answers read as a page. */\nexport function ChatBubble({ variant = \"assistant\", streaming = false, className, children, ...props }: ChatBubbleProps) {\n // Once a bubble has streamed it keeps its word wrappers and its caret slot, so\n // the last words finish landing and the caret fades out instead of vanishing.\n const [live, setLive] = React.useState(streaming);\n if (streaming && !live) setLive(true);\n return (\n <div\n data-slot=\"chat-bubble\"\n data-variant={variant}\n data-streaming={streaming ? \"\" : undefined}\n data-caret={live ? \"\" : undefined}\n className={cn(CHAT_BUBBLE_BASE, chatBubbleVariantClasses[variant], className)}\n {...props}\n >\n {live && typeof children === \"string\" ? <ChatStreamText text={children} streaming={streaming} /> : children}\n </div>\n );\n}\n\nexport interface ChatMessageProps extends Omit<React.ComponentProps<\"div\">, \"role\"> {\n role: \"assistant\" | \"user\";\n /** \"Assistant\", \"You\". */\n name?: React.ReactNode;\n /** Already formatted: \"13:04\". */\n time?: React.ReactNode;\n /** The assistant's tile; a default sparkle tile when omitted. */\n avatar?: React.ReactNode;\n /** Drops the name row and the avatar, for a narrow dock. */\n compact?: boolean;\n}\n\n/** A turn: who spoke, when, then the bubbles and markers. Fades in and rises once when it mounts. */\nexport function ChatMessage({ role, name, time, avatar, compact = false, className, children, ...props }: ChatMessageProps) {\n const user = role === \"user\";\n return (\n <div\n data-slot=\"chat-message\"\n data-role={role}\n data-enter=\"rise\"\n className={cn(CHAT_MESSAGE_BASE, user && CHAT_MESSAGE_USER, className)}\n {...props}\n >\n {!user && !compact && (\n <div className={CHAT_MESSAGE_AVATAR}>\n {avatar ?? (\n <span className={CHAT_MESSAGE_AVATAR_TILE}>\n <UiIcon name=\"sparkles\" />\n </span>\n )}\n </div>\n )}\n <div className={cn(CHAT_MESSAGE_BODY, user ? CHAT_MESSAGE_BODY_USER : CHAT_MESSAGE_BODY_ASSISTANT)}>\n {!compact && (name != null || time != null) && (\n <div className={CHAT_MESSAGE_HEADER}>\n {name != null && <span className={CHAT_MESSAGE_NAME}>{name}</span>}\n {time != null && <span className={CHAT_MESSAGE_TIME}>{time}</span>}\n </div>\n )}\n {children}\n </div>\n </div>\n );\n}\n\nexport type ChatMarkerProps = Omit<React.ComponentProps<\"div\">, \"title\"> &\n (\n | { type: \"status\"; state?: ChatStatusState }\n | {\n type: \"tool\";\n state?: ChatToolState;\n /** The tool's name. */\n title: React.ReactNode;\n detail?: React.ReactNode;\n /** The right-hand note: \"Running…\", \"0.4s\", \"Failed · 1.2s\". */\n meta?: React.ReactNode;\n }\n | { type: \"separator\" }\n );\n\n/** The quiet lines between messages: progress, a tool call, or a day break. */\nexport function ChatMarker(allProps: ChatMarkerProps) {\n if (allProps.type === \"separator\") {\n const { type: _type, className, children, ...rest } = allProps;\n return (\n <div data-slot=\"chat-marker\" data-type=\"separator\" className={cn(CHAT_MARKER_SEPARATOR, className)} {...rest}>\n <span className={CHAT_MARKER_RULE} />\n <span className={CHAT_MARKER_SEPARATOR_LABEL}>{children}</span>\n <span className={CHAT_MARKER_RULE} />\n </div>\n );\n }\n if (allProps.type === \"status\") {\n const { type: _type, state = \"done\", className, children, ...rest } = allProps;\n return (\n <div\n data-slot=\"chat-marker\"\n data-type=\"status\"\n data-state={state}\n role={state === \"running\" ? \"status\" : undefined}\n className={cn(CHAT_MARKER_STATUS, className)}\n {...rest}\n >\n <span\n aria-hidden\n className={cn(CHAT_MARKER_DOT, chatStatusDotClasses[state])}\n />\n {children}\n </div>\n );\n }\n const { type: _type, state = \"done\", title, detail, meta, className, children, ...rest } = allProps;\n return (\n <div\n data-slot=\"chat-marker\"\n data-type=\"tool\"\n data-state={state}\n className={cn(\n CHAT_MARKER_TOOL,\n state === \"error\" ? CHAT_MARKER_TOOL_ERROR_BORDER : CHAT_MARKER_TOOL_BORDER,\n className,\n )}\n {...rest}\n >\n <UiIcon name={state === \"error\" ? \"alert\" : \"sparkles\"} className={CHAT_MARKER_TOOL_ICON} />\n <div className={CHAT_MARKER_TOOL_BODY}>\n <span className={CHAT_MARKER_TOOL_TITLE}>{title}</span>\n {detail != null && <span className={CHAT_MARKER_TOOL_DETAIL}>{detail}</span>}\n {children}\n </div>\n {meta != null && (\n <span className={cn(CHAT_MARKER_TOOL_META, chatToolMetaClasses[state])}>{meta}\n </span>\n )}\n {state === \"done\" && <UiIcon name=\"check\" className={CHAT_MARKER_TOOL_CHECK} />}\n </div>\n );\n}\n\nexport interface ChatAttachmentProps extends Omit<React.ComponentProps<\"div\">, \"children\"> {\n type?: \"file\" | \"image\";\n state?: \"ready\" | \"uploading\";\n name: string;\n /** \"PDF · 1.2 MB\"; while uploading, defaults to the percentage. */\n meta?: React.ReactNode;\n /** 0 to 100, while uploading. */\n progress?: number;\n /** Shows the remove button. */\n onRemove?: () => void;\n /** Default `\"Remove {name}\"`. */\n removeLabel?: string;\n}\n\n/** A card for a file in a message or in the composer. */\nexport function ChatAttachment({\n type = \"file\",\n state = \"ready\",\n name,\n meta,\n progress = 0,\n onRemove,\n removeLabel,\n className,\n ...props\n}: ChatAttachmentProps) {\n const pct = Math.min(100, Math.max(0, Math.round(progress)));\n return (\n <div\n data-slot=\"chat-attachment\"\n data-type={type}\n data-state={state}\n className={cn(CHAT_ATTACHMENT_BASE, className)}\n {...props}\n >\n <div className={CHAT_ATTACHMENT_ROW}>\n <span className={CHAT_ATTACHMENT_ICON}>\n <UiIcon name={type === \"image\" ? \"image\" : \"file\"} />\n </span>\n <div className={CHAT_ATTACHMENT_BODY}>\n <span className={CHAT_ATTACHMENT_NAME}>{name}</span>\n <span className={CHAT_ATTACHMENT_META}>{meta ?? (state === \"uploading\" ? `Uploading… ${pct}%` : null)}</span>\n </div>\n {onRemove && (\n <IconButton aria-label={removeLabel ?? `Remove ${name}`} size=\"sm\" onClick={onRemove}>\n <UiIcon name=\"x\" />\n </IconButton>\n )}\n </div>\n {state === \"uploading\" && (\n <div\n role=\"progressbar\"\n aria-label={name}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuenow={pct}\n className={CHAT_ATTACHMENT_PROGRESS}\n >\n <div className={CHAT_ATTACHMENT_PROGRESS_FILL} style={{ width: `${pct}%` }} />\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["_type","className","children","rest","state"]}
1
+ {"version":3,"sources":["../../../src/components/shell/chat-message.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\nimport {\n CHAT_ATTACHMENT_BASE,\n CHAT_ATTACHMENT_BODY,\n CHAT_ATTACHMENT_ICON,\n CHAT_ATTACHMENT_META,\n CHAT_ATTACHMENT_NAME,\n CHAT_ATTACHMENT_PROGRESS,\n CHAT_ATTACHMENT_PROGRESS_FILL,\n CHAT_ATTACHMENT_ROW,\n CHAT_BUBBLE_BASE,\n CHAT_MARKER_DOT,\n CHAT_MARKER_RULE,\n CHAT_MARKER_SEPARATOR,\n CHAT_MARKER_SEPARATOR_LABEL,\n CHAT_MARKER_STATUS,\n CHAT_MARKER_TOOL,\n CHAT_MARKER_TOOL_BODY,\n CHAT_MARKER_TOOL_BORDER,\n CHAT_MARKER_TOOL_CHECK,\n CHAT_MARKER_TOOL_DETAIL,\n CHAT_MARKER_TOOL_ERROR_BORDER,\n CHAT_MARKER_TOOL_ICON,\n CHAT_MARKER_TOOL_META,\n CHAT_MARKER_TOOL_TITLE,\n CHAT_MESSAGE_AVATAR,\n CHAT_MESSAGE_AVATAR_TILE,\n CHAT_MESSAGE_BASE,\n CHAT_MESSAGE_BODY,\n CHAT_MESSAGE_BODY_ASSISTANT,\n CHAT_MESSAGE_BODY_USER,\n CHAT_MESSAGE_HEADER,\n CHAT_MESSAGE_NAME,\n CHAT_MESSAGE_TIME,\n CHAT_MESSAGE_USER,\n chatBubbleVariantClasses,\n chatStatusDotClasses,\n chatToolMetaClasses,\n} from \"../../generated/chat-message\";\nimport type { ChatBubbleVariant, ChatStatusState, ChatToolState } from \"../../generated/chat-message\";\nimport { ChatStreamText } from \"./chat-stream-text\";\nimport { IconButton } from \"../derived/icon-button\";\nimport { UiIcon } from \"../../icons/ui-icons\";\nimport type { ChatAttachmentState, ChatAttachmentType, ChatMessageRole } from \"../../generated/chat-message\";\n\nexport type { ChatBubbleVariant };\n\nexport interface ChatBubbleProps extends React.ComponentProps<\"div\"> {\n variant?: ChatBubbleVariant;\n /**\n * Still receiving text: a soft caret pulses after it, and string children\n * fade in word by word (`ChatStreamText`). Stays set until the text is complete.\n */\n streaming?: boolean;\n}\n\n/** One block of message text; `assistant` has no box so long answers read as a page. */\nexport function ChatBubble({ variant = \"assistant\", streaming = false, className, children, ...props }: ChatBubbleProps) {\n // Once a bubble has streamed it keeps its word wrappers and its caret slot, so\n // the last words finish landing and the caret fades out instead of vanishing.\n const [live, setLive] = React.useState(streaming);\n if (streaming && !live) setLive(true);\n return (\n <div\n data-slot=\"chat-bubble\"\n data-variant={variant}\n data-streaming={streaming ? \"\" : undefined}\n data-caret={live ? \"\" : undefined}\n className={cn(CHAT_BUBBLE_BASE, chatBubbleVariantClasses[variant], className)}\n {...props}\n >\n {live && typeof children === \"string\" ? <ChatStreamText text={children} streaming={streaming} /> : children}\n </div>\n );\n}\n\nexport interface ChatMessageProps extends Omit<React.ComponentProps<\"div\">, \"role\"> {\n role: ChatMessageRole;\n /** \"Assistant\", \"You\". */\n name?: React.ReactNode;\n /** Already formatted: \"13:04\". */\n time?: React.ReactNode;\n /** The assistant's tile; a default sparkle tile when omitted. */\n avatar?: React.ReactNode;\n /** Drops the name row and the avatar, for a narrow dock. */\n compact?: boolean;\n}\n\n/** A turn: who spoke, when, then the bubbles and markers. Fades in and rises once when it mounts. */\nexport function ChatMessage({ role, name, time, avatar, compact = false, className, children, ...props }: ChatMessageProps) {\n const user = role === \"user\";\n return (\n <div\n data-slot=\"chat-message\"\n data-role={role}\n data-enter=\"rise\"\n className={cn(CHAT_MESSAGE_BASE, user && CHAT_MESSAGE_USER, className)}\n {...props}\n >\n {!user && !compact && (\n <div className={CHAT_MESSAGE_AVATAR}>\n {avatar ?? (\n <span className={CHAT_MESSAGE_AVATAR_TILE}>\n <UiIcon name=\"sparkles\" />\n </span>\n )}\n </div>\n )}\n <div className={cn(CHAT_MESSAGE_BODY, user ? CHAT_MESSAGE_BODY_USER : CHAT_MESSAGE_BODY_ASSISTANT)}>\n {!compact && (name != null || time != null) && (\n <div className={CHAT_MESSAGE_HEADER}>\n {name != null && <span className={CHAT_MESSAGE_NAME}>{name}</span>}\n {time != null && <span className={CHAT_MESSAGE_TIME}>{time}</span>}\n </div>\n )}\n {children}\n </div>\n </div>\n );\n}\n\nexport type ChatMarkerProps = Omit<React.ComponentProps<\"div\">, \"title\"> &\n (\n | { type: \"status\"; state?: ChatStatusState }\n | {\n type: \"tool\";\n state?: ChatToolState;\n /** The tool's name. */\n title: React.ReactNode;\n detail?: React.ReactNode;\n /** The right-hand note: \"Running…\", \"0.4s\", \"Failed · 1.2s\". */\n meta?: React.ReactNode;\n }\n | { type: \"separator\" }\n );\n\n/** The quiet lines between messages: progress, a tool call, or a day break. */\nexport function ChatMarker(allProps: ChatMarkerProps) {\n if (allProps.type === \"separator\") {\n const { type: _type, className, children, ...rest } = allProps;\n return (\n <div data-slot=\"chat-marker\" data-type=\"separator\" className={cn(CHAT_MARKER_SEPARATOR, className)} {...rest}>\n <span className={CHAT_MARKER_RULE} />\n <span className={CHAT_MARKER_SEPARATOR_LABEL}>{children}</span>\n <span className={CHAT_MARKER_RULE} />\n </div>\n );\n }\n if (allProps.type === \"status\") {\n const { type: _type, state = \"done\", className, children, ...rest } = allProps;\n return (\n <div\n data-slot=\"chat-marker\"\n data-type=\"status\"\n data-state={state}\n role={state === \"running\" ? \"status\" : undefined}\n className={cn(CHAT_MARKER_STATUS, className)}\n {...rest}\n >\n <span\n aria-hidden\n className={cn(CHAT_MARKER_DOT, chatStatusDotClasses[state])}\n />\n {children}\n </div>\n );\n }\n const { type: _type, state = \"done\", title, detail, meta, className, children, ...rest } = allProps;\n return (\n <div\n data-slot=\"chat-marker\"\n data-type=\"tool\"\n data-state={state}\n className={cn(\n CHAT_MARKER_TOOL,\n state === \"error\" ? CHAT_MARKER_TOOL_ERROR_BORDER : CHAT_MARKER_TOOL_BORDER,\n className,\n )}\n {...rest}\n >\n <UiIcon name={state === \"error\" ? \"alert\" : \"sparkles\"} className={CHAT_MARKER_TOOL_ICON} />\n <div className={CHAT_MARKER_TOOL_BODY}>\n <span className={CHAT_MARKER_TOOL_TITLE}>{title}</span>\n {detail != null && <span className={CHAT_MARKER_TOOL_DETAIL}>{detail}</span>}\n {children}\n </div>\n {meta != null && (\n <span className={cn(CHAT_MARKER_TOOL_META, chatToolMetaClasses[state])}>{meta}\n </span>\n )}\n {state === \"done\" && <UiIcon name=\"check\" className={CHAT_MARKER_TOOL_CHECK} />}\n </div>\n );\n}\n\nexport interface ChatAttachmentProps extends Omit<React.ComponentProps<\"div\">, \"children\"> {\n type?: ChatAttachmentType;\n state?: ChatAttachmentState;\n name: string;\n /** \"PDF · 1.2 MB\"; while uploading, defaults to the percentage. */\n meta?: React.ReactNode;\n /** 0 to 100, while uploading. */\n progress?: number;\n /** Shows the remove button. */\n onRemove?: () => void;\n /** Default `\"Remove {name}\"`. */\n removeLabel?: string;\n}\n\n/** A card for a file in a message or in the composer. */\nexport function ChatAttachment({\n type = \"file\",\n state = \"ready\",\n name,\n meta,\n progress = 0,\n onRemove,\n removeLabel,\n className,\n ...props\n}: ChatAttachmentProps) {\n const pct = Math.min(100, Math.max(0, Math.round(progress)));\n return (\n <div\n data-slot=\"chat-attachment\"\n data-type={type}\n data-state={state}\n className={cn(CHAT_ATTACHMENT_BASE, className)}\n {...props}\n >\n <div className={CHAT_ATTACHMENT_ROW}>\n <span className={CHAT_ATTACHMENT_ICON}>\n <UiIcon name={type === \"image\" ? \"image\" : \"file\"} />\n </span>\n <div className={CHAT_ATTACHMENT_BODY}>\n <span className={CHAT_ATTACHMENT_NAME}>{name}</span>\n <span className={CHAT_ATTACHMENT_META}>{meta ?? (state === \"uploading\" ? `Uploading… ${pct}%` : null)}</span>\n </div>\n {onRemove && (\n <IconButton aria-label={removeLabel ?? `Remove ${name}`} size=\"sm\" onClick={onRemove}>\n <UiIcon name=\"x\" />\n </IconButton>\n )}\n </div>\n {state === \"uploading\" && (\n <div\n role=\"progressbar\"\n aria-label={name}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuenow={pct}\n className={CHAT_ATTACHMENT_PROGRESS}\n >\n <div className={CHAT_ATTACHMENT_PROGRESS_FILL} style={{ width: `${pct}%` }} />\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["_type","className","children","rest","state"]}
@@ -2,7 +2,8 @@ import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import * as React from "react";
3
3
  import { CHAT_TYPING_BASE, CHAT_TYPING_DOT, CHAT_TYPING_STILL } from "../../generated/chat-typing.js";
4
4
  import { cn } from "../../lib/cn.js";
5
- function ChatTyping({ label = "Assistant is thinking", className, ...props }) {
5
+ import * as copy from "../../generated/copy.js";
6
+ function ChatTyping({ label = copy.CHAT_TYPING, className, ...props }) {
6
7
  return /* @__PURE__ */ jsxs(
7
8
  "span",
8
9
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/shell/chat-typing.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { CHAT_TYPING_BASE, CHAT_TYPING_DOT, CHAT_TYPING_STILL } from \"../../generated/chat-typing\";\nimport { cn } from \"../../lib/cn\";\n\nexport interface ChatTypingProps extends Omit<React.ComponentProps<\"span\">, \"children\"> {\n /** Names the state for assistive tech. Default `\"Assistant is thinking\"`. */\n label?: string;\n}\n\n/**\n * The wait before the first token: three dots whose opacity ripples. Under\n * reduced motion CSS swaps them for a still \"…\". It is a status region, so a\n * screen reader hears the label once when it appears.\n */\nexport function ChatTyping({ label = \"Assistant is thinking\", className, ...props }: ChatTypingProps) {\n return (\n <span\n role=\"status\"\n aria-label={label}\n data-slot=\"chat-typing\"\n className={cn(CHAT_TYPING_BASE, className)}\n {...props}\n >\n <span aria-hidden data-dot className={CHAT_TYPING_DOT} />\n <span aria-hidden data-dot className={CHAT_TYPING_DOT} />\n <span aria-hidden data-dot className={CHAT_TYPING_DOT} />\n <span aria-hidden data-still className={CHAT_TYPING_STILL}>\n …\n </span>\n </span>\n );\n}\n"],"mappings":"AAgBI,SAOE,KAPF;AAhBJ,YAAY,WAAW;AACvB,SAAS,kBAAkB,iBAAiB,yBAAyB;AACrE,SAAS,UAAU;AAYZ,SAAS,WAAW,EAAE,QAAQ,yBAAyB,WAAW,GAAG,MAAM,GAAoB;AACpG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY;AAAA,MACZ,aAAU;AAAA,MACV,WAAW,GAAG,kBAAkB,SAAS;AAAA,MACxC,GAAG;AAAA,MAEJ;AAAA,4BAAC,UAAK,eAAW,MAAC,YAAQ,MAAC,WAAW,iBAAiB;AAAA,QACvD,oBAAC,UAAK,eAAW,MAAC,YAAQ,MAAC,WAAW,iBAAiB;AAAA,QACvD,oBAAC,UAAK,eAAW,MAAC,YAAQ,MAAC,WAAW,iBAAiB;AAAA,QACvD,oBAAC,UAAK,eAAW,MAAC,cAAU,MAAC,WAAW,mBAAmB,oBAE3D;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/shell/chat-typing.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { CHAT_TYPING_BASE, CHAT_TYPING_DOT, CHAT_TYPING_STILL } from \"../../generated/chat-typing\";\nimport { cn } from \"../../lib/cn\";\nimport * as copy from \"../../generated/copy\";\n\nexport interface ChatTypingProps extends Omit<React.ComponentProps<\"span\">, \"children\"> {\n /** Names the state for assistive tech. Default `\"Assistant is thinking\"`. */\n label?: string;\n}\n\n/**\n * The wait before the first token: three dots whose opacity ripples. Under\n * reduced motion CSS swaps them for a still \"…\". It is a status region, so a\n * screen reader hears the label once when it appears.\n */\nexport function ChatTyping({ label = copy.CHAT_TYPING, className, ...props }: ChatTypingProps) {\n return (\n <span\n role=\"status\"\n aria-label={label}\n data-slot=\"chat-typing\"\n className={cn(CHAT_TYPING_BASE, className)}\n {...props}\n >\n <span aria-hidden data-dot className={CHAT_TYPING_DOT} />\n <span aria-hidden data-dot className={CHAT_TYPING_DOT} />\n <span aria-hidden data-dot className={CHAT_TYPING_DOT} />\n <span aria-hidden data-still className={CHAT_TYPING_STILL}>\n …\n </span>\n </span>\n );\n}\n"],"mappings":"AAiBI,SAOE,KAPF;AAjBJ,YAAY,WAAW;AACvB,SAAS,kBAAkB,iBAAiB,yBAAyB;AACrE,SAAS,UAAU;AACnB,YAAY,UAAU;AAYf,SAAS,WAAW,EAAE,QAAQ,KAAK,aAAa,WAAW,GAAG,MAAM,GAAoB;AAC7F,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY;AAAA,MACZ,aAAU;AAAA,MACV,WAAW,GAAG,kBAAkB,SAAS;AAAA,MACxC,GAAG;AAAA,MAEJ;AAAA,4BAAC,UAAK,eAAW,MAAC,YAAQ,MAAC,WAAW,iBAAiB;AAAA,QACvD,oBAAC,UAAK,eAAW,MAAC,YAAQ,MAAC,WAAW,iBAAiB;AAAA,QACvD,oBAAC,UAAK,eAAW,MAAC,YAAQ,MAAC,WAAW,iBAAiB;AAAA,QACvD,oBAAC,UAAK,eAAW,MAAC,cAAU,MAAC,WAAW,mBAAmB,oBAE3D;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -1,7 +1,9 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import * as React from "react";
3
3
  import { Container } from "../atomic/container.js";
4
- const HEADING = "font-mono text-xs text-ink uppercase tracking-widest mb-6";
4
+ import * as copy from "../../generated/copy.js";
5
+ import { FOOTER_BRAND, FOOTER_COPYRIGHT, FOOTER_DESCRIPTION, FOOTER_GRID, FOOTER_HEADING, FOOTER_LEGAL_LINK, FOOTER_LEGAL_LINKS, FOOTER_LEGAL_SEPARATOR, FOOTER_LOCKUP, FOOTER_LOCKUP_TEXT, FOOTER_NAV_COLUMN, FOOTER_NAV_LINK, FOOTER_NAV_LIST, FOOTER_NEWSLETTER_BLURB, FOOTER_NEWSLETTER_COLUMN, FOOTER_OFFICES_LIST, FOOTER_OFFICE_NAME, FOOTER_ROOT, FOOTER_TAGLINE, FOOTER_VERSION, FOOTER_WIDE_COLUMN } from "../../generated/footer.js";
6
+ const HEADING = FOOTER_HEADING;
5
7
  function Footer({
6
8
  lockup,
7
9
  brand,
@@ -11,10 +13,10 @@ function Footer({
11
13
  copyright,
12
14
  nav = [],
13
15
  offices = [],
14
- officesHeading = "Offices",
16
+ officesHeading = copy.FOOTER_OFFICES,
15
17
  legalLinks = [],
16
18
  newsletter,
17
- newsletterHeading = "Newsletter",
19
+ newsletterHeading = copy.FOOTER_NEWSLETTER,
18
20
  newsletterBlurb = "",
19
21
  version,
20
22
  commitHref,
@@ -24,47 +26,47 @@ function Footer({
24
26
  const L = linkComponent ?? "a";
25
27
  const composed = brand !== void 0 || mark !== void 0;
26
28
  const hasBrandColumn = lockup !== void 0 || composed || description !== void 0 || legalLinks.length > 0;
27
- return /* @__PURE__ */ jsxs("footer", { "data-slot": "footer", className: "bg-background border-t border-ink/10 py-16", children: [
29
+ return /* @__PURE__ */ jsxs("footer", { "data-slot": "footer", className: FOOTER_ROOT, children: [
28
30
  children,
29
31
  /* @__PURE__ */ jsxs(Container, { children: [
30
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-x-8 gap-y-8 lg:grid-cols-12 mb-12", children: [
31
- hasBrandColumn && /* @__PURE__ */ jsxs("div", { className: "col-span-2 lg:col-span-3", children: [
32
+ /* @__PURE__ */ jsxs("div", { className: FOOTER_GRID, children: [
33
+ hasBrandColumn && /* @__PURE__ */ jsxs("div", { className: FOOTER_WIDE_COLUMN, children: [
32
34
  lockup !== void 0 ? /* @__PURE__ */ jsx("div", { "data-slot": "footer-lockup", className: "mb-6", children: lockup }) : composed && /* @__PURE__ */ jsx(FooterLockup, { brand, mark, tagline }),
33
- description !== void 0 && /* @__PURE__ */ jsx("p", { className: "text-ink/40 text-xs font-light max-w-sm leading-relaxed mb-6", children: description }),
34
- legalLinks.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex gap-4 text-xs font-mono text-primary-ink", children: legalLinks.map((link, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
35
- i > 0 && /* @__PURE__ */ jsx("span", { className: "text-ink/20", children: "|" }),
36
- /* @__PURE__ */ jsx(L, { href: link.href, className: "hover:underline", children: link.label })
35
+ description !== void 0 && /* @__PURE__ */ jsx("p", { className: FOOTER_DESCRIPTION, children: description }),
36
+ legalLinks.length > 0 && /* @__PURE__ */ jsx("div", { className: FOOTER_LEGAL_LINKS, children: legalLinks.map((link, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
37
+ i > 0 && /* @__PURE__ */ jsx("span", { className: FOOTER_LEGAL_SEPARATOR, children: "|" }),
38
+ /* @__PURE__ */ jsx(L, { href: link.href, className: FOOTER_LEGAL_LINK, children: link.label })
37
39
  ] }, link.label)) })
38
40
  ] }),
39
- nav.map((group) => /* @__PURE__ */ jsxs("nav", { "aria-label": `Footer ${group.heading} links`, className: "lg:col-span-2", children: [
41
+ nav.map((group) => /* @__PURE__ */ jsxs("nav", { "aria-label": `Footer ${group.heading} links`, className: FOOTER_NAV_COLUMN, children: [
40
42
  /* @__PURE__ */ jsx("h4", { className: HEADING, children: group.heading }),
41
- /* @__PURE__ */ jsx("ul", { className: "space-y-3", children: group.links.map((link) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
43
+ /* @__PURE__ */ jsx("ul", { className: FOOTER_NAV_LIST, children: group.links.map((link) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
42
44
  L,
43
45
  {
44
46
  href: link.href,
45
- className: "text-xs font-light text-ink/70 hover:text-primary-ink transition-colors",
47
+ className: FOOTER_NAV_LINK,
46
48
  children: link.label
47
49
  }
48
50
  ) }, link.href)) })
49
51
  ] }, group.heading)),
50
- offices.length > 0 && /* @__PURE__ */ jsxs("div", { className: "col-span-2 lg:col-span-3", children: [
52
+ offices.length > 0 && /* @__PURE__ */ jsxs("div", { className: FOOTER_WIDE_COLUMN, children: [
51
53
  /* @__PURE__ */ jsx("h4", { className: HEADING, children: officesHeading }),
52
- /* @__PURE__ */ jsx("ul", { className: "space-y-4 text-xs text-ink/70 font-light leading-relaxed", children: offices.map((office) => /* @__PURE__ */ jsxs("li", { children: [
53
- /* @__PURE__ */ jsx("strong", { className: "text-ink block font-mono text-[10px] uppercase tracking-wider mb-1", children: office.name }),
54
+ /* @__PURE__ */ jsx("ul", { className: FOOTER_OFFICES_LIST, children: offices.map((office) => /* @__PURE__ */ jsxs("li", { children: [
55
+ /* @__PURE__ */ jsx("strong", { className: FOOTER_OFFICE_NAME, children: office.name }),
54
56
  office.address
55
57
  ] }, office.name)) })
56
58
  ] }),
57
- newsletter && /* @__PURE__ */ jsxs("div", { className: "col-span-2 lg:col-span-2", children: [
59
+ newsletter && /* @__PURE__ */ jsxs("div", { className: FOOTER_NEWSLETTER_COLUMN, children: [
58
60
  /* @__PURE__ */ jsx("h4", { className: HEADING, children: newsletterHeading }),
59
- /* @__PURE__ */ jsx("p", { className: "text-sm sm:text-xs font-light leading-relaxed text-ink/70 mb-4", children: newsletterBlurb }),
61
+ /* @__PURE__ */ jsx("p", { className: FOOTER_NEWSLETTER_BLURB, children: newsletterBlurb }),
60
62
  newsletter
61
63
  ] })
62
64
  ] }),
63
- (copyright !== void 0 || version !== void 0) && /* @__PURE__ */ jsx("div", { className: "border-t border-ink/10 pt-8 text-[10px] font-mono text-ink/40", children: /* @__PURE__ */ jsxs("p", { children: [
65
+ (copyright !== void 0 || version !== void 0) && /* @__PURE__ */ jsx("div", { className: FOOTER_COPYRIGHT, children: /* @__PURE__ */ jsxs("p", { children: [
64
66
  copyright,
65
67
  version && /* @__PURE__ */ jsxs(Fragment, { children: [
66
68
  copyright !== void 0 && " ",
67
- /* @__PURE__ */ jsx("a", { href: commitHref, className: "text-ink/30", children: version })
69
+ /* @__PURE__ */ jsx("a", { href: commitHref, className: FOOTER_VERSION, children: version })
68
70
  ] })
69
71
  ] }) })
70
72
  ] })
@@ -75,11 +77,11 @@ function FooterLockup({
75
77
  mark,
76
78
  tagline
77
79
  }) {
78
- return /* @__PURE__ */ jsxs("div", { "data-slot": "footer-lockup", className: "flex items-center gap-3 mb-6", children: [
80
+ return /* @__PURE__ */ jsxs("div", { "data-slot": "footer-lockup", className: FOOTER_LOCKUP, children: [
79
81
  mark,
80
- (brand !== void 0 || tagline !== void 0) && /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
81
- brand !== void 0 && /* @__PURE__ */ jsx("span", { className: "font-serif font-bold text-base tracking-wider text-ink", children: brand }),
82
- tagline !== void 0 && /* @__PURE__ */ jsx("span", { className: "text-[8px] font-mono tracking-[0.3em] text-primary-ink uppercase", children: tagline })
82
+ (brand !== void 0 || tagline !== void 0) && /* @__PURE__ */ jsxs("div", { className: FOOTER_LOCKUP_TEXT, children: [
83
+ brand !== void 0 && /* @__PURE__ */ jsx("span", { className: FOOTER_BRAND, children: brand }),
84
+ tagline !== void 0 && /* @__PURE__ */ jsx("span", { className: FOOTER_TAGLINE, children: tagline })
83
85
  ] })
84
86
  ] });
85
87
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/shell/footer.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { Container } from \"../atomic/container\";\n\n/**\n * A 12-col footer grid — brand 3 | sitemap groups 2 each | offices 3 |\n * newsletter 2 — with the legal links and copyright line. On mobile the sitemap\n * columns sit side by side. The kit brings no content of its own: every string,\n * the mark and the lock-up are the caller's, and each column renders only when\n * it has something in it. `children` render right after the `<footer>` tag —\n * the slot for app-side extras like a build-version easter egg.\n *\n * The lock-up is either composed from `mark` + `brand` + `tagline`, or handed\n * over whole as `lockup` when a brand's arrangement is not that one.\n *\n * `linkComponent` lets Next hosts pass `next/link`; everyone else gets `<a>`.\n */\nexport interface FooterLink {\n label: string;\n href: string;\n}\n\nexport interface FooterLinkGroup {\n heading: string;\n links: readonly FooterLink[];\n}\n\nexport interface FooterOffice {\n name: string;\n address: string;\n}\n\nexport interface FooterProps {\n /** The whole lock-up; replaces the one `mark` + `brand` + `tagline` compose. */\n lockup?: React.ReactNode;\n /** The wordmark beside the mark, set in the display family. */\n brand?: string;\n /** The mark beside the brand name. Sized by the caller. */\n mark?: React.ReactNode;\n tagline?: string;\n description?: React.ReactNode;\n copyright?: React.ReactNode;\n nav?: readonly FooterLinkGroup[];\n offices?: readonly FooterOffice[];\n officesHeading?: string;\n legalLinks?: readonly FooterLink[];\n /** The newsletter form; the newsletter column renders only when present. */\n newsletter?: React.ReactNode;\n newsletterHeading?: string;\n newsletterBlurb?: string;\n version?: string;\n commitHref?: string;\n linkComponent?: React.ElementType;\n children?: React.ReactNode;\n}\n\nconst HEADING = \"font-mono text-xs text-ink uppercase tracking-widest mb-6\";\n\nexport function Footer({\n lockup,\n brand,\n mark,\n tagline,\n description,\n copyright,\n nav = [],\n offices = [],\n officesHeading = \"Offices\",\n legalLinks = [],\n newsletter,\n newsletterHeading = \"Newsletter\",\n newsletterBlurb = \"\",\n version,\n commitHref,\n linkComponent,\n children,\n}: FooterProps) {\n const L = linkComponent ?? \"a\";\n const composed = brand !== undefined || mark !== undefined;\n const hasBrandColumn = lockup !== undefined || composed || description !== undefined || legalLinks.length > 0;\n\n return (\n <footer data-slot=\"footer\" className=\"bg-background border-t border-ink/10 py-16\">\n {children}\n <Container>\n <div className=\"grid grid-cols-2 gap-x-8 gap-y-8 lg:grid-cols-12 mb-12\">\n {hasBrandColumn && (\n <div className=\"col-span-2 lg:col-span-3\">\n {lockup !== undefined ? (\n <div data-slot=\"footer-lockup\" className=\"mb-6\">{lockup}</div>\n ) : (\n composed && <FooterLockup brand={brand} mark={mark} tagline={tagline} />\n )}\n {description !== undefined && (\n <p className=\"text-ink/40 text-xs font-light max-w-sm leading-relaxed mb-6\">\n {description}\n </p>\n )}\n {legalLinks.length > 0 && (\n <div className=\"flex gap-4 text-xs font-mono text-primary-ink\">\n {legalLinks.map((link, i) => (\n <React.Fragment key={link.label}>\n {i > 0 && <span className=\"text-ink/20\">|</span>}\n <L href={link.href} className=\"hover:underline\">\n {link.label}\n </L>\n </React.Fragment>\n ))}\n </div>\n )}\n </div>\n )}\n\n {nav.map((group) => (\n <nav key={group.heading} aria-label={`Footer ${group.heading} links`} className=\"lg:col-span-2\">\n <h4 className={HEADING}>{group.heading}</h4>\n <ul className=\"space-y-3\">\n {group.links.map((link) => (\n <li key={link.href}>\n <L\n href={link.href}\n className=\"text-xs font-light text-ink/70 hover:text-primary-ink transition-colors\"\n >\n {link.label}\n </L>\n </li>\n ))}\n </ul>\n </nav>\n ))}\n\n {offices.length > 0 && (\n <div className=\"col-span-2 lg:col-span-3\">\n <h4 className={HEADING}>{officesHeading}</h4>\n <ul className=\"space-y-4 text-xs text-ink/70 font-light leading-relaxed\">\n {offices.map((office) => (\n <li key={office.name}>\n <strong className=\"text-ink block font-mono text-[10px] uppercase tracking-wider mb-1\">\n {office.name}\n </strong>\n {office.address}\n </li>\n ))}\n </ul>\n </div>\n )}\n\n {newsletter && (\n <div className=\"col-span-2 lg:col-span-2\">\n <h4 className={HEADING}>{newsletterHeading}</h4>\n {/* What the source's <Tier tier=\"alt\"><Text> emits: alt body size\n + the info variant, then the mb-4 the caller adds. */}\n <p className=\"text-sm sm:text-xs font-light leading-relaxed text-ink/70 mb-4\">\n {newsletterBlurb}\n </p>\n {newsletter}\n </div>\n )}\n </div>\n\n {(copyright !== undefined || version !== undefined) && (\n <div className=\"border-t border-ink/10 pt-8 text-[10px] font-mono text-ink/40\">\n <p>\n {copyright}\n {version && (\n <>\n {copyright !== undefined && \" \"}\n <a href={commitHref} className=\"text-ink/30\">\n {version}\n </a>\n </>\n )}\n </p>\n </div>\n )}\n </Container>\n </footer>\n );\n}\n\nfunction FooterLockup({\n brand,\n mark,\n tagline,\n}: {\n brand: string | undefined;\n mark: React.ReactNode;\n tagline: string | undefined;\n}) {\n return (\n <div data-slot=\"footer-lockup\" className=\"flex items-center gap-3 mb-6\">\n {mark}\n {(brand !== undefined || tagline !== undefined) && (\n <div className=\"flex flex-col\">\n {brand !== undefined && (\n <span className=\"font-serif font-bold text-base tracking-wider text-ink\">{brand}</span>\n )}\n {tagline !== undefined && (\n <span className=\"text-[8px] font-mono tracking-[0.3em] text-primary-ink uppercase\">\n {tagline}\n </span>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"AAwFgB,SA4EA,UA5EA,KAYI,YAZJ;AAxFhB,YAAY,WAAW;AACvB,SAAS,iBAAiB;AAsD1B,MAAM,UAAU;AAET,SAAS,OAAO;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,MAAM,CAAC;AAAA,EACP,UAAU,CAAC;AAAA,EACX,iBAAiB;AAAA,EACjB,aAAa,CAAC;AAAA,EACd;AAAA,EACA,oBAAoB;AAAA,EACpB,kBAAkB;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAgB;AACd,QAAM,IAAI,iBAAiB;AAC3B,QAAM,WAAW,UAAU,UAAa,SAAS;AACjD,QAAM,iBAAiB,WAAW,UAAa,YAAY,gBAAgB,UAAa,WAAW,SAAS;AAE5G,SACE,qBAAC,YAAO,aAAU,UAAS,WAAU,8CAClC;AAAA;AAAA,IACD,qBAAC,aACC;AAAA,2BAAC,SAAI,WAAU,0DACZ;AAAA,0BACC,qBAAC,SAAI,WAAU,4BACZ;AAAA,qBAAW,SACV,oBAAC,SAAI,aAAU,iBAAgB,WAAU,QAAQ,kBAAO,IAExD,YAAY,oBAAC,gBAAa,OAAc,MAAY,SAAkB;AAAA,UAEvE,gBAAgB,UACf,oBAAC,OAAE,WAAU,gEACV,uBACH;AAAA,UAED,WAAW,SAAS,KACnB,oBAAC,SAAI,WAAU,iDACZ,qBAAW,IAAI,CAAC,MAAM,MACrB,qBAAC,MAAM,UAAN,EACE;AAAA,gBAAI,KAAK,oBAAC,UAAK,WAAU,eAAc,eAAC;AAAA,YACzC,oBAAC,KAAE,MAAM,KAAK,MAAM,WAAU,mBAC3B,eAAK,OACR;AAAA,eAJmB,KAAK,KAK1B,CACD,GACH;AAAA,WAEJ;AAAA,QAGD,IAAI,IAAI,CAAC,UACR,qBAAC,SAAwB,cAAY,UAAU,MAAM,OAAO,UAAU,WAAU,iBAC9E;AAAA,8BAAC,QAAG,WAAW,SAAU,gBAAM,SAAQ;AAAA,UACvC,oBAAC,QAAG,WAAU,aACX,gBAAM,MAAM,IAAI,CAAC,SAChB,oBAAC,QACC;AAAA,YAAC;AAAA;AAAA,cACC,MAAM,KAAK;AAAA,cACX,WAAU;AAAA,cAET,eAAK;AAAA;AAAA,UACR,KANO,KAAK,IAOd,CACD,GACH;AAAA,aAbQ,MAAM,OAchB,CACD;AAAA,QAEA,QAAQ,SAAS,KAChB,qBAAC,SAAI,WAAU,4BACb;AAAA,8BAAC,QAAG,WAAW,SAAU,0BAAe;AAAA,UACxC,oBAAC,QAAG,WAAU,4DACX,kBAAQ,IAAI,CAAC,WACZ,qBAAC,QACC;AAAA,gCAAC,YAAO,WAAU,sEACf,iBAAO,MACV;AAAA,YACC,OAAO;AAAA,eAJD,OAAO,IAKhB,CACD,GACH;AAAA,WACF;AAAA,QAGD,cACC,qBAAC,SAAI,WAAU,4BACb;AAAA,8BAAC,QAAG,WAAW,SAAU,6BAAkB;AAAA,UAG3C,oBAAC,OAAE,WAAU,kEACV,2BACH;AAAA,UACC;AAAA,WACH;AAAA,SAEJ;AAAA,OAEE,cAAc,UAAa,YAAY,WACvC,oBAAC,SAAI,WAAU,iEACb,+BAAC,OACE;AAAA;AAAA,QACA,WACC,iCACG;AAAA,wBAAc,UAAa;AAAA,UAC5B,oBAAC,OAAE,MAAM,YAAY,WAAU,eAC5B,mBACH;AAAA,WACF;AAAA,SAEJ,GACF;AAAA,OAEJ;AAAA,KACF;AAEJ;AAEA,SAAS,aAAa;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,SACE,qBAAC,SAAI,aAAU,iBAAgB,WAAU,gCACtC;AAAA;AAAA,KACC,UAAU,UAAa,YAAY,WACnC,qBAAC,SAAI,WAAU,iBACZ;AAAA,gBAAU,UACT,oBAAC,UAAK,WAAU,0DAA0D,iBAAM;AAAA,MAEjF,YAAY,UACX,oBAAC,UAAK,WAAU,oEACb,mBACH;AAAA,OAEJ;AAAA,KAEJ;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/shell/footer.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { Container } from \"../atomic/container\";\nimport * as copy from \"../../generated/copy\";\nimport type { FooterLink, FooterLinkGroup, FooterOffice } from \"../../generated/footer\";\nimport { FOOTER_BRAND, FOOTER_COPYRIGHT, FOOTER_DESCRIPTION, FOOTER_GRID, FOOTER_HEADING, FOOTER_LEGAL_LINK, FOOTER_LEGAL_LINKS, FOOTER_LEGAL_SEPARATOR, FOOTER_LOCKUP, FOOTER_LOCKUP_TEXT, FOOTER_NAV_COLUMN, FOOTER_NAV_LINK, FOOTER_NAV_LIST, FOOTER_NEWSLETTER_BLURB, FOOTER_NEWSLETTER_COLUMN, FOOTER_OFFICES_LIST, FOOTER_OFFICE_NAME, FOOTER_ROOT, FOOTER_TAGLINE, FOOTER_VERSION, FOOTER_WIDE_COLUMN } from \"../../generated/footer\";\n\nexport type { FooterLink, FooterLinkGroup, FooterOffice };\n\nexport interface FooterProps {\n /** The whole lock-up; replaces the one `mark` + `brand` + `tagline` compose. */\n lockup?: React.ReactNode;\n /** The wordmark beside the mark, set in the display family. */\n brand?: string;\n /** The mark beside the brand name. Sized by the caller. */\n mark?: React.ReactNode;\n tagline?: string;\n description?: React.ReactNode;\n copyright?: React.ReactNode;\n nav?: readonly FooterLinkGroup[];\n offices?: readonly FooterOffice[];\n officesHeading?: string;\n legalLinks?: readonly FooterLink[];\n /** The newsletter form; the newsletter column renders only when present. */\n newsletter?: React.ReactNode;\n newsletterHeading?: string;\n newsletterBlurb?: string;\n version?: string;\n commitHref?: string;\n linkComponent?: React.ElementType;\n children?: React.ReactNode;\n}\n\nconst HEADING = FOOTER_HEADING;\n\n/**\n * A 12-col footer grid — brand 3 | sitemap groups 2 each | offices 3 |\n * newsletter 2 — with the legal links and copyright line. On mobile the sitemap\n * columns sit side by side. The kit brings no content of its own: every string,\n * the mark and the lock-up are the caller's, and each column renders only when\n * it has something in it. `children` render right after the `<footer>` tag —\n * the slot for app-side extras like a build-version easter egg.\n *\n * The lock-up is either composed from `mark` + `brand` + `tagline`, or handed\n * over whole as `lockup` when a brand's arrangement is not that one.\n *\n * `linkComponent` lets Next hosts pass `next/link`; everyone else gets `<a>`.\n */\nexport function Footer({\n lockup,\n brand,\n mark,\n tagline,\n description,\n copyright,\n nav = [],\n offices = [],\n officesHeading = copy.FOOTER_OFFICES,\n legalLinks = [],\n newsletter,\n newsletterHeading = copy.FOOTER_NEWSLETTER,\n newsletterBlurb = \"\",\n version,\n commitHref,\n linkComponent,\n children,\n}: FooterProps) {\n const L = linkComponent ?? \"a\";\n const composed = brand !== undefined || mark !== undefined;\n const hasBrandColumn = lockup !== undefined || composed || description !== undefined || legalLinks.length > 0;\n\n return (\n <footer data-slot=\"footer\" className={FOOTER_ROOT}>\n {children}\n <Container>\n <div className={FOOTER_GRID}>\n {hasBrandColumn && (\n <div className={FOOTER_WIDE_COLUMN}>\n {lockup !== undefined ? (\n <div data-slot=\"footer-lockup\" className=\"mb-6\">{lockup}</div>\n ) : (\n composed && <FooterLockup brand={brand} mark={mark} tagline={tagline} />\n )}\n {description !== undefined && (\n <p className={FOOTER_DESCRIPTION}>\n {description}\n </p>\n )}\n {legalLinks.length > 0 && (\n <div className={FOOTER_LEGAL_LINKS}>\n {legalLinks.map((link, i) => (\n <React.Fragment key={link.label}>\n {i > 0 && <span className={FOOTER_LEGAL_SEPARATOR}>|</span>}\n <L href={link.href} className={FOOTER_LEGAL_LINK}>\n {link.label}\n </L>\n </React.Fragment>\n ))}\n </div>\n )}\n </div>\n )}\n\n {nav.map((group) => (\n <nav key={group.heading} aria-label={`Footer ${group.heading} links`} className={FOOTER_NAV_COLUMN}>\n <h4 className={HEADING}>{group.heading}</h4>\n <ul className={FOOTER_NAV_LIST}>\n {group.links.map((link) => (\n <li key={link.href}>\n <L\n href={link.href}\n className={FOOTER_NAV_LINK}\n >\n {link.label}\n </L>\n </li>\n ))}\n </ul>\n </nav>\n ))}\n\n {offices.length > 0 && (\n <div className={FOOTER_WIDE_COLUMN}>\n <h4 className={HEADING}>{officesHeading}</h4>\n <ul className={FOOTER_OFFICES_LIST}>\n {offices.map((office) => (\n <li key={office.name}>\n <strong className={FOOTER_OFFICE_NAME}>\n {office.name}\n </strong>\n {office.address}\n </li>\n ))}\n </ul>\n </div>\n )}\n\n {newsletter && (\n <div className={FOOTER_NEWSLETTER_COLUMN}>\n <h4 className={HEADING}>{newsletterHeading}</h4>\n {/* What the source's <Tier tier=\"alt\"><Text> emits: alt body size\n + the info variant, then the mb-4 the caller adds. */}\n <p className={FOOTER_NEWSLETTER_BLURB}>\n {newsletterBlurb}\n </p>\n {newsletter}\n </div>\n )}\n </div>\n\n {(copyright !== undefined || version !== undefined) && (\n <div className={FOOTER_COPYRIGHT}>\n <p>\n {copyright}\n {version && (\n <>\n {copyright !== undefined && \" \"}\n <a href={commitHref} className={FOOTER_VERSION}>\n {version}\n </a>\n </>\n )}\n </p>\n </div>\n )}\n </Container>\n </footer>\n );\n}\n\nfunction FooterLockup({\n brand,\n mark,\n tagline,\n}: {\n brand: string | undefined;\n mark: React.ReactNode;\n tagline: string | undefined;\n}) {\n return (\n <div data-slot=\"footer-lockup\" className={FOOTER_LOCKUP}>\n {mark}\n {(brand !== undefined || tagline !== undefined) && (\n <div className={FOOTER_LOCKUP_TEXT}>\n {brand !== undefined && (\n <span className={FOOTER_BRAND}>{brand}</span>\n )}\n {tagline !== undefined && (\n <span className={FOOTER_TAGLINE}>\n {tagline}\n </span>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -26,9 +26,9 @@ import {
26
26
  NOTIFICATION_BELL_ITEM_TITLE_UNREAD,
27
27
  NOTIFICATION_BELL_LIST,
28
28
  NOTIFICATION_BELL_TRIGGER,
29
- NOTIFICATION_DOT,
30
- headerBalanceToneClasses
29
+ NOTIFICATION_DOT
31
30
  } from "../../generated/header-controls.js";
31
+ import { toneClasses } from "../../generated/tone.js";
32
32
  import { AnimatedNumber } from "../derived/animated-number.js";
33
33
  import { IconButton } from "../derived/icon-button.js";
34
34
  import { Kbd } from "../atomic/kbd.js";
@@ -36,22 +36,23 @@ import { Popover, PopoverContent, PopoverTrigger } from "../atomic/popover.js";
36
36
  import { Settled } from "./settled.js";
37
37
  import { Skeleton } from "../atomic/skeleton.js";
38
38
  import { UiIcon } from "../../icons/ui-icons.js";
39
+ import * as copy from "../../generated/copy.js";
39
40
  const USD = { style: "currency", currency: "USD" };
40
41
  function HeaderBalance({
41
- label = "Balance:",
42
+ label = copy.HEADER_BALANCE,
42
43
  value,
43
44
  amount,
44
45
  format = USD,
45
46
  locales,
46
47
  loading = false,
47
48
  icon,
48
- tone = "default",
49
+ tone = "neutral",
49
50
  onClick,
50
51
  disabled,
51
52
  className,
52
53
  ...props
53
54
  }) {
54
- const face = cn(HEADER_BALANCE_BASE, onClick && HEADER_BALANCE_INTERACTIVE, className);
55
+ const face = cn(HEADER_BALANCE_BASE, toneClasses[tone], onClick && HEADER_BALANCE_INTERACTIVE, className);
55
56
  const body = /* @__PURE__ */ jsxs(Fragment, { children: [
56
57
  icon,
57
58
  /* @__PURE__ */ jsx("span", { className: HEADER_BALANCE_LABEL, children: label }),
@@ -61,7 +62,7 @@ function HeaderBalance({
61
62
  loading,
62
63
  skeleton: /* @__PURE__ */ jsx(Skeleton, { className: HEADER_BALANCE_SKELETON }),
63
64
  className: HEADER_BALANCE_SETTLED,
64
- children: /* @__PURE__ */ jsx("span", { className: cn(HEADER_BALANCE_FIGURE, headerBalanceToneClasses[tone]), children: amount === void 0 ? value : /* @__PURE__ */ jsx(AnimatedNumber, { value: amount, format, locales }) })
65
+ children: /* @__PURE__ */ jsx("span", { className: HEADER_BALANCE_FIGURE, children: amount === void 0 ? value : /* @__PURE__ */ jsx(AnimatedNumber, { value: amount, format, locales }) })
65
66
  }
66
67
  ),
67
68
  onClick && /* @__PURE__ */ jsx(UiIcon, { name: "chevron-down", className: HEADER_BALANCE_CHEVRON })
@@ -73,7 +74,7 @@ function HeaderBalance({
73
74
  }
74
75
  function NotificationBell({ notifications, onSelect, labels = {}, footer, className }) {
75
76
  const unread = notifications.filter((n) => n.unread).length;
76
- const name = labels.trigger ?? "Notifications";
77
+ const name = labels.trigger ?? copy.NOTIFICATION_BELL_TRIGGER;
77
78
  const spoken = unread > 0 ? `${name}, ${(labels.unread ?? ((n) => `${n} unread`))(unread)}` : name;
78
79
  return /* @__PURE__ */ jsxs(Popover, { children: [
79
80
  /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(IconButton, { "aria-label": spoken, variant: "outline", className: cn(NOTIFICATION_BELL_TRIGGER, className), children: [
@@ -81,7 +82,7 @@ function NotificationBell({ notifications, onSelect, labels = {}, footer, classN
81
82
  unread > 0 && /* @__PURE__ */ jsx("span", { "data-slot": "notification-dot", "aria-hidden": true, className: NOTIFICATION_DOT })
82
83
  ] }) }),
83
84
  /* @__PURE__ */ jsxs(PopoverContent, { align: "end", className: NOTIFICATION_BELL_CONTENT, children: [
84
- notifications.length === 0 ? /* @__PURE__ */ jsx("p", { className: NOTIFICATION_BELL_EMPTY, children: labels.empty ?? "No notifications" }) : /* @__PURE__ */ jsx("ul", { className: NOTIFICATION_BELL_LIST, children: notifications.map((n) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
85
+ notifications.length === 0 ? /* @__PURE__ */ jsx("p", { className: NOTIFICATION_BELL_EMPTY, children: labels.empty ?? copy.NOTIFICATION_BELL_EMPTY }) : /* @__PURE__ */ jsx("ul", { className: NOTIFICATION_BELL_LIST, children: notifications.map((n) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
85
86
  "button",
86
87
  {
87
88
  type: "button",
@@ -111,21 +112,21 @@ function NotificationBell({ notifications, onSelect, labels = {}, footer, classN
111
112
  ] });
112
113
  }
113
114
  const APPLE = /Mac|iPhone|iPad|iPod/;
114
- function usePlatformShortcut() {
115
- const [shortcut, setShortcut] = React.useState("Ctrl K");
115
+ function useAppleKeys() {
116
+ const [apple, setApple] = React.useState(false);
116
117
  React.useEffect(() => {
117
118
  const nav = navigator;
118
- if (APPLE.test(nav.userAgentData?.platform ?? nav.platform ?? "")) setShortcut("\u2318K");
119
+ setApple(APPLE.test(nav.userAgentData?.platform ?? nav.platform ?? ""));
119
120
  }, []);
120
- return shortcut;
121
+ return apple;
121
122
  }
122
123
  function HeaderSearch({
123
- placeholder = "Search or jump to\u2026",
124
+ placeholder = copy.HEADER_SEARCH,
124
125
  shortcut,
125
126
  className,
126
127
  ...props
127
128
  }) {
128
- const platformShortcut = usePlatformShortcut();
129
+ const platformShortcut = useAppleKeys() ? copy.COMMAND_K : copy.CTRL_K;
129
130
  return /* @__PURE__ */ jsxs(
130
131
  "button",
131
132
  {
@@ -144,6 +145,7 @@ function HeaderSearch({
144
145
  export {
145
146
  HeaderBalance,
146
147
  HeaderSearch,
147
- NotificationBell
148
+ NotificationBell,
149
+ useAppleKeys
148
150
  };
149
151
  //# sourceMappingURL=header-controls.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/shell/header-controls.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\nimport {\n HEADER_BALANCE_BASE,\n HEADER_BALANCE_CHEVRON,\n HEADER_BALANCE_FIGURE,\n HEADER_BALANCE_INTERACTIVE,\n HEADER_BALANCE_LABEL,\n HEADER_BALANCE_SETTLED,\n HEADER_BALANCE_SKELETON,\n HEADER_SEARCH_BASE,\n HEADER_SEARCH_PLACEHOLDER,\n NOTIFICATION_BELL_CONTENT,\n NOTIFICATION_BELL_EMPTY,\n NOTIFICATION_BELL_FOOTER,\n NOTIFICATION_BELL_ITEM,\n NOTIFICATION_BELL_ITEM_BODY,\n NOTIFICATION_BELL_ITEM_DESCRIPTION,\n NOTIFICATION_BELL_ITEM_DOT,\n NOTIFICATION_BELL_ITEM_DOT_READ,\n NOTIFICATION_BELL_ITEM_DOT_UNREAD,\n NOTIFICATION_BELL_ITEM_TIME,\n NOTIFICATION_BELL_ITEM_TITLE,\n NOTIFICATION_BELL_ITEM_TITLE_UNREAD,\n NOTIFICATION_BELL_LIST,\n NOTIFICATION_BELL_TRIGGER,\n NOTIFICATION_DOT,\n headerBalanceToneClasses,\n type HeaderBalanceTone,\n} from \"../../generated/header-controls\";\nimport { AnimatedNumber } from \"../derived/animated-number\";\nimport { IconButton } from \"../derived/icon-button\";\nimport { Kbd } from \"../atomic/kbd\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"../atomic/popover\";\nimport { Settled } from \"./settled\";\nimport { Skeleton } from \"../atomic/skeleton\";\nimport { UiIcon } from \"../../icons/ui-icons\";\n\nexport interface HeaderBalanceProps extends Omit<React.HTMLAttributes<HTMLElement>, \"children\"> {\n /** Only meaningful with `onClick`. */\n disabled?: boolean;\n label?: React.ReactNode;\n /** The figure, already formatted: \"$0.00\". Ignored when `amount` is given. */\n value?: React.ReactNode;\n /** The figure as a number: it rolls to each new amount, formatted by `format` and `locales`. */\n amount?: number;\n /** Formatting for `amount`. Default: USD currency. */\n format?: Intl.NumberFormatOptions;\n locales?: Intl.LocalesArgument;\n /** True until the balance is known; the figure then reveals in place of the skeleton. */\n loading?: boolean;\n /** A glyph before the label. */\n icon?: React.ReactNode;\n /** `warn` colours the figure as a low-funds signal. */\n tone?: HeaderBalanceTone;\n}\n\nconst USD: Intl.NumberFormatOptions = { style: \"currency\", currency: \"USD\" };\n\n/** \"Balance: $0.00\" in the header; a button only when given `onClick`. */\nexport function HeaderBalance({\n label = \"Balance:\",\n value,\n amount,\n format = USD,\n locales,\n loading = false,\n icon,\n tone = \"default\",\n onClick,\n disabled,\n className,\n ...props\n}: HeaderBalanceProps) {\n const face = cn(HEADER_BALANCE_BASE, onClick && HEADER_BALANCE_INTERACTIVE, className);\n const body = (\n <>\n {icon}\n <span className={HEADER_BALANCE_LABEL}>{label}</span>\n <Settled\n loading={loading}\n skeleton={<Skeleton className={HEADER_BALANCE_SKELETON} />}\n className={HEADER_BALANCE_SETTLED}\n >\n <span className={cn(HEADER_BALANCE_FIGURE, headerBalanceToneClasses[tone])}>\n {amount === undefined ? value : <AnimatedNumber value={amount} format={format} locales={locales} />}\n </span>\n </Settled>\n {onClick && <UiIcon name=\"chevron-down\" className={HEADER_BALANCE_CHEVRON} />}\n </>\n );\n if (!onClick) {\n return (\n <div data-slot=\"header-balance\" className={face} {...props}>\n {body}\n </div>\n );\n }\n return (\n <button type=\"button\" data-slot=\"header-balance\" className={face} onClick={onClick} disabled={disabled} {...props}>\n {body}\n </button>\n );\n}\n\nexport interface HeaderNotification {\n id: string;\n title: React.ReactNode;\n description?: React.ReactNode;\n /** Already formatted: \"2m ago\". */\n time?: React.ReactNode;\n unread?: boolean;\n}\n\nexport interface NotificationBellLabels {\n /** The bell's name. Default `\"Notifications\"`. */\n trigger?: string;\n /** Spoken after the name when something is unread; gets the count. Default `\"{n} unread\"`. */\n unread?: (count: number) => string;\n /** Default `\"No notifications\"`. */\n empty?: string;\n}\n\nexport interface NotificationBellProps {\n notifications: readonly HeaderNotification[];\n onSelect?: (id: string) => void;\n labels?: NotificationBellLabels;\n /** Under the list: \"View all\". */\n footer?: React.ReactNode;\n className?: string;\n}\n\n/** The bell with an unread dot; the list opens in a popover. */\nexport function NotificationBell({ notifications, onSelect, labels = {}, footer, className }: NotificationBellProps) {\n const unread = notifications.filter((n) => n.unread).length;\n const name = labels.trigger ?? \"Notifications\";\n const spoken = unread > 0 ? `${name}, ${(labels.unread ?? ((n) => `${n} unread`))(unread)}` : name;\n return (\n <Popover>\n <PopoverTrigger asChild>\n <IconButton aria-label={spoken} variant=\"outline\" className={cn(NOTIFICATION_BELL_TRIGGER, className)}>\n <UiIcon name=\"bell\" />\n {unread > 0 && (\n <span data-slot=\"notification-dot\" aria-hidden className={NOTIFICATION_DOT} />\n )}\n </IconButton>\n </PopoverTrigger>\n <PopoverContent align=\"end\" className={NOTIFICATION_BELL_CONTENT}>\n {notifications.length === 0 ? (\n <p className={NOTIFICATION_BELL_EMPTY}>{labels.empty ?? \"No notifications\"}</p>\n ) : (\n <ul className={NOTIFICATION_BELL_LIST}>\n {notifications.map((n) => (\n <li key={n.id}>\n <button\n type=\"button\"\n onClick={() => onSelect?.(n.id)}\n className={NOTIFICATION_BELL_ITEM}\n >\n <span\n aria-hidden\n className={cn(\n NOTIFICATION_BELL_ITEM_DOT,\n n.unread ? NOTIFICATION_BELL_ITEM_DOT_UNREAD : NOTIFICATION_BELL_ITEM_DOT_READ,\n )}\n />\n <span className={NOTIFICATION_BELL_ITEM_BODY}>\n <span className={cn(NOTIFICATION_BELL_ITEM_TITLE, n.unread && NOTIFICATION_BELL_ITEM_TITLE_UNREAD)}>{n.title}</span>\n {n.description != null && <span className={NOTIFICATION_BELL_ITEM_DESCRIPTION}>{n.description}</span>}\n </span>\n {n.time != null && <span className={NOTIFICATION_BELL_ITEM_TIME}>{n.time}</span>}\n </button>\n </li>\n ))}\n </ul>\n )}\n {footer != null && <div className={NOTIFICATION_BELL_FOOTER}>{footer}</div>}\n </PopoverContent>\n </Popover>\n );\n}\n\nexport interface HeaderSearchProps extends React.ComponentProps<\"button\"> {\n placeholder?: string;\n /** The shortcut hint at the end. Default: `⌘K` on Apple platforms, `Ctrl K` elsewhere. */\n shortcut?: string;\n}\n\nconst APPLE = /Mac|iPhone|iPad|iPod/;\n\n// Read after mount, so the server render and the first client render agree.\nfunction usePlatformShortcut(): string {\n const [shortcut, setShortcut] = React.useState(\"Ctrl K\");\n React.useEffect(() => {\n const nav = navigator as Navigator & { userAgentData?: { platform?: string } };\n if (APPLE.test(nav.userAgentData?.platform ?? nav.platform ?? \"\")) setShortcut(\"⌘K\");\n }, []);\n return shortcut;\n}\n\n/** A button dressed as the search field; it opens the palette rather than taking input. */\nexport function HeaderSearch({\n placeholder = \"Search or jump to…\",\n shortcut,\n className,\n ...props\n}: HeaderSearchProps) {\n const platformShortcut = usePlatformShortcut();\n return (\n <button\n type=\"button\"\n data-slot=\"header-search\"\n className={cn(HEADER_SEARCH_BASE, className)}\n {...props}\n >\n <UiIcon name=\"search\" />\n <span className={HEADER_SEARCH_PLACEHOLDER}>{placeholder}</span>\n <Kbd>{shortcut ?? platformShortcut}</Kbd>\n </button>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/components/shell/header-controls.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn\";\nimport {\n HEADER_BALANCE_BASE,\n HEADER_BALANCE_CHEVRON,\n HEADER_BALANCE_FIGURE,\n HEADER_BALANCE_INTERACTIVE,\n HEADER_BALANCE_LABEL,\n HEADER_BALANCE_SETTLED,\n HEADER_BALANCE_SKELETON,\n HEADER_SEARCH_BASE,\n HEADER_SEARCH_PLACEHOLDER,\n NOTIFICATION_BELL_CONTENT,\n NOTIFICATION_BELL_EMPTY,\n NOTIFICATION_BELL_FOOTER,\n NOTIFICATION_BELL_ITEM,\n NOTIFICATION_BELL_ITEM_BODY,\n NOTIFICATION_BELL_ITEM_DESCRIPTION,\n NOTIFICATION_BELL_ITEM_DOT,\n NOTIFICATION_BELL_ITEM_DOT_READ,\n NOTIFICATION_BELL_ITEM_DOT_UNREAD,\n NOTIFICATION_BELL_ITEM_TIME,\n NOTIFICATION_BELL_ITEM_TITLE,\n NOTIFICATION_BELL_ITEM_TITLE_UNREAD,\n NOTIFICATION_BELL_LIST,\n NOTIFICATION_BELL_TRIGGER,\n NOTIFICATION_DOT,\n} from \"../../generated/header-controls\";\nimport { toneClasses, type Tone } from \"../../generated/tone\";\nimport { AnimatedNumber } from \"../derived/animated-number\";\nimport { IconButton } from \"../derived/icon-button\";\nimport { Kbd } from \"../atomic/kbd\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"../atomic/popover\";\nimport { Settled } from \"./settled\";\nimport { Skeleton } from \"../atomic/skeleton\";\nimport { UiIcon } from \"../../icons/ui-icons\";\nimport * as copy from \"../../generated/copy\";\n\nexport interface HeaderBalanceProps extends Omit<React.HTMLAttributes<HTMLElement>, \"children\"> {\n /** Only meaningful with `onClick`. */\n disabled?: boolean;\n label?: React.ReactNode;\n /** The figure, already formatted: \"$0.00\". Ignored when `amount` is given. */\n value?: React.ReactNode;\n /** The figure as a number: it rolls to each new amount, formatted by `format` and `locales`. */\n amount?: number;\n /** Formatting for `amount`. Default: USD currency. */\n format?: Intl.NumberFormatOptions;\n locales?: Intl.LocalesArgument;\n /** True until the balance is known; the figure then reveals in place of the skeleton. */\n loading?: boolean;\n /** A glyph before the label. */\n icon?: React.ReactNode;\n /** `warn` colours the figure as a low-funds signal. */\n tone?: Tone;\n}\n\nconst USD: Intl.NumberFormatOptions = { style: \"currency\", currency: \"USD\" };\n\n/** \"Balance: $0.00\" in the header; a button only when given `onClick`. */\nexport function HeaderBalance({\n label = copy.HEADER_BALANCE,\n value,\n amount,\n format = USD,\n locales,\n loading = false,\n icon,\n tone = \"neutral\",\n onClick,\n disabled,\n className,\n ...props\n}: HeaderBalanceProps) {\n const face = cn(HEADER_BALANCE_BASE, toneClasses[tone], onClick && HEADER_BALANCE_INTERACTIVE, className);\n const body = (\n <>\n {icon}\n <span className={HEADER_BALANCE_LABEL}>{label}</span>\n <Settled\n loading={loading}\n skeleton={<Skeleton className={HEADER_BALANCE_SKELETON} />}\n className={HEADER_BALANCE_SETTLED}\n >\n <span className={HEADER_BALANCE_FIGURE}>\n {amount === undefined ? value : <AnimatedNumber value={amount} format={format} locales={locales} />}\n </span>\n </Settled>\n {onClick && <UiIcon name=\"chevron-down\" className={HEADER_BALANCE_CHEVRON} />}\n </>\n );\n if (!onClick) {\n return (\n <div data-slot=\"header-balance\" className={face} {...props}>\n {body}\n </div>\n );\n }\n return (\n <button type=\"button\" data-slot=\"header-balance\" className={face} onClick={onClick} disabled={disabled} {...props}>\n {body}\n </button>\n );\n}\n\nexport interface HeaderNotification {\n id: string;\n title: React.ReactNode;\n description?: React.ReactNode;\n /** Already formatted: \"2m ago\". */\n time?: React.ReactNode;\n unread?: boolean;\n}\n\nexport interface NotificationBellLabels {\n /** The bell's name. Default `\"Notifications\"`. */\n trigger?: string;\n /** Spoken after the name when something is unread; gets the count. Default `\"{n} unread\"`. */\n unread?: (count: number) => string;\n /** Default `\"No notifications\"`. */\n empty?: string;\n}\n\nexport interface NotificationBellProps {\n notifications: readonly HeaderNotification[];\n onSelect?: (id: string) => void;\n labels?: NotificationBellLabels;\n /** Under the list: \"View all\". */\n footer?: React.ReactNode;\n className?: string;\n}\n\n/** The bell with an unread dot; the list opens in a popover. */\nexport function NotificationBell({ notifications, onSelect, labels = {}, footer, className }: NotificationBellProps) {\n const unread = notifications.filter((n) => n.unread).length;\n const name = labels.trigger ?? copy.NOTIFICATION_BELL_TRIGGER;\n const spoken = unread > 0 ? `${name}, ${(labels.unread ?? ((n) => `${n} unread`))(unread)}` : name;\n return (\n <Popover>\n <PopoverTrigger asChild>\n <IconButton aria-label={spoken} variant=\"outline\" className={cn(NOTIFICATION_BELL_TRIGGER, className)}>\n <UiIcon name=\"bell\" />\n {unread > 0 && (\n <span data-slot=\"notification-dot\" aria-hidden className={NOTIFICATION_DOT} />\n )}\n </IconButton>\n </PopoverTrigger>\n <PopoverContent align=\"end\" className={NOTIFICATION_BELL_CONTENT}>\n {notifications.length === 0 ? (\n <p className={NOTIFICATION_BELL_EMPTY}>{labels.empty ?? copy.NOTIFICATION_BELL_EMPTY}</p>\n ) : (\n <ul className={NOTIFICATION_BELL_LIST}>\n {notifications.map((n) => (\n <li key={n.id}>\n <button\n type=\"button\"\n onClick={() => onSelect?.(n.id)}\n className={NOTIFICATION_BELL_ITEM}\n >\n <span\n aria-hidden\n className={cn(\n NOTIFICATION_BELL_ITEM_DOT,\n n.unread ? NOTIFICATION_BELL_ITEM_DOT_UNREAD : NOTIFICATION_BELL_ITEM_DOT_READ,\n )}\n />\n <span className={NOTIFICATION_BELL_ITEM_BODY}>\n <span className={cn(NOTIFICATION_BELL_ITEM_TITLE, n.unread && NOTIFICATION_BELL_ITEM_TITLE_UNREAD)}>{n.title}</span>\n {n.description != null && <span className={NOTIFICATION_BELL_ITEM_DESCRIPTION}>{n.description}</span>}\n </span>\n {n.time != null && <span className={NOTIFICATION_BELL_ITEM_TIME}>{n.time}</span>}\n </button>\n </li>\n ))}\n </ul>\n )}\n {footer != null && <div className={NOTIFICATION_BELL_FOOTER}>{footer}</div>}\n </PopoverContent>\n </Popover>\n );\n}\n\nexport interface HeaderSearchProps extends React.ComponentProps<\"button\"> {\n placeholder?: string;\n /** The shortcut hint at the end. Default: `⌘K` on Apple platforms, `Ctrl K` elsewhere. */\n shortcut?: string;\n}\n\nconst APPLE = /Mac|iPhone|iPad|iPod/;\n\n/** Whether ⌘ is the modifier: read after mount, so the server render and the first client render agree. */\nexport function useAppleKeys(): boolean {\n const [apple, setApple] = React.useState(false);\n React.useEffect(() => {\n const nav = navigator as Navigator & { userAgentData?: { platform?: string } };\n setApple(APPLE.test(nav.userAgentData?.platform ?? nav.platform ?? \"\"));\n }, []);\n return apple;\n}\n\n/** A button dressed as the search field; it opens the palette rather than taking input. */\nexport function HeaderSearch({\n placeholder = copy.HEADER_SEARCH,\n shortcut,\n className,\n ...props\n}: HeaderSearchProps) {\n const platformShortcut = useAppleKeys() ? copy.COMMAND_K : copy.CTRL_K;\n return (\n <button\n type=\"button\"\n data-slot=\"header-search\"\n className={cn(HEADER_SEARCH_BASE, className)}\n {...props}\n >\n <UiIcon name=\"search\" />\n <span className={HEADER_SEARCH_PLACEHOLDER}>{placeholder}</span>\n <Kbd>{shortcut ?? platformShortcut}</Kbd>\n </button>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,158 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../../lib/cn.js";
5
+ import { toneClasses } from "../../generated/tone.js";
6
+ import { iconShapes } from "../../icons/icon-shapes.js";
7
+ import { Button } from "../atomic/button.js";
8
+ import { Checkbox } from "../atomic/checkbox.js";
9
+ import { Kbd } from "../atomic/kbd.js";
10
+ import { Popover, PopoverContent, PopoverTrigger } from "../atomic/popover.js";
11
+ import { useAppleKeys } from "./header-controls.js";
12
+ import { TriangleAlertIcon } from "./shell-icons.js";
13
+ function parseShortcut(shortcut) {
14
+ const mod = shortcut.startsWith("mod+");
15
+ const key = mod ? shortcut.slice(4) : shortcut;
16
+ return { mod, key, code: /\d/.test(key) ? `Digit${key}` : `Key${key}` };
17
+ }
18
+ const STORAGE_KEY = "ev-hotbar";
19
+ const NO_OVERRIDES = {};
20
+ let snapshot = { raw: null, value: NO_OVERRIDES };
21
+ const listeners = /* @__PURE__ */ new Set();
22
+ function readOverrides() {
23
+ const raw = localStorage.getItem(STORAGE_KEY);
24
+ if (raw === snapshot.raw) return snapshot.value;
25
+ const value = raw === null ? NO_OVERRIDES : JSON.parse(raw);
26
+ if (typeof value !== "object" || value === null || Array.isArray(value) || Object.values(value).some((v) => typeof v !== "boolean"))
27
+ throw new Error(`localStorage["${STORAGE_KEY}"] is not an {actionId: boolean} map: ${raw}`);
28
+ snapshot = { raw, value };
29
+ return snapshot.value;
30
+ }
31
+ function writeOverrides(next) {
32
+ if (Object.keys(next).length === 0) localStorage.removeItem(STORAGE_KEY);
33
+ else localStorage.setItem(STORAGE_KEY, JSON.stringify(next));
34
+ listeners.forEach((l) => l());
35
+ }
36
+ function subscribeOverrides(listener) {
37
+ const onStorage = (e) => {
38
+ if (e.key === STORAGE_KEY || e.key === null) listener();
39
+ };
40
+ listeners.add(listener);
41
+ window.addEventListener("storage", onStorage);
42
+ return () => {
43
+ listeners.delete(listener);
44
+ window.removeEventListener("storage", onStorage);
45
+ };
46
+ }
47
+ const EDITABLE = "input, textarea, select, [contenteditable]:not([contenteditable='false'])";
48
+ function Hotbar({ actions, status, takeover, labels = {}, className, ...props }) {
49
+ const parsed = React.useMemo(() => {
50
+ const ids = /* @__PURE__ */ new Set();
51
+ const codes = /* @__PURE__ */ new Set();
52
+ return actions.map((action) => {
53
+ if (ids.has(action.id)) throw new Error(`Hotbar: two actions have the id "${action.id}"`);
54
+ ids.add(action.id);
55
+ if (action.shortcut === void 0) return { action, shortcut: void 0 };
56
+ const shortcut = parseShortcut(action.shortcut);
57
+ const chord = `${shortcut.mod}:${shortcut.code}`;
58
+ if (codes.has(chord)) throw new Error(`Hotbar: two actions are bound to ${action.shortcut}`);
59
+ codes.add(chord);
60
+ return { action, shortcut };
61
+ });
62
+ }, [actions]);
63
+ const latest = React.useRef(parsed);
64
+ React.useEffect(() => {
65
+ latest.current = parsed;
66
+ });
67
+ React.useEffect(() => {
68
+ const onKeyDown = (e) => {
69
+ if (e.defaultPrevented || e.altKey || e.shiftKey) return;
70
+ const mod = e.metaKey || e.ctrlKey;
71
+ const hit = latest.current.find(({ shortcut }) => shortcut?.code === e.code && shortcut.mod === mod);
72
+ if (hit === void 0) return;
73
+ if (!mod && e.target instanceof Element && e.target.closest(EDITABLE)) return;
74
+ e.preventDefault();
75
+ if (!e.repeat) hit.action.onSelect();
76
+ };
77
+ document.addEventListener("keydown", onKeyDown);
78
+ return () => document.removeEventListener("keydown", onKeyDown);
79
+ }, []);
80
+ const overrides = React.useSyncExternalStore(subscribeOverrides, readOverrides, () => NO_OVERRIDES);
81
+ const isPinned = (action) => overrides[action.id] ?? action.pinned ?? true;
82
+ const setPinned = (action, pinned) => {
83
+ const next = { ...overrides };
84
+ delete next[action.id];
85
+ if (pinned !== (action.pinned ?? true)) next[action.id] = pinned;
86
+ writeOverrides(next);
87
+ };
88
+ const apple = useAppleKeys();
89
+ const kbd = (s) => s.mod ? apple ? `\u2318${s.key}` : `Ctrl ${s.key}` : s.key;
90
+ const [customizing, setCustomizing] = React.useState(false);
91
+ return /* @__PURE__ */ jsx(
92
+ "section",
93
+ {
94
+ "data-slot": "hotbar",
95
+ "aria-label": labels.region ?? "Hotbar",
96
+ className: cn(
97
+ "@container border-t border-border-subtle bg-background",
98
+ takeover && [toneClasses[takeover.tone], "border-(color:--tone-edge) bg-[image:linear-gradient(var(--tone-wash),var(--tone-wash))]"],
99
+ className
100
+ ),
101
+ ...props,
102
+ children: /* @__PURE__ */ jsxs("div", { className: "flex h-(--control-h-touch) items-center gap-2 overflow-x-auto px-3 @5xl:h-(--control-h) @5xl:gap-1.5 @5xl:overflow-visible", children: [
103
+ takeover ? /* @__PURE__ */ jsxs("div", { "data-slot": "hotbar-takeover", role: "alert", className: "flex min-w-0 items-center gap-2 text-body-s font-medium text-(color:--tone-ink)", children: [
104
+ /* @__PURE__ */ jsx(TriangleAlertIcon, { className: "size-4 shrink-0" }),
105
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: takeover.message }),
106
+ takeover.action
107
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
108
+ parsed.filter(({ action }) => isPinned(action)).map(({ action, shortcut }) => /* @__PURE__ */ jsxs(
109
+ "button",
110
+ {
111
+ type: "button",
112
+ "data-slot": "hotbar-action",
113
+ "aria-pressed": action.pressed,
114
+ onClick: action.onSelect,
115
+ className: cn(ITEM, action.pressed && PRESSED),
116
+ children: [
117
+ /* @__PURE__ */ jsx(action.icon, { className: "size-4 shrink-0" }),
118
+ action.label,
119
+ shortcut && /* @__PURE__ */ jsx(Kbd, { className: "hidden @5xl:inline-flex", children: kbd(shortcut) })
120
+ ]
121
+ },
122
+ action.id
123
+ )),
124
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "hidden h-4 w-px shrink-0 bg-border @5xl:block" }),
125
+ /* @__PURE__ */ jsxs(Popover, { open: customizing, onOpenChange: setCustomizing, children: [
126
+ /* @__PURE__ */ jsxs(PopoverTrigger, { type: "button", "data-slot": "hotbar-customize", className: cn(ITEM, "@5xl:w-(--control-h-2xs) @5xl:justify-center @5xl:px-0"), children: [
127
+ /* @__PURE__ */ jsx("svg", { className: "size-4 shrink-0", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: iconShapes("sliders-horizontal") }),
128
+ /* @__PURE__ */ jsx("span", { className: "@5xl:sr-only", children: labels.customize ?? "Customize" })
129
+ ] }),
130
+ /* @__PURE__ */ jsxs(PopoverContent, { side: "top", align: "start", className: "w-80 p-0", children: [
131
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5 px-3 pt-3 pb-2", children: [
132
+ /* @__PURE__ */ jsx("p", { className: "text-body-l font-medium", children: labels.title ?? "Customize hotbar" }),
133
+ /* @__PURE__ */ jsx("p", { className: "text-caption text-ink-soft", children: labels.description ?? "Pinned actions show in the bar and in the mobile chip row." })
134
+ ] }),
135
+ /* @__PURE__ */ jsx("ul", { className: "flex flex-col px-1 pb-1", children: parsed.map(({ action, shortcut }) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("label", { className: "flex h-(--control-h-sm) cursor-pointer items-center gap-2.5 rounded-control px-2 text-body text-ink-mid hover:bg-hover", children: [
136
+ /* @__PURE__ */ jsx(Checkbox, { checked: isPinned(action), onCheckedChange: (pinned) => setPinned(action, pinned) }),
137
+ /* @__PURE__ */ jsx(action.icon, { className: "size-4 shrink-0" }),
138
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: action.label }),
139
+ shortcut && /* @__PURE__ */ jsx(Kbd, { children: kbd(shortcut) })
140
+ ] }) }, action.id)) }),
141
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t border-border px-3 py-2", children: [
142
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => writeOverrides(NO_OVERRIDES), children: labels.reset ?? "Reset to default" }),
143
+ /* @__PURE__ */ jsx(Button, { size: "sm", onClick: () => setCustomizing(false), children: labels.done ?? "Done" })
144
+ ] })
145
+ ] })
146
+ ] })
147
+ ] }),
148
+ status != null && /* @__PURE__ */ jsx("div", { "data-slot": "hotbar-status", className: "ml-auto hidden shrink-0 items-center @5xl:flex", children: status })
149
+ ] })
150
+ }
151
+ );
152
+ }
153
+ const ITEM = "inline-flex h-(--control-h-xs) shrink-0 items-center gap-1.5 whitespace-nowrap rounded-chip border border-border bg-secondary pr-3 pl-2 text-body-s text-ink-mid transition-colors outline-none hover:text-ink focus-visible:ring-2 focus-visible:ring-ring @5xl:h-(--control-h-2xs) @5xl:rounded-control @5xl:border-transparent @5xl:bg-transparent @5xl:px-2 @5xl:hover:bg-hover";
154
+ const PRESSED = `${toneClasses.primary} border-(color:--tone-edge) bg-(color:--tone-fill) text-(color:--tone-ink) hover:text-(color:--tone-ink) @5xl:border-(color:--tone-edge) @5xl:bg-(color:--tone-fill)`;
155
+ export {
156
+ Hotbar
157
+ };
158
+ //# sourceMappingURL=hotbar.js.map