@open-mercato/core 0.7.1-develop.7203.1.9396450b78 → 0.8.1-develop.7204.1.83330e271e

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 (416) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/build.mjs +11 -0
  3. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +23 -0
  4. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  5. package/dist/modules/customers/backend/customers/companies/[id]/page.js +2 -1
  6. package/dist/modules/customers/backend/customers/companies/[id]/page.js.map +2 -2
  7. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +2 -1
  8. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
  9. package/dist/modules/customers/backend/customers/people/[id]/page.js +2 -2
  10. package/dist/modules/customers/backend/customers/people/[id]/page.js.map +2 -2
  11. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +2 -1
  12. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
  13. package/dist/modules/customers/lib/detailHelpers.js +4 -0
  14. package/dist/modules/customers/lib/detailHelpers.js.map +2 -2
  15. package/dist/modules/customers/lib/todoCompatibility.js +5 -0
  16. package/dist/modules/customers/lib/todoCompatibility.js.map +2 -2
  17. package/dist/modules/design_system/backend/design-system/page.meta.js +3 -2
  18. package/dist/modules/design_system/backend/design-system/page.meta.js.map +2 -2
  19. package/dist/modules/design_system/gallery/assets/ai-prompt-image.png +0 -0
  20. package/dist/modules/design_system/gallery/assets/avatar-illustration.png +0 -0
  21. package/dist/modules/design_system/gallery/assets/avatar-memoji.png +0 -0
  22. package/dist/modules/design_system/gallery/assets/avatar-photo.png +0 -0
  23. package/dist/modules/design_system/gallery/assets/blog-photo-0.png +0 -0
  24. package/dist/modules/design_system/gallery/assets/contact-support-portrait.png +0 -0
  25. package/dist/modules/design_system/gallery/assets/crypto-ada.png +0 -0
  26. package/dist/modules/design_system/gallery/assets/crypto-avax.png +0 -0
  27. package/dist/modules/design_system/gallery/assets/crypto-bnb.png +0 -0
  28. package/dist/modules/design_system/gallery/assets/crypto-brand.png +0 -0
  29. package/dist/modules/design_system/gallery/assets/crypto-btc.png +0 -0
  30. package/dist/modules/design_system/gallery/assets/crypto-clock.svg +3 -0
  31. package/dist/modules/design_system/gallery/assets/crypto-crown.svg +3 -0
  32. package/dist/modules/design_system/gallery/assets/crypto-dot.png +0 -0
  33. package/dist/modules/design_system/gallery/assets/crypto-eth.png +0 -0
  34. package/dist/modules/design_system/gallery/assets/crypto-hbar.png +0 -0
  35. package/dist/modules/design_system/gallery/assets/crypto-history.svg +3 -0
  36. package/dist/modules/design_system/gallery/assets/crypto-link.svg +3 -0
  37. package/dist/modules/design_system/gallery/assets/crypto-menu.svg +3 -0
  38. package/dist/modules/design_system/gallery/assets/crypto-metamask.png +0 -0
  39. package/dist/modules/design_system/gallery/assets/crypto-more.svg +3 -0
  40. package/dist/modules/design_system/gallery/assets/crypto-phantom.png +0 -0
  41. package/dist/modules/design_system/gallery/assets/crypto-rewards.svg +3 -0
  42. package/dist/modules/design_system/gallery/assets/crypto-sad-face.png +0 -0
  43. package/dist/modules/design_system/gallery/assets/crypto-sol.png +0 -0
  44. package/dist/modules/design_system/gallery/assets/crypto-star.svg +3 -0
  45. package/dist/modules/design_system/gallery/assets/crypto-status-cancelled.svg +3 -0
  46. package/dist/modules/design_system/gallery/assets/crypto-status-pending.svg +3 -0
  47. package/dist/modules/design_system/gallery/assets/crypto-status-success.svg +3 -0
  48. package/dist/modules/design_system/gallery/assets/crypto-status-warning.svg +3 -0
  49. package/dist/modules/design_system/gallery/assets/crypto-trx.png +0 -0
  50. package/dist/modules/design_system/gallery/assets/crypto-usdc.png +0 -0
  51. package/dist/modules/design_system/gallery/assets/crypto-usdt.png +0 -0
  52. package/dist/modules/design_system/gallery/assets/crypto-walletconnect.png +0 -0
  53. package/dist/modules/design_system/gallery/assets/crypto-wei.png +0 -0
  54. package/dist/modules/design_system/gallery/assets/crypto-xrp.png +0 -0
  55. package/dist/modules/design_system/gallery/assets/cryptocurrency-artwork.js +67 -0
  56. package/dist/modules/design_system/gallery/assets/cryptocurrency-artwork.js.map +7 -0
  57. package/dist/modules/design_system/gallery/assets/github-logo.png +0 -0
  58. package/dist/modules/design_system/gallery/assets/images.d.js +1 -0
  59. package/dist/modules/design_system/gallery/assets/images.d.js.map +7 -0
  60. package/dist/modules/design_system/gallery/assets/input-european-union.svg +18 -0
  61. package/dist/modules/design_system/gallery/assets/landing-teams-01.png +0 -0
  62. package/dist/modules/design_system/gallery/assets/marketing-artwork.js +83 -0
  63. package/dist/modules/design_system/gallery/assets/marketing-artwork.js.map +7 -0
  64. package/dist/modules/design_system/gallery/assets/menu-apex.svg +18 -0
  65. package/dist/modules/design_system/gallery/assets/menu-mastercard.svg +7 -0
  66. package/dist/modules/design_system/gallery/assets/menu-spotify.svg +4 -0
  67. package/dist/modules/design_system/gallery/assets/menu-united-states.svg +16 -0
  68. package/dist/modules/design_system/gallery/assets/select-google.png +0 -0
  69. package/dist/modules/design_system/gallery/assets/select-poland.png +0 -0
  70. package/dist/modules/design_system/gallery/assets/selection-artwork.js +5 -0
  71. package/dist/modules/design_system/gallery/assets/selection-artwork.js.map +7 -0
  72. package/dist/modules/design_system/gallery/assets/social-apple-filled.svg +4 -0
  73. package/dist/modules/design_system/gallery/assets/social-apple-stroke.svg +4 -0
  74. package/dist/modules/design_system/gallery/assets/social-dropbox-filled.svg +5 -0
  75. package/dist/modules/design_system/gallery/assets/social-dropbox-stroke.svg +5 -0
  76. package/dist/modules/design_system/gallery/assets/social-facebook-filled.svg +4 -0
  77. package/dist/modules/design_system/gallery/assets/social-facebook-stroke.svg +10 -0
  78. package/dist/modules/design_system/gallery/assets/social-github-filled.svg +4 -0
  79. package/dist/modules/design_system/gallery/assets/social-github-stroke.svg +4 -0
  80. package/dist/modules/design_system/gallery/assets/social-google-filled.svg +7 -0
  81. package/dist/modules/design_system/gallery/assets/social-google-stroke.svg +7 -0
  82. package/dist/modules/design_system/gallery/assets/social-linkedin-filled.svg +4 -0
  83. package/dist/modules/design_system/gallery/assets/social-linkedin-stroke.svg +4 -0
  84. package/dist/modules/design_system/gallery/assets/social-x-filled.svg +4 -0
  85. package/dist/modules/design_system/gallery/assets/social-x-stroke.svg +4 -0
  86. package/dist/modules/design_system/gallery/assets/style-studio-product.png +0 -0
  87. package/dist/modules/design_system/gallery/assets/upload-images.js +8 -0
  88. package/dist/modules/design_system/gallery/assets/upload-images.js.map +7 -0
  89. package/dist/modules/design_system/gallery/components/BrandStylePreview.js +74 -0
  90. package/dist/modules/design_system/gallery/components/BrandStylePreview.js.map +7 -0
  91. package/dist/modules/design_system/gallery/components/CodeSnippet.js +31 -31
  92. package/dist/modules/design_system/gallery/components/CodeSnippet.js.map +2 -2
  93. package/dist/modules/design_system/gallery/components/ColorPlayground.js +408 -0
  94. package/dist/modules/design_system/gallery/components/ColorPlayground.js.map +7 -0
  95. package/dist/modules/design_system/gallery/components/ColorStudioCompositions.js +166 -0
  96. package/dist/modules/design_system/gallery/components/ColorStudioCompositions.js.map +7 -0
  97. package/dist/modules/design_system/gallery/components/ColorStudioSpecimen.js +182 -0
  98. package/dist/modules/design_system/gallery/components/ColorStudioSpecimen.js.map +7 -0
  99. package/dist/modules/design_system/gallery/components/CompactEntrySpecimen.js +80 -0
  100. package/dist/modules/design_system/gallery/components/CompactEntrySpecimen.js.map +7 -0
  101. package/dist/modules/design_system/gallery/components/ComponentIndex.js +42 -0
  102. package/dist/modules/design_system/gallery/components/ComponentIndex.js.map +7 -0
  103. package/dist/modules/design_system/gallery/components/DesignSystemHome.js +69 -0
  104. package/dist/modules/design_system/gallery/components/DesignSystemHome.js.map +7 -0
  105. package/dist/modules/design_system/gallery/components/EntryCard.js +47 -41
  106. package/dist/modules/design_system/gallery/components/EntryCard.js.map +2 -2
  107. package/dist/modules/design_system/gallery/components/EntryHeader.js +68 -0
  108. package/dist/modules/design_system/gallery/components/EntryHeader.js.map +7 -0
  109. package/dist/modules/design_system/gallery/components/FamilyEntryPreview.js +19 -0
  110. package/dist/modules/design_system/gallery/components/FamilyEntryPreview.js.map +7 -0
  111. package/dist/modules/design_system/gallery/components/FoundationGuide.js +46 -0
  112. package/dist/modules/design_system/gallery/components/FoundationGuide.js.map +7 -0
  113. package/dist/modules/design_system/gallery/components/GalleryLink.js +21 -0
  114. package/dist/modules/design_system/gallery/components/GalleryLink.js.map +7 -0
  115. package/dist/modules/design_system/gallery/components/GalleryNavigation.js +82 -0
  116. package/dist/modules/design_system/gallery/components/GalleryNavigation.js.map +7 -0
  117. package/dist/modules/design_system/gallery/components/GalleryShell.js +147 -67
  118. package/dist/modules/design_system/gallery/components/GalleryShell.js.map +3 -3
  119. package/dist/modules/design_system/gallery/components/IconsBrowser.js +33 -0
  120. package/dist/modules/design_system/gallery/components/IconsBrowser.js.map +7 -0
  121. package/dist/modules/design_system/gallery/components/SourceLibrary.js +93 -0
  122. package/dist/modules/design_system/gallery/components/SourceLibrary.js.map +7 -0
  123. package/dist/modules/design_system/gallery/components/StyleAgents.js +139 -0
  124. package/dist/modules/design_system/gallery/components/StyleAgents.js.map +7 -0
  125. package/dist/modules/design_system/gallery/components/ThemeTokenReference.js +101 -0
  126. package/dist/modules/design_system/gallery/components/ThemeTokenReference.js.map +7 -0
  127. package/dist/modules/design_system/gallery/components/VariantPreview.js +2 -2
  128. package/dist/modules/design_system/gallery/components/VariantPreview.js.map +2 -2
  129. package/dist/modules/design_system/gallery/components/colorPreview.js +58 -0
  130. package/dist/modules/design_system/gallery/components/colorPreview.js.map +7 -0
  131. package/dist/modules/design_system/gallery/components/colorStudio.js +218 -0
  132. package/dist/modules/design_system/gallery/components/colorStudio.js.map +7 -0
  133. package/dist/modules/design_system/gallery/components/styleAgentBranding.js +46 -0
  134. package/dist/modules/design_system/gallery/components/styleAgentBranding.js.map +7 -0
  135. package/dist/modules/design_system/gallery/components/themeTokens.js +32 -0
  136. package/dist/modules/design_system/gallery/components/themeTokens.js.map +7 -0
  137. package/dist/modules/design_system/gallery/demos/ai-product-code.generated.js +194 -0
  138. package/dist/modules/design_system/gallery/demos/ai-product-code.generated.js.map +7 -0
  139. package/dist/modules/design_system/gallery/demos/ai-product.js +423 -0
  140. package/dist/modules/design_system/gallery/demos/ai-product.js.map +7 -0
  141. package/dist/modules/design_system/gallery/demos/buttons.js +289 -0
  142. package/dist/modules/design_system/gallery/demos/buttons.js.map +7 -0
  143. package/dist/modules/design_system/gallery/demos/cryptocurrency.js +1708 -0
  144. package/dist/modules/design_system/gallery/demos/cryptocurrency.js.map +7 -0
  145. package/dist/modules/design_system/gallery/demos/feeds-code.generated.js +92 -0
  146. package/dist/modules/design_system/gallery/demos/feeds-code.generated.js.map +7 -0
  147. package/dist/modules/design_system/gallery/demos/feeds.js +148 -0
  148. package/dist/modules/design_system/gallery/demos/feeds.js.map +7 -0
  149. package/dist/modules/design_system/gallery/demos/file-upload.js +72 -0
  150. package/dist/modules/design_system/gallery/demos/file-upload.js.map +7 -0
  151. package/dist/modules/design_system/gallery/demos/foundation-reference-data.js +4875 -0
  152. package/dist/modules/design_system/gallery/demos/foundation-reference-data.js.map +7 -0
  153. package/dist/modules/design_system/gallery/demos/foundation-references.js +188 -0
  154. package/dist/modules/design_system/gallery/demos/foundation-references.js.map +7 -0
  155. package/dist/modules/design_system/gallery/demos/key-components-code.generated.js +6 -0
  156. package/dist/modules/design_system/gallery/demos/key-components-code.generated.js.map +7 -0
  157. package/dist/modules/design_system/gallery/demos/key-components.js +127 -0
  158. package/dist/modules/design_system/gallery/demos/key-components.js.map +7 -0
  159. package/dist/modules/design_system/gallery/demos/marketing-controls-code.generated.js +98 -0
  160. package/dist/modules/design_system/gallery/demos/marketing-controls-code.generated.js.map +7 -0
  161. package/dist/modules/design_system/gallery/demos/marketing-controls.js +134 -0
  162. package/dist/modules/design_system/gallery/demos/marketing-controls.js.map +7 -0
  163. package/dist/modules/design_system/gallery/demos/marketing-widgets-code.generated.js +195 -0
  164. package/dist/modules/design_system/gallery/demos/marketing-widgets-code.generated.js.map +7 -0
  165. package/dist/modules/design_system/gallery/demos/marketing-widgets.js +586 -0
  166. package/dist/modules/design_system/gallery/demos/marketing-widgets.js.map +7 -0
  167. package/dist/modules/design_system/gallery/demos/menu-fixtures.js +40 -0
  168. package/dist/modules/design_system/gallery/demos/menu-fixtures.js.map +7 -0
  169. package/dist/modules/design_system/gallery/demos/menu-sources.js +295 -0
  170. package/dist/modules/design_system/gallery/demos/menu-sources.js.map +7 -0
  171. package/dist/modules/design_system/gallery/demos/menus.js +150 -0
  172. package/dist/modules/design_system/gallery/demos/menus.js.map +7 -0
  173. package/dist/modules/design_system/gallery/demos/rating-reviews.js +53 -0
  174. package/dist/modules/design_system/gallery/demos/rating-reviews.js.map +7 -0
  175. package/dist/modules/design_system/gallery/demos/registered-icons.js +104 -0
  176. package/dist/modules/design_system/gallery/demos/registered-icons.js.map +7 -0
  177. package/dist/modules/design_system/gallery/demos/selection-cards.js +99 -0
  178. package/dist/modules/design_system/gallery/demos/selection-cards.js.map +7 -0
  179. package/dist/modules/design_system/gallery/demos/shell-code.generated.js +189 -0
  180. package/dist/modules/design_system/gallery/demos/shell-code.generated.js.map +7 -0
  181. package/dist/modules/design_system/gallery/demos/shell.js +330 -0
  182. package/dist/modules/design_system/gallery/demos/shell.js.map +7 -0
  183. package/dist/modules/design_system/gallery/demos/source-artwork.js +120 -0
  184. package/dist/modules/design_system/gallery/demos/source-artwork.js.map +7 -0
  185. package/dist/modules/design_system/gallery/demos/source-filters-code.generated.js +125 -0
  186. package/dist/modules/design_system/gallery/demos/source-filters-code.generated.js.map +7 -0
  187. package/dist/modules/design_system/gallery/demos/source-filters.js +185 -0
  188. package/dist/modules/design_system/gallery/demos/source-filters.js.map +7 -0
  189. package/dist/modules/design_system/gallery/demos/source-icons.js +69 -0
  190. package/dist/modules/design_system/gallery/demos/source-icons.js.map +7 -0
  191. package/dist/modules/design_system/gallery/demos/table-cells-code.generated.js +150 -0
  192. package/dist/modules/design_system/gallery/demos/table-cells-code.generated.js.map +7 -0
  193. package/dist/modules/design_system/gallery/demos/table-cells.js +181 -0
  194. package/dist/modules/design_system/gallery/demos/table-cells.js.map +7 -0
  195. package/dist/modules/design_system/gallery/demos/text-inputs.js +113 -0
  196. package/dist/modules/design_system/gallery/demos/text-inputs.js.map +7 -0
  197. package/dist/modules/design_system/gallery/demos/time-picker-code.generated.js +41 -0
  198. package/dist/modules/design_system/gallery/demos/time-picker-code.generated.js.map +7 -0
  199. package/dist/modules/design_system/gallery/demos/time-picker.js +41 -0
  200. package/dist/modules/design_system/gallery/demos/time-picker.js.map +7 -0
  201. package/dist/modules/design_system/gallery/entries/banners.js +123 -60
  202. package/dist/modules/design_system/gallery/entries/banners.js.map +2 -2
  203. package/dist/modules/design_system/gallery/entries/buttons-snippets.js +433 -0
  204. package/dist/modules/design_system/gallery/entries/buttons-snippets.js.map +7 -0
  205. package/dist/modules/design_system/gallery/entries/buttons.js +127 -148
  206. package/dist/modules/design_system/gallery/entries/buttons.js.map +2 -2
  207. package/dist/modules/design_system/gallery/entries/charts.js +532 -186
  208. package/dist/modules/design_system/gallery/entries/charts.js.map +2 -2
  209. package/dist/modules/design_system/gallery/entries/dates.js +191 -3
  210. package/dist/modules/design_system/gallery/entries/dates.js.map +2 -2
  211. package/dist/modules/design_system/gallery/entries/detail.js +118 -77
  212. package/dist/modules/design_system/gallery/entries/detail.js.map +2 -2
  213. package/dist/modules/design_system/gallery/entries/display.js +1449 -240
  214. package/dist/modules/design_system/gallery/entries/display.js.map +2 -2
  215. package/dist/modules/design_system/gallery/entries/feedback.js +1058 -132
  216. package/dist/modules/design_system/gallery/entries/feedback.js.map +2 -2
  217. package/dist/modules/design_system/gallery/entries/filters.js +231 -103
  218. package/dist/modules/design_system/gallery/entries/filters.js.map +2 -2
  219. package/dist/modules/design_system/gallery/entries/foundations.js +368 -57
  220. package/dist/modules/design_system/gallery/entries/foundations.js.map +2 -2
  221. package/dist/modules/design_system/gallery/entries/icons.js +29 -91
  222. package/dist/modules/design_system/gallery/entries/icons.js.map +2 -2
  223. package/dist/modules/design_system/gallery/entries/inputs-snippets.js +154 -0
  224. package/dist/modules/design_system/gallery/entries/inputs-snippets.js.map +7 -0
  225. package/dist/modules/design_system/gallery/entries/inputs.js +2043 -306
  226. package/dist/modules/design_system/gallery/entries/inputs.js.map +2 -2
  227. package/dist/modules/design_system/gallery/entries/library.js +20 -0
  228. package/dist/modules/design_system/gallery/entries/library.js.map +7 -0
  229. package/dist/modules/design_system/gallery/entries/messages.js +121 -103
  230. package/dist/modules/design_system/gallery/entries/messages.js.map +2 -2
  231. package/dist/modules/design_system/gallery/entries/navigation.js +676 -103
  232. package/dist/modules/design_system/gallery/entries/navigation.js.map +2 -2
  233. package/dist/modules/design_system/gallery/entries/notifications.js +131 -78
  234. package/dist/modules/design_system/gallery/entries/notifications.js.map +2 -2
  235. package/dist/modules/design_system/gallery/entries/overlays.js +611 -203
  236. package/dist/modules/design_system/gallery/entries/overlays.js.map +2 -2
  237. package/dist/modules/design_system/gallery/entries/scaffolding.js +1175 -120
  238. package/dist/modules/design_system/gallery/entries/scaffolding.js.map +2 -2
  239. package/dist/modules/design_system/gallery/entries/schedule.js +79 -54
  240. package/dist/modules/design_system/gallery/entries/schedule.js.map +2 -2
  241. package/dist/modules/design_system/gallery/presentation.js +108 -0
  242. package/dist/modules/design_system/gallery/presentation.js.map +7 -0
  243. package/dist/modules/design_system/gallery/registry.js +8 -5
  244. package/dist/modules/design_system/gallery/registry.js.map +2 -2
  245. package/dist/modules/design_system/gallery/source-library.generated.js +5 -0
  246. package/dist/modules/design_system/gallery/source-library.generated.js.map +7 -0
  247. package/dist/modules/design_system/i18n/de.json +7167 -0
  248. package/dist/modules/design_system/i18n/en.json +7167 -0
  249. package/dist/modules/design_system/i18n/es.json +7167 -0
  250. package/dist/modules/design_system/i18n/ko.json +7167 -0
  251. package/dist/modules/design_system/i18n/pl.json +7167 -0
  252. package/dist/modules/push_notifications/lib/queue.js +1 -31
  253. package/dist/modules/push_notifications/lib/queue.js.map +2 -2
  254. package/jest.config.cjs +1 -0
  255. package/jest.mocks/image.js +1 -0
  256. package/package.json +8 -8
  257. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +50 -0
  258. package/src/modules/customers/backend/customers/companies/[id]/page.tsx +2 -1
  259. package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +2 -1
  260. package/src/modules/customers/backend/customers/people/[id]/page.tsx +2 -2
  261. package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +2 -1
  262. package/src/modules/customers/lib/detailHelpers.ts +4 -0
  263. package/src/modules/customers/lib/todoCompatibility.ts +10 -0
  264. package/src/modules/design_system/backend/design-system/page.meta.ts +3 -2
  265. package/src/modules/design_system/gallery/assets/README.md +30 -0
  266. package/src/modules/design_system/gallery/assets/ai-prompt-image.png +0 -0
  267. package/src/modules/design_system/gallery/assets/avatar-illustration.png +0 -0
  268. package/src/modules/design_system/gallery/assets/avatar-memoji.png +0 -0
  269. package/src/modules/design_system/gallery/assets/avatar-photo.png +0 -0
  270. package/src/modules/design_system/gallery/assets/blog-photo-0.png +0 -0
  271. package/src/modules/design_system/gallery/assets/contact-support-portrait.png +0 -0
  272. package/src/modules/design_system/gallery/assets/crypto-ada.png +0 -0
  273. package/src/modules/design_system/gallery/assets/crypto-avax.png +0 -0
  274. package/src/modules/design_system/gallery/assets/crypto-bnb.png +0 -0
  275. package/src/modules/design_system/gallery/assets/crypto-brand.png +0 -0
  276. package/src/modules/design_system/gallery/assets/crypto-btc.png +0 -0
  277. package/src/modules/design_system/gallery/assets/crypto-clock.svg +3 -0
  278. package/src/modules/design_system/gallery/assets/crypto-crown.svg +3 -0
  279. package/src/modules/design_system/gallery/assets/crypto-dot.png +0 -0
  280. package/src/modules/design_system/gallery/assets/crypto-eth.png +0 -0
  281. package/src/modules/design_system/gallery/assets/crypto-hbar.png +0 -0
  282. package/src/modules/design_system/gallery/assets/crypto-history.svg +3 -0
  283. package/src/modules/design_system/gallery/assets/crypto-link.svg +3 -0
  284. package/src/modules/design_system/gallery/assets/crypto-menu.svg +3 -0
  285. package/src/modules/design_system/gallery/assets/crypto-metamask.png +0 -0
  286. package/src/modules/design_system/gallery/assets/crypto-more.svg +3 -0
  287. package/src/modules/design_system/gallery/assets/crypto-phantom.png +0 -0
  288. package/src/modules/design_system/gallery/assets/crypto-rewards.svg +3 -0
  289. package/src/modules/design_system/gallery/assets/crypto-sad-face.png +0 -0
  290. package/src/modules/design_system/gallery/assets/crypto-sol.png +0 -0
  291. package/src/modules/design_system/gallery/assets/crypto-star.svg +3 -0
  292. package/src/modules/design_system/gallery/assets/crypto-status-cancelled.svg +3 -0
  293. package/src/modules/design_system/gallery/assets/crypto-status-pending.svg +3 -0
  294. package/src/modules/design_system/gallery/assets/crypto-status-success.svg +3 -0
  295. package/src/modules/design_system/gallery/assets/crypto-status-warning.svg +3 -0
  296. package/src/modules/design_system/gallery/assets/crypto-trx.png +0 -0
  297. package/src/modules/design_system/gallery/assets/crypto-usdc.png +0 -0
  298. package/src/modules/design_system/gallery/assets/crypto-usdt.png +0 -0
  299. package/src/modules/design_system/gallery/assets/crypto-walletconnect.png +0 -0
  300. package/src/modules/design_system/gallery/assets/crypto-wei.png +0 -0
  301. package/src/modules/design_system/gallery/assets/crypto-xrp.png +0 -0
  302. package/src/modules/design_system/gallery/assets/cryptocurrency-artwork.ts +63 -0
  303. package/src/modules/design_system/gallery/assets/github-logo.png +0 -0
  304. package/src/modules/design_system/gallery/assets/images.d.ts +9 -0
  305. package/src/modules/design_system/gallery/assets/input-european-union.svg +18 -0
  306. package/src/modules/design_system/gallery/assets/landing-teams-01.png +0 -0
  307. package/src/modules/design_system/gallery/assets/marketing-artwork.ts +79 -0
  308. package/src/modules/design_system/gallery/assets/menu-apex.svg +18 -0
  309. package/src/modules/design_system/gallery/assets/menu-mastercard.svg +7 -0
  310. package/src/modules/design_system/gallery/assets/menu-spotify.svg +4 -0
  311. package/src/modules/design_system/gallery/assets/menu-united-states.svg +16 -0
  312. package/src/modules/design_system/gallery/assets/select-google.png +0 -0
  313. package/src/modules/design_system/gallery/assets/select-poland.png +0 -0
  314. package/src/modules/design_system/gallery/assets/selection-artwork.ts +1 -0
  315. package/src/modules/design_system/gallery/assets/social-apple-filled.svg +4 -0
  316. package/src/modules/design_system/gallery/assets/social-apple-stroke.svg +4 -0
  317. package/src/modules/design_system/gallery/assets/social-dropbox-filled.svg +5 -0
  318. package/src/modules/design_system/gallery/assets/social-dropbox-stroke.svg +5 -0
  319. package/src/modules/design_system/gallery/assets/social-facebook-filled.svg +4 -0
  320. package/src/modules/design_system/gallery/assets/social-facebook-stroke.svg +10 -0
  321. package/src/modules/design_system/gallery/assets/social-github-filled.svg +4 -0
  322. package/src/modules/design_system/gallery/assets/social-github-stroke.svg +4 -0
  323. package/src/modules/design_system/gallery/assets/social-google-filled.svg +7 -0
  324. package/src/modules/design_system/gallery/assets/social-google-stroke.svg +7 -0
  325. package/src/modules/design_system/gallery/assets/social-linkedin-filled.svg +4 -0
  326. package/src/modules/design_system/gallery/assets/social-linkedin-stroke.svg +4 -0
  327. package/src/modules/design_system/gallery/assets/social-x-filled.svg +4 -0
  328. package/src/modules/design_system/gallery/assets/social-x-stroke.svg +4 -0
  329. package/src/modules/design_system/gallery/assets/style-studio-product.png +0 -0
  330. package/src/modules/design_system/gallery/assets/upload-images.ts +5 -0
  331. package/src/modules/design_system/gallery/components/BrandStylePreview.tsx +94 -0
  332. package/src/modules/design_system/gallery/components/CodeSnippet.tsx +34 -34
  333. package/src/modules/design_system/gallery/components/ColorPlayground.tsx +324 -0
  334. package/src/modules/design_system/gallery/components/ColorStudioCompositions.tsx +96 -0
  335. package/src/modules/design_system/gallery/components/ColorStudioSpecimen.tsx +164 -0
  336. package/src/modules/design_system/gallery/components/CompactEntrySpecimen.tsx +65 -0
  337. package/src/modules/design_system/gallery/components/ComponentIndex.tsx +47 -0
  338. package/src/modules/design_system/gallery/components/DesignSystemHome.tsx +66 -0
  339. package/src/modules/design_system/gallery/components/EntryCard.tsx +52 -37
  340. package/src/modules/design_system/gallery/components/EntryHeader.tsx +80 -0
  341. package/src/modules/design_system/gallery/components/FamilyEntryPreview.tsx +22 -0
  342. package/src/modules/design_system/gallery/components/FoundationGuide.tsx +43 -0
  343. package/src/modules/design_system/gallery/components/GalleryLink.tsx +19 -0
  344. package/src/modules/design_system/gallery/components/GalleryNavigation.tsx +86 -0
  345. package/src/modules/design_system/gallery/components/GalleryShell.tsx +149 -63
  346. package/src/modules/design_system/gallery/components/IconsBrowser.tsx +40 -0
  347. package/src/modules/design_system/gallery/components/SourceLibrary.tsx +90 -0
  348. package/src/modules/design_system/gallery/components/StyleAgents.tsx +115 -0
  349. package/src/modules/design_system/gallery/components/ThemeTokenReference.tsx +76 -0
  350. package/src/modules/design_system/gallery/components/VariantPreview.tsx +2 -2
  351. package/src/modules/design_system/gallery/components/colorPreview.ts +63 -0
  352. package/src/modules/design_system/gallery/components/colorStudio.ts +260 -0
  353. package/src/modules/design_system/gallery/components/styleAgentBranding.ts +42 -0
  354. package/src/modules/design_system/gallery/components/themeTokens.ts +26 -0
  355. package/src/modules/design_system/gallery/demos/ai-product-code.generated.ts +3 -0
  356. package/src/modules/design_system/gallery/demos/ai-product.tsx +188 -0
  357. package/src/modules/design_system/gallery/demos/buttons.tsx +331 -0
  358. package/src/modules/design_system/gallery/demos/cryptocurrency.tsx +1943 -0
  359. package/src/modules/design_system/gallery/demos/feeds-code.generated.ts +4 -0
  360. package/src/modules/design_system/gallery/demos/feeds.tsx +82 -0
  361. package/src/modules/design_system/gallery/demos/file-upload.tsx +61 -0
  362. package/src/modules/design_system/gallery/demos/foundation-reference-data.ts +4872 -0
  363. package/src/modules/design_system/gallery/demos/foundation-references.tsx +140 -0
  364. package/src/modules/design_system/gallery/demos/key-components-code.generated.ts +3 -0
  365. package/src/modules/design_system/gallery/demos/key-components.tsx +106 -0
  366. package/src/modules/design_system/gallery/demos/marketing-controls-code.generated.ts +3 -0
  367. package/src/modules/design_system/gallery/demos/marketing-controls.tsx +92 -0
  368. package/src/modules/design_system/gallery/demos/marketing-widgets-code.generated.ts +3 -0
  369. package/src/modules/design_system/gallery/demos/marketing-widgets.tsx +189 -0
  370. package/src/modules/design_system/gallery/demos/menu-fixtures.tsx +48 -0
  371. package/src/modules/design_system/gallery/demos/menu-sources.ts +5 -0
  372. package/src/modules/design_system/gallery/demos/menus.tsx +122 -0
  373. package/src/modules/design_system/gallery/demos/rating-reviews.tsx +44 -0
  374. package/src/modules/design_system/gallery/demos/registered-icons.tsx +117 -0
  375. package/src/modules/design_system/gallery/demos/selection-cards.tsx +97 -0
  376. package/src/modules/design_system/gallery/demos/shell-code.generated.ts +3 -0
  377. package/src/modules/design_system/gallery/demos/shell.tsx +183 -0
  378. package/src/modules/design_system/gallery/demos/source-artwork.tsx +88 -0
  379. package/src/modules/design_system/gallery/demos/source-filters-code.generated.ts +3 -0
  380. package/src/modules/design_system/gallery/demos/source-filters.tsx +119 -0
  381. package/src/modules/design_system/gallery/demos/source-icons.tsx +62 -0
  382. package/src/modules/design_system/gallery/demos/table-cells-code.generated.ts +3 -0
  383. package/src/modules/design_system/gallery/demos/table-cells.tsx +144 -0
  384. package/src/modules/design_system/gallery/demos/text-inputs.tsx +90 -0
  385. package/src/modules/design_system/gallery/demos/time-picker-code.generated.ts +5 -0
  386. package/src/modules/design_system/gallery/demos/time-picker.tsx +31 -0
  387. package/src/modules/design_system/gallery/entries/banners.tsx +143 -94
  388. package/src/modules/design_system/gallery/entries/buttons-snippets.ts +432 -0
  389. package/src/modules/design_system/gallery/entries/buttons.tsx +125 -196
  390. package/src/modules/design_system/gallery/entries/charts.tsx +545 -233
  391. package/src/modules/design_system/gallery/entries/dates.tsx +171 -3
  392. package/src/modules/design_system/gallery/entries/detail.tsx +142 -115
  393. package/src/modules/design_system/gallery/entries/display.tsx +1691 -319
  394. package/src/modules/design_system/gallery/entries/feedback.tsx +1082 -169
  395. package/src/modules/design_system/gallery/entries/filters.tsx +238 -143
  396. package/src/modules/design_system/gallery/entries/foundations.tsx +404 -61
  397. package/src/modules/design_system/gallery/entries/icons.tsx +31 -117
  398. package/src/modules/design_system/gallery/entries/inputs-snippets.ts +156 -0
  399. package/src/modules/design_system/gallery/entries/inputs.tsx +2146 -503
  400. package/src/modules/design_system/gallery/entries/library.tsx +16 -0
  401. package/src/modules/design_system/gallery/entries/messages.tsx +114 -106
  402. package/src/modules/design_system/gallery/entries/navigation.tsx +782 -192
  403. package/src/modules/design_system/gallery/entries/notifications.tsx +145 -120
  404. package/src/modules/design_system/gallery/entries/overlays.tsx +696 -280
  405. package/src/modules/design_system/gallery/entries/scaffolding.tsx +1192 -158
  406. package/src/modules/design_system/gallery/entries/schedule.tsx +72 -58
  407. package/src/modules/design_system/gallery/presentation.ts +106 -0
  408. package/src/modules/design_system/gallery/registry.ts +8 -6
  409. package/src/modules/design_system/gallery/source-library.generated.ts +2 -0
  410. package/src/modules/design_system/i18n/de.json +7112 -3
  411. package/src/modules/design_system/i18n/en.json +7112 -3
  412. package/src/modules/design_system/i18n/es.json +7112 -3
  413. package/src/modules/design_system/i18n/ko.json +7112 -3
  414. package/src/modules/design_system/i18n/pl.json +7112 -3
  415. package/src/modules/push_notifications/AGENTS.md +9 -5
  416. package/src/modules/push_notifications/lib/queue.ts +15 -44
