@open-mercato/core 0.7.1-develop.7203.1.9396450b78 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 -9
  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,189 @@
1
+ import * as React from 'react'
2
+ import { Area, AreaChart, Bar, BarChart, CartesianGrid, Cell, Line, LineChart, Pie, PieChart, PolarAngleAxis, PolarGrid, Radar, RadarChart, ReferenceLine, ResponsiveContainer, Tooltip, XAxis } from 'recharts'
3
+ import { ArrowDownLeft, ArrowUp, ArrowUpRight, CheckCircle2, ChevronLeft, ChevronRight, Clock3, Code, Info, Minus, Plus, Store, Target, UserRound } from 'lucide-react'
4
+ import { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'
5
+ import { cn } from '@open-mercato/shared/lib/utils'
6
+ import { ChartContainer, ChartTooltipContent } from '@open-mercato/ui/backend/charts'
7
+ import { Avatar } from '@open-mercato/ui/primitives/avatar'
8
+ import { Badge } from '@open-mercato/ui/primitives/badge'
9
+ import { Button } from '@open-mercato/ui/primitives/button'
10
+ import { Card } from '@open-mercato/ui/primitives/card'
11
+ import { CompactButton } from '@open-mercato/ui/primitives/compact-button'
12
+ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@open-mercato/ui/primitives/dialog'
13
+ import { ScrollArea } from '@open-mercato/ui/primitives/scroll-area'
14
+ import { SearchInput } from '@open-mercato/ui/primitives/search-input'
15
+ import { SegmentedControl, SegmentedControlItem } from '@open-mercato/ui/primitives/segmented-control'
16
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@open-mercato/ui/primitives/select'
17
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'
18
+ import { marketingBrands, marketingProducts, marketingWorldMap, marketingActivityIcons } from '../assets/marketing-artwork'
19
+
20
+ export type MarketingWidgetKind = 'total-sales' | 'total-visitors' | 'conversion-rate' | 'user-retention' | 'weekly-visitors' | 'visitor-channels' | 'geography' | 'realtime-visitors' | 'marketing-channels' | 'product-performance' | 'shipping-tracking' | 'my-products' | 'product-categories' | 'customer-segments' | 'sales-channels' | 'campaign-data' | 'support-analytics' | 'recent-activities'
21
+ const key = (name: string) => `design_system.gallery.samples.marketing.${name}`
22
+ const chartColor = 'var(--chart-orange)'
23
+ const palette = ['var(--chart-orange)','var(--chart-amber)','var(--chart-teal)','var(--chart-cyan)','var(--chart-violet)','var(--chart-indigo)']
24
+ function useMarketing() {
25
+ const t = useT()
26
+ const locale = useLocale()
27
+ const number = (value: number, digits = 0) => new Intl.NumberFormat(locale,{maximumFractionDigits:digits,minimumFractionDigits:digits}).format(value)
28
+ const money = (value: number) => new Intl.NumberFormat(locale,{style:'currency',currency:'USD',maximumFractionDigits:value % 1 ? 2 : 0}).format(value)
29
+ const date = (value: string, options: Intl.DateTimeFormatOptions = {month:'short',day:'numeric',year:'numeric'}) => new Intl.DateTimeFormat(locale,{...options,timeZone:'UTC'}).format(new Date(value))
30
+ return { t, locale, number, money, date }
31
+ }
32
+ function Trend({ value, badge = false, previous = false }: { value: number; badge?: boolean; previous?: boolean }) {
33
+ const { t, number } = useMarketing()
34
+ const label = `${value > 0 ? '+' : ''}${number(value,1)}%`
35
+ return <span className="inline-flex items-center gap-1 whitespace-nowrap text-sm">{badge ? <Badge appearance="light" tone={value < 0 ? 'error' : 'success'} size={20}>{label}</Badge> : <span className={value < 0 ? 'text-status-error-icon' : 'text-status-success-icon'}>{label}</span>}{previous ? <span className="text-muted-foreground">{t(key('versusWeek'))}</span> : null}</span>
36
+ }
37
+ function MarketingFrame({ children, compact = false, tall = false, flush = false, className }: { children: React.ReactNode; compact?: boolean; tall?: boolean; flush?: boolean; className?: string }) {
38
+ return <Card data-slot="marketing-widget" className={cn('relative w-88 max-w-full overflow-hidden rounded-xl border-0 bg-background ring-1 ring-inset ring-border shadow-xs',tall ? 'h-196' : compact ? 'h-44.5' : 'h-95',flush ? 'gap-0 p-0' : 'gap-5 p-5',className)}>{children}</Card>
39
+ }
40
+ function Header({ title, value, action, children, info = false, className }: { title: string; value?: React.ReactNode; action?: React.ReactNode; children?: React.ReactNode; info?: boolean; className?: string }) {
41
+ return <div className={cn('flex h-14 shrink-0 items-start gap-2',className)}><div className="min-w-0 flex-1"><h3 className="flex items-center gap-1 text-sm font-medium leading-5 text-muted-foreground">{title}{info ? <Info aria-hidden="true" className="size-3 shrink-0 text-text-disabled" /> : null}</h3><div className="mt-1 flex items-center gap-2"><span className="whitespace-nowrap text-2xl font-medium leading-8 tracking-tight">{value}</span>{children}</div></div>{action}</div>
42
+ }
43
+ function Details({ title, children, label, className }: { title: string; children: React.ReactNode; label?: string; className?: string }) {
44
+ const {t} = useMarketing()
45
+ const [open,setOpen] = React.useState(false)
46
+ return <Dialog open={open} onOpenChange={setOpen}><Button asChild variant="outline" className={cn('h-7 shrink-0 rounded-lg px-2.5 text-sm font-normal',className)}><DialogTrigger>{label ?? t(key('details'))}</DialogTrigger></Button><DialogContent onKeyDownCapture={event=>{if(event.key==='Enter'&&(event.ctrlKey||event.metaKey)){event.preventDefault();setOpen(false)}}}><DialogHeader><DialogTitle>{title}</DialogTitle><DialogDescription>{t(key('localData'))}</DialogDescription></DialogHeader><ScrollArea className="max-h-96" scrollbarSize="sm">{children}</ScrollArea><DialogFooter><Button variant="outline" onClick={()=>setOpen(false)}>{t(key('close'))}</Button></DialogFooter></DialogContent></Dialog>
47
+ }
48
+ function Period({ value, onChange, compact = false }: { value: string; onChange: (value: string)=>void; compact?: boolean }) {
49
+ const {t}=useMarketing()
50
+ return compact ? <Select value={value} onValueChange={onChange}><SelectTrigger aria-label={t(key('period'))} className="h-7 w-auto gap-2 rounded-lg px-2.5 text-sm"><SelectValue /></SelectTrigger><SelectContent>{['weekly','monthly','yearly'].map(option=><SelectItem key={option} value={option}>{t(key(option))}</SelectItem>)}</SelectContent></Select> : <SegmentedControl value={value} onValueChange={onChange} aria-label={t(key('period'))} className="h-6 w-full shrink-0 rounded-sm bg-background p-0">{['1d','1w','1m','3m','1y'].map(option=><SegmentedControlItem key={option} value={option} className="h-6 flex-1 rounded-none border-r border-border p-0 text-xs last:border-0 data-[state=checked]:bg-muted data-[state=checked]:font-medium data-[state=checked]:shadow-none">{t(key(`period${option}`))}</SegmentedControlItem>)}</SegmentedControl>
51
+ }
52
+ function Choices({ values, value, onChange, label }: { values: string[]; value: string; onChange: (value:string)=>void; label: string }) {
53
+ const {t}=useMarketing()
54
+ return <SegmentedControl value={value} onValueChange={onChange} aria-label={label} className="h-7 max-w-full gap-2 rounded-none border-0 bg-transparent p-0">{values.map(option=><SegmentedControlItem key={option} value={option} className="h-7 rounded-lg bg-muted px-2.5 text-sm font-normal data-[state=checked]:bg-accent-orange/10 data-[state=checked]:font-normal data-[state=checked]:text-accent-orange-text data-[state=checked]:shadow-none data-[state=unchecked]:bg-muted">{t(key(option))}</SegmentedControlItem>)}</SegmentedControl>
55
+ }
56
+ function DataList({ rows }: { rows: { label: string; value: React.ReactNode; detail?: React.ReactNode; icon?: React.ReactNode }[] }) {
57
+ return <div className="flex flex-col gap-4">{rows.map(row=><div key={row.label} className="flex min-h-5 items-center gap-2 text-sm text-muted-foreground">{row.icon}<span className="min-w-0 flex-1">{row.label}</span><span>{row.value}</span>{row.detail ? <span className="ml-3 flex w-18 items-center justify-end gap-1">{row.detail}</span> : null}</div>)}</div>
58
+ }
59
+ const salesSeries = [28,14,33,11,57,26,57,7,42,39,24,44,31,36,15,25,13,21,41,46,15,30,24,44,18]
60
+ function TotalSales() {
61
+ const {t,money}=useMarketing()
62
+ const [period,setPeriod]=React.useState('1w')
63
+ const factor = {'1d':0.4,'1w':1,'1m':2,'3m':3,'1y':7}[period] ?? 1
64
+ const rows=[{label:t(key('onlineStore')),value:money(52.12*factor),icon:<Store aria-hidden="true" className="size-5" />,delta:4.5},{label:'Facebook',value:money(38.45*factor),icon:<img src={marketingBrands.facebook} alt="" className="size-5" />,delta:-2.8},{label:'Instagram',value:money(37.75*factor),icon:<img src={marketingBrands.instagram} alt="" className="size-5" />,delta:3.2}].map(row=>({...row,detail:<><ArrowUp aria-hidden="true" className={cn('size-4',row.delta < 0 ? 'rotate-180 text-status-error-icon':'text-status-success-icon')} />{row.delta>0?'+':''}{row.delta}%</>}))
65
+ return <MarketingFrame><Header title={t(key('totalSales'))} value={money(128.32*factor)} action={<Details title={t(key('totalSales'))} label={t(key('report'))}><DataList rows={rows} /></Details>}><Trend value={2} badge /></Header><Period value={period} onChange={setPeriod}/><ChartContainer config={{value:{label:t(key('sales')),color:chartColor}}} className="h-27 min-h-0 w-full shrink-0" aria-label={t(key('totalSales'))}><ResponsiveContainer width="100%" height="100%"><LineChart data={salesSeries.map((value,index)=>({index,value:value*factor}))} margin={{top:0,right:0,bottom:0,left:0}}><CartesianGrid stroke="var(--border)" strokeDasharray="3 3"/><Tooltip content={<ChartTooltipContent/>}/><Line dataKey="value" type="linear" stroke={chartColor} strokeWidth={1.5} dot={false} isAnimationActive={false}/></LineChart></ResponsiveContainer></ChartContainer><DataList rows={rows}/></MarketingFrame>
66
+ }
67
+ function TotalVisitors() {
68
+ const {t,number}=useMarketing()
69
+ const values=[{key:'desktop',value:27,delta:-3.2,color:'bg-chart-amber'},{key:'tablet',value:12,delta:-6.4,color:'bg-chart-cyan'},{key:'mobile',value:61,delta:0.8,color:'bg-chart-violet'}]
70
+ return <MarketingFrame><Header title={t(key('totalVisitors'))} value={number(237456)}><Trend value={-1.4} badge/></Header><div className="flex min-h-0 flex-1 border-t border-border pt-5">{values.map(item=><div key={item.key} className="flex min-w-0 flex-1 flex-col border-r border-dashed border-border px-4 first:pl-0 last:border-0 last:pr-0"><p className="text-sm text-muted-foreground">{t(key(item.key))}</p><p className="mt-1 text-xl font-medium">{item.value}%</p><span className="mt-auto flex items-center gap-1 text-sm text-muted-foreground">{item.delta>0?'+':''}{number(item.delta,1)}%{item.delta<0?<ArrowDownLeft aria-hidden="true" className="size-3 text-status-error-icon"/>:<ArrowUpRight aria-hidden="true" className="size-3 text-status-success-icon"/>}</span><span className={cn('mt-4 h-2 w-full rounded-sm',item.color)}/></div>)}</div></MarketingFrame>
71
+ }
72
+ function ConversionRate() {
73
+ const {t,number}=useMarketing()
74
+ const rows=[['addedToCart',3842,1.8],['reachedCheckout',1256,-1.2],['purchased',649,2.4]] as const
75
+ const list=<DataList rows={rows.map(([label,value,delta])=>({label:t(key(label)),value:number(value),detail:<Trend value={delta}/>}))}/>
76
+ const series=[12,9,6,12,3,8,7,6,23,19,28,3,14,4].map((value,index)=>({month:index,value,previous:value+8+(index%3)*5}))
77
+ return <MarketingFrame><Header title={t(key('conversionRate'))} value={`${number(16.9,1)}%`} action={<Details title={t(key('conversionRate'))}>{list}</Details>}><Trend value={2.1} badge/></Header><div className="border-t border-border pt-5">{list}</div><ChartContainer config={{value:{label:t(key('purchased')),color:chartColor},previous:{label:t(key('previousPeriod')),color:'var(--chart-orange)'}}} className="min-h-0 flex-1" aria-label={t(key('conversionRate'))}><ResponsiveContainer width="100%" height="100%"><AreaChart data={series} margin={{top:0,right:0,left:0,bottom:0}}><CartesianGrid stroke="var(--border)"/><Tooltip content={<ChartTooltipContent/>}/><Area dataKey="previous" stroke="none" fill={chartColor} fillOpacity={0.18} isAnimationActive={false}/><Area dataKey="value" stroke="none" fill={chartColor} fillOpacity={1} isAnimationActive={false}/></AreaChart></ResponsiveContainer></ChartContainer><div className="-mt-3 flex justify-between text-overline text-muted-foreground">{['feb','mar','apr','may','jun','jul'].map(month=><span key={month}>{t(key(month))}</span>)}</div></MarketingFrame>
78
+ }
79
+ function Retention() {
80
+ const {t,number}=useMarketing()
81
+ const [selected,setSelected]=React.useState<[number,number]|null>(null)
82
+ const cells=Array.from({length:12},(_,row)=>Array.from({length:12-row},(_,column)=>({row,column,value:Math.max(8,100-row*6-column*4+(row*column%5)*5)}))).flat()
83
+ return <MarketingFrame><Header title={t(key('retention'))} value="24%" action={<Details title={t(key('retention'))}><DataList rows={cells.filter(cell=>cell.column===0).map(cell=>({label:t(key('month'),undefined,{month:cell.row+1}),value:`${cell.value}%`}))}/></Details>}><Trend value={2} badge/></Header><div className="grid h-48 shrink-0 grid-cols-12 grid-rows-12 gap-0.5" role="group" aria-label={t(key('retentionCohorts'))}>{cells.map(cell=><button key={`${cell.row}-${cell.column}`} type="button" aria-label={t(key('cohortCell'),undefined,{cohort:cell.row+1,month:cell.column+1,value:cell.value})} aria-pressed={selected?.[0]===cell.row&&selected?.[1]===cell.column} className="min-h-0 min-w-0 bg-chart-orange outline-none focus-visible:ring-2 focus-visible:ring-foreground focus-visible:ring-offset-1" style={{gridRow:cell.row+1,gridColumn:cell.column+1,opacity:cell.value/100}} onClick={()=>setSelected([cell.row,cell.column])}/>)}</div><div className="-mt-3 grid grid-cols-12 text-center text-overline text-muted-foreground">{Array.from({length:12},(_,index)=><span key={index}>{index+1}</span>)}</div><p aria-live="polite" className="mt-auto flex h-7 items-center gap-2 rounded-lg border border-border px-2 text-xs text-muted-foreground"><Info aria-hidden="true" className="size-3 shrink-0"/><span className="truncate">{selected?t(key('cohortCell'),undefined,{cohort:selected[0]+1,month:selected[1]+1,value:number(cells.find(cell=>cell.row===selected[0]&&cell.column===selected[1])?.value??0)}):t(key('retentionUpdated'))}</span></p></MarketingFrame>
84
+ }
85
+ function WeeklyVisitors() {
86
+ const {t,number}=useMarketing()
87
+ const values=['happiness','sadness','anger','fear','surprise','disgust','love','excitement']
88
+ const [series,setSeries]=React.useState({fresh:true,returning:true})
89
+ return <MarketingFrame className="gap-4"><Header title={t(key('weeklyVisitors'))} value={number(16008)}><Trend value={1.1} badge/></Header><div className="flex h-7 shrink-0 rounded-lg border border-border">{(['fresh','returning'] as const).map((item,index)=><Button key={item} variant="ghost" aria-pressed={series[item]} onClick={()=>setSeries(current=>({...current,[item]:!current[item]}))} className="h-7 min-w-0 flex-1 gap-2 p-0 text-xs font-normal"><span className={cn('size-2 rounded-full',index?'bg-chart-emerald':'bg-chart-orange',!series[item]&&'opacity-30')}/>{t(key(item==='fresh'?'newVisitors':'returningVisitors'))}</Button>)}</div><ChartContainer config={{fresh:{label:t(key('newVisitors')),color:chartColor},returning:{label:t(key('returningVisitors')),color:'var(--chart-emerald)'}}} className="min-h-0 flex-1" aria-label={t(key('weeklyVisitors'))}><ResponsiveContainer width="100%" height="100%"><RadarChart data={values.map((label,index)=>({label:t(key(label)),fresh:[76,39,84,72,40,70,74,32][index],returning:[43,75,39,40,0,54,39,75][index]}))} outerRadius="62%"><PolarGrid stroke="var(--border)"/><PolarAngleAxis dataKey="label" tick={{fontSize:11,fill:'var(--muted-foreground)'}}/>{series.fresh?<Radar name={t(key('newVisitors'))} dataKey="fresh" stroke={chartColor} fill={chartColor} fillOpacity={0.08} dot={{r:2}} isAnimationActive={false}/>:null}{series.returning?<Radar name={t(key('returningVisitors'))} dataKey="returning" stroke="var(--chart-emerald)" fill="var(--chart-emerald)" fillOpacity={0.08} dot={{r:2}} isAnimationActive={false}/>:null}<Tooltip content={<ChartTooltipContent/>}/></RadarChart></ResponsiveContainer></ChartContainer></MarketingFrame>
90
+ }
91
+ function ChannelRows({ marketing = false }: { marketing?: boolean }) {
92
+ const {t,money,number}=useMarketing()
93
+ const names=marketing?['acquisition','conversion','roi']:['Facebook','Instagram','Google']
94
+ return <div className="flex flex-col gap-4"><div className="flex items-center text-xs text-muted-foreground"><span className="flex-1">{t(key('channels'))}</span><span className="w-22">{t(key(marketing?'metric':'percent'))}</span><span className="w-12">{t(key('total'))}</span></div>{names.map((name,index)=><div key={name} className="flex h-5 items-center gap-2 text-sm text-muted-foreground">{marketing ? index===0?<UserRound aria-hidden="true" className="size-5"/>:index===1?<Clock3 aria-hidden="true" className="size-5"/>:<Target aria-hidden="true" className="size-5"/>:<img src={marketingBrands[name.toLowerCase() as 'facebook'|'instagram'|'google']} alt="" className="size-5"/>}<span className="min-w-0 flex-1">{marketing?t(key(name)):name}</span><span className="w-18">{marketing?[money(38.25),t(key('days'),undefined,{count:4.2}),'324%'][index]:`${[28,23,32][index]}%`}</span><span className="w-12 text-right">{marketing?<Trend value={[5.2,3.8,4.5][index]}/>:number([6958,5716,7952][index])}</span></div>)}</div>
95
+ }
96
+ function Channels({ marketing = false }: { marketing?: boolean }) {
97
+ const {t}=useMarketing()
98
+ const title=t(key(marketing?'marketingChannels':'visitorChannels'))
99
+ return <MarketingFrame><Header title={title} info={marketing} value={marketing?'82%':'78%'} action={marketing?<Details title={title}><ChannelRows marketing/></Details>:undefined}><Trend value={marketing?2.1:-0.4} badge={!marketing} previous={marketing}/></Header><div className="space-y-4"><div className="flex h-2 gap-1">{[41,41,16].map((width,index)=><span key={index} className="rounded-sm" style={{width:`${width}%`,background:marketing?palette[index]:['var(--chart-amber)','var(--chart-cyan)','var(--chart-violet)'][index]}}/>)}</div><div className="flex justify-between gap-1 text-xs text-muted-foreground">{(marketing?['organicSearch','socialMedia','direct']:['organic','referral','direct']).map((name,index)=><span key={name} className="flex items-center gap-2"><span className="size-2 shrink-0 rounded-full" style={{background:marketing?palette[index]:['var(--chart-amber)','var(--chart-cyan)','var(--chart-violet)'][index]}}/>{t(key(name))}</span>)}</div></div><div className={cn('border-t border-border pt-5',marketing&&'border-dashed')}><ChannelRows marketing={marketing}/></div><Details title={title} label={t(key('viewReports'))} className="mt-auto h-8 w-full"><ChannelRows marketing={marketing}/></Details></MarketingFrame>
100
+ }
101
+ const mapPoints=[{key:'greenland',x:161,y:7,visitors:1032},{key:'usa',x:80,y:69,visitors:8530},{key:'brazil',x:129,y:126,visitors:3240},{key:'turkey',x:222,y:110,visitors:6504},{key:'china',x:276,y:57,visitors:9322}]
102
+ function Geography() {
103
+ const {t,number}=useMarketing()
104
+ const [selected,setSelected]=React.useState('turkey')
105
+ const [zoom,setZoom]=React.useState(1)
106
+ const place=mapPoints.find(point=>point.key===selected)??mapPoints[3]
107
+ return <MarketingFrame className="gap-4"><Header title={t(key('geography'))} value={number(place.visitors)} action={<Details title={t(key('geography'))}><DataList rows={mapPoints.map(point=>({label:t(key(point.key)),value:number(point.visitors)}))}/></Details>}><span className="flex h-6 items-center gap-1 rounded-sm border border-border px-1 text-xs text-muted-foreground">{selected==='turkey'?<img src={marketingBrands.turkey} alt="" className="size-4"/>:null}{t(key(selected))}</span></Header><div className="flex h-7 shrink-0 items-center justify-around rounded-lg border border-border text-xs text-muted-foreground">{['men','women','other'].map((item,index)=><span key={item}>{t(key(item))} <span className="text-foreground">{[32,60,8][index]}%</span></span>)}</div><div data-slot="marketing-map" className="relative h-56 w-full shrink-0 overflow-hidden"><div className="absolute inset-0 origin-center transition-transform" style={{transform:`scale(${zoom})`}}><span aria-hidden="true" className="absolute left-0 top-0 h-56 w-83 bg-text-disabled" style={{maskImage:`url("${marketingWorldMap}")`,WebkitMaskImage:`url("${marketingWorldMap}")`,maskSize:'100% 100%',WebkitMaskSize:'100% 100%'}}/>{mapPoints.map(point=><button key={point.key} type="button" aria-label={t(key(point.key))} aria-pressed={selected===point.key} onClick={()=>setSelected(point.key)} style={{left:`${point.x/312*100}%`,top:`${point.y/224*100}%`}} className={cn('absolute flex size-4 items-center justify-center rounded-full bg-background ring-1 ring-inset ring-border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground',selected===point.key&&'bg-accent-orange ring-4 ring-accent-orange/20')}><span className={cn('size-2 rounded-full',selected===point.key?'bg-accent-orange-foreground':'bg-text-disabled')}/></button>)}</div><div className="absolute bottom-0 left-0 flex flex-col gap-2"><CompactButton aria-label={t(key('zoomIn'))} disabled={zoom>=2} onClick={()=>setZoom(current=>Math.min(2,current+0.25))}><Plus aria-hidden="true"/></CompactButton><CompactButton aria-label={t(key('zoomOut'))} disabled={zoom<=1} onClick={()=>setZoom(current=>Math.max(1,current-0.25))}><Minus aria-hidden="true"/></CompactButton></div><CompactButton className="absolute bottom-0 right-0" aria-label={t(key('resetMap'))} onClick={()=>{setZoom(1);setSelected('turkey')}}><Code aria-hidden="true"/></CompactButton></div></MarketingFrame>
108
+ }
109
+ function RealtimeVisitors() {
110
+ const {t,number}=useMarketing()
111
+ return <MarketingFrame><Header title={t(key('realtimeVisitors'))} value={t(key('millions'),undefined,{value:number(32.6,1)})}><Trend value={8.4} badge/></Header><div className="relative h-39 shrink-0" aria-label={t(key('regionalVisitors'))}><div className="absolute left-1/2 top-1/2 -ml-10.5 -mt-9.5 flex size-20 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full bg-chart-emerald/25 text-xl text-status-success-foreground">20%</div><div className="absolute left-1/2 top-1/2 ml-10 mt-1 flex size-33 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full bg-chart-orange/25 text-title-4 font-medium text-accent-orange-text">48%</div><div className="absolute left-1/2 top-1/2 -ml-14.5 mt-7.5 flex size-24 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full bg-chart-amber/25 text-2xl font-medium text-status-warning-foreground">32%</div></div><DataList rows={['europe','asia','americas'].map((region,index)=>({label:t(key(region)),value:t(key('millions'),undefined,{value:number([15.8,10.2,6.6][index],1)}),icon:<span className="ml-1 size-2 rounded-full" style={{background:[palette[0],palette[1],'var(--chart-emerald)'][index]}}/>,detail:<><ArrowUpRight aria-hidden="true" className={cn('size-4',index===1?'rotate-90 text-status-error-icon':'text-status-success-icon')}/>+{number([4.7,6.2,3.8][index],1)}%</>}))}/></MarketingFrame>
112
+ }
113
+ function ProductPerformance() {
114
+ const {t,number}=useMarketing()
115
+ const [period,setPeriod]=React.useState('1w')
116
+ const factor=period==='1w'?1:period==='1d'?0.6:0.85
117
+ return <MarketingFrame><Header title={t(key('productPerformance'))} value={`${number(22.8*factor,1)}%`}><Trend value={8.4} badge/></Header><Period value={period} onChange={setPeriod}/><div className="flex h-39.5 shrink-0 items-end gap-2.5" aria-label={t(key('productPerformance'))}>{[50,80,100,60,40].map((value,index)=><div key={index} className="flex min-w-0 flex-1 flex-col items-center justify-between rounded-lg bg-chart-orange px-1 py-2 text-xs font-medium text-accent-orange-foreground" style={{height:`${value*factor}%`}}><span>{Math.round(value*factor)}%</span><span>{String.fromCharCode(65+index)}</span></div>)}</div><div className="flex flex-1">{['averageRating','satisfaction','returnRate'].map((name,index)=><div key={name} className="flex min-w-0 flex-1 flex-col items-center justify-center border-r border-border last:border-0"><p className="text-base font-medium text-muted-foreground">{[number(4.7,1),'92%',`${number(4.2,1)}%`][index]}</p><p className="mt-1 text-xs text-muted-foreground">{t(key(name))}</p></div>)}</div></MarketingFrame>
118
+ }
119
+ const productData=[{image:0,name:'Apple Watch S5 GPS 40MM',count:500},{image:4,name:'AirPods Max Green',count:960},{image:6,name:'iMac M1 24-inch Purple',count:45},{image:8,name:'iPhone 15 Pro 512GB',count:150},{image:1,name:'MacBook Air',count:380},{image:3,name:'AirPods Pro',count:780}]
120
+ function ProductRows({ query = '' }: { query?: string }) {
121
+ const {t,number}=useMarketing()
122
+ const matches=productData.filter(product=>product.name.toLocaleLowerCase().includes(query.toLocaleLowerCase()))
123
+ return matches.length ? <div>{matches.map(product=><div key={product.name} className="flex min-h-17 items-center gap-3 border-b border-border px-5 py-3 last:border-0"><img src={marketingProducts[product.image].src} alt="" className="size-8 shrink-0 object-contain"/><div className="min-w-0"><p className="truncate text-sm font-medium">{product.name}</p><p className="mt-1 text-xs text-muted-foreground">{t(key('unitsSold'),undefined,{count:number(product.count)})}</p></div></div>)}</div> : <p role="status" className="p-5 text-sm text-muted-foreground">{t(key('noProducts'))}</p>
124
+ }
125
+ function MyProducts() {
126
+ const {t,number}=useMarketing()
127
+ const [query,setQuery]=React.useState('')
128
+ return <MarketingFrame flush><Header title={t(key('myProducts'))} info value={number(468)} className="h-23 px-5 py-5" action={<Details title={t(key('myProducts'))} label={t(key('seeAll'))}><ProductRows query={query}/></Details>}><Trend value={2.1} previous/></Header><div className="border-y border-border px-5"><SearchInput aria-label={t(key('searchProducts'))} placeholder={t(key('searchProducts'))} value={query} onChange={setQuery} className="h-11 rounded-none border-0 px-0 shadow-none"/></div><ScrollArea className="min-h-0 flex-1" scrollbarSize="xs"><ProductRows query={query}/></ScrollArea></MarketingFrame>
129
+ }
130
+ function ShippingTracking() {
131
+ const {t,number,date}=useMarketing()
132
+ const [period,setPeriod]=React.useState('weekly')
133
+ const [status,setStatus]=React.useState('delivered')
134
+ const [product,setProduct]=React.useState(0)
135
+ const factor=(status==='delivered'?1:status==='inTransit'?0.55:0.2)*(period==='weekly'?1:period==='monthly'?3:12)
136
+ const data=[0,10,44,59,0,29,0,0,45,29,29,10,29,29,0,59,29,29].map((value,index)=>({day:date(`2024-02-${String(19+Math.floor(index/3)).padStart(2,'0')}T00:00:00Z`,{weekday:'short'}),value:(value+product*3)*factor}))
137
+ return <MarketingFrame flush><div className="flex h-35 shrink-0 flex-col gap-4 p-5"><Header title={t(key('shippingTracking'))} value={number(Math.round(3844*factor))} info action={<Period compact value={period} onChange={setPeriod}/>}><Trend value={1.8} badge/></Header><Choices values={['delivered','inTransit','returned']} value={status} onChange={setStatus} label={t(key('shippingStatus'))}/></div><div className="flex h-12 shrink-0 items-center gap-2 border-y border-border px-5"><img src={marketingProducts[product].src} alt="" className="size-6"/><span className="min-w-0 flex-1 truncate text-sm text-muted-foreground">{marketingProducts[product].name}</span><div className="flex"><CompactButton aria-label={t(key('previousProduct'))} onClick={()=>setProduct(current=>(current+marketingProducts.length-1)%marketingProducts.length)}><ChevronLeft aria-hidden="true"/></CompactButton><CompactButton aria-label={t(key('nextProduct'))} onClick={()=>setProduct(current=>(current+1)%marketingProducts.length)}><ChevronRight aria-hidden="true"/></CompactButton></div></div><ChartContainer config={{value:{label:t(key(status)),color:chartColor}}} className="min-h-0 flex-1" aria-label={t(key('shippingTracking'))}><ResponsiveContainer width="100%" height="100%"><LineChart data={data} margin={{top:0,right:20,left:20,bottom:0}}><CartesianGrid stroke="var(--border)"/><XAxis dataKey="day" interval={2} tickLine={false} axisLine={false} tick={{fontSize:12,fill:'var(--muted-foreground)'}}/><Tooltip content={<ChartTooltipContent/>}/><Line type="stepAfter" dataKey="value" dot={false} stroke={chartColor} strokeWidth={1.5} isAnimationActive={false}/></LineChart></ResponsiveContainer></ChartContainer></MarketingFrame>
138
+ }
139
+ function ProductCategories() {
140
+ const {t,number}=useMarketing()
141
+ const [category,setCategory]=React.useState(0)
142
+ const categories=['accessories','computers','audio']
143
+ const percentage=[58,72,34][category]
144
+ const details=<DataList rows={categories.map((name,index)=>({label:t(key(name)),value:t(key('productsCount'),undefined,{count:[45,32,18][index]})}))}/>
145
+ return <MarketingFrame compact className="gap-4"><Header title={t(key('productCategories'))} value={`${percentage}%`} info action={<Details title={t(key('productCategories'))}>{details}</Details>}><Trend value={2.1} previous/></Header><div className="flex h-7.5 shrink-0 gap-0.5" role="meter" aria-label={t(key('productCategories'))} aria-valuemin={0} aria-valuemax={100} aria-valuenow={percentage}>{Array.from({length:36},(_,index)=><span key={index} className={cn('min-w-0 flex-1',index<Math.round(percentage/100*36)?'bg-chart-orange':'bg-border')}/>)}</div><div className="flex min-h-5 items-center gap-2 text-sm text-muted-foreground"><span>{t(key(categories[category]))}</span><div className="flex"><CompactButton aria-label={t(key('previousCategory'))} onClick={()=>setCategory(current=>(current+2)%3)}><ChevronLeft aria-hidden="true"/></CompactButton><CompactButton aria-label={t(key('nextCategory'))} onClick={()=>setCategory(current=>(current+1)%3)}><ChevronRight aria-hidden="true"/></CompactButton></div><span className="ml-auto whitespace-nowrap">{t(key('productsCount'),undefined,{count:number([45,32,18][category])})}</span><Trend value={3.2}/></div></MarketingFrame>
146
+ }
147
+ function CustomerSegments() {
148
+ const {t,money}=useMarketing()
149
+ const data=[{label:'premium',value:32,amount:9450},{label:'regular',value:46,amount:8320},{label:'new',value:20,amount:3280}]
150
+ return <MarketingFrame compact><div className="flex h-5 shrink-0 items-center justify-between gap-2"><h3 className="flex items-center gap-1 text-sm font-medium text-muted-foreground">{t(key('customerSegments'))}<Info aria-hidden="true" className="size-3 text-text-disabled"/></h3><Trend value={5.8} previous/></div><div className="flex h-25 items-center gap-5"><ChartContainer config={Object.fromEntries(data.map((item,index)=>[item.label,{label:t(key(item.label)),color:palette[index]}]))} className="size-25 shrink-0" aria-label={t(key('customerSegments'))}><PieChart width={100} height={100}><Pie data={data} dataKey="value" nameKey="label" cx={45} cy={45} innerRadius={34} outerRadius={49} paddingAngle={3} stroke="none" startAngle={90} endAngle={450} isAnimationActive={false}>{data.map((item,index)=><Cell key={item.label} fill={palette[index]}/>)}</Pie><Tooltip content={<ChartTooltipContent/>}/></PieChart></ChartContainer><div className="flex min-w-0 flex-1 flex-col gap-3">{data.map((item,index)=><div key={item.label} className="flex items-center gap-2 text-sm text-muted-foreground"><span className="size-2 shrink-0 rounded-full" style={{background:palette[index]}}/><span className="min-w-0 flex-1 truncate">{t(key(item.label))}</span><span>{money(item.amount)}</span><span className="text-xs">{item.value}%</span></div>)}</div></div></MarketingFrame>
151
+ }
152
+ function SalesChannels() {
153
+ const {t,number,date}=useMarketing()
154
+ const [period,setPeriod]=React.useState('weekly')
155
+ const multiplier=period==='weekly'?1:period==='monthly'?4:52
156
+ return <MarketingFrame compact className="gap-4"><Header title={t(key('salesChannels'))} value={number(246*multiplier)} info action={<Period compact value={period} onChange={setPeriod}/>}><Trend value={2.1} previous/></Header><div className="space-y-2"><div className="flex justify-between text-xs text-muted-foreground"><span>{date('2024-02-21T00:00:00Z')}</span><span>{date(period==='weekly'?'2024-02-28T00:00:00Z':period==='monthly'?'2024-03-21T00:00:00Z':'2025-02-21T00:00:00Z')}</span></div><div className="flex h-10.5 gap-1" aria-label={t(key('salesChannels'))}>{[26,21,16,13,10,5,2].map((width,index)=><span key={index} className="rounded-sm" style={{flex:width,background:['var(--chart-indigo)','var(--chart-cyan)','var(--chart-violet)','var(--chart-orange)','var(--chart-amber)','var(--chart-emerald)','var(--chart-teal)'][index]}}/>)}</div></div></MarketingFrame>
157
+ }
158
+ function CampaignData() {
159
+ const {t,money}=useMarketing()
160
+ return <MarketingFrame compact flush><Header title={t(key('campaignData'))} value={money(1750)} info className="h-23.5 px-5 py-5" action={<Details title={t(key('campaignData'))}><DataList rows={[{label:t(key('campaignBudget')),value:money(1750)},{label:t(key('usedBudget')),value:money(32900)},{label:t(key('campaignProgress')),value:'45%'}]}/></Details>}><Badge size={20} appearance="lighter" tone="success">{t(key('lastDays'),undefined,{count:15})}</Badge></Header><div className="flex min-h-0 flex-1 border-t border-border"><ChartContainer config={{value:{label:t(key('campaignData')),color:chartColor}}} className="w-44 min-w-0 shrink-0 border-r border-accent-orange-border" aria-label={t(key('campaignData'))}><ResponsiveContainer width="100%" height="100%"><AreaChart data={salesSeries.slice(0,18).map((value,index)=>({index,value:value+index*2}))} margin={{top:0,left:0,right:0,bottom:0}}><Tooltip content={<ChartTooltipContent/>}/><Area dataKey="value" type="linear" stroke={chartColor} strokeWidth={1.5} fill={chartColor} fillOpacity={0.18} isAnimationActive={false}/></AreaChart></ResponsiveContainer></ChartContainer><div className="flex min-w-0 flex-1 flex-col justify-end p-4"><p className="text-base font-medium">45%</p><p className="text-xs text-muted-foreground">{t(key('budgetUsed'),undefined,{amount:money(32900)})}</p></div></div></MarketingFrame>
161
+ }
162
+ type TicketStatus = 'solved' | 'inProgress' | 'pending'
163
+ const ticketSamples: {name:string;status:TicketStatus;minutes:number;priority:string;id:number}[]=[{name:'James Brown',status:'solved',minutes:12,priority:'high',id:98745},{name:'Sophia Williams',status:'inProgress',minutes:48,priority:'medium',id:98744},{name:'Matthew Johnson',status:'pending',minutes:19,priority:'low',id:98743}]
164
+ function SupportAnalytics() {
165
+ const {t,number,date}=useMarketing()
166
+ const [period,setPeriod]=React.useState('weekly')
167
+ const [category,setCategory]=React.useState('technical')
168
+ const [tab,setTab]=React.useState('allTickets')
169
+ const [tickets,setTickets]=React.useState(ticketSamples)
170
+ const [selected,setSelected]=React.useState<number|null>(null)
171
+ const factor=(['technical','billing','account','product'].indexOf(category)+4)/4*(period==='weekly'?1:period==='monthly'?4:52)
172
+ const current=tickets.find(ticket=>ticket.id===selected)
173
+ const visible=tickets.filter(ticket=>tab==='allTickets'||(tab==='openTickets'?ticket.status!=='solved':ticket.status==='solved'))
174
+ const metrics=[{label:'firstResponse',detail:'belowSla',value:t(key('minutes'),undefined,{count:Math.round(15*factor)}),delta:-22},{label:'resolutionTime',detail:'meetingSla',value:t(key('minutes'),undefined,{count:Math.round(48*factor)}),delta:-18},{label:'csat',detail:'aboveTarget',value:'4.8/5',delta:-0.3}]
175
+ return <><MarketingFrame tall flush><div className="flex h-34 shrink-0 flex-col gap-3 p-5"><Header title={t(key('supportAnalytics'))} value={number(Math.round(2450*factor))} info action={<Period compact value={period} onChange={setPeriod}/>}><Trend value={5.4}/><span className="text-sm text-muted-foreground">{t(key('totalTickets'))}</span></Header><Choices values={['technical','billing','account','product']} value={category} onChange={setCategory} label={t(key('ticketCategory'))}/></div><ChartContainer config={{value:{label:t(key('responseTime')),color:chartColor}}} className="h-44 shrink-0 border-y border-border px-3 pt-4" aria-label={t(key('responseTime'))}><ResponsiveContainer width="100%" height="100%"><BarChart data={[10,34,44,58,44,34,34].map((value,index)=>({day:date(`2024-02-${19+index}T00:00:00Z`,{weekday:'short'}),value:Math.round(value*factor)}))} margin={{top:0,right:0,left:0,bottom:0}}><XAxis dataKey="day" axisLine={false} tickLine={false} tick={{fontSize:12,fill:'var(--muted-foreground)'}}/><Tooltip content={<ChartTooltipContent/>}/><ReferenceLine y={30*factor} stroke="var(--muted-foreground)" strokeDasharray="4 4" label={{value:t(key('targetMinutes'),undefined,{count:30}),fill:'var(--foreground)',fontSize:12,position:'insideTopLeft'}}/><Bar dataKey="value" radius={[6,6,6,6]} isAnimationActive={false}>{Array.from({length:7},(_,index)=><Cell key={index} fill={index===4?chartColor:'var(--border)'}/>)}</Bar></BarChart></ResponsiveContainer></ChartContainer><Tabs value={tab} onValueChange={setTab} variant="underline" className="min-h-0 flex-1 gap-0"><TabsList className="h-12 w-full shrink-0 justify-start gap-4 px-5"><TabsTrigger value="allTickets" className="min-w-0 flex-auto justify-center px-0 text-sm font-normal data-[state=active]:border-accent-orange-border hover:text-accent-orange-text">{t(key('allTickets'))}</TabsTrigger><TabsTrigger value="openTickets" className="min-w-0 flex-auto justify-center px-0 text-sm font-normal data-[state=active]:border-accent-orange-border hover:text-accent-orange-text">{t(key('openTickets'))}</TabsTrigger><TabsTrigger value="solvedTickets" className="min-w-0 flex-auto justify-center px-0 text-sm font-normal data-[state=active]:border-accent-orange-border hover:text-accent-orange-text">{t(key('solvedTickets'))}</TabsTrigger></TabsList>{['allTickets','openTickets','solvedTickets'].map(value=><TabsContent key={value} value={value} className="m-0 flex min-h-0 flex-1 flex-col"><div className="flex shrink-0 flex-col gap-4 px-5 pb-5 pt-4"><div className="flex text-xs text-muted-foreground"><span className="flex-1">{t(key('channels'))}</span><span className="w-18">{t(key('actual'))}</span><span className="w-10">{t(key('average'))}</span></div>{metrics.map((metric,index)=><div key={metric.label} className="flex items-center text-sm"><div className="min-w-0 flex-1"><p>{t(key(metric.label))}</p><p className="mt-1 text-xs text-muted-foreground">{t(key(metric.detail))}</p></div><span className="w-18 text-muted-foreground">{metric.value}</span><span className={cn('w-10',index===2?'text-status-error-icon':'text-status-success-icon')}>{number(metric.delta,index===2?1:0)}%</span></div>)}</div><div className="min-h-0 flex-1 border-t border-border px-5 pt-4"><p className="mb-3 text-xs text-muted-foreground">{t(key('recentTickets'))}</p><ScrollArea className="h-40" scrollbarSize="xs"><div className="flex flex-col gap-4">{visible.map((ticket,index)=><Button key={ticket.id} variant="ghost" onClick={()=>setSelected(ticket.id)} className="h-auto min-h-10 w-full justify-start gap-3 p-0 text-left font-normal hover:bg-muted"><Avatar label={ticket.name} size={32} className={index===1?'bg-chart-amber/20 text-status-warning-foreground':index===2?'bg-chart-violet/20 text-accent-indigo':'bg-muted'}/><div className="min-w-0 flex-1"><p className="truncate text-sm font-medium">{ticket.name}</p><p className="mt-1 flex items-center gap-1 text-xs text-muted-foreground">{t(key(ticket.priority))} · <span className={ticket.status==='solved'?'text-status-success-icon':'text-status-warning-icon'}>{t(key(ticket.status))}</span></p></div><div className="text-right"><p className="text-sm">{t(key('minutes'),undefined,{count:ticket.minutes})}</p><p className="mt-1 text-xs text-muted-foreground">#TKT-{ticket.id}</p></div></Button>)}</div></ScrollArea></div></TabsContent>)}</Tabs></MarketingFrame><Dialog open={selected!==null} onOpenChange={open=>{if(!open)setSelected(null)}}><DialogContent onKeyDownCapture={event=>{if(event.key==='Enter'&&(event.ctrlKey||event.metaKey)){event.preventDefault();setSelected(null)}}}><DialogHeader><DialogTitle>{current?.name}</DialogTitle><DialogDescription>{current?`#TKT-${current.id}`:t(key('recentTickets'))}</DialogDescription></DialogHeader>{current?<DataList rows={[{label:t(key('status')),value:t(key(current.status))},{label:t(key('priority')),value:t(key(current.priority))}]}/>:null}<DialogFooter><Button variant="outline" onClick={()=>setSelected(null)}>{t(key('close'))}</Button><Button disabled={current?.status==='solved'} onClick={()=>{setTickets(items=>items.map(ticket=>ticket.id===selected?{...ticket,status:'solved'}:ticket));setSelected(null)}}>{t(key('resolveTicket'))}</Button></DialogFooter></DialogContent></Dialog></>
176
+ }
177
+ const activityKinds=[{key:'inventory',icon:'stack-line',color:'text-chart-violet',detail:'inventoryDetail'},{key:'price',icon:'price-tag-line',color:'text-status-success-icon',detail:'priceDetail'},{key:'newProduct',icon:'add-circle-line',color:'text-chart-violet',detail:'newProductDetail'},{key:'images',icon:'image-line',color:'text-status-warning-icon',detail:'imagesDetail'},{key:'descriptionUpdated',icon:'pencil-line',color:'text-muted-foreground',detail:'descriptionDetail'}] satisfies {key:string;icon:string;color:string;detail:string}[]
178
+ function RecentActivities() {
179
+ const {t,date}=useMarketing()
180
+ const [period,setPeriod]=React.useState('today')
181
+ const [query,setQuery]=React.useState('')
182
+ const records=Array.from({length:period==='today'?7:period==='yesterday'?2:9},(_,index)=>({id:index,...activityKinds[index%5]})).filter(item=>[t(key(item.key)),t(key(item.detail)),t(key('activityProduct'))].join(' ').toLocaleLowerCase().includes(query.toLocaleLowerCase()))
183
+ const list=<div className="flex flex-col gap-5">{records.map((item,index)=><div key={item.id} className="relative flex gap-4"><div className="relative flex w-7 shrink-0 justify-center"><span className="z-10 flex size-7 items-center justify-center rounded-full border border-border bg-background shadow-xs"><span aria-hidden="true" className={cn('size-4 bg-current',item.color)} style={{maskImage:`url("${marketingActivityIcons[item.key as keyof typeof marketingActivityIcons]}")`,WebkitMaskImage:`url("${marketingActivityIcons[item.key as keyof typeof marketingActivityIcons]}")`,maskSize:'contain',WebkitMaskSize:'contain'}}/></span>{index<records.length-1?<span aria-hidden="true" className="absolute bottom-0 top-8 w-px bg-border"/>:null}</div><div className="min-w-0 flex-1"><div className="flex items-center gap-2"><p className="min-w-0 flex-1 text-sm font-medium">{t(key(item.key))}</p><time className="text-overline text-muted-foreground">{date('2024-02-21T11:30:00Z',{hour:'numeric',minute:'2-digit'})}</time></div><p className="mt-1 text-xs text-muted-foreground">{item.key==='price'?t(key('seasonalDiscount')):t(key('activityProduct'))}</p><p className="mt-1 text-xs text-muted-foreground">{t(key(item.detail))}</p></div></div>)}{!records.length?<p role="status" className="text-sm text-muted-foreground">{t(key('noActivities'))}</p>:null}</div>
184
+ return <MarketingFrame tall flush><div className="flex h-33 shrink-0 flex-col gap-4 p-5"><div className="flex items-start"><div className="flex-1"><h3 className="text-sm font-medium">{t(key('recentActivities'))}</h3><p className="mt-1 text-sm text-muted-foreground">{t(key('newActivities'),undefined,{count:5})}</p></div><Details title={t(key('recentActivities'))}>{list}</Details></div><Choices values={['today','yesterday','thisWeek']} value={period} onChange={setPeriod} label={t(key('activityPeriod'))}/></div><div className="border-y border-border px-5"><SearchInput aria-label={t(key('searchActivities'))} placeholder={t(key('searchActivities'))} value={query} onChange={setQuery} className="h-12 rounded-none border-0 px-0 shadow-none"/></div><ScrollArea className="min-h-0 flex-1 px-5 py-5" scrollbarSize="xs">{list}</ScrollArea></MarketingFrame>
185
+ }
186
+ const widgets: Record<MarketingWidgetKind, React.ComponentType> = {
187
+ 'total-sales':TotalSales,'total-visitors':TotalVisitors,'conversion-rate':ConversionRate,'user-retention':Retention,'weekly-visitors':WeeklyVisitors,'visitor-channels':Channels,geography:Geography,'realtime-visitors':RealtimeVisitors,'marketing-channels':()=> <Channels marketing/>,'product-performance':ProductPerformance,'shipping-tracking':ShippingTracking,'my-products':MyProducts,'product-categories':ProductCategories,'customer-segments':CustomerSegments,'sales-channels':SalesChannels,'campaign-data':CampaignData,'support-analytics':SupportAnalytics,'recent-activities':RecentActivities,
188
+ }
189
+ export function MarketingWidgetDemo({kind}:{kind:MarketingWidgetKind}) { const Widget=widgets[kind]; return <Widget/> }
@@ -0,0 +1,48 @@
1
+ import * as React from 'react'
2
+ import { Globe, UserRound } from 'lucide-react'
3
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
4
+ import { Avatar } from '@open-mercato/ui/primitives/avatar'
5
+ import mastercard from '../assets/menu-mastercard.svg'
6
+ import apex from '../assets/menu-apex.svg'
7
+ import spotify from '../assets/menu-spotify.svg'
8
+ import unitedStates from '../assets/menu-united-states.svg'
9
+ import google from '../assets/select-google.png'
10
+ import poland from '../assets/select-poland.png'
11
+ import avatar from '../assets/avatar-illustration.png'
12
+
13
+ export type MenuKind = 'basic' | 'country' | 'avatar' | 'provider' | 'brand' | 'company' | 'icon'
14
+ export const selectKinds: MenuKind[] = ['basic', 'country', 'avatar', 'provider', 'brand', 'company']
15
+ export const commandKinds: MenuKind[] = ['basic', 'avatar', 'icon', 'brand', 'company', 'country']
16
+
17
+ const imageSource = (asset: string | { readonly src: string }) => typeof asset === 'string' ? asset : asset.src
18
+
19
+ export function useMenuChoices(kind: MenuKind) {
20
+ const t = useT()
21
+ return [0, 1, 2].map((index) => ({
22
+ value: `${kind}-${index}`,
23
+ label: t(`design_system.gallery.samples.menu.${kind}.${index}`),
24
+ description: t('design_system.gallery.samples.menu.description'),
25
+ disabled: index === 2,
26
+ alternative: index !== 0,
27
+ }))
28
+ }
29
+
30
+ export function MenuVisual({ kind, large = false, alternative = false, providerSize = 'row' }: {
31
+ kind: MenuKind
32
+ large?: boolean
33
+ alternative?: boolean
34
+ providerSize?: 'row' | 'select'
35
+ }) {
36
+ if (kind === 'avatar') {
37
+ return <Avatar size={large ? 40 : 20} label={alternative ? 'Sophia Williams' : 'James Brown'} src={alternative ? undefined : imageSource(avatar)} />
38
+ }
39
+ const asset = kind === 'country' ? alternative ? poland : unitedStates
40
+ : kind === 'provider' ? mastercard
41
+ : kind === 'brand' ? alternative ? google : spotify
42
+ : kind === 'company' ? apex : undefined
43
+ const Icon = kind === 'icon' ? UserRound : Globe
44
+ const content = asset
45
+ ? <img src={imageSource(asset)} alt="" className={kind === 'provider' ? providerSize === 'select' ? 'h-6 w-8 object-contain' : 'h-5 w-6.5 object-contain' : large ? 'size-6 object-contain' : 'size-5 object-contain'} />
46
+ : <Icon aria-hidden="true" className={large ? 'size-6' : 'size-5'} />
47
+ return <span aria-hidden="true" className={large ? 'flex size-10 shrink-0 items-center justify-center rounded-full border border-input bg-background text-muted-foreground' : 'inline-flex shrink-0 items-center justify-center text-muted-foreground'}>{content}</span>
48
+ }
@@ -0,0 +1,5 @@
1
+ export const selectSourceCode = "import * as React from 'react'\nimport { Globe, UserRound } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport mastercard from '@open-mercato/core/modules/design_system/gallery/assets/menu-mastercard.svg'\nimport apex from '@open-mercato/core/modules/design_system/gallery/assets/menu-apex.svg'\nimport spotify from '@open-mercato/core/modules/design_system/gallery/assets/menu-spotify.svg'\nimport unitedStates from '@open-mercato/core/modules/design_system/gallery/assets/menu-united-states.svg'\nimport google from '@open-mercato/core/modules/design_system/gallery/assets/select-google.png'\nimport poland from '@open-mercato/core/modules/design_system/gallery/assets/select-poland.png'\nimport avatar from '@open-mercato/core/modules/design_system/gallery/assets/avatar-illustration.png'\n\nexport type MenuKind = 'basic' | 'country' | 'avatar' | 'provider' | 'brand' | 'company' | 'icon'\nexport const selectKinds: MenuKind[] = ['basic', 'country', 'avatar', 'provider', 'brand', 'company']\nexport const commandKinds: MenuKind[] = ['basic', 'avatar', 'icon', 'brand', 'company', 'country']\n\nconst imageSource = (asset: string | { readonly src: string }) => typeof asset === 'string' ? asset : asset.src\n\nexport function useMenuChoices(kind: MenuKind) {\n const t = useT()\n return [0, 1, 2].map((index) => ({\n value: `${kind}-${index}`,\n label: t(`design_system.gallery.samples.menu.${kind}.${index}`),\n description: t('design_system.gallery.samples.menu.description'),\n disabled: index === 2,\n alternative: index !== 0,\n }))\n}\n\nexport function MenuVisual({ kind, large = false, alternative = false, providerSize = 'row' }: {\n kind: MenuKind\n large?: boolean\n alternative?: boolean\n providerSize?: 'row' | 'select'\n}) {\n if (kind === 'avatar') {\n return <Avatar size={large ? 40 : 20} label={alternative ? 'Sophia Williams' : 'James Brown'} src={alternative ? undefined : imageSource(avatar)} />\n }\n const asset = kind === 'country' ? alternative ? poland : unitedStates\n : kind === 'provider' ? mastercard\n : kind === 'brand' ? alternative ? google : spotify\n : kind === 'company' ? apex : undefined\n const Icon = kind === 'icon' ? UserRound : Globe\n const content = asset\n ? <img src={imageSource(asset)} alt=\"\" className={kind === 'provider' ? providerSize === 'select' ? 'h-6 w-8 object-contain' : 'h-5 w-6.5 object-contain' : large ? 'size-6 object-contain' : 'size-5 object-contain'} />\n : <Icon aria-hidden=\"true\" className={large ? 'size-6' : 'size-5'} />\n return <span aria-hidden=\"true\" className={large ? 'flex size-10 shrink-0 items-center justify-center rounded-full border border-input bg-background text-muted-foreground' : 'inline-flex shrink-0 items-center justify-center text-muted-foreground'}>{content}</span>\n}\n\nimport { Info, Plus } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Kbd } from '@open-mercato/ui/primitives/kbd'\nimport { FormField } from '@open-mercato/ui/primitives/form-field'\nimport { SimpleTooltip } from '@open-mercato/ui/primitives/tooltip'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectTriggerLeading, SelectValue } from '@open-mercato/ui/primitives/select'\nimport { Dropdown, DropdownTrigger, DropdownContent, DropdownInput, DropdownList, DropdownEmpty, DropdownItem, DropdownFooter, DropdownCaption } from '@open-mercato/ui/primitives/dropdown'\nimport { CommandMenu, CommandMenuTrigger, CommandMenuContent, CommandMenuInput, CommandMenuList, CommandMenuEmpty, CommandMenuItem, CommandMenuGroup, CommandMenuSeparator, CommandMenuFooter } from '@open-mercato/ui/primitives/command-menu'\n\nexport function SelectSourceExample({ kind, size = 36, state = 'filled' }: {\n kind: MenuKind\n size?: 32 | 36 | 40\n state?: 'default' | 'filled' | 'disabled' | 'error'\n}) {\n const t = useT()\n const choices = useMenuChoices(kind)\n const [value, setValue] = React.useState(state === 'default' || state === 'error' ? '' : choices[0].value)\n const selected = choices.find((choice) => choice.value === value)\n const disabled = state === 'disabled'\n const label = t(`design_system.gallery.samples.menu.kind.${kind}`)\n const providerPadding = kind !== 'provider' ? undefined : size === 40 ? 'p-2' : size === 36 ? 'p-1.5' : 'p-1'\n return (\n <Select value={value} onValueChange={setValue} disabled={disabled}>\n <FormField label={label} disabled={disabled} description={t('design_system.gallery.samples.menu.hint')} error={state === 'error' ? t('design_system.gallery.samples.menu.required') : undefined}>\n <SelectTrigger size={size} className={providerPadding}>\n {kind !== 'basic' && <SelectTriggerLeading><MenuVisual kind={kind} alternative={selected?.alternative} providerSize=\"select\" /></SelectTriggerLeading>}\n <SelectValue className=\"min-w-0 flex-1 text-left\" placeholder={t('design_system.gallery.samples.menu.placeholder')} />\n </SelectTrigger>\n </FormField>\n <SelectContent>\n {choices.map((choice) => <SelectItem key={choice.value} value={choice.value} disabled={choice.disabled} leading={kind === 'basic' ? undefined : <MenuVisual kind={kind} alternative={choice.alternative} />}>\n {choice.label}\n </SelectItem>)}\n </SelectContent>\n </Select>\n )\n}\n\nexport function SelectSourceMatrix({ size = 36, state = 'filled' }: { size?: 32 | 36 | 40; state?: 'default' | 'filled' | 'disabled' | 'error' }) {\n return <div className=\"grid w-full gap-6 sm:grid-cols-2 xl:grid-cols-3\">{selectKinds.map((kind) => <SelectSourceExample key={kind} kind={kind} size={size} state={state} />)}</div>\n}\n\n"
2
+
3
+ export const dropdownSourceCode = "import * as React from 'react'\nimport { Globe, UserRound } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport mastercard from '@open-mercato/core/modules/design_system/gallery/assets/menu-mastercard.svg'\nimport apex from '@open-mercato/core/modules/design_system/gallery/assets/menu-apex.svg'\nimport spotify from '@open-mercato/core/modules/design_system/gallery/assets/menu-spotify.svg'\nimport unitedStates from '@open-mercato/core/modules/design_system/gallery/assets/menu-united-states.svg'\nimport google from '@open-mercato/core/modules/design_system/gallery/assets/select-google.png'\nimport poland from '@open-mercato/core/modules/design_system/gallery/assets/select-poland.png'\nimport avatar from '@open-mercato/core/modules/design_system/gallery/assets/avatar-illustration.png'\n\nexport type MenuKind = 'basic' | 'country' | 'avatar' | 'provider' | 'brand' | 'company' | 'icon'\nexport const selectKinds: MenuKind[] = ['basic', 'country', 'avatar', 'provider', 'brand', 'company']\nexport const commandKinds: MenuKind[] = ['basic', 'avatar', 'icon', 'brand', 'company', 'country']\n\nconst imageSource = (asset: string | { readonly src: string }) => typeof asset === 'string' ? asset : asset.src\n\nexport function useMenuChoices(kind: MenuKind) {\n const t = useT()\n return [0, 1, 2].map((index) => ({\n value: `${kind}-${index}`,\n label: t(`design_system.gallery.samples.menu.${kind}.${index}`),\n description: t('design_system.gallery.samples.menu.description'),\n disabled: index === 2,\n alternative: index !== 0,\n }))\n}\n\nexport function MenuVisual({ kind, large = false, alternative = false, providerSize = 'row' }: {\n kind: MenuKind\n large?: boolean\n alternative?: boolean\n providerSize?: 'row' | 'select'\n}) {\n if (kind === 'avatar') {\n return <Avatar size={large ? 40 : 20} label={alternative ? 'Sophia Williams' : 'James Brown'} src={alternative ? undefined : imageSource(avatar)} />\n }\n const asset = kind === 'country' ? alternative ? poland : unitedStates\n : kind === 'provider' ? mastercard\n : kind === 'brand' ? alternative ? google : spotify\n : kind === 'company' ? apex : undefined\n const Icon = kind === 'icon' ? UserRound : Globe\n const content = asset\n ? <img src={imageSource(asset)} alt=\"\" className={kind === 'provider' ? providerSize === 'select' ? 'h-6 w-8 object-contain' : 'h-5 w-6.5 object-contain' : large ? 'size-6 object-contain' : 'size-5 object-contain'} />\n : <Icon aria-hidden=\"true\" className={large ? 'size-6' : 'size-5'} />\n return <span aria-hidden=\"true\" className={large ? 'flex size-10 shrink-0 items-center justify-center rounded-full border border-input bg-background text-muted-foreground' : 'inline-flex shrink-0 items-center justify-center text-muted-foreground'}>{content}</span>\n}\n\nimport { Info, Plus } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Kbd } from '@open-mercato/ui/primitives/kbd'\nimport { FormField } from '@open-mercato/ui/primitives/form-field'\nimport { SimpleTooltip } from '@open-mercato/ui/primitives/tooltip'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectTriggerLeading, SelectValue } from '@open-mercato/ui/primitives/select'\nimport { Dropdown, DropdownTrigger, DropdownContent, DropdownInput, DropdownList, DropdownEmpty, DropdownItem, DropdownFooter, DropdownCaption } from '@open-mercato/ui/primitives/dropdown'\nimport { CommandMenu, CommandMenuTrigger, CommandMenuContent, CommandMenuInput, CommandMenuList, CommandMenuEmpty, CommandMenuItem, CommandMenuGroup, CommandMenuSeparator, CommandMenuFooter } from '@open-mercato/ui/primitives/command-menu'\n\nexport function DropdownSourceExample({ size = 36, empty = false }: { size?: 36 | 56; empty?: boolean }) {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n const [value, setValue] = React.useState('basic')\n const [query, setQuery] = React.useState(empty ? 'zzzz' : '')\n const [workspaceCount, setWorkspaceCount] = React.useState(0)\n return (\n <div className=\"flex w-full flex-col items-start gap-3\">\n <Dropdown open={open} onOpenChange={setOpen}>\n <DropdownTrigger asChild><Button variant=\"outline\">{t('design_system.gallery.samples.menu.openDropdown')}</Button></DropdownTrigger>\n <DropdownContent className=\"w-96 max-w-full\" aria-label={t('design_system.gallery.samples.menu.openDropdown')} commandProps={{ label: t('design_system.gallery.samples.menu.options') }}>\n <DropdownInput value={query} onValueChange={setQuery} aria-label={t('design_system.gallery.samples.menu.search')} placeholder={t('design_system.gallery.samples.menu.search')} />\n <DropdownList>\n <DropdownEmpty>{t('design_system.gallery.samples.menu.noResults')}</DropdownEmpty>\n {selectKinds.map((kind) => {\n const label = t(`design_system.gallery.samples.menu.${kind}.0`)\n return <DropdownItem key={kind} value={label} size={size} checked={value === kind} leading={<MenuVisual kind={kind} large={size === 56} />}\n description={size === 56 ? t('design_system.gallery.samples.menu.description') : undefined}\n sublabel={kind === 'basic' ? t('design_system.gallery.samples.menu.sublabel') : undefined}\n badge={kind === 'brand' ? <Badge appearance=\"lighter\" tone=\"info\" size={20}>{t('design_system.gallery.samples.menu.new')}</Badge> : undefined}\n shortcut={kind === 'company' ? <Kbd>\u23181</Kbd> : undefined}\n onSelect={() => { setValue(kind); setOpen(false) }}>\n {label}\n </DropdownItem>\n })}\n <DropdownItem value={t('design_system.gallery.samples.menu.unavailable')} size={size} disabled leading={<MenuVisual kind=\"basic\" large={size === 56} />}>{t('design_system.gallery.samples.menu.unavailable')}</DropdownItem>\n </DropdownList>\n <DropdownFooter><Button variant=\"outline\" size=\"sm\" className=\"w-full\" onClick={() => { setWorkspaceCount((count) => count + 1); setOpen(false) }}><Plus className=\"size-5\" />{t('design_system.gallery.samples.menu.addWorkspace')}</Button></DropdownFooter>\n <DropdownCaption>{t('design_system.gallery.samples.menu.localCaption')}</DropdownCaption>\n </DropdownContent>\n </Dropdown>\n <output aria-live=\"polite\" className=\"text-sm text-muted-foreground\">{t('design_system.gallery.samples.menu.selected', { value: t(`design_system.gallery.samples.menu.${value}.0`) })}</output>\n {workspaceCount > 0 && <output aria-live=\"polite\" className=\"text-sm text-muted-foreground\">{t('design_system.gallery.samples.menu.created', { count: workspaceCount })}</output>}\n </div>\n )\n}\n\n"
4
+
5
+ export const commandSourceCode = "import * as React from 'react'\nimport { Globe, UserRound } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport mastercard from '@open-mercato/core/modules/design_system/gallery/assets/menu-mastercard.svg'\nimport apex from '@open-mercato/core/modules/design_system/gallery/assets/menu-apex.svg'\nimport spotify from '@open-mercato/core/modules/design_system/gallery/assets/menu-spotify.svg'\nimport unitedStates from '@open-mercato/core/modules/design_system/gallery/assets/menu-united-states.svg'\nimport google from '@open-mercato/core/modules/design_system/gallery/assets/select-google.png'\nimport poland from '@open-mercato/core/modules/design_system/gallery/assets/select-poland.png'\nimport avatar from '@open-mercato/core/modules/design_system/gallery/assets/avatar-illustration.png'\n\nexport type MenuKind = 'basic' | 'country' | 'avatar' | 'provider' | 'brand' | 'company' | 'icon'\nexport const selectKinds: MenuKind[] = ['basic', 'country', 'avatar', 'provider', 'brand', 'company']\nexport const commandKinds: MenuKind[] = ['basic', 'avatar', 'icon', 'brand', 'company', 'country']\n\nconst imageSource = (asset: string | { readonly src: string }) => typeof asset === 'string' ? asset : asset.src\n\nexport function useMenuChoices(kind: MenuKind) {\n const t = useT()\n return [0, 1, 2].map((index) => ({\n value: `${kind}-${index}`,\n label: t(`design_system.gallery.samples.menu.${kind}.${index}`),\n description: t('design_system.gallery.samples.menu.description'),\n disabled: index === 2,\n alternative: index !== 0,\n }))\n}\n\nexport function MenuVisual({ kind, large = false, alternative = false, providerSize = 'row' }: {\n kind: MenuKind\n large?: boolean\n alternative?: boolean\n providerSize?: 'row' | 'select'\n}) {\n if (kind === 'avatar') {\n return <Avatar size={large ? 40 : 20} label={alternative ? 'Sophia Williams' : 'James Brown'} src={alternative ? undefined : imageSource(avatar)} />\n }\n const asset = kind === 'country' ? alternative ? poland : unitedStates\n : kind === 'provider' ? mastercard\n : kind === 'brand' ? alternative ? google : spotify\n : kind === 'company' ? apex : undefined\n const Icon = kind === 'icon' ? UserRound : Globe\n const content = asset\n ? <img src={imageSource(asset)} alt=\"\" className={kind === 'provider' ? providerSize === 'select' ? 'h-6 w-8 object-contain' : 'h-5 w-6.5 object-contain' : large ? 'size-6 object-contain' : 'size-5 object-contain'} />\n : <Icon aria-hidden=\"true\" className={large ? 'size-6' : 'size-5'} />\n return <span aria-hidden=\"true\" className={large ? 'flex size-10 shrink-0 items-center justify-center rounded-full border border-input bg-background text-muted-foreground' : 'inline-flex shrink-0 items-center justify-center text-muted-foreground'}>{content}</span>\n}\n\nimport { Info, Plus } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Kbd } from '@open-mercato/ui/primitives/kbd'\nimport { FormField } from '@open-mercato/ui/primitives/form-field'\nimport { SimpleTooltip } from '@open-mercato/ui/primitives/tooltip'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectTriggerLeading, SelectValue } from '@open-mercato/ui/primitives/select'\nimport { Dropdown, DropdownTrigger, DropdownContent, DropdownInput, DropdownList, DropdownEmpty, DropdownItem, DropdownFooter, DropdownCaption } from '@open-mercato/ui/primitives/dropdown'\nimport { CommandMenu, CommandMenuTrigger, CommandMenuContent, CommandMenuInput, CommandMenuList, CommandMenuEmpty, CommandMenuItem, CommandMenuGroup, CommandMenuSeparator, CommandMenuFooter } from '@open-mercato/ui/primitives/command-menu'\n\nfunction CommandSourceRows({ size, onSelect }: { size: 40 | 64; onSelect: (value: string) => void }) {\n const t = useT()\n return <>{commandKinds.map((kind) => {\n const label = t(`design_system.gallery.samples.menu.${kind}.0`)\n return <CommandMenuItem key={kind} value={label} size={size} leading={kind === 'basic' ? undefined : <MenuVisual kind={kind} large={size === 64} />}\n description={size === 64 ? t('design_system.gallery.samples.menu.description') : undefined}\n sublabel={kind === 'basic' ? t('design_system.gallery.samples.menu.sublabel') : undefined}\n badge={kind === 'brand' ? <Badge appearance=\"lighter\" tone=\"info\" size={20}>{t('design_system.gallery.samples.menu.new')}</Badge> : undefined}\n shortcut={kind === 'company' ? <Kbd>\u2318K</Kbd> : undefined}\n onSelect={() => onSelect(label)}>{label}</CommandMenuItem>\n })}</>\n}\n\nexport function CommandSourceExample({ size = 40, empty = false, withGroups = false }: { size?: 40 | 64; empty?: boolean; withGroups?: boolean }) {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n const [query, setQuery] = React.useState(empty ? 'zzzz' : '')\n const [result, setResult] = React.useState('')\n const onSelect = (label: string) => { setResult(label); setOpen(false) }\n const rows = <CommandSourceRows size={size} onSelect={onSelect} />\n return (\n <div className=\"flex flex-col items-start gap-3\">\n <CommandMenu open={open} onOpenChange={setOpen}>\n <CommandMenuTrigger asChild><Button variant=\"outline\">{t('design_system.gallery.samples.menu.openCommand')}</Button></CommandMenuTrigger>\n <CommandMenuContent title={t('design_system.gallery.samples.menu.openCommand')} commandProps={{ loop: true }}>\n <CommandMenuInput appearance=\"source\" value={query} onValueChange={setQuery} aria-label={t('design_system.gallery.samples.menu.search')} placeholder={t('design_system.gallery.samples.menu.search')}\n trailing={<SimpleTooltip content={t('design_system.gallery.samples.menu.hint')}><Button variant=\"ghost\" size=\"2xs\" aria-label={t('design_system.gallery.samples.menu.help')}><Info className=\"size-5\" /></Button></SimpleTooltip>} />\n <CommandMenuList>\n <CommandMenuEmpty>{t('design_system.gallery.samples.menu.noResults')}</CommandMenuEmpty>\n {withGroups ? <CommandMenuGroup heading={t('design_system.gallery.samples.menu.caption')} actionLabel={t('design_system.gallery.samples.menu.seeAll')} onAction={() => setQuery('')}>{rows}</CommandMenuGroup> : rows}\n {withGroups && <><CommandMenuSeparator /><CommandMenuGroup heading={t('design_system.gallery.samples.menu.unavailable')}><CommandMenuItem value=\"unavailable\" disabled size={size}>{t('design_system.gallery.samples.menu.unavailable')}</CommandMenuItem></CommandMenuGroup></>}\n </CommandMenuList>\n <CommandMenuFooter className=\"min-h-12 flex-wrap px-5 py-3.5\" helpSlot={<Button variant=\"link\" size=\"2xs\" onClick={() => setResult(t('design_system.gallery.samples.menu.hint'))}>{t('design_system.gallery.samples.menu.help')}</Button>} />\n </CommandMenuContent>\n </CommandMenu>\n {result && <output aria-live=\"polite\" className=\"text-sm text-muted-foreground\">{t('design_system.gallery.samples.menu.selected', { value: result })}</output>}\n </div>\n )\n}\n"
@@ -0,0 +1,122 @@
1
+ import * as React from 'react'
2
+ import { Info, Plus } from 'lucide-react'
3
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
4
+ import { Button } from '@open-mercato/ui/primitives/button'
5
+ import { Badge } from '@open-mercato/ui/primitives/badge'
6
+ import { Kbd } from '@open-mercato/ui/primitives/kbd'
7
+ import { FormField } from '@open-mercato/ui/primitives/form-field'
8
+ import { SimpleTooltip } from '@open-mercato/ui/primitives/tooltip'
9
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectTriggerLeading, SelectValue } from '@open-mercato/ui/primitives/select'
10
+ import { Dropdown, DropdownTrigger, DropdownContent, DropdownInput, DropdownList, DropdownEmpty, DropdownItem, DropdownFooter, DropdownCaption } from '@open-mercato/ui/primitives/dropdown'
11
+ import { CommandMenu, CommandMenuTrigger, CommandMenuContent, CommandMenuInput, CommandMenuList, CommandMenuEmpty, CommandMenuItem, CommandMenuGroup, CommandMenuSeparator, CommandMenuFooter } from '@open-mercato/ui/primitives/command-menu'
12
+ import { MenuVisual, useMenuChoices, selectKinds, commandKinds, type MenuKind } from './menu-fixtures'
13
+
14
+ export function SelectSourceExample({ kind, size = 36, state = 'filled' }: {
15
+ kind: MenuKind
16
+ size?: 32 | 36 | 40
17
+ state?: 'default' | 'filled' | 'disabled' | 'error'
18
+ }) {
19
+ const t = useT()
20
+ const choices = useMenuChoices(kind)
21
+ const [value, setValue] = React.useState(state === 'default' || state === 'error' ? '' : choices[0].value)
22
+ const selected = choices.find((choice) => choice.value === value)
23
+ const disabled = state === 'disabled'
24
+ const label = t(`design_system.gallery.samples.menu.kind.${kind}`)
25
+ const providerPadding = kind !== 'provider' ? undefined : size === 40 ? 'p-2' : size === 36 ? 'p-1.5' : 'p-1'
26
+ return (
27
+ <Select value={value} onValueChange={setValue} disabled={disabled}>
28
+ <FormField label={label} disabled={disabled} description={t('design_system.gallery.samples.menu.hint')} error={state === 'error' ? t('design_system.gallery.samples.menu.required') : undefined}>
29
+ <SelectTrigger size={size} className={providerPadding}>
30
+ {kind !== 'basic' && <SelectTriggerLeading><MenuVisual kind={kind} alternative={selected?.alternative} providerSize="select" /></SelectTriggerLeading>}
31
+ <SelectValue className="min-w-0 flex-1 text-left" placeholder={t('design_system.gallery.samples.menu.placeholder')} />
32
+ </SelectTrigger>
33
+ </FormField>
34
+ <SelectContent>
35
+ {choices.map((choice) => <SelectItem key={choice.value} value={choice.value} disabled={choice.disabled} leading={kind === 'basic' ? undefined : <MenuVisual kind={kind} alternative={choice.alternative} />}>
36
+ {choice.label}
37
+ </SelectItem>)}
38
+ </SelectContent>
39
+ </Select>
40
+ )
41
+ }
42
+
43
+ export function SelectSourceMatrix({ size = 36, state = 'filled' }: { size?: 32 | 36 | 40; state?: 'default' | 'filled' | 'disabled' | 'error' }) {
44
+ return <div className="grid w-full gap-6 sm:grid-cols-2 xl:grid-cols-3">{selectKinds.map((kind) => <SelectSourceExample key={kind} kind={kind} size={size} state={state} />)}</div>
45
+ }
46
+
47
+ export function DropdownSourceExample({ size = 36, empty = false }: { size?: 36 | 56; empty?: boolean }) {
48
+ const t = useT()
49
+ const [open, setOpen] = React.useState(false)
50
+ const [value, setValue] = React.useState('basic')
51
+ const [query, setQuery] = React.useState(empty ? 'zzzz' : '')
52
+ const [workspaceCount, setWorkspaceCount] = React.useState(0)
53
+ return (
54
+ <div className="flex w-full flex-col items-start gap-3">
55
+ <Dropdown open={open} onOpenChange={setOpen}>
56
+ <DropdownTrigger asChild><Button variant="outline">{t('design_system.gallery.samples.menu.openDropdown')}</Button></DropdownTrigger>
57
+ <DropdownContent className="w-96 max-w-full" aria-label={t('design_system.gallery.samples.menu.openDropdown')} commandProps={{ label: t('design_system.gallery.samples.menu.options') }}>
58
+ <DropdownInput value={query} onValueChange={setQuery} aria-label={t('design_system.gallery.samples.menu.search')} placeholder={t('design_system.gallery.samples.menu.search')} />
59
+ <DropdownList>
60
+ <DropdownEmpty>{t('design_system.gallery.samples.menu.noResults')}</DropdownEmpty>
61
+ {selectKinds.map((kind) => {
62
+ const label = t(`design_system.gallery.samples.menu.${kind}.0`)
63
+ return <DropdownItem key={kind} value={label} size={size} checked={value === kind} leading={<MenuVisual kind={kind} large={size === 56} />}
64
+ description={size === 56 ? t('design_system.gallery.samples.menu.description') : undefined}
65
+ sublabel={kind === 'basic' ? t('design_system.gallery.samples.menu.sublabel') : undefined}
66
+ badge={kind === 'brand' ? <Badge appearance="lighter" tone="info" size={20}>{t('design_system.gallery.samples.menu.new')}</Badge> : undefined}
67
+ shortcut={kind === 'company' ? <Kbd>⌘1</Kbd> : undefined}
68
+ onSelect={() => { setValue(kind); setOpen(false) }}>
69
+ {label}
70
+ </DropdownItem>
71
+ })}
72
+ <DropdownItem value={t('design_system.gallery.samples.menu.unavailable')} size={size} disabled leading={<MenuVisual kind="basic" large={size === 56} />}>{t('design_system.gallery.samples.menu.unavailable')}</DropdownItem>
73
+ </DropdownList>
74
+ <DropdownFooter><Button variant="outline" size="sm" className="w-full" onClick={() => { setWorkspaceCount((count) => count + 1); setOpen(false) }}><Plus className="size-5" />{t('design_system.gallery.samples.menu.addWorkspace')}</Button></DropdownFooter>
75
+ <DropdownCaption>{t('design_system.gallery.samples.menu.localCaption')}</DropdownCaption>
76
+ </DropdownContent>
77
+ </Dropdown>
78
+ <output aria-live="polite" className="text-sm text-muted-foreground">{t('design_system.gallery.samples.menu.selected', { value: t(`design_system.gallery.samples.menu.${value}.0`) })}</output>
79
+ {workspaceCount > 0 && <output aria-live="polite" className="text-sm text-muted-foreground">{t('design_system.gallery.samples.menu.created', { count: workspaceCount })}</output>}
80
+ </div>
81
+ )
82
+ }
83
+
84
+ function CommandSourceRows({ size, onSelect }: { size: 40 | 64; onSelect: (value: string) => void }) {
85
+ const t = useT()
86
+ return <>{commandKinds.map((kind) => {
87
+ const label = t(`design_system.gallery.samples.menu.${kind}.0`)
88
+ return <CommandMenuItem key={kind} value={label} size={size} leading={kind === 'basic' ? undefined : <MenuVisual kind={kind} large={size === 64} />}
89
+ description={size === 64 ? t('design_system.gallery.samples.menu.description') : undefined}
90
+ sublabel={kind === 'basic' ? t('design_system.gallery.samples.menu.sublabel') : undefined}
91
+ badge={kind === 'brand' ? <Badge appearance="lighter" tone="info" size={20}>{t('design_system.gallery.samples.menu.new')}</Badge> : undefined}
92
+ shortcut={kind === 'company' ? <Kbd>⌘K</Kbd> : undefined}
93
+ onSelect={() => onSelect(label)}>{label}</CommandMenuItem>
94
+ })}</>
95
+ }
96
+
97
+ export function CommandSourceExample({ size = 40, empty = false, withGroups = false }: { size?: 40 | 64; empty?: boolean; withGroups?: boolean }) {
98
+ const t = useT()
99
+ const [open, setOpen] = React.useState(false)
100
+ const [query, setQuery] = React.useState(empty ? 'zzzz' : '')
101
+ const [result, setResult] = React.useState('')
102
+ const onSelect = (label: string) => { setResult(label); setOpen(false) }
103
+ const rows = <CommandSourceRows size={size} onSelect={onSelect} />
104
+ return (
105
+ <div className="flex flex-col items-start gap-3">
106
+ <CommandMenu open={open} onOpenChange={setOpen}>
107
+ <CommandMenuTrigger asChild><Button variant="outline">{t('design_system.gallery.samples.menu.openCommand')}</Button></CommandMenuTrigger>
108
+ <CommandMenuContent title={t('design_system.gallery.samples.menu.openCommand')} commandProps={{ loop: true }}>
109
+ <CommandMenuInput appearance="source" value={query} onValueChange={setQuery} aria-label={t('design_system.gallery.samples.menu.search')} placeholder={t('design_system.gallery.samples.menu.search')}
110
+ trailing={<SimpleTooltip content={t('design_system.gallery.samples.menu.hint')}><Button variant="ghost" size="2xs" aria-label={t('design_system.gallery.samples.menu.help')}><Info className="size-5" /></Button></SimpleTooltip>} />
111
+ <CommandMenuList>
112
+ <CommandMenuEmpty>{t('design_system.gallery.samples.menu.noResults')}</CommandMenuEmpty>
113
+ {withGroups ? <CommandMenuGroup heading={t('design_system.gallery.samples.menu.caption')} actionLabel={t('design_system.gallery.samples.menu.seeAll')} onAction={() => setQuery('')}>{rows}</CommandMenuGroup> : rows}
114
+ {withGroups && <><CommandMenuSeparator /><CommandMenuGroup heading={t('design_system.gallery.samples.menu.unavailable')}><CommandMenuItem value="unavailable" disabled size={size}>{t('design_system.gallery.samples.menu.unavailable')}</CommandMenuItem></CommandMenuGroup></>}
115
+ </CommandMenuList>
116
+ <CommandMenuFooter className="min-h-12 flex-wrap px-5 py-3.5" helpSlot={<Button variant="link" size="2xs" onClick={() => setResult(t('design_system.gallery.samples.menu.hint'))}>{t('design_system.gallery.samples.menu.help')}</Button>} />
117
+ </CommandMenuContent>
118
+ </CommandMenu>
119
+ {result && <output aria-live="polite" className="text-sm text-muted-foreground">{t('design_system.gallery.samples.menu.selected', { value: result })}</output>}
120
+ </div>
121
+ )
122
+ }
@@ -0,0 +1,44 @@
1
+ import * as React from 'react'
2
+ import { cn } from '@open-mercato/shared/lib/utils'
3
+ import { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'
4
+ import { Rating } from '@open-mercato/ui/primitives/rating'
5
+ import { LinkButton } from '@open-mercato/ui/primitives/link-button'
6
+ import { Button } from '@open-mercato/ui/primitives/button'
7
+ import { ScrollArea } from '@open-mercato/ui/primitives/scroll-area'
8
+ import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@open-mercato/ui/primitives/dialog'
9
+
10
+ export function RatingCellDemo({ icon, initialValue = 0, max = 1, disabled = false }: { icon: 'star' | 'heart'; initialValue?: number; max?: number; disabled?: boolean }) {
11
+ const t = useT()
12
+ const [value, setValue] = React.useState(initialValue)
13
+ return <Rating value={value} onChange={setValue} max={max} icon={icon} appearance="cell" disabled={disabled} aria-label={t('design_system.gallery.samples.rating.label')} />
14
+ }
15
+
16
+ export function RatingReviewDemo({ icon, alignment }: { icon: 'star' | 'heart'; alignment: 'vertical' | 'horizontal' }) {
17
+ const t = useT()
18
+ const locale = useLocale()
19
+ const [open, setOpen] = React.useState(false)
20
+ const value = new Intl.NumberFormat(locale).format(4.5)
21
+ const count = new Intl.NumberFormat(locale, { notation: 'compact', maximumFractionDigits: 1 }).format(5200)
22
+ const summary = t('design_system.gallery.samples.rating.summary', '{value} ∙ {count} Ratings', { value, count })
23
+ const reviews = t('design_system.gallery.samples.rating.reviews', '{count} reviews', { count: 18 })
24
+ return (
25
+ <Dialog open={open} onOpenChange={setOpen}>
26
+ <div data-slot="rating-review" data-alignment={alignment} className={cn('inline-flex max-w-full items-start gap-2', alignment === 'vertical' ? 'flex-col' : 'flex-wrap')}>
27
+ <Rating value={4.5} allowHalf icon={icon} />
28
+ <div className="flex flex-wrap items-start gap-1 text-sm leading-5">
29
+ <span>{summary}</span>
30
+ <LinkButton asChild variant="gray" underline="always"><DialogTrigger>{reviews}</DialogTrigger></LinkButton>
31
+ </div>
32
+ </div>
33
+ <DialogContent onKeyDownCapture={event => { if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) { event.preventDefault(); setOpen(false) } }}>
34
+ <DialogHeader><DialogTitle>{reviews}</DialogTitle><DialogDescription>{summary}</DialogDescription></DialogHeader>
35
+ <ScrollArea className="h-72" scrollbarSize="md">
36
+ <ol className="flex flex-col gap-3 pr-6">
37
+ {Array.from({ length: 18 }, (_, index) => <li key={index} className="flex items-center justify-between gap-3 text-sm"><span>{t('design_system.gallery.samples.rating.reviewNumber', 'Review {number}', { number: index + 1 })}</span><Rating value={index < 9 ? 5 : 4} icon={icon} /></li>)}
38
+ </ol>
39
+ </ScrollArea>
40
+ <DialogFooter><Button asChild variant="outline"><DialogClose>{t('common.close')}</DialogClose></Button></DialogFooter>
41
+ </DialogContent>
42
+ </Dialog>
43
+ )
44
+ }