@@ -0,0 +1,1943 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import { Command } from 'cmdk'
5
+ import {
6
+ ArrowDown,
7
+ ArrowDownUp,
8
+ ArrowUpRight,
9
+ Bell,
10
+ Check,
11
+ ChevronDown,
12
+ ChevronRight,
13
+ Copy,
14
+ Gift,
15
+ House,
16
+ Info,
17
+ LoaderCircle,
18
+ LogOut,
19
+ MessageSquare,
20
+ Moon,
21
+ Search,
22
+ Settings,
23
+ Share2,
24
+ ShieldCheck,
25
+ SlidersHorizontal,
26
+ Star,
27
+ X,
28
+ } from 'lucide-react'
29
+ import { useT, type TranslateParams } from '@open-mercato/shared/lib/i18n/context'
30
+ import { cn } from '@open-mercato/shared/lib/utils'
31
+ import { Avatar } from '@open-mercato/ui/primitives/avatar'
32
+ import { Button } from '@open-mercato/ui/primitives/button'
33
+ import { Card } from '@open-mercato/ui/primitives/card'
34
+ import { Checkbox } from '@open-mercato/ui/primitives/checkbox'
35
+ import {
36
+ CommandMenuInput,
37
+ CommandMenuList,
38
+ CommandMenuItem,
39
+ CommandMenuEmpty,
40
+ CommandMenuFooter,
41
+ } from '@open-mercato/ui/primitives/command-menu'
42
+ import { Dialog, DialogContent, DialogTitle, DialogDescription } from '@open-mercato/ui/primitives/dialog'
43
+ import { Input } from '@open-mercato/ui/primitives/input'
44
+ import { Popover, PopoverContent, PopoverTrigger } from '@open-mercato/ui/primitives/popover'
45
+ import { SearchInput } from '@open-mercato/ui/primitives/search-input'
46
+ import { SegmentedControl, SegmentedControlItem } from '@open-mercato/ui/primitives/segmented-control'
47
+ import { Switch } from '@open-mercato/ui/primitives/switch'
48
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from '@open-mercato/ui/primitives/tabs'
49
+ import { cryptoArtwork } from '../assets/cryptocurrency-artwork'
50
+
51
+ export type CryptoPreviewState =
52
+ | 'default'
53
+ | 'hover'
54
+ | 'active'
55
+ | 'filled'
56
+ | 'disabled'
57
+ | 'error'
58
+ | 'selected'
59
+ | 'searching'
60
+ | 'empty'
61
+ export type CryptoPart =
62
+ | 'status-icon'
63
+ | 'token-icon'
64
+ | 'navigation-item'
65
+ | 'navigation-search'
66
+ | 'navigation-wallet'
67
+ | 'navigation-action'
68
+ | 'chart-item'
69
+ | 'swap-select'
70
+ | 'chart-switch'
71
+ | 'table-button'
72
+ | 'table-switch-item'
73
+ | 'table-tab'
74
+ | 'table-filter'
75
+ | 'table-switch'
76
+ | 'swap-input'
77
+ | 'swap'
78
+ | 'search-modal'
79
+ | 'search-input'
80
+ | 'navigation'
81
+ | 'social-button'
82
+ | 'menu-dropdown'
83
+ | 'favorites-dropdown'
84
+ | 'profile-dropdown'
85
+ | 'wallet-dropdown'
86
+ | 'swap-token-dropdown'
87
+ | 'notifications-dropdown'
88
+ | 'connect-wallet'
89
+ | 'banner'
90
+ | 'filter-type'
91
+ | 'filter-date'
92
+ | 'filter-status'
93
+ | 'filter-token'
94
+ | 'mobile-navigation'
95
+ | 'mobile-bottom-navigation'
96
+ export type CryptocurrencyDemoProps = {
97
+ part: CryptoPart
98
+ state?: CryptoPreviewState
99
+ kind?: string
100
+ size?: 24 | 28
101
+ onlyIcon?: boolean
102
+ profile?: boolean
103
+ step?: 1 | 2 | 3
104
+ }
105
+
106
+ const useCryptoLabels = () => {
107
+ const t = useT()
108
+ return (key: string, params?: TranslateParams) => t(`design_system.gallery.samples.crypto.${key}`, params)
109
+ }
110
+
111
+ const tokenFixtures = [
112
+ { symbol: 'btc', name: 'Bitcoin', price: 116236.51, change: 2.5, balance: 0.0095 },
113
+ { symbol: 'eth', name: 'Ethereum', price: 2348, change: 1.3, balance: 242.4 },
114
+ { symbol: 'xrp', name: 'XRP', price: 1.55, change: -0.2, balance: 510.82 },
115
+ { symbol: 'usdt', name: 'TetherUS', price: 1, change: 0, balance: 125.63 },
116
+ { symbol: 'trx', name: 'Tron', price: 123.08, change: -1.1, balance: 0 },
117
+ { symbol: 'sol', name: 'Solana', price: 765.12, change: -2.3, balance: 0 },
118
+ { symbol: 'usdc', name: 'USDC', price: 1, change: 0, balance: 100 },
119
+ { symbol: 'bnb', name: 'BNB', price: 456.18, change: -1.5, balance: 0 },
120
+ { symbol: 'avax', name: 'Avalanche', price: 789.7, change: -2, balance: 0 },
121
+ { symbol: 'ada', name: 'Cardano', price: 101.26, change: 0.4, balance: 0 },
122
+ { symbol: 'hbar', name: 'Hedera', price: 0.18, change: 0.1, balance: 0 },
123
+ { symbol: 'dot', name: 'Polkadot', price: 4.2, change: -0.4, balance: 0 },
124
+ ] as const
125
+
126
+ type CryptoToken = (typeof tokenFixtures)[number]['symbol']
127
+ const tokenBySymbol = (symbol: string) => tokenFixtures.find((token) => token.symbol === symbol) ?? tokenFixtures[0]
128
+ const formatAmount = (amount: number, maximumFractionDigits = 2) =>
129
+ new Intl.NumberFormat('en-US', { maximumFractionDigits }).format(amount)
130
+ const money = (amount: number) =>
131
+ `$${new Intl.NumberFormat('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(amount)}`
132
+ const greenButton =
133
+ 'rounded-full bg-status-success-solid text-status-success-solid-foreground hover:bg-status-success-solid/90'
134
+ const pill = 'rounded-full text-muted-foreground hover:bg-muted hover:text-foreground'
135
+ const previewSurface = (state: CryptoPreviewState) =>
136
+ (state === 'hover' || state === 'active') && 'bg-muted text-foreground'
137
+
138
+ function CryptoGlyph({
139
+ name,
140
+ className,
141
+ }: {
142
+ name: 'crown' | 'star' | 'menu' | 'history' | 'clock' | 'rewards' | 'link' | 'more'
143
+ className?: string
144
+ }) {
145
+ return (
146
+ <span
147
+ aria-hidden="true"
148
+ className={cn('inline-block size-4.5 shrink-0 bg-current', className)}
149
+ style={{
150
+ maskImage: `url(${cryptoArtwork[name]})`,
151
+ WebkitMaskImage: `url(${cryptoArtwork[name]})`,
152
+ maskSize: 'contain',
153
+ WebkitMaskSize: 'contain',
154
+ maskRepeat: 'no-repeat',
155
+ WebkitMaskRepeat: 'no-repeat',
156
+ maskPosition: 'center',
157
+ WebkitMaskPosition: 'center',
158
+ }}
159
+ />
160
+ )
161
+ }
162
+
163
+ function TokenIcon({ symbol, className }: { symbol: CryptoToken; className?: string }) {
164
+ return <img src={cryptoArtwork[symbol]} alt="" className={cn('size-4 shrink-0 rounded-full', className)} />
165
+ }
166
+
167
+ function CryptoPanel({ children, className }: { children: React.ReactNode; className?: string }) {
168
+ return (
169
+ <Card
170
+ className={cn(
171
+ 'gap-0 overflow-hidden rounded-2xl border-0 bg-background p-0 shadow-md ring-1 ring-inset ring-border',
172
+ className,
173
+ )}
174
+ >
175
+ {children}
176
+ </Card>
177
+ )
178
+ }
179
+
180
+ function CryptoNotice({ message }: { message: string }) {
181
+ return message ? (
182
+ <p role="status" className="px-4 py-2 text-xs text-status-success-text">
183
+ {message}
184
+ </p>
185
+ ) : null
186
+ }
187
+
188
+ function CryptoNavigationItem({
189
+ state = 'default',
190
+ size = 28,
191
+ label,
192
+ onClick,
193
+ pressed,
194
+ }: {
195
+ state?: CryptoPreviewState
196
+ size?: 24 | 28
197
+ label?: string
198
+ onClick?: () => void
199
+ pressed?: boolean
200
+ }) {
201
+ const cryptoLabel = useCryptoLabels()
202
+ const [selected, setSelected] = React.useState(state === 'active')
203
+ return (
204
+ <Button
205
+ type="button"
206
+ variant="ghost"
207
+ size="2xs"
208
+ aria-pressed={pressed ?? selected}
209
+ onClick={() => {
210
+ setSelected(!selected)
211
+ onClick?.()
212
+ }}
213
+ className={cn(
214
+ pill,
215
+ 'px-3 py-0.5 font-medium',
216
+ size === 24 ? 'h-6 text-compact' : 'h-7 text-sm',
217
+ previewSurface(state),
218
+ (pressed ?? selected) && 'bg-muted text-foreground',
219
+ )}
220
+ >
221
+ {label ?? cryptoLabel('overview')}
222
+ </Button>
223
+ )
224
+ }
225
+
226
+ function CryptoSegments({
227
+ type = 'chart',
228
+ initialValue,
229
+ size = 24,
230
+ itemOnly = false,
231
+ state = 'default',
232
+ }: {
233
+ type?: 'chart' | 'table'
234
+ initialValue?: string
235
+ size?: 24 | 28
236
+ itemOnly?: boolean
237
+ state?: CryptoPreviewState
238
+ }) {
239
+ const cryptoLabel = useCryptoLabels()
240
+ const values = type === 'chart' ? ['1D', '1W', '1M', '3M', '1Y'] : ['compact', 'detailed']
241
+ const [value, setValue] = React.useState(initialValue ?? (itemOnly && state !== 'active' ? '' : values[0]))
242
+ return (
243
+ <SegmentedControl
244
+ size="sm"
245
+ value={value}
246
+ onValueChange={setValue}
247
+ aria-label={cryptoLabel(type === 'chart' ? 'chartRange' : 'tableDensity')}
248
+ className={cn(
249
+ 'border-0 bg-muted',
250
+ type === 'chart' ? 'h-6 p-0.75' : 'p-0',
251
+ type === 'table' && (size === 28 ? 'h-7' : 'h-6'),
252
+ itemOnly && 'h-auto p-0',
253
+ )}
254
+ >
255
+ {(itemOnly ? [values[0]] : values).map((option) => (
256
+ <SegmentedControlItem
257
+ key={option}
258
+ value={option}
259
+ className={cn(
260
+ 'px-0 text-xs leading-4 font-medium data-[state=checked]:font-medium',
261
+ type === 'chart' ? 'h-4.5 w-10' : size === 28 ? 'h-7 w-20' : 'h-6 w-20',
262
+ previewSurface(state),
263
+ )}
264
+ >
265
+ {type === 'chart' ? option : cryptoLabel(option)}
266
+ </SegmentedControlItem>
267
+ ))}
268
+ </SegmentedControl>
269
+ )
270
+ }
271
+
272
+ function CryptoTokenList({
273
+ onSelect,
274
+ favorites,
275
+ onFavorite,
276
+ showPrices = false,
277
+ query = '',
278
+ limit = 10,
279
+ }: {
280
+ onSelect?: (token: CryptoToken) => void
281
+ favorites?: readonly string[]
282
+ onFavorite?: (token: CryptoToken) => void
283
+ showPrices?: boolean
284
+ query?: string
285
+ limit?: number
286
+ }) {
287
+ const cryptoLabel = useCryptoLabels()
288
+ const matches = tokenFixtures
289
+ .slice(0, limit)
290
+ .filter((token) => `${token.name} ${token.symbol}`.toLowerCase().includes(query.toLowerCase()))
291
+ return (
292
+ <div className="min-h-0 overflow-auto p-2" data-slot="crypto-token-list">
293
+ {matches.length === 0 ? (
294
+ <div className="p-6 text-center text-sm text-muted-foreground">{cryptoLabel('noTokens')}</div>
295
+ ) : (
296
+ matches.map((token) => (
297
+ <div key={token.symbol} className="flex items-center gap-1 rounded-xl hover:bg-muted">
298
+ <Button
299
+ type="button"
300
+ variant="ghost"
301
+ size="sm"
302
+ onClick={() => onSelect?.(token.symbol)}
303
+ className="h-10 min-w-0 flex-1 justify-start gap-2 rounded-xl px-2 text-compact font-medium"
304
+ aria-label={token.name}
305
+ >
306
+ <TokenIcon symbol={token.symbol} className="size-4.5" />
307
+ <span className="truncate">
308
+ {token.name}{' '}
309
+ <span className="font-mono text-xs text-muted-foreground">{`{${token.symbol.toUpperCase()}}`}</span>
310
+ </span>
311
+ {showPrices && (
312
+ <span className="ml-auto flex shrink-0 items-center gap-2">
313
+ <span className="font-mono text-sm">${formatAmount(token.price)}</span>
314
+ <span
315
+ className={cn(
316
+ 'text-xs',
317
+ token.change < 0
318
+ ? 'text-status-error-text'
319
+ : token.change > 0
320
+ ? 'text-status-success-text'
321
+ : 'text-muted-foreground',
322
+ )}
323
+ >
324
+ {token.change > 0 ? '+' : ''}
325
+ {token.change}%
326
+ </span>
327
+ </span>
328
+ )}
329
+ </Button>
330
+ {onFavorite && (
331
+ <Button
332
+ type="button"
333
+ variant="ghost"
334
+ size="2xs"
335
+ className="size-7 rounded-full p-1"
336
+ aria-label={cryptoLabel(favorites?.includes(token.symbol) ? 'removeFavorite' : 'addFavorite', {
337
+ token: token.name,
338
+ })}
339
+ aria-pressed={favorites?.includes(token.symbol)}
340
+ onClick={() => onFavorite(token.symbol)}
341
+ >
342
+ <Star
343
+ className={cn(
344
+ 'size-4',
345
+ favorites?.includes(token.symbol)
346
+ ? 'fill-status-warning-icon text-status-warning-icon'
347
+ : 'text-muted-foreground',
348
+ )}
349
+ />
350
+ </Button>
351
+ )}
352
+ </div>
353
+ ))
354
+ )}
355
+ </div>
356
+ )
357
+ }
358
+
359
+ function CryptoTokenPicker({
360
+ value = 'btc',
361
+ onValueChange,
362
+ appearance = 'lighter',
363
+ state = 'default',
364
+ disabled,
365
+ }: {
366
+ value?: CryptoToken
367
+ onValueChange?: (token: CryptoToken) => void
368
+ appearance?: string
369
+ state?: CryptoPreviewState
370
+ disabled?: boolean
371
+ }) {
372
+ const cryptoLabel = useCryptoLabels()
373
+ const [selection, setSelection] = React.useState(value)
374
+ const [open, setOpen] = React.useState(false)
375
+ const [query, setQuery] = React.useState('')
376
+ React.useEffect(() => setSelection(value), [value])
377
+ return (
378
+ <Popover open={open} onOpenChange={setOpen}>
379
+ <PopoverTrigger asChild>
380
+ <Button
381
+ type="button"
382
+ variant="ghost"
383
+ size="2xs"
384
+ disabled={disabled}
385
+ aria-label={cryptoLabel('selectToken')}
386
+ className={cn(
387
+ pill,
388
+ 'h-7 gap-1 px-1.5 font-mono text-sm',
389
+ appearance === 'stroke' ? 'border border-border bg-background' : 'bg-muted',
390
+ previewSurface(state),
391
+ )}
392
+ >
393
+ <TokenIcon symbol={selection} />
394
+ {selection.toUpperCase()}
395
+ <ChevronDown className={cn('size-4 transition-transform', open && 'rotate-180')} />
396
+ </Button>
397
+ </PopoverTrigger>
398
+ <PopoverContent className="w-70 min-w-0 overflow-hidden rounded-2xl" align="end">
399
+ <SearchInput
400
+ aria-label={cryptoLabel('searchTokens')}
401
+ placeholder={cryptoLabel('searchTokens')}
402
+ value={query}
403
+ onChange={setQuery}
404
+ className="h-10 rounded-none border-0 border-b shadow-none"
405
+ inputClassName="text-compact"
406
+ />
407
+ <div className="max-h-68 overflow-auto">
408
+ <CryptoTokenList
409
+ query={query}
410
+ onSelect={(token) => {
411
+ setSelection(token)
412
+ onValueChange?.(token)
413
+ setOpen(false)
414
+ }}
415
+ limit={12}
416
+ />
417
+ </div>
418
+ </PopoverContent>
419
+ </Popover>
420
+ )
421
+ }
422
+
423
+ function CryptoSearch({
424
+ state = 'default',
425
+ inputOnly = false,
426
+ onSelect,
427
+ }: {
428
+ state?: CryptoPreviewState
429
+ inputOnly?: boolean
430
+ onSelect?: (token: CryptoToken) => void
431
+ }) {
432
+ const cryptoLabel = useCryptoLabels()
433
+ const [query, setQuery] = React.useState(
434
+ state === 'empty' ? 'unlisted token' : state === 'searching' || state === 'active' ? 'Ethereum' : '',
435
+ )
436
+ const [selected, setSelected] = React.useState('')
437
+ const [tab, setTab] = React.useState('tokens')
438
+ const searchTokens = query.toLowerCase().includes('ethereum')
439
+ ? [
440
+ { ...tokenFixtures[1], name: 'Ethereum' },
441
+ { ...tokenFixtures[1], name: 'Arbitrum ETH', price: 123.08, change: -0.2 },
442
+ { ...tokenFixtures[1], name: 'Optimistic ETH', price: 98.04, change: 0.9 },
443
+ { ...tokenFixtures[1], name: 'Base ETH', price: 456.18, change: -2.3 },
444
+ ]
445
+ : tokenFixtures.slice(0, 10)
446
+ return (
447
+ <CryptoPanel
448
+ className={cn(
449
+ 'w-120 max-w-full',
450
+ !inputOnly && (query ? 'h-99' : 'h-137'),
451
+ inputOnly && 'rounded-none border-0 shadow-none',
452
+ )}
453
+ >
454
+ <Command
455
+ shouldFilter={!query.toLowerCase().includes('ethereum')}
456
+ className="flex min-h-0 flex-1 flex-col"
457
+ label={cryptoLabel('searchTokens')}
458
+ >
459
+ <CommandMenuInput
460
+ value={query}
461
+ onValueChange={setQuery}
462
+ showShortcut={false}
463
+ showClear
464
+ appearance="source"
465
+ placeholder={cryptoLabel('searchPairs')}
466
+ aria-label={cryptoLabel('searchPairs')}
467
+ className="text-compact"
468
+ wrapperClassName="h-12 shrink-0 px-4 py-3.5 [&>svg]:size-4.5 focus-within:[&>svg]:text-status-success-icon"
469
+ />
470
+ {!inputOnly && (
471
+ <>
472
+ {!query && (
473
+ <div
474
+ className="flex h-9 shrink-0 items-center gap-5 border-b border-border px-5"
475
+ role="tablist"
476
+ aria-label={cryptoLabel('searchCategory')}
477
+ >
478
+ {['tokens', 'trade', 'rewards'].map((option) => (
479
+ <Button
480
+ type="button"
481
+ role="tab"
482
+ aria-selected={tab === option}
483
+ key={option}
484
+ variant="ghost"
485
+ size="2xs"
486
+ onClick={() => setTab(option)}
487
+ className={cn(
488
+ 'h-10 rounded-none border-b px-0 font-mono text-xs uppercase',
489
+ tab === option
490
+ ? 'border-status-success-icon text-foreground'
491
+ : 'border-transparent text-muted-foreground',
492
+ )}
493
+ >
494
+ {cryptoLabel(option)}
495
+ </Button>
496
+ ))}
497
+ </div>
498
+ )}
499
+ {query && (
500
+ <div className="flex h-9 shrink-0 items-end px-5 pb-1 font-mono text-overline text-muted-foreground">
501
+ {cryptoLabel('results')}
502
+ </div>
503
+ )}
504
+ <CommandMenuList className="min-h-0 max-h-none flex-1 p-2">
505
+ <CommandMenuEmpty className="flex h-full flex-col items-center justify-center gap-3 p-8 text-center">
506
+ <span className="flex size-20 items-center justify-center rounded-full bg-muted">
507
+ <img src={cryptoArtwork.sadFace} alt="" className="size-8" />
508
+ </span>
509
+ <strong className="text-compact font-medium text-foreground">{cryptoLabel('noTokens')}</strong>
510
+ <span className="text-xs">{cryptoLabel('tryAnotherSearch')}</span>
511
+ </CommandMenuEmpty>
512
+ {searchTokens.map((token) => (
513
+ <CommandMenuItem
514
+ key={token.name}
515
+ value={token.name}
516
+ keywords={[token.symbol]}
517
+ onSelect={() => {
518
+ setSelected(cryptoLabel('selectedToken', { token: token.name }))
519
+ onSelect?.(token.symbol)
520
+ }}
521
+ hideChevron
522
+ className="h-10 gap-2 rounded-xl px-2 py-2 text-compact [&_[data-slot=command-menu-item-text]>div>span]:text-compact"
523
+ leading={<TokenIcon symbol={token.symbol} />}
524
+ sublabel={`{${token.symbol.toUpperCase()}}`}
525
+ shortcut={
526
+ <span className="flex items-center gap-2">
527
+ <span className="font-mono text-sm">${formatAmount(token.price)}</span>
528
+ <span
529
+ className={cn(
530
+ 'text-xs',
531
+ token.change < 0 ? 'text-status-error-text' : 'text-status-success-text',
532
+ )}
533
+ >
534
+ {token.change > 0 ? '+' : ''}
535
+ {token.change}%
536
+ </span>
537
+ </span>
538
+ }
539
+ >
540
+ {token.name}
541
+ </CommandMenuItem>
542
+ ))}
543
+ </CommandMenuList>
544
+ <CommandMenuFooter className="h-12 shrink-0 border-t border-border px-4 font-mono text-overline uppercase [&_kbd]:rounded-full" />
545
+ </>
546
+ )}
547
+ </Command>
548
+ <CryptoNotice message={selected} />
549
+ </CryptoPanel>
550
+ )
551
+ }
552
+
553
+ function CryptoNavigationSearch({ state = 'default' }: { state?: CryptoPreviewState }) {
554
+ const cryptoLabel = useCryptoLabels()
555
+ const [open, setOpen] = React.useState(false)
556
+ React.useEffect(() => {
557
+ const onKey = (event: KeyboardEvent) => {
558
+ if (event.defaultPrevented || (event.target instanceof HTMLElement && event.target.isContentEditable)) return
559
+ if (
560
+ ((event.ctrlKey || event.metaKey) && event.key === 'k') ||
561
+ (event.key === '/' &&
562
+ !(event.target instanceof HTMLInputElement) &&
563
+ !(event.target instanceof HTMLTextAreaElement))
564
+ ) {
565
+ event.preventDefault()
566
+ setOpen(true)
567
+ }
568
+ }
569
+ window.addEventListener('keydown', onKey)
570
+ return () => window.removeEventListener('keydown', onKey)
571
+ }, [])
572
+ return (
573
+ <>
574
+ <Button
575
+ type="button"
576
+ variant="ghost"
577
+ size="sm"
578
+ onClick={() => setOpen(true)}
579
+ className={cn(
580
+ 'h-8 w-91 max-w-full justify-start gap-2 rounded-full bg-muted py-1.5 pl-2 pr-1.5 text-compact text-muted-foreground',
581
+ previewSurface(state),
582
+ )}
583
+ >
584
+ <Search className="size-4" />
585
+ {cryptoLabel('searchPairs')}
586
+ <span className="ml-auto flex size-5 items-center justify-center rounded-full bg-background text-xs">/</span>
587
+ </Button>
588
+ <Dialog open={open} onOpenChange={setOpen}>
589
+ <DialogContent className="w-120 max-w-full border-0 bg-transparent p-0 shadow-none [&>button]:hidden">
590
+ <DialogTitle className="sr-only">{cryptoLabel('searchTokens')}</DialogTitle>
591
+ <DialogDescription className="sr-only">{cryptoLabel('chooseToken')}</DialogDescription>
592
+ <CryptoSearch onSelect={() => setOpen(false)} />
593
+ </DialogContent>
594
+ </Dialog>
595
+ </>
596
+ )
597
+ }
598
+
599
+ function CryptoSwapInput({
600
+ type = 'selling',
601
+ state = 'default',
602
+ value: controlledValue,
603
+ onValueChange,
604
+ token: controlledToken,
605
+ onTokenChange,
606
+ available = 0.0095,
607
+ }: {
608
+ type?: string
609
+ state?: CryptoPreviewState
610
+ value?: string
611
+ onValueChange?: (value: string) => void
612
+ token?: CryptoToken
613
+ onTokenChange?: (token: CryptoToken) => void
614
+ available?: number
615
+ }) {
616
+ const cryptoLabel = useCryptoLabels()
617
+ const [inputValue, setInputValue] = React.useState(
618
+ ['active', 'filled', 'error'].includes(state) ? (type === 'receive' ? '1050.64' : '0.0095') : '',
619
+ )
620
+ const [token, setToken] = React.useState<CryptoToken>(type === 'receive' ? 'usdc' : 'btc')
621
+ const value = controlledValue ?? inputValue
622
+ const activeToken = controlledToken ?? token
623
+ const error =
624
+ state === 'error' ||
625
+ (type !== 'receive' &&
626
+ value !== '' &&
627
+ (!Number.isFinite(Number(value)) || Number(value) < 0 || Number(value) > available))
628
+ const tokenValue = tokenBySymbol(activeToken)
629
+ return (
630
+ <div
631
+ data-slot="crypto-swap-input"
632
+ className="relative flex h-32 w-94 max-w-full gap-4 bg-background px-6 pt-6 pb-5"
633
+ >
634
+ <div
635
+ className={cn(
636
+ 'min-w-0 flex-1 border-b border-border pb-4 focus-within:border-status-success-icon',
637
+ error && 'border-status-error-icon focus-within:border-status-error-icon',
638
+ state === 'active' && 'border-status-success-icon',
639
+ )}
640
+ >
641
+ <div className="flex items-start justify-between gap-2">
642
+ <label className="min-w-0 flex-1 text-compact text-muted-foreground">
643
+ {cryptoLabel(type === 'receive' ? 'receiving' : 'selling')}
644
+ <Input
645
+ aria-label={cryptoLabel(type === 'receive' ? 'receiving' : 'selling')}
646
+ type="text"
647
+ inputMode="decimal"
648
+ value={value}
649
+ onChange={(event) => {
650
+ setInputValue(event.target.value)
651
+ onValueChange?.(event.target.value)
652
+ }}
653
+ placeholder="0"
654
+ disabled={state === 'disabled'}
655
+ aria-invalid={error || undefined}
656
+ className="h-10 border-0 bg-transparent p-0 shadow-none hover:bg-transparent focus-within:shadow-none has-[input:disabled]:bg-transparent"
657
+ inputClassName={cn('text-title-4 tracking-tight text-foreground', error && 'text-status-error-text')}
658
+ />
659
+ </label>
660
+ <div className="flex shrink-0 flex-col items-end gap-4">
661
+ <CryptoTokenPicker
662
+ value={activeToken}
663
+ onValueChange={(next) => {
664
+ setToken(next)
665
+ onTokenChange?.(next)
666
+ }}
667
+ disabled={state === 'disabled'}
668
+ />
669
+ {type !== 'receive' && value && (
670
+ <Button
671
+ type="button"
672
+ variant="ghost"
673
+ size="2xs"
674
+ className="h-4 gap-1 px-0 text-xs text-muted-foreground hover:bg-transparent"
675
+ disabled={state === 'disabled'}
676
+ onClick={() => {
677
+ const next = String(available)
678
+ setInputValue(next)
679
+ onValueChange?.(next)
680
+ }}
681
+ >
682
+ <span className="hidden sm:inline">{cryptoLabel('available')}</span>
683
+ <span className="underline">
684
+ {available} {activeToken.toUpperCase()}
685
+ </span>
686
+ </Button>
687
+ )}
688
+ </div>
689
+ </div>
690
+ <p className="font-mono text-xs text-muted-foreground">
691
+ {money(Number.isFinite(Number(value)) ? Number(value) * tokenValue.price : 0)}
692
+ </p>
693
+ </div>
694
+ </div>
695
+ )
696
+ }
697
+
698
+ function CryptoSwap({ step = 1 }: { step?: 1 | 2 | 3 }) {
699
+ const cryptoLabel = useCryptoLabels()
700
+ const [amount, setAmount] = React.useState(step === 1 ? '' : '0.0095')
701
+ const [sellingToken, setSellingToken] = React.useState<CryptoToken>('btc')
702
+ const [receivingToken, setReceivingToken] = React.useState<CryptoToken>('usdc')
703
+ const [phase, setPhase] = React.useState<'input' | 'quote' | 'ready' | 'confirmed'>(
704
+ step === 1 ? 'input' : step === 2 ? 'quote' : 'ready',
705
+ )
706
+ const [mode, setMode] = React.useState('swap')
707
+ const [slippage, setSlippage] = React.useState('0.5')
708
+ const selling = tokenBySymbol(sellingToken)
709
+ const receiving = tokenBySymbol(receivingToken)
710
+ const numericAmount = Number(amount)
711
+ const valid =
712
+ amount.trim() !== '' &&
713
+ Number.isFinite(numericAmount) &&
714
+ numericAmount > 0 &&
715
+ numericAmount <= selling.balance &&
716
+ sellingToken !== receivingToken
717
+ const quote = valid ? (numericAmount * selling.price) / receiving.price : 0
718
+ const updateAmount = (next: string) => {
719
+ setAmount(next)
720
+ setPhase('input')
721
+ }
722
+ const submit = () => {
723
+ if (!valid) return
724
+ setPhase(phase === 'ready' ? 'confirmed' : 'ready')
725
+ }
726
+ return (
727
+ <div
728
+ className="flex min-h-129 w-94 max-w-full flex-col bg-background"
729
+ data-slot="crypto-swap"
730
+ onKeyDown={(event) => {
731
+ if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
732
+ event.preventDefault()
733
+ submit()
734
+ }
735
+ }}
736
+ >
737
+ <div className="flex h-12 items-center justify-between gap-2 border-b border-border px-4">
738
+ <SegmentedControl
739
+ size="sm"
740
+ value={mode}
741
+ onValueChange={setMode}
742
+ className="h-6 border-0 bg-transparent p-0"
743
+ aria-label={cryptoLabel('transactionMode')}
744
+ >
745
+ {['swap', 'send', 'buy'].map((option) => (
746
+ <SegmentedControlItem
747
+ key={option}
748
+ value={option}
749
+ className="h-6 px-3 text-compact data-[state=checked]:bg-muted data-[state=checked]:shadow-none"
750
+ >
751
+ {cryptoLabel(option)}
752
+ </SegmentedControlItem>
753
+ ))}
754
+ </SegmentedControl>
755
+ <Popover>
756
+ <PopoverTrigger asChild>
757
+ <Button
758
+ type="button"
759
+ variant="ghost"
760
+ size="2xs"
761
+ className="size-7 rounded-full p-1"
762
+ aria-label={cryptoLabel('swapSettings')}
763
+ >
764
+ <Settings className="size-4" />
765
+ </Button>
766
+ </PopoverTrigger>
767
+ <PopoverContent className="w-60 min-w-0 p-4">
768
+ <label className="grid gap-2 text-sm">
769
+ {cryptoLabel('slippage')}
770
+ <Input
771
+ type="number"
772
+ min="0"
773
+ max="5"
774
+ step="0.1"
775
+ value={slippage}
776
+ onChange={(event) => setSlippage(event.target.value)}
777
+ />
778
+ </label>
779
+ </PopoverContent>
780
+ </Popover>
781
+ </div>
782
+ <div className="relative flex flex-1 flex-col ring-1 ring-inset ring-border">
783
+ <CryptoSwapInput
784
+ value={amount}
785
+ onValueChange={updateAmount}
786
+ token={sellingToken}
787
+ onTokenChange={(token) => {
788
+ setSellingToken(token)
789
+ setAmount('')
790
+ setPhase('input')
791
+ }}
792
+ available={selling.balance}
793
+ />
794
+ <Button
795
+ type="button"
796
+ variant="ghost"
797
+ size="2xs"
798
+ className="absolute top-28 left-1/2 z-10 size-9 -translate-x-1/2 rounded-full border-4 border-background bg-muted p-1.5"
799
+ aria-label={cryptoLabel('reverseTokens')}
800
+ onClick={() => {
801
+ setSellingToken(receivingToken)
802
+ setReceivingToken(sellingToken)
803
+ setAmount('')
804
+ setPhase('input')
805
+ }}
806
+ >
807
+ <ArrowDown className="size-4" />
808
+ </Button>
809
+ <CryptoSwapInput
810
+ type="receive"
811
+ value={phase === 'ready' || phase === 'confirmed' ? String(Number(quote.toFixed(receivingToken === 'usdc' || receivingToken === 'usdt' ? 2 : 6))) : ''}
812
+ onValueChange={(next) => {
813
+ const input = (Number(next) * receiving.price) / selling.price
814
+ updateAmount(Number.isFinite(input) ? String(input) : '')
815
+ }}
816
+ token={receivingToken}
817
+ onTokenChange={(token) => {
818
+ setReceivingToken(token)
819
+ setPhase('input')
820
+ }}
821
+ />
822
+ <div className="grid min-h-44 flex-1 gap-3 border-t border-border px-6 py-6">
823
+ <div className="grid gap-2 text-xs text-muted-foreground">
824
+ {['transactionFee', 'serviceFee', 'executionDifference'].map((key, index) => (
825
+ <div key={key} className="flex justify-between">
826
+ <span>{cryptoLabel(key)}</span>
827
+ <span className="font-mono">{index === 0 ? `0 ${receivingToken.toUpperCase()}` : '0%'}</span>
828
+ </div>
829
+ ))}
830
+ </div>
831
+ <Button
832
+ type="button"
833
+ disabled={!valid || phase === 'confirmed'}
834
+ onClick={submit}
835
+ className={cn(
836
+ 'w-full rounded-full',
837
+ phase === 'ready' ? greenButton : 'bg-muted text-foreground hover:bg-muted/70',
838
+ )}
839
+ >
840
+ {phase === 'quote' && (
841
+ <LoaderCircle aria-hidden="true" className="size-4 animate-spin text-status-success-icon" />
842
+ )}
843
+ {cryptoLabel(
844
+ phase === 'confirmed'
845
+ ? 'swapConfirmed'
846
+ : phase === 'ready'
847
+ ? 'confirmSwap'
848
+ : phase === 'quote'
849
+ ? 'finalizingQuote'
850
+ : valid
851
+ ? 'getQuote'
852
+ : 'enterAmount',
853
+ )}
854
+ </Button>
855
+ {amount && !valid && (
856
+ <p role="alert" className="text-xs text-status-error-text">
857
+ {cryptoLabel(sellingToken === receivingToken ? 'differentTokens' : 'invalidAmount')}
858
+ </p>
859
+ )}
860
+ </div>
861
+ </div>
862
+ <p className="flex h-9 shrink-0 items-center justify-center px-6 text-center text-xs text-muted-foreground">
863
+ {cryptoLabel('localSimulation')}
864
+ </p>
865
+ </div>
866
+ )
867
+ }
868
+
869
+ const filterValues: Record<string, string[]> = {
870
+ type: ['swap', 'buy', 'send'],
871
+ date: ['last24Hours', 'last7Days', 'lastMonth', 'lastQuarter', 'lastYear'],
872
+ status: ['success', 'pending', 'failed', 'cancelled'],
873
+ token: tokenFixtures.slice(0, 7).map((token) => token.symbol),
874
+ }
875
+
876
+ function CryptoFilterOptions({
877
+ kind,
878
+ value,
879
+ onChange,
880
+ }: {
881
+ kind: string
882
+ value: string[]
883
+ onChange: (value: string[]) => void
884
+ }) {
885
+ const cryptoLabel = useCryptoLabels()
886
+ const [query, setQuery] = React.useState('')
887
+ const options = filterValues[kind] ?? filterValues.type
888
+ const shown = options.filter((option) =>
889
+ (kind === 'token' ? tokenBySymbol(option).name : cryptoLabel(option)).toLowerCase().includes(query.toLowerCase()),
890
+ )
891
+ const toggle = (option: string) =>
892
+ onChange(value.includes(option) ? value.filter((item) => item !== option) : [...value, option])
893
+ const all = options.every((option) => value.includes(option))
894
+ return (
895
+ <div className={cn(kind === 'token' ? 'w-93 max-w-full' : 'w-42')} data-slot="crypto-filter-options">
896
+ {kind === 'token' && (
897
+ <SearchInput
898
+ aria-label={cryptoLabel('searchTokens')}
899
+ placeholder={cryptoLabel('searchTokens')}
900
+ value={query}
901
+ onChange={setQuery}
902
+ className="h-10 rounded-none border-0 border-b shadow-none"
903
+ inputClassName="text-compact"
904
+ />
905
+ )}
906
+ <div className={cn('overflow-auto p-1', kind === 'token' ? 'max-h-79.5' : 'max-h-80 pb-1.5')}>
907
+ <label className="mx-1 flex h-8 cursor-pointer items-center gap-2 border-b border-border px-1 text-xs">
908
+ <Checkbox
909
+ checked={all ? true : value.length ? 'indeterminate' : false}
910
+ onCheckedChange={() => onChange(all ? [] : [...options])}
911
+ className="size-3.5 rounded-sm data-[state=checked]:border-status-success-icon data-[state=checked]:bg-status-success-icon data-[state=indeterminate]:bg-status-success-icon data-[state=indeterminate]:border-status-success-icon"
912
+ />
913
+ {cryptoLabel(kind === 'date' ? 'allTime' : 'selectAll')}
914
+ {kind === 'token' && (
915
+ <Button
916
+ type="button"
917
+ variant="ghost"
918
+ size="2xs"
919
+ className="ml-auto h-6 px-1 text-xs text-muted-foreground"
920
+ onClick={(event) => {
921
+ event.preventDefault()
922
+ onChange([])
923
+ }}
924
+ >
925
+ {cryptoLabel('clear')}
926
+ </Button>
927
+ )}
928
+ </label>
929
+ {shown.map((option) => (
930
+ <label
931
+ key={option}
932
+ className={cn(
933
+ 'flex cursor-pointer items-center gap-2 rounded-lg px-2 text-xs hover:bg-muted',
934
+ kind === 'token' ? 'h-10' : 'h-7.5',
935
+ )}
936
+ >
937
+ <Checkbox
938
+ checked={value.includes(option)}
939
+ onCheckedChange={() => toggle(option)}
940
+ className="size-3.5 rounded-sm data-[state=checked]:border-status-success-icon data-[state=checked]:bg-status-success-icon"
941
+ />
942
+ {kind === 'token' ? (
943
+ <>
944
+ <TokenIcon symbol={tokenBySymbol(option).symbol} />
945
+ {tokenBySymbol(option).name}
946
+ <span className="font-mono text-muted-foreground">{`{${option.toUpperCase()}}`}</span>
947
+ </>
948
+ ) : (
949
+ cryptoLabel(option)
950
+ )}
951
+ </label>
952
+ ))}
953
+ {shown.length === 0 && <p className="p-4 text-xs text-muted-foreground">{cryptoLabel('noTokens')}</p>}
954
+ </div>
955
+ </div>
956
+ )
957
+ }
958
+
959
+ function CryptoFilter({
960
+ kind = 'type',
961
+ state = 'default',
962
+ standalone = false,
963
+ }: {
964
+ kind?: string
965
+ state?: CryptoPreviewState
966
+ standalone?: boolean
967
+ }) {
968
+ const cryptoLabel = useCryptoLabels()
969
+ const [value, setValue] = React.useState<string[]>(
970
+ state === 'selected'
971
+ ? [kind === 'date' ? 'last24Hours' : kind === 'token' ? 'eth' : kind === 'status' ? 'pending' : 'swap']
972
+ : standalone
973
+ ? [kind === 'date' ? 'lastMonth' : kind === 'token' ? 'usdt' : kind === 'status' ? 'failed' : 'send']
974
+ : [],
975
+ )
976
+ const [open, setOpen] = React.useState(false)
977
+ if (standalone)
978
+ return (
979
+ <CryptoPanel className="w-fit max-w-full">
980
+ <CryptoFilterOptions kind={kind} value={value} onChange={setValue} />
981
+ </CryptoPanel>
982
+ )
983
+ const selectedLabel =
984
+ value.length === 0
985
+ ? cryptoLabel(kind === 'date' ? 'allTime' : 'all')
986
+ : value.length === 1
987
+ ? kind === 'token'
988
+ ? tokenBySymbol(value[0]).name
989
+ : cryptoLabel(value[0])
990
+ : cryptoLabel('selectedCount', { count: value.length })
991
+ return (
992
+ <Popover open={open} onOpenChange={setOpen}>
993
+ <PopoverTrigger asChild>
994
+ <Button
995
+ type="button"
996
+ variant="ghost"
997
+ size="2xs"
998
+ aria-label={cryptoLabel('filterBy', { filter: cryptoLabel(kind) })}
999
+ className="h-6 gap-0 overflow-hidden rounded-full border border-border p-0 text-xs"
1000
+ >
1001
+ <span className="flex h-full items-center border-r border-border px-3 text-muted-foreground">{cryptoLabel(kind)}</span>
1002
+ <span className={cn('flex h-full items-center gap-1 pr-2 pl-2.5', previewSurface(state))}>
1003
+ {kind === 'token' && value.length === 1 && (
1004
+ <TokenIcon symbol={tokenBySymbol(value[0]).symbol} className="size-3.5" />
1005
+ )}
1006
+ {kind === 'status' && value.length === 1 && (
1007
+ <img src={cryptoArtwork.statusPending} alt="" className="size-3" />
1008
+ )}
1009
+ {selectedLabel}
1010
+ <ChevronDown className={cn('size-4', open && 'rotate-180')} />
1011
+ </span>
1012
+ </Button>
1013
+ </PopoverTrigger>
1014
+ <PopoverContent className="w-fit min-w-0 rounded-xl">
1015
+ <CryptoFilterOptions kind={kind} value={value} onChange={setValue} />
1016
+ </PopoverContent>
1017
+ </Popover>
1018
+ )
1019
+ }
1020
+
1021
+ function CryptoTableTabs({ kind = 'transaction', state = 'default' }: { kind?: string; state?: CryptoPreviewState }) {
1022
+ const cryptoLabel = useCryptoLabels()
1023
+ const [value, setValue] = React.useState(state === 'active' ? kind : '')
1024
+ return (
1025
+ <Tabs variant="underline" value={value} onValueChange={setValue}>
1026
+ <TabsList className="h-9 gap-6 border-0 p-0">
1027
+ {[kind].map((option) => (
1028
+ <TabsTrigger
1029
+ key={option}
1030
+ value={option}
1031
+ leading={
1032
+ <CryptoGlyph
1033
+ name={option === 'transaction' ? 'history' : option === 'open-orders' ? 'clock' : 'rewards'}
1034
+ />
1035
+ }
1036
+ className={cn(
1037
+ 'h-9 gap-1.5 rounded-none border-b border-border px-0 pt-0 pb-4 text-sm hover:bg-transparent hover:text-foreground [&_[data-slot=tabs-trigger-leading]]:text-current [&_[data-slot=tabs-trigger-leading]]:group-hover:text-current data-[state=active]:border-status-success-icon data-[state=active]:text-foreground after:hidden',
1038
+ previewSurface(state),
1039
+ )}
1040
+ >
1041
+ {cryptoLabel(option === 'transaction' ? 'transactions' : option === 'open-orders' ? 'openOrders' : 'rewards')}
1042
+ </TabsTrigger>
1043
+ ))}
1044
+ </TabsList>
1045
+ <TabsContent value={kind} className="sr-only">
1046
+ <p className="sr-only">{cryptoLabel('selectedTab')}</p>
1047
+ </TabsContent>
1048
+ </Tabs>
1049
+ )
1050
+ }
1051
+
1052
+ function CryptoTableButton({ kind = 'explorer', state = 'default' }: { kind?: string; state?: CryptoPreviewState }) {
1053
+ const cryptoLabel = useCryptoLabels()
1054
+ return (
1055
+ <Popover>
1056
+ <PopoverTrigger asChild>
1057
+ <Button
1058
+ type="button"
1059
+ variant="ghost"
1060
+ size="2xs"
1061
+ aria-label={cryptoLabel(kind === 'explorer' ? 'explorer' : 'transactionDetails')}
1062
+ className={cn(pill, 'size-7 p-1', previewSurface(state))}
1063
+ >
1064
+ <CryptoGlyph name={kind === 'explorer' ? 'link' : 'more'} className="size-5" />
1065
+ </Button>
1066
+ </PopoverTrigger>
1067
+ <PopoverContent className="w-72 p-4">
1068
+ <h4 className="text-sm font-medium">{cryptoLabel('transactionDetails')}</h4>
1069
+ <dl className="mt-3 grid gap-2 text-xs">
1070
+ <div className="flex justify-between">
1071
+ <dt>{cryptoLabel('type')}</dt>
1072
+ <dd>{cryptoLabel('swap')}</dd>
1073
+ </div>
1074
+ <div className="flex justify-between">
1075
+ <dt>{cryptoLabel('status')}</dt>
1076
+ <dd className="text-status-success-text">{cryptoLabel('success')}</dd>
1077
+ </div>
1078
+ <div className="flex justify-between">
1079
+ <dt>{cryptoLabel('transactionId')}</dt>
1080
+ <dd className="font-mono">0x7a4b…c8f2</dd>
1081
+ </div>
1082
+ </dl>
1083
+ <p className="mt-3 text-xs text-muted-foreground">{cryptoLabel('localSimulation')}</p>
1084
+ </PopoverContent>
1085
+ </Popover>
1086
+ )
1087
+ }
1088
+
1089
+ function CryptoSocialButton({
1090
+ provider = 'phantom',
1091
+ onlyIcon = false,
1092
+ state = 'default',
1093
+ onConnect,
1094
+ }: {
1095
+ provider?: string
1096
+ onlyIcon?: boolean
1097
+ state?: CryptoPreviewState
1098
+ onConnect?: (provider: string) => void
1099
+ }) {
1100
+ const cryptoLabel = useCryptoLabels()
1101
+ const [connected, setConnected] = React.useState(false)
1102
+ const key = provider === 'walletconnect' ? 'walletconnect' : provider === 'metamask' ? 'metamask' : 'phantom'
1103
+ const name = key === 'walletconnect' ? 'WalletConnect' : key === 'metamask' ? 'MetaMask' : 'Phantom'
1104
+ return (
1105
+ <Button
1106
+ type="button"
1107
+ variant="ghost"
1108
+ aria-label={cryptoLabel(connected ? 'connectedProvider' : 'connectProvider', { provider: name })}
1109
+ onClick={() => {
1110
+ setConnected(true)
1111
+ onConnect?.(name)
1112
+ }}
1113
+ className={cn(
1114
+ 'max-w-full gap-3 rounded-full bg-muted text-sm text-foreground hover:bg-accent',
1115
+ onlyIcon ? 'w-30' : 'w-94',
1116
+ state === 'hover' && 'bg-accent',
1117
+ )}
1118
+ >
1119
+ <img src={cryptoArtwork[key]} alt="" className="size-5" />
1120
+ {!onlyIcon && (
1121
+ <span>
1122
+ {connected ? (
1123
+ <>
1124
+ <Check className="mr-1 inline size-4" />
1125
+ {cryptoLabel('connectedProvider', { provider: name })}
1126
+ </>
1127
+ ) : (
1128
+ <>
1129
+ <span className="text-muted-foreground">{cryptoLabel('connect')} </span>
1130
+ {name}
1131
+ </>
1132
+ )}
1133
+ </span>
1134
+ )}
1135
+ </Button>
1136
+ )
1137
+ }
1138
+
1139
+ function CryptoConnectWallet({ onConnect }: { onConnect?: (provider: string) => void }) {
1140
+ const cryptoLabel = useCryptoLabels()
1141
+ const [connected, setConnected] = React.useState('')
1142
+ const [help, setHelp] = React.useState(false)
1143
+ return (
1144
+ <CryptoPanel className="w-112 max-w-full rounded-4xl p-9 text-center">
1145
+ <img src={cryptoArtwork.brand} className="mx-auto size-14 rounded-full border border-border p-3" alt="" />
1146
+ <h3 className="mt-4 text-base font-medium">{cryptoLabel(connected ? 'walletConnected' : 'connectWallet')}</h3>
1147
+ <p className="mt-1 text-sm text-muted-foreground">
1148
+ {connected ? cryptoLabel('connectedProvider', { provider: connected }) : cryptoLabel('chooseWallet')}
1149
+ </p>
1150
+ <div className="mt-6 grid gap-2">
1151
+ {['phantom', 'walletconnect', 'metamask'].map((provider) => (
1152
+ <CryptoSocialButton
1153
+ key={provider}
1154
+ provider={provider}
1155
+ onConnect={(name) => {
1156
+ setConnected(name)
1157
+ onConnect?.(name)
1158
+ }}
1159
+ />
1160
+ ))}
1161
+ </div>
1162
+ <Button
1163
+ type="button"
1164
+ variant="link"
1165
+ size="2xs"
1166
+ onClick={() => setHelp(!help)}
1167
+ className="mt-5 h-6 text-status-success-text"
1168
+ >
1169
+ {cryptoLabel('setUpWallet')}
1170
+ <ArrowUpRight className="size-4" />
1171
+ </Button>
1172
+ {help && (
1173
+ <p role="status" className="mt-2 text-xs text-muted-foreground">
1174
+ {cryptoLabel('walletHelp')}
1175
+ </p>
1176
+ )}
1177
+ </CryptoPanel>
1178
+ )
1179
+ }
1180
+
1181
+ function CryptoFavorites() {
1182
+ const cryptoLabel = useCryptoLabels()
1183
+ const [favorites, setFavorites] = React.useState<string[]>(['btc', 'eth', 'xrp', 'usdt'])
1184
+ const [query, setQuery] = React.useState('')
1185
+ const [selected, setSelected] = React.useState('')
1186
+ return (
1187
+ <CryptoPanel className="w-93 max-w-full">
1188
+ <div className="flex h-12 items-center justify-between border-b border-border px-4 text-compact">
1189
+ <span>{cryptoLabel('favoritesCount', { count: favorites.length })}</span>
1190
+ <Button
1191
+ type="button"
1192
+ variant="ghost"
1193
+ size="2xs"
1194
+ onClick={() => setFavorites([])}
1195
+ className="px-0 text-compact text-muted-foreground"
1196
+ >
1197
+ {cryptoLabel('clear')}
1198
+ </Button>
1199
+ </div>
1200
+ <SearchInput
1201
+ aria-label={cryptoLabel('searchTokens')}
1202
+ placeholder={cryptoLabel('searchTokens')}
1203
+ value={query}
1204
+ onChange={setQuery}
1205
+ className="h-11 rounded-none border-0 border-b px-4 shadow-none"
1206
+ inputClassName="text-compact"
1207
+ />
1208
+ <div className="h-99 overflow-auto">
1209
+ <CryptoTokenList
1210
+ query={query}
1211
+ favorites={favorites}
1212
+ onFavorite={(token) =>
1213
+ setFavorites(favorites.includes(token) ? favorites.filter((item) => item !== token) : [...favorites, token])
1214
+ }
1215
+ onSelect={(token) => setSelected(cryptoLabel('selectedToken', { token: tokenBySymbol(token).name }))}
1216
+ />
1217
+ </div>
1218
+ <CryptoNotice message={selected} />
1219
+ </CryptoPanel>
1220
+ )
1221
+ }
1222
+
1223
+ function CryptoMenu({ profile = false }: { profile?: boolean }) {
1224
+ const cryptoLabel = useCryptoLabels()
1225
+ const [dark, setDark] = React.useState(false)
1226
+ const [message, setMessage] = React.useState('')
1227
+ const items = [
1228
+ { key: profile ? 'accountSettings' : 'settings', Icon: Settings },
1229
+ { key: 'preferences', Icon: SlidersHorizontal },
1230
+ { key: 'helpCenter', Icon: MessageSquare },
1231
+ { key: 'community', Icon: Share2 },
1232
+ ]
1233
+ const divider = (
1234
+ <div className="flex h-1 items-center">
1235
+ <div className="h-px w-full bg-border" />
1236
+ </div>
1237
+ )
1238
+ return (
1239
+ <div className={dark ? 'dark' : undefined} data-slot="crypto-menu-theme">
1240
+ <CryptoPanel className="w-74 max-w-full gap-1 p-1.5">
1241
+ {profile && (
1242
+ <>
1243
+ <div className="flex h-14 items-center gap-3 p-2">
1244
+ <Avatar label="Wei Chen" src={cryptoArtwork.wei} size={40} />
1245
+ <div>
1246
+ <strong className="block text-sm font-medium">Wei Chen</strong>
1247
+ <span className="text-sm text-muted-foreground">wei@example.com</span>
1248
+ </div>
1249
+ </div>
1250
+ {divider}
1251
+ </>
1252
+ )}
1253
+ <label className="flex h-9 cursor-pointer items-center gap-2 px-2.5 text-sm">
1254
+ <Moon className="size-5 text-muted-foreground" />
1255
+ {cryptoLabel('darkMode')}
1256
+ <Switch checked={dark} onCheckedChange={setDark} className="ml-auto" />
1257
+ </label>
1258
+ {divider}
1259
+ {items.map(({ key, Icon }, index) => (
1260
+ <React.Fragment key={key}>
1261
+ {index === 3 && !profile && divider}
1262
+ <Button
1263
+ type="button"
1264
+ variant="ghost"
1265
+ size="sm"
1266
+ className="h-9 w-full justify-start rounded-xl px-2.5 text-sm font-normal"
1267
+ onClick={() => setMessage(cryptoLabel('openedPanel', { panel: cryptoLabel(key) }))}
1268
+ >
1269
+ <Icon className="size-5 text-muted-foreground" />
1270
+ {cryptoLabel(key)}
1271
+ {key === 'preferences' && <ChevronRight className="ml-auto size-5" />}
1272
+ </Button>
1273
+ </React.Fragment>
1274
+ ))}
1275
+ {profile && (
1276
+ <>
1277
+ {divider}
1278
+ <Button
1279
+ type="button"
1280
+ variant="ghost"
1281
+ size="sm"
1282
+ className="h-9 justify-start px-2.5 text-sm font-normal"
1283
+ onClick={() => setMessage(cryptoLabel('signedOut'))}
1284
+ >
1285
+ <LogOut className="size-5 text-status-error-icon" />
1286
+ {cryptoLabel('signOut')}
1287
+ </Button>
1288
+ </>
1289
+ )}
1290
+ <p className="flex h-8 items-center px-2.5 text-xs text-muted-foreground">{cryptoLabel('demoTerms')}</p>
1291
+ <CryptoNotice message={message} />
1292
+ </CryptoPanel>
1293
+ </div>
1294
+ )
1295
+ }
1296
+
1297
+ function CryptoWallet() {
1298
+ const cryptoLabel = useCryptoLabels()
1299
+ const [copied, setCopied] = React.useState(false)
1300
+ const [connected, setConnected] = React.useState(true)
1301
+ const [verified, setVerified] = React.useState(false)
1302
+ const [all, setAll] = React.useState(false)
1303
+ const [action, setAction] = React.useState('')
1304
+ const copy = async () => {
1305
+ try {
1306
+ await navigator.clipboard.writeText('0x1cf2000000000000000000000000000000009a56')
1307
+ setCopied(true)
1308
+ } catch {
1309
+ setAction(cryptoLabel('copyUnavailable'))
1310
+ }
1311
+ }
1312
+ return (
1313
+ <CryptoPanel className="w-91 max-w-full">
1314
+ <div className="flex h-18 items-center gap-3 border-b border-border px-4">
1315
+ <span className="flex size-10 items-center justify-center rounded-full bg-muted">
1316
+ <CryptoGlyph name="crown" className="size-5 text-status-success-icon" />
1317
+ </span>
1318
+ <div className="min-w-0">
1319
+ <p className="text-sm">0x1cf2…9a56</p>
1320
+ <p className={cn('text-compact', connected ? 'text-status-success-text' : 'text-muted-foreground')}>
1321
+ {cryptoLabel(connected ? 'connected' : 'disconnected')}
1322
+ </p>
1323
+ </div>
1324
+ <Button
1325
+ type="button"
1326
+ variant="ghost"
1327
+ size="2xs"
1328
+ onClick={copy}
1329
+ className="ml-auto h-6 rounded-full bg-muted text-xs"
1330
+ >
1331
+ <Copy className="size-3" />
1332
+ {cryptoLabel(copied ? 'copied' : 'copy')}
1333
+ </Button>
1334
+ </div>
1335
+ <div className="grid min-h-33 gap-4 border-b border-border p-4">
1336
+ <div className="flex items-start justify-between">
1337
+ <div>
1338
+ <p className="text-xs text-muted-foreground">{cryptoLabel('totalBalance')}</p>
1339
+ <p className="mt-1 text-2xl font-medium tracking-tight">$460,890.02</p>
1340
+ </div>
1341
+ <CryptoTokenPicker value="eth" appearance="stroke" />
1342
+ </div>
1343
+ <div className="flex gap-2">
1344
+ {['swap', 'buy', 'send'].map((mode) => (
1345
+ <Button
1346
+ type="button"
1347
+ key={mode}
1348
+ variant="ghost"
1349
+ size="2xs"
1350
+ disabled={!connected}
1351
+ className="rounded-full bg-muted px-3 text-sm"
1352
+ onClick={() => setAction(mode)}
1353
+ >
1354
+ <ArrowDownUp className="size-4 text-status-success-icon" />
1355
+ {cryptoLabel(mode)}
1356
+ </Button>
1357
+ ))}
1358
+ </div>
1359
+ </div>
1360
+ <div className="flex flex-col gap-1 p-2">
1361
+ <div className="flex h-7.5 items-center justify-between px-2 text-xs uppercase text-muted-foreground">
1362
+ <span>{cryptoLabel('topAssets')}</span>
1363
+ <Button
1364
+ type="button"
1365
+ variant="ghost"
1366
+ size="2xs"
1367
+ className="px-0 font-mono text-xs text-status-success-text"
1368
+ onClick={() => setAll(!all)}
1369
+ >
1370
+ {cryptoLabel(all ? 'showLess' : 'viewAll')}
1371
+ </Button>
1372
+ </div>
1373
+ <div className="flex h-1 items-center">
1374
+ <div className="h-px w-full bg-border" />
1375
+ </div>
1376
+ {tokenFixtures.slice(0, all ? 10 : 4).map((token) => (
1377
+ <div key={token.symbol} className="flex h-9 items-center gap-2 rounded-xl px-2 hover:bg-muted">
1378
+ <TokenIcon symbol={token.symbol} />
1379
+ <span className="text-compact">
1380
+ {token.name} <span className="font-mono text-xs text-muted-foreground">{token.symbol.toUpperCase()}</span>
1381
+ </span>
1382
+ <span className="ml-auto font-mono text-xs">{formatAmount(token.balance, 6)}</span>
1383
+ </div>
1384
+ ))}
1385
+ </div>
1386
+ <div className="flex h-18 items-center gap-3 border-y border-border px-4">
1387
+ <div>
1388
+ <p className="text-sm">{cryptoLabel('verify2FA')}</p>
1389
+ <p className="text-compact text-muted-foreground">{cryptoLabel('securityHint')}</p>
1390
+ </div>
1391
+ <Button
1392
+ type="button"
1393
+ variant="ghost"
1394
+ size="2xs"
1395
+ className="ml-auto rounded-full bg-status-success-bg px-4 text-status-success-text"
1396
+ onClick={() => setVerified(!verified)}
1397
+ >
1398
+ {cryptoLabel(verified ? 'verified' : 'verify')}
1399
+ </Button>
1400
+ </div>
1401
+ <div className="grid gap-2 p-2">
1402
+ <Button
1403
+ type="button"
1404
+ variant="ghost"
1405
+ size="sm"
1406
+ className="h-9 justify-start text-sm font-normal"
1407
+ onClick={() => setAction('settings')}
1408
+ >
1409
+ <Settings className="size-4.5 text-muted-foreground" />
1410
+ {cryptoLabel('walletSettings')}
1411
+ </Button>
1412
+ <Button
1413
+ type="button"
1414
+ variant="ghost"
1415
+ size="sm"
1416
+ className="h-9 justify-start text-sm font-normal"
1417
+ onClick={() => setConnected(!connected)}
1418
+ >
1419
+ <LogOut className="size-4.5 text-status-error-icon" />
1420
+ {cryptoLabel(connected ? 'disconnectWallet' : 'connectWallet')}
1421
+ </Button>
1422
+ </div>
1423
+ <p className="px-4 pb-4 text-xs text-muted-foreground">{cryptoLabel('demoTerms')}</p>
1424
+ {action && (
1425
+ <CryptoNotice
1426
+ message={
1427
+ ['swap', 'buy', 'send', 'settings'].includes(action) ? cryptoLabel('openedPanel', { panel: cryptoLabel(action) }) : action
1428
+ }
1429
+ />
1430
+ )}
1431
+ </CryptoPanel>
1432
+ )
1433
+ }
1434
+
1435
+ function CryptoNotifications() {
1436
+ const cryptoLabel = useCryptoLabels()
1437
+ const [read, setRead] = React.useState<string[]>([])
1438
+ const [viewAll, setViewAll] = React.useState(false)
1439
+ const rows = [
1440
+ {
1441
+ id: 'security',
1442
+ title: 'notificationSecurity',
1443
+ body: 'notificationDevice',
1444
+ time: 'fiveMinutes',
1445
+ Icon: Info,
1446
+ tone: 'text-status-error-icon',
1447
+ },
1448
+ {
1449
+ id: 'swap',
1450
+ title: 'notificationSwap',
1451
+ body: 'notificationSwapBody',
1452
+ time: 'now',
1453
+ Icon: ArrowDownUp,
1454
+ tone: 'text-status-success-icon',
1455
+ },
1456
+ {
1457
+ id: 'reward',
1458
+ title: 'notificationRewards',
1459
+ body: 'notificationRewardsBody',
1460
+ time: 'oneHour',
1461
+ Icon: Gift,
1462
+ tone: 'text-status-success-icon',
1463
+ },
1464
+ {
1465
+ id: 'login',
1466
+ title: 'notificationLogin',
1467
+ body: 'notificationDevice',
1468
+ time: 'tenMinutes',
1469
+ Icon: ShieldCheck,
1470
+ tone: 'text-status-warning-icon',
1471
+ },
1472
+ {
1473
+ id: 'maintenance',
1474
+ title: 'notificationMaintenance',
1475
+ body: 'notificationMaintenanceBody',
1476
+ time: 'yesterday',
1477
+ Icon: Settings,
1478
+ tone: 'text-badge-purple-solid',
1479
+ },
1480
+ ]
1481
+ return (
1482
+ <CryptoPanel className="w-100 max-w-full">
1483
+ <div className="flex h-12 items-center justify-between border-b border-border px-4 text-compact">
1484
+ <span>{cryptoLabel('notifications')}</span>
1485
+ <Button
1486
+ type="button"
1487
+ variant="ghost"
1488
+ size="2xs"
1489
+ className="px-0 text-xs text-status-success-text"
1490
+ onClick={() => setRead(rows.map((row) => row.id))}
1491
+ >
1492
+ {cryptoLabel('markAllRead')}
1493
+ </Button>
1494
+ </div>
1495
+ {rows.map(({ id, title, body, time, Icon, tone }) => (
1496
+ <Button
1497
+ type="button"
1498
+ key={id}
1499
+ variant="ghost"
1500
+ className={cn(
1501
+ 'w-full items-start gap-3 rounded-none border-b border-border px-4 py-4 text-left font-normal',
1502
+ !read.includes(id) && ['swap', 'login'].includes(id) && 'bg-muted',
1503
+ ['security', 'reward'].includes(id) ? 'h-18' : 'h-19',
1504
+ )}
1505
+ onClick={() => setRead([...read, id])}
1506
+ >
1507
+ <Icon className={cn('mt-0.5 size-4.5 shrink-0', tone)} />
1508
+ <span className="min-w-0 flex-1">
1509
+ <strong className="block whitespace-normal text-compact font-medium">{cryptoLabel(title)}</strong>
1510
+ <span
1511
+ className={cn(
1512
+ 'block whitespace-normal text-compact text-muted-foreground',
1513
+ !['security', 'reward'].includes(id) && 'mt-1',
1514
+ )}
1515
+ >
1516
+ {cryptoLabel(body)}
1517
+ </span>
1518
+ </span>
1519
+ <span className="flex shrink-0 items-center gap-2 text-xs text-muted-foreground">
1520
+ {cryptoLabel(time)}
1521
+ {!read.includes(id) && ['swap', 'login'].includes(id) && (
1522
+ <span className="size-1.5 rounded-full bg-status-success-icon" />
1523
+ )}
1524
+ </span>
1525
+ </Button>
1526
+ ))}
1527
+ <Button
1528
+ type="button"
1529
+ variant="ghost"
1530
+ className="h-12 rounded-none text-compact"
1531
+ onClick={() => setViewAll(!viewAll)}
1532
+ >
1533
+ {cryptoLabel('viewAllNotifications')}
1534
+ </Button>
1535
+ {viewAll && <CryptoNotice message={cryptoLabel('allNotificationsShown', { count: rows.length })} />}
1536
+ </CryptoPanel>
1537
+ )
1538
+ }
1539
+
1540
+ function CryptoWalletTrigger({ state = 'default' }: { state?: CryptoPreviewState }) {
1541
+ const cryptoLabel = useCryptoLabels()
1542
+ return (
1543
+ <Popover>
1544
+ <PopoverTrigger asChild>
1545
+ <Button
1546
+ type="button"
1547
+ variant="ghost"
1548
+ size="2xs"
1549
+ className={cn(pill, 'h-7 gap-1.5 pl-2.5 pr-1.75 text-sm', previewSurface(state))}
1550
+ aria-label={cryptoLabel('wallet')}
1551
+ >
1552
+ <CryptoGlyph name="crown" className="size-4 text-status-success-icon" />
1553
+ 0x1cf2…9a56
1554
+ <ChevronDown className="size-5" />
1555
+ </Button>
1556
+ </PopoverTrigger>
1557
+ <PopoverContent className="w-fit min-w-0 border-0 bg-transparent shadow-none">
1558
+ <CryptoWallet />
1559
+ </PopoverContent>
1560
+ </Popover>
1561
+ )
1562
+ }
1563
+
1564
+ function CryptoAction({
1565
+ kind = 'favorite',
1566
+ state = 'default',
1567
+ profile = false,
1568
+ }: {
1569
+ kind?: string
1570
+ state?: CryptoPreviewState
1571
+ profile?: boolean
1572
+ }) {
1573
+ const cryptoLabel = useCryptoLabels()
1574
+ return (
1575
+ <Popover>
1576
+ <PopoverTrigger asChild>
1577
+ <Button
1578
+ type="button"
1579
+ variant="ghost"
1580
+ size="2xs"
1581
+ className={cn(pill, 'relative size-7 p-1.25', previewSurface(state))}
1582
+ aria-label={cryptoLabel(
1583
+ profile
1584
+ ? 'profile'
1585
+ : kind === 'favorite'
1586
+ ? 'favorites'
1587
+ : kind === 'notification'
1588
+ ? 'notifications'
1589
+ : 'menu',
1590
+ )}
1591
+ >
1592
+ {profile ? (
1593
+ <Avatar label="Wei Chen" src={cryptoArtwork.wei} size={24} />
1594
+ ) : kind === 'notification' ? (
1595
+ <>
1596
+ <Bell className="size-4.5" />
1597
+ <span className="absolute top-1 right-1 size-1 rounded-full bg-status-warning-icon" />
1598
+ </>
1599
+ ) : (
1600
+ <CryptoGlyph name={kind === 'favorite' ? 'star' : 'menu'} />
1601
+ )}
1602
+ </Button>
1603
+ </PopoverTrigger>
1604
+ <PopoverContent className="w-fit min-w-0 border-0 bg-transparent shadow-none" align="end">
1605
+ {profile ? (
1606
+ <CryptoMenu profile />
1607
+ ) : kind === 'favorite' ? (
1608
+ <CryptoFavorites />
1609
+ ) : kind === 'notification' ? (
1610
+ <CryptoNotifications />
1611
+ ) : (
1612
+ <CryptoMenu />
1613
+ )}
1614
+ </PopoverContent>
1615
+ </Popover>
1616
+ )
1617
+ }
1618
+
1619
+ function CryptoNavigation({ profile = false, mobile = false }: { profile?: boolean; mobile?: boolean }) {
1620
+ const cryptoLabel = useCryptoLabels()
1621
+ const [selected, setSelected] = React.useState('overview')
1622
+ return (
1623
+ <div
1624
+ className={cn(
1625
+ 'flex h-16 items-center gap-3 border-y border-border bg-background',
1626
+ mobile ? 'w-97.5 max-w-full px-4' : 'w-full min-w-0 flex-wrap px-4 lg:flex-nowrap lg:px-11',
1627
+ )}
1628
+ data-slot="crypto-navigation"
1629
+ >
1630
+ {mobile && <CryptoAction kind="hamburger" />}
1631
+ <img src={cryptoArtwork.brand} className="size-7 shrink-0" alt={cryptoLabel('cryptoBrand')} />
1632
+ {!mobile && (
1633
+ <div className="hidden items-center gap-1 md:flex">
1634
+ {['overview', 'markets', 'trade', 'rewards'].map((item) => (
1635
+ <CryptoNavigationItem
1636
+ key={item}
1637
+ state={selected === item ? 'active' : 'default'}
1638
+ pressed={selected === item}
1639
+ label={cryptoLabel(item)}
1640
+ onClick={() => setSelected(item)}
1641
+ />
1642
+ ))}
1643
+ </div>
1644
+ )}
1645
+ {!mobile && (
1646
+ <div className="mx-auto hidden min-w-0 lg:block">
1647
+ <CryptoNavigationSearch />
1648
+ </div>
1649
+ )}
1650
+ <div className="ml-auto flex items-center gap-2">
1651
+ {!mobile && (
1652
+ <div className="hidden sm:block">
1653
+ <CryptoWalletTrigger />
1654
+ </div>
1655
+ )}
1656
+ <CryptoAction kind="favorite" />
1657
+ <CryptoAction kind="notification" />
1658
+ {mobile ? (
1659
+ <Popover>
1660
+ <PopoverTrigger asChild>
1661
+ <Button
1662
+ type="button"
1663
+ variant="ghost"
1664
+ size="2xs"
1665
+ aria-label={cryptoLabel('searchTokens')}
1666
+ className="size-7 rounded-full p-1"
1667
+ >
1668
+ <Search className="size-4.5" />
1669
+ </Button>
1670
+ </PopoverTrigger>
1671
+ <PopoverContent className="w-fit min-w-0 border-0 bg-transparent shadow-none" align="end">
1672
+ <CryptoSearch />
1673
+ </PopoverContent>
1674
+ </Popover>
1675
+ ) : (
1676
+ <CryptoAction kind="hamburger" profile={profile} />
1677
+ )}
1678
+ {mobile && (
1679
+ <Popover>
1680
+ <PopoverTrigger asChild>
1681
+ <Button
1682
+ type="button"
1683
+ variant="ghost"
1684
+ size="sm"
1685
+ aria-label={cryptoLabel('wallet')}
1686
+ className="size-8 rounded-full bg-status-success-bg p-2"
1687
+ >
1688
+ <CryptoGlyph name="crown" className="size-4 text-status-success-icon" />
1689
+ </Button>
1690
+ </PopoverTrigger>
1691
+ <PopoverContent className="w-fit min-w-0 border-0 bg-transparent shadow-none" align="end">
1692
+ <CryptoWallet />
1693
+ </PopoverContent>
1694
+ </Popover>
1695
+ )}
1696
+ </div>
1697
+ </div>
1698
+ )
1699
+ }
1700
+
1701
+ function CryptoBanner() {
1702
+ const cryptoLabel = useCryptoLabels()
1703
+ const [visible, setVisible] = React.useState(true)
1704
+ const [claimed, setClaimed] = React.useState(false)
1705
+ return visible ? (
1706
+ <div className="flex min-h-9 w-full items-center justify-center gap-2 border-b border-border px-3 py-1 text-xs">
1707
+ <Gift className="size-3.5 shrink-0 text-status-success-icon" />
1708
+ <span>{cryptoLabel(claimed ? 'bonusClaimed' : 'bonusOffer')}</span>
1709
+ <Button
1710
+ type="button"
1711
+ variant="ghost"
1712
+ size="2xs"
1713
+ disabled={claimed}
1714
+ className="h-6 px-1 text-xs text-status-success-text"
1715
+ onClick={() => setClaimed(true)}
1716
+ >
1717
+ {cryptoLabel('claimNow')}
1718
+ <ArrowUpRight className="size-3" />
1719
+ </Button>
1720
+ <Button
1721
+ type="button"
1722
+ variant="ghost"
1723
+ size="2xs"
1724
+ className="ml-auto size-6 p-1 text-muted-foreground"
1725
+ aria-label={cryptoLabel('dismissBanner')}
1726
+ onClick={() => setVisible(false)}
1727
+ >
1728
+ <X className="size-3" />
1729
+ </Button>
1730
+ </div>
1731
+ ) : (
1732
+ <Button type="button" variant="ghost" size="2xs" onClick={() => setVisible(true)}>
1733
+ {cryptoLabel('showBanner')}
1734
+ </Button>
1735
+ )
1736
+ }
1737
+
1738
+ function CryptoMobileBottom() {
1739
+ const cryptoLabel = useCryptoLabels()
1740
+ const [selected, setSelected] = React.useState('home')
1741
+ const [open, setOpen] = React.useState(false)
1742
+ return (
1743
+ <div className="w-97.5 max-w-full bg-background pt-9">
1744
+ <Button type="button" className={cn(greenButton, 'mb-4 w-full')} onClick={() => setOpen(true)}>
1745
+ {cryptoLabel('swapSendBuy')}
1746
+ </Button>
1747
+ <div className="grid h-16 grid-cols-4 border-t border-border">
1748
+ {[
1749
+ { key: 'home', Icon: House },
1750
+ { key: 'markets', Icon: SlidersHorizontal },
1751
+ { key: 'trade', Icon: ArrowDownUp },
1752
+ { key: 'rewards', Icon: Gift },
1753
+ ].map(({ key, Icon }) => (
1754
+ <Button
1755
+ type="button"
1756
+ variant="ghost"
1757
+ key={key}
1758
+ onClick={() => setSelected(key)}
1759
+ aria-pressed={selected === key}
1760
+ className={cn(
1761
+ 'h-16 flex-col gap-1 rounded-none border-t px-1 font-mono text-xs uppercase',
1762
+ selected === key
1763
+ ? 'border-status-success-icon text-status-success-text'
1764
+ : 'border-transparent text-muted-foreground',
1765
+ )}
1766
+ >
1767
+ <Icon className="size-4" />
1768
+ {cryptoLabel(key)}
1769
+ </Button>
1770
+ ))}
1771
+ </div>
1772
+ <Dialog open={open} onOpenChange={setOpen}>
1773
+ <DialogContent className="w-94 border-0 p-0 [&>button]:hidden">
1774
+ <DialogTitle className="sr-only">{cryptoLabel('swapSendBuy')}</DialogTitle>
1775
+ <DialogDescription className="sr-only">{cryptoLabel('localSimulation')}</DialogDescription>
1776
+ <CryptoSwap />
1777
+ </DialogContent>
1778
+ </Dialog>
1779
+ </div>
1780
+ )
1781
+ }
1782
+
1783
+ export function CryptocurrencyDemo({
1784
+ part,
1785
+ state = 'default',
1786
+ kind,
1787
+ size = 28,
1788
+ onlyIcon = false,
1789
+ profile = false,
1790
+ step = 1,
1791
+ }: CryptocurrencyDemoProps) {
1792
+ const cryptoLabel = useCryptoLabels()
1793
+ let content: React.ReactNode
1794
+ switch (part) {
1795
+ case 'status-icon': {
1796
+ const key =
1797
+ kind === 'pending'
1798
+ ? 'statusPending'
1799
+ : kind === 'warning'
1800
+ ? 'statusWarning'
1801
+ : kind === 'cancelled'
1802
+ ? 'statusCancelled'
1803
+ : 'statusSuccess'
1804
+ content = <img src={cryptoArtwork[key]} alt={cryptoLabel(kind ?? 'success')} className="size-3" />
1805
+ break
1806
+ }
1807
+ case 'token-icon':
1808
+ content = (
1809
+ <img
1810
+ src={cryptoArtwork[tokenBySymbol(kind ?? 'btc').symbol]}
1811
+ alt={tokenBySymbol(kind ?? 'btc').name}
1812
+ className="size-6 rounded-full"
1813
+ />
1814
+ )
1815
+ break
1816
+ case 'navigation-item':
1817
+ content = <CryptoNavigationItem state={state} size={size} />
1818
+ break
1819
+ case 'navigation-search':
1820
+ content = <CryptoNavigationSearch state={state} />
1821
+ break
1822
+ case 'navigation-wallet':
1823
+ content = <CryptoWalletTrigger state={state} />
1824
+ break
1825
+ case 'navigation-action':
1826
+ content = <CryptoAction kind={kind} state={state} />
1827
+ break
1828
+ case 'chart-item':
1829
+ content = <CryptoSegments itemOnly state={state} />
1830
+ break
1831
+ case 'swap-select':
1832
+ content = <CryptoTokenPicker appearance={kind} state={state} />
1833
+ break
1834
+ case 'chart-switch':
1835
+ content = <CryptoSegments initialValue={kind} />
1836
+ break
1837
+ case 'table-button':
1838
+ content = <CryptoTableButton kind={kind} state={state} />
1839
+ break
1840
+ case 'table-switch-item':
1841
+ content = <CryptoSegments type="table" itemOnly size={24} state={state} />
1842
+ break
1843
+ case 'table-tab':
1844
+ content = <CryptoTableTabs kind={kind} state={state} />
1845
+ break
1846
+ case 'table-filter':
1847
+ content = <CryptoFilter kind={kind} state={state} />
1848
+ break
1849
+ case 'table-switch':
1850
+ content = <CryptoSegments type="table" initialValue={kind} size={size} />
1851
+ break
1852
+ case 'swap-input':
1853
+ content = <CryptoSwapInput type={kind} state={state} available={state === 'error' ? 0 : 0.0095} />
1854
+ break
1855
+ case 'swap':
1856
+ content = <CryptoSwap step={step} />
1857
+ break
1858
+ case 'search-modal':
1859
+ content = <CryptoSearch state={state} />
1860
+ break
1861
+ case 'search-input':
1862
+ content = <CryptoSearch state={state} inputOnly />
1863
+ break
1864
+ case 'navigation':
1865
+ content = <CryptoNavigation profile={profile} />
1866
+ break
1867
+ case 'social-button':
1868
+ content = <CryptoSocialButton provider={kind} onlyIcon={onlyIcon} state={state} />
1869
+ break
1870
+ case 'menu-dropdown':
1871
+ content = <CryptoMenu />
1872
+ break
1873
+ case 'favorites-dropdown':
1874
+ content = <CryptoFavorites />
1875
+ break
1876
+ case 'profile-dropdown':
1877
+ content = <CryptoMenu profile />
1878
+ break
1879
+ case 'wallet-dropdown':
1880
+ content = <CryptoWallet />
1881
+ break
1882
+ case 'swap-token-dropdown':
1883
+ content = <CryptoStandaloneTokenList />
1884
+ break
1885
+ case 'notifications-dropdown':
1886
+ content = <CryptoNotifications />
1887
+ break
1888
+ case 'connect-wallet':
1889
+ content = <CryptoConnectWallet />
1890
+ break
1891
+ case 'banner':
1892
+ content = <CryptoBanner />
1893
+ break
1894
+ case 'filter-type':
1895
+ case 'filter-date':
1896
+ case 'filter-status':
1897
+ case 'filter-token':
1898
+ content = <CryptoFilter kind={part.slice(7)} standalone />
1899
+ break
1900
+ case 'mobile-navigation':
1901
+ content = <CryptoNavigation mobile />
1902
+ break
1903
+ case 'mobile-bottom-navigation':
1904
+ content = <CryptoMobileBottom />
1905
+ break
1906
+ }
1907
+ return (
1908
+ <div
1909
+ data-slot="crypto-demo"
1910
+ data-crypto-part={part}
1911
+ data-preview-state={state}
1912
+ className={cn('flex max-w-full text-foreground', part === 'navigation' || part === 'banner' ? 'w-full' : 'w-fit')}
1913
+ >
1914
+ {content}
1915
+ </div>
1916
+ )
1917
+ }
1918
+
1919
+ function CryptoStandaloneTokenList() {
1920
+ const cryptoLabel = useCryptoLabels()
1921
+ const [query, setQuery] = React.useState('')
1922
+ const [selected, setSelected] = React.useState('')
1923
+ return (
1924
+ <CryptoPanel className="w-70 max-w-full">
1925
+ <SearchInput
1926
+ aria-label={cryptoLabel('searchTokens')}
1927
+ placeholder={cryptoLabel('searchTokens')}
1928
+ value={query}
1929
+ onChange={setQuery}
1930
+ className="h-10 rounded-none border-0 border-b shadow-none"
1931
+ inputClassName="text-compact"
1932
+ />
1933
+ <div className="h-68.5 overflow-auto">
1934
+ <CryptoTokenList
1935
+ query={query}
1936
+ limit={12}
1937
+ onSelect={(token) => setSelected(cryptoLabel('selectedToken', { token: tokenBySymbol(token).name }))}
1938
+ />
1939
+ </div>
1940
+ <CryptoNotice message={selected} />
1941
+ </CryptoPanel>
1942
+ )
1943
+ }