@open-mercato/core 0.7.1-develop.7202.1.a52dc6707c → 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,117 @@
1
+ import * as React from 'react'
2
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
3
+ import { LUCIDE_ICON_REGISTRY } from '@open-mercato/ui/backend/icons/lucideRegistry'
4
+ import { SearchInput } from '@open-mercato/ui/primitives/search-input'
5
+ import { Button } from '@open-mercato/ui/primitives/button'
6
+ import { Pagination } from '@open-mercato/ui/primitives/pagination'
7
+ import { EmptyState } from '@open-mercato/ui/primitives/empty-state'
8
+ import { Popover, PopoverClose, PopoverContent, PopoverTrigger } from '@open-mercato/ui/primitives/popover'
9
+ import { flash } from '@open-mercato/ui/backend/FlashMessages'
10
+
11
+ function pascalCase(registryName: string): string {
12
+ return registryName
13
+ .split(/[-_]/)
14
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
15
+ .join('')
16
+ }
17
+
18
+ function IconTile({ name }: { name: string }) {
19
+ const t = useT()
20
+ const Icon = LUCIDE_ICON_REGISTRY[name]
21
+ const jsxSnippet = `<${pascalCase(name)} aria-hidden className="size-4" />`
22
+
23
+ const copy = React.useCallback(async (payload: string) => {
24
+ try {
25
+ await navigator.clipboard.writeText(payload)
26
+ flash(t('design_system.gallery.iconCopied', 'Copied: {snippet}', { snippet: payload }), 'success')
27
+ } catch {
28
+ flash(t('design_system.gallery.copyFailed', 'Could not copy the snippet'), 'error')
29
+ }
30
+ }, [t])
31
+
32
+ return (
33
+ <Popover>
34
+ <PopoverTrigger asChild>
35
+ <Button
36
+ type="button"
37
+ variant="ghost"
38
+ className="h-auto min-h-28 min-w-0 flex-col gap-3 whitespace-normal rounded-lg border border-border bg-background p-3"
39
+ >
40
+ <Icon aria-hidden className="size-7 text-foreground" strokeWidth={1.75} />
41
+ <code className="max-w-full break-words text-xs leading-4 text-muted-foreground">{name}</code>
42
+ </Button>
43
+ </PopoverTrigger>
44
+ <PopoverContent align="center" className="w-auto max-w-xs p-2">
45
+ <div className="space-y-1">
46
+ <PopoverClose asChild>
47
+ <Button
48
+ type="button"
49
+ variant="ghost"
50
+ onClick={() => copy(name)}
51
+ className="h-auto w-full flex-col items-start gap-0.5 rounded-md px-2 py-1.5 text-left transition-colors hover:bg-muted focus-visible:outline-none focus-visible:shadow-focus"
52
+ >
53
+ <span className="text-xs font-medium text-foreground">
54
+ {t('design_system.gallery.iconCopyMeta', 'Copy name for page.meta icon')}
55
+ </span>
56
+ <code className="text-xs text-muted-foreground">{name}</code>
57
+ </Button>
58
+ </PopoverClose>
59
+ <PopoverClose asChild>
60
+ <Button
61
+ type="button"
62
+ variant="ghost"
63
+ onClick={() => copy(jsxSnippet)}
64
+ className="h-auto w-full flex-col items-start gap-0.5 rounded-md px-2 py-1.5 text-left transition-colors hover:bg-muted focus-visible:outline-none focus-visible:shadow-focus"
65
+ >
66
+ <span className="text-xs font-medium text-foreground">
67
+ {t('design_system.gallery.iconCopyJsx', 'Copy JSX (lucide-react)')}
68
+ </span>
69
+ <code className="max-w-full truncate text-xs text-muted-foreground">{jsxSnippet}</code>
70
+ </Button>
71
+ </PopoverClose>
72
+ </div>
73
+ </PopoverContent>
74
+ </Popover>
75
+ )
76
+ }
77
+
78
+ export function IconGrid({ collectionControl }: { collectionControl?: React.ReactNode } = {}) {
79
+ const t = useT()
80
+ const [query, setQuery] = React.useState('')
81
+ const names = React.useMemo(() => Object.keys(LUCIDE_ICON_REGISTRY).sort((a, b) => a.localeCompare(b)), [])
82
+ const [page, setPage] = React.useState(1)
83
+ const needle = query.trim().toLowerCase()
84
+ const filtered = needle ? names.filter((name) => name.toLowerCase().includes(needle)) : names
85
+ const visible = filtered.slice((page - 1) * 48, page * 48)
86
+
87
+ return (
88
+ <div className="space-y-4">
89
+ <div className="flex flex-wrap items-center gap-3">
90
+ {collectionControl}
91
+ <div className="max-w-sm flex-1">
92
+ <SearchInput
93
+ value={query}
94
+ onChange={value => { setQuery(value); setPage(1) }}
95
+ placeholder={t('design_system.gallery.iconSearchPlaceholder', 'Filter icons…')}
96
+ aria-label={t('design_system.gallery.iconSearchPlaceholder', 'Filter icons…')}
97
+ />
98
+ </div>
99
+ <p className="text-xs text-muted-foreground">
100
+ {needle
101
+ ? t('design_system.gallery.iconCountFiltered', '{visible} of {total} icons', {
102
+ visible: filtered.length,
103
+ total: names.length,
104
+ })
105
+ : t('design_system.gallery.iconCountAll', '{total} icons', { total: names.length })}
106
+ </p>
107
+ </div>
108
+ {visible.length ? <div className="grid grid-cols-2 gap-3 sm:grid-cols-4 lg:grid-cols-6">
109
+ {visible.map((name) => (
110
+ <IconTile key={name} name={name} />
111
+ ))}
112
+ </div> : <EmptyState title={t('design_system.gallery.noResults')} />}
113
+ {filtered.length > 48 ? <Pagination page={page} pageSize={48} total={filtered.length} showPageSize={false} onPageChange={setPage} aria-label={t('design_system.gallery.sourceIcons.pages')} /> : null}
114
+ </div>
115
+ )
116
+ }
117
+
@@ -0,0 +1,97 @@
1
+ import * as React from 'react'
2
+ import { Settings } from 'lucide-react'
3
+ import { cn } from '@open-mercato/shared/lib/utils'
4
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
5
+ import { Avatar } from '@open-mercato/ui/primitives/avatar'
6
+ import { Badge } from '@open-mercato/ui/primitives/badge'
7
+ import { Button } from '@open-mercato/ui/primitives/button'
8
+ import { CheckboxField } from '@open-mercato/ui/primitives/checkbox-field'
9
+ import { RadioGroup } from '@open-mercato/ui/primitives/radio'
10
+ import { RadioField } from '@open-mercato/ui/primitives/radio-field'
11
+ import { Switch } from '@open-mercato/ui/primitives/switch'
12
+ import { SwitchField } from '@open-mercato/ui/primitives/switch-field'
13
+ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@open-mercato/ui/primitives/dialog'
14
+ import avatar from '../assets/avatar-photo.png'
15
+ import mastercard from '../assets/menu-mastercard.svg'
16
+ import spotify from '../assets/menu-spotify.svg'
17
+ import company from '../assets/menu-apex.svg'
18
+ import { selectionArtwork } from '../assets/selection-artwork'
19
+
20
+ export type SelectionCardKind = 'icon' | 'avatar' | 'provider' | 'brand' | 'company'
21
+ type SelectionControl = 'checkbox' | 'radio' | 'switch'
22
+ const imageSource = (asset: string | { readonly src: string }) => typeof asset === 'string' ? asset : asset.src
23
+
24
+ export const selectionSourceCardClassName = 'relative flex w-90 max-w-full items-start gap-3.5 rounded-selection-card bg-background p-4 ring-1 ring-inset ring-border shadow-xs transition-colors hover:bg-muted hover:shadow-none has-[[data-state=checked]]:ring-accent-indigo has-[[data-state=checked]]:shadow-none has-[:focus-visible]:shadow-focus [&_label]:after:absolute [&_label]:after:inset-0 [&_label]:after:rounded-selection-card [&_button]:relative [&_button]:z-10'
25
+
26
+ function SelectionLeading({ kind }: { kind: SelectionCardKind }) {
27
+ if (kind === 'avatar') return <Avatar label="James Brown" size={40} src={imageSource(avatar)} aria-hidden="true" />
28
+ const source = kind === 'icon' ? selectionArtwork.keyIcon : imageSource(kind === 'provider' ? mastercard : kind === 'brand' ? spotify : company)
29
+ return <img src={source} alt="" aria-hidden="true" className={kind === 'provider' ? 'h-6 w-8 shrink-0 object-contain' : 'size-10 shrink-0 object-contain'} />
30
+ }
31
+
32
+ function SourceSelectionCard({ control, kind, state }: { control: SelectionControl; kind: SelectionCardKind; state: 'default' | 'active' | 'disabled' }) {
33
+ const t = useT()
34
+ const disabled = state === 'disabled'
35
+ const fields = {
36
+ label: t('design_system.gallery.samples.selection.label'),
37
+ sublabel: t('design_system.gallery.samples.selection.sublabel'),
38
+ description: t('design_system.gallery.samples.selection.description'),
39
+ badge: <Badge size={16} appearance={disabled ? 'stroke' : 'light'} tone={disabled ? 'neutral' : 'info'} className={disabled ? 'border-border text-text-disabled' : undefined}>{t('design_system.gallery.samples.selection.new')}</Badge>,
40
+ disabled,
41
+ containerClassName: 'min-w-0 flex-1 gap-3.5',
42
+ contentClassName: disabled ? '[&_label]:text-text-disabled [&_label]:opacity-100 [&_p]:text-text-disabled [&_span]:text-text-disabled' : undefined,
43
+ className: cn('mt-0', disabled && control !== 'switch' && 'disabled:opacity-100 disabled:data-[state=checked]:bg-bg-disabled disabled:data-[state=checked]:border-border-disabled disabled:data-[state=checked]:text-background'),
44
+ }
45
+ return (
46
+ <div data-slot="selection-source-card" data-kind={kind} data-disabled={disabled} className={cn(selectionSourceCardClassName, disabled && 'shadow-none hover:bg-background has-[[data-state=checked]]:ring-border')}>
47
+ <SelectionLeading kind={kind} />
48
+ {control === 'radio' ? <RadioField value={state} flip {...fields} /> : control === 'switch' ? <SwitchField defaultChecked={state === 'active'} {...fields} /> : <CheckboxField defaultChecked={state !== 'default'} flip {...fields} />}
49
+ </div>
50
+ )
51
+ }
52
+
53
+ export function SelectionSourceCardsDemo({ control, kind }: { control: SelectionControl; kind: SelectionCardKind }) {
54
+ const t = useT()
55
+ if (control === 'radio') return (
56
+ <div className="grid gap-3">
57
+ <RadioGroup defaultValue="active" aria-label={t('design_system.gallery.samples.selection.preferences')} className="gap-3">
58
+ <SourceSelectionCard control={control} kind={kind} state="default" />
59
+ <SourceSelectionCard control={control} kind={kind} state="active" />
60
+ </RadioGroup>
61
+ <RadioGroup value="disabled" disabled aria-label={t('design_system.gallery.samples.selection.preferences')}>
62
+ <SourceSelectionCard control={control} kind={kind} state="disabled" />
63
+ </RadioGroup>
64
+ </div>
65
+ )
66
+ return <div className="grid gap-3">{(['default', 'active', 'disabled'] as const).map(state => <SourceSelectionCard key={state} control={control} kind={kind} state={state} />)}</div>
67
+ }
68
+
69
+ export function IntegrationSwitchDemo({ alignment, appearance }: { alignment: 'horizontal' | 'vertical'; appearance: 'card' | 'list' }) {
70
+ const t = useT()
71
+ const id = React.useId()
72
+ const [checked, setChecked] = React.useState(false)
73
+ const [open, setOpen] = React.useState(false)
74
+ const horizontal = alignment === 'horizontal'
75
+ const card = appearance === 'card'
76
+ return (
77
+ <Dialog open={open} onOpenChange={setOpen}>
78
+ <div data-slot="integration-switch" data-alignment={alignment} data-appearance={appearance} className={cn('relative flex max-w-full gap-3.5', horizontal ? 'w-154 flex-wrap items-center sm:flex-nowrap' : 'w-95 flex-col items-start', card && 'rounded-selection-card bg-background p-4 ring-1 ring-inset ring-border shadow-xs')}>
79
+ <img src={card ? selectionArtwork.officeCard : selectionArtwork.officeList} alt="" className={cn('shrink-0', card ? 'size-10' : 'size-12')} />
80
+ <div className={cn('flex min-w-0 flex-col gap-1', horizontal && 'min-w-40 flex-1')}>
81
+ <div className="flex flex-wrap items-center gap-1.5">
82
+ <span id={id} className={cn('font-medium text-foreground', card ? 'text-sm leading-5' : 'text-base leading-6')}>Microsoft Office 365</span>
83
+ <Badge size={16} appearance="light" tone="info">{t('design_system.gallery.samples.selection.new')}</Badge>
84
+ </div>
85
+ <p className={cn('text-muted-foreground', card ? 'text-xs leading-4' : 'text-sm leading-5')}>{t('design_system.gallery.samples.selection.integrationDescription')}</p>
86
+ </div>
87
+ <Button asChild variant="outline" size="default" className={horizontal ? undefined : 'w-full'}><DialogTrigger><Settings aria-hidden="true" />{t('design_system.gallery.samples.selection.integrationManage')}</DialogTrigger></Button>
88
+ <Switch aria-labelledby={id} checked={checked} onCheckedChange={setChecked} className={horizontal ? undefined : card ? 'absolute top-5 right-5' : 'absolute top-3 right-3'} />
89
+ </div>
90
+ <DialogContent onKeyDownCapture={event => { if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) { event.preventDefault(); setOpen(false) } }}>
91
+ <DialogHeader><DialogTitle>{t('design_system.gallery.samples.selection.integrationDialog')}</DialogTitle><DialogDescription>{t('design_system.gallery.samples.selection.integrationDescription')}</DialogDescription></DialogHeader>
92
+ <SwitchField label={t('design_system.gallery.samples.selection.integrationEnabled')} checked={checked} onCheckedChange={setChecked} />
93
+ <DialogFooter><Button variant="outline" onClick={() => setOpen(false)}>{t('common.close')}</Button></DialogFooter>
94
+ </DialogContent>
95
+ </Dialog>
96
+ )
97
+ }
@@ -0,0 +1,3 @@
1
+ // Generated by yarn storybook:generate from shell.tsx.
2
+ const source = "import * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { BadgeCheck, Bell, CalendarDays, ChevronDown, ChevronRight, ChevronsUpDown, Cloud, CreditCard, Download, FileText, Folder, Gift, Headphones, Home, Layers, PanelLeftClose, PanelLeftOpen, Pause, Play, Plus, Search, Settings, UserRound, Users, X } from 'lucide-react'\nimport { PageHeader } from '@open-mercato/ui/backend/Page'\nimport { Avatar, AvatarStack } from '@open-mercato/ui/primitives/avatar'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { CompactButton } from '@open-mercato/ui/primitives/compact-button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { LinkButton } from '@open-mercato/ui/primitives/link-button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\nimport { SearchInput } from '@open-mercato/ui/primitives/search-input'\nimport { Progress } from '@open-mercato/ui/primitives/progress'\nimport { Popover, PopoverContent, PopoverTrigger } from '@open-mercato/ui/primitives/popover'\nimport { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { Sidebar, SidebarContent, SidebarFeatureCard, SidebarFooter, SidebarHeader, SidebarIdentity, SidebarItem, Topbar } from '@open-mercato/ui/primitives/sidebar'\nimport { navigationBrandArtwork } from '@open-mercato/ui/assets/navigation-brand-artwork'\nimport avatarIllustration from '../assets/avatar-illustration.png'\nimport avatarMemoji from '../assets/avatar-memoji.png'\nimport avatarPhoto from '../assets/avatar-photo.png'\n\nconst avatarImages = [avatarIllustration, avatarMemoji, avatarPhoto].map(image => typeof image === 'string' ? image : image.src)\ntype Product = 'hr' | 'finance'\ntype HeaderType = 'basic' | 'avatar' | 'icon' | 'brand' | 'company'\ntype FeatureType = 'meeting' | 'progress' | 'link' | 'gift' | 'storage' | 'support'\ntype FeatureAppearance = 'stroke' | 'gray' | 'primary' | 'neutral'\nconst navItems = {\n hr: ['dashboard', 'calendar', 'timeOff', 'projects', 'teams', 'integrations', 'benefits', 'documents'],\n finance: ['dashboard', 'cards', 'transfer', 'transactions', 'payments', 'exchange'],\n} as const\nconst navIcons = [Home, CalendarDays, CreditCard, Folder, Users, Layers, Gift, FileText]\n\nfunction ShellAvatar({ size = 40, product = 'hr' }: { size?: 24 | 32 | 40 | 48; product?: Product }) {\n return <Avatar src={product === 'hr' ? navigationBrandArtwork.sophiaAvatar : navigationBrandArtwork.arthurAvatar} label={product === 'hr' ? 'Sophia Williams' : 'Arthur Taylor'} size={size} />\n}\n\nfunction WorkspaceIdentity({ product, onProductChange, collapsed = false, profile = false }: { product: Product; onProductChange?: (product: Product) => void; collapsed?: boolean; profile?: boolean }) {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n const name = product === 'hr' ? 'Synergy' : 'Apex'\n return <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <SidebarIdentity collapsed={collapsed} label={profile ? product === 'hr' ? 'Sophia Williams' : 'Arthur Taylor' : name}\n description={profile ? product === 'hr' ? 'sophia@example.com' : 'arthur@example.com' : t(`design_system.gallery.examples.shell.${product}`)}\n leading={profile ? <ShellAvatar product={product} /> : <img src={navigationBrandArtwork[product === 'hr' ? 'synergyOriginal' : 'apexOriginal']} alt=\"\" className=\"size-10\" />}\n badge={profile ? <BadgeCheck className=\"size-3.5 fill-status-info-icon stroke-status-info-solid-foreground\" aria-hidden=\"true\" /> : undefined}\n trailing={profile ? <ChevronRight className=\"size-5\" /> : <span className=\"flex size-6 items-center justify-center rounded-sm border border-border bg-background shadow-xs\"><ChevronsUpDown className=\"size-5\" /></span>} />\n </PopoverTrigger>\n <PopoverContent className=\"w-64 p-3\">\n {profile ? <p className=\"text-sm\">{t('design_system.gallery.examples.shell.localProfile')}</p> : <div className=\"flex flex-col gap-1\">\n {(['hr', 'finance'] as const).map(value => <Button key={value} type=\"button\" variant=\"ghost\" aria-pressed={product === value} onClick={() => { onProductChange?.(value); setOpen(false) }}>{value === 'hr' ? 'Synergy' : 'Apex'}</Button>)}\n </div>}\n </PopoverContent>\n </Popover>\n}\n\nexport function PageHeaderExample({ appearance = 'page', type = 'basic', configurable = false }: { appearance?: 'page' | 'section'; type?: HeaderType; configurable?: boolean }) {\n const t = useT()\n const [options, setOptions] = React.useState({ divider: true, search: true, notification: true, actions: true, second: true, dropdown: false })\n const [query, setQuery] = React.useState('')\n const [dialog, setDialog] = React.useState(false)\n const [draft, setDraft] = React.useState('')\n const [saved, setSaved] = React.useState('')\n const [period, setPeriod] = React.useState('month')\n const [workspace, setWorkspace] = React.useState<Product>('hr')\n const submit = () => { if (draft.trim()) { setSaved(draft.trim()); setDialog(false) } }\n const copy = (key: string) => t(`design_system.gallery.examples.shell.${key}`)\n const leading = type === 'avatar' ? <ShellAvatar size={48} />\n : type === 'icon' ? <span className=\"flex size-12 items-center justify-center rounded-full border border-border\"><UserRound className=\"size-6\" aria-hidden=\"true\" /></span>\n : type === 'brand' ? <img src={navigationBrandArtwork.loom} alt=\"Loom\" className=\"size-12\" />\n : type === 'company' ? <img src={navigationBrandArtwork.apexOriginal} alt=\"Apex\" className=\"size-12\" /> : undefined\n const periodMenu = <Popover><PopoverTrigger asChild><Button type=\"button\" variant=\"outline\" size=\"lg\"><CalendarDays className=\"size-5\" />{copy(period)}<ChevronDown className=\"size-5\" /></Button></PopoverTrigger><PopoverContent className=\"p-2\">{['month', 'year'].map(value => <Button key={value} type=\"button\" variant=\"ghost\" aria-pressed={period === value} onClick={() => setPeriod(value)}>{copy(value)}</Button>)}</PopoverContent></Popover>\n const titleAction = options.dropdown && appearance === 'section' ? <Popover><PopoverTrigger asChild><CompactButton fullRadius aria-label={copy('switchWorkspace')}><ChevronDown /></CompactButton></PopoverTrigger><PopoverContent className=\"p-3\">{(['hr', 'finance'] as const).map(value => <Button key={value} type=\"button\" variant=\"ghost\" onClick={() => setWorkspace(value)}>{copy(value)}</Button>)}</PopoverContent></Popover> : undefined\n return <div className=\"w-full\">\n {configurable ? <div className=\"mb-4 flex flex-wrap gap-4\">{(Object.keys(options) as Array<keyof typeof options>).map(key => <label key={key} className=\"flex items-center gap-2 text-sm\"><Checkbox checked={options[key]} onCheckedChange={checked => setOptions(current => ({ ...current, [key]: checked === true }))} />{copy(key)}</label>)}</div> : null}\n <PageHeader appearance={appearance} leading={leading} divider={options.divider} title={configurable ? copy(workspace) : copy('title')} description={copy('description')} titleAction={titleAction}\n actions={<>\n {options.search ? <Popover><PopoverTrigger asChild><IconButton variant=\"ghost\" className=\"size-10 rounded-lg data-[state=open]:bg-muted\" aria-label={copy('search')}><Search className=\"size-5\" /></IconButton></PopoverTrigger><PopoverContent className=\"p-3\"><SearchInput value={query} onChange={setQuery} aria-label={copy('search')} /><p className=\"mt-2 text-sm\" role=\"status\">{query ? t('design_system.gallery.examples.shell.localQuery', { query }) : copy('searchHint')}</p></PopoverContent></Popover> : null}\n {options.notification && appearance === 'page' ? <Popover><PopoverTrigger asChild><IconButton variant=\"ghost\" className=\"size-10 rounded-lg data-[state=open]:bg-muted\" aria-label={copy('notifications')}><Bell className=\"size-5\" /></IconButton></PopoverTrigger><PopoverContent className=\"p-4 text-sm\">{copy('noNotifications')}</PopoverContent></Popover> : null}\n {options.dropdown && appearance === 'page' ? periodMenu : null}\n {options.actions ? <>\n {options.second ? appearance === 'page' ? <Button type=\"button\" variant=\"outline\" size=\"lg\" onClick={() => { setDraft(''); setDialog(true) }}><CalendarDays className=\"size-5\" />{copy('schedule')}</Button> : <Button asChild variant=\"outline\" size=\"lg\"><a download=\"page-header-example.json\" href={`data:application/json;charset=utf-8,${encodeURIComponent(JSON.stringify({ title: copy('title'), description: copy('description') }, null, 2))}`}><Download className=\"size-5\" />{copy('export')}</a></Button> : null}\n <Button type=\"button\" variant=\"primary-filled\" size=\"lg\" onClick={() => { setDraft(''); setDialog(true) }}><Plus className=\"size-5\" />{copy(appearance === 'page' ? 'create' : 'invite')}</Button>\n </> : null}\n </>} />\n {saved ? <output className=\"block px-8 pt-3 text-sm\">{t('design_system.gallery.examples.shell.savedDraft', { title: saved })}</output> : null}\n <Dialog open={dialog} onOpenChange={setDialog}><DialogContent onKeyDown={event => { if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') { event.preventDefault(); submit() } }}>\n <DialogHeader><DialogTitle>{copy('localDraft')}</DialogTitle><DialogDescription>{copy('localDraftHint')}</DialogDescription></DialogHeader>\n <Input value={draft} onChange={event => setDraft(event.target.value)} aria-label={copy('draftTitle')} autoFocus />\n <DialogFooter><Button type=\"button\" variant=\"outline\" onClick={() => setDialog(false)}>{copy('cancel')}</Button><Button type=\"button\" disabled={!draft.trim()} onClick={submit}>{copy('saveDraft')}</Button></DialogFooter>\n </DialogContent></Dialog>\n </div>\n}\n\nfunction FeatureCard({ type, appearance }: { type: FeatureType; appearance: FeatureAppearance }) {\n const t = useT()\n const [dismissed, setDismissed] = React.useState(false)\n const [paused, setPaused] = React.useState(true)\n const [details, setDetails] = React.useState(false)\n const id = React.useId()\n const copy = (key: string) => t(`design_system.gallery.examples.shell.${key}`)\n const inverse = appearance === 'primary' || appearance === 'neutral'\n const descriptionClass = inverse ? 'text-current' : 'text-muted-foreground'\n const link = (key: string) => <LinkButton asChild variant=\"modifiable\" underline=\"always\" className={type === 'link' ? undefined : 'self-start'}><a href={`#${id}`} onClick={() => setDetails(true)}>{copy(key)}{type === 'meeting' ? <ChevronRight className=\"size-5\" /> : null}</a></LinkButton>\n if (dismissed) return <div className=\"flex min-h-27 w-58 items-center justify-center rounded-lg border border-dashed border-border\"><Button type=\"button\" variant=\"outline\" onClick={() => setDismissed(false)}>{copy('restoreCard')}</Button></div>\n return <div className=\"w-58 max-w-full\">\n <SidebarFeatureCard appearance={appearance} className={cn('flex flex-col', type === 'gift' ? 'flex-row items-center gap-3 p-3' : type === 'link' || type === 'support' ? 'gap-3' : 'gap-4', type === 'link' && 'items-center text-center', type === 'storage' && 'px-2 pt-4 pb-2')}>\n {(type === 'meeting' || type === 'support') ? <CompactButton appearance=\"modifiable\" className=\"absolute top-3 right-3 text-current\" aria-label={copy('dismissCard')} onClick={() => setDismissed(true)}><X /></CompactButton> : null}\n {type === 'meeting' ? <>\n <div data-slot=\"sidebar-meeting-avatars\" className=\"flex w-fit items-center gap-1.5 rounded-full bg-background p-0.5 pr-2 text-foreground ring-1 ring-inset ring-border\"><AvatarStack size={24} max={3} className=\"[&_[data-slot=avatar-stack-item]+[data-slot=avatar-stack-item]]:-ml-0.5\">{avatarImages.map((src, index) => <Avatar key={`meeting-avatar-${index}`} label={String(index + 1)} src={src} size={24} />)}</AvatarStack><span className=\"w-4 text-xs leading-4 text-muted-foreground\">+4</span></div>\n <div className=\"flex flex-col gap-1\"><p className=\"text-sm font-medium\">{copy('meeting')}</p><p className={cn('text-xs', descriptionClass)}>{copy('meetingTime')}</p></div>{link('join')}\n </> : null}\n {type === 'progress' ? <><div className=\"flex flex-col gap-1\"><p className=\"text-sm font-medium\">{copy('capacity')}</p><p className={cn('text-xs', descriptionClass)}>{copy('capacityHint')}</p></div><Progress value={90} size=\"md\" aria-label={copy('capacity')} className={inverse ? 'bg-current/20' : undefined} fillClassName={inverse ? 'bg-current' : undefined} />{link('upgrade')}</> : null}\n {(type === 'link' || type === 'gift') ? <span className={cn('flex size-10 shrink-0 items-center justify-center rounded-full', inverse ? 'bg-background text-status-info-icon' : 'bg-status-info-bg text-status-info-icon')}>{type === 'link' ? <Cloud className=\"size-5\" /> : <Gift className=\"size-5\" />}</span> : null}\n {type === 'link' ? <><p className={cn('text-sm', descriptionClass)}>{copy('plansHint')}</p>{link('plans')}</> : null}\n {type === 'gift' ? <div className=\"flex min-w-0 flex-col gap-1\"><p className=\"text-sm font-medium\">{copy('gift')}</p><p className={cn('text-xs', descriptionClass)}>{copy('giftHint')}</p></div> : null}\n {type === 'storage' ? <><div className=\"px-2\"><div className=\"mb-1.5 flex items-center justify-between text-sm\"><span>{copy('storage')}</span><span className=\"text-xs\">80%</span></div><Progress value={80} size=\"md\" aria-label={copy('storage')} className={inverse ? 'bg-current/20' : undefined} fillClassName={inverse ? 'bg-current' : undefined} /><p className={cn('mt-1.5 text-xs', descriptionClass)}>{copy('storageHint')}</p></div><Button type=\"button\" variant=\"ghost\" className=\"h-8 w-full justify-start gap-1 rounded-md bg-background px-2.5 py-1.5 text-xs text-foreground hover:bg-muted\" aria-pressed={!paused} onClick={() => setPaused(value => !value)}>{copy('sync')}<span className=\"flex-1 text-left text-muted-foreground\">{copy(paused ? 'paused' : 'running')}</span>{paused ? <Play className=\"size-5\" /> : <Pause className=\"size-5\" />}</Button></> : null}\n {type === 'support' ? <><div className=\"flex items-center gap-2.5 pr-4\"><Headphones className=\"size-6\" /><p className=\"text-base\">{copy('supportTitle')}</p></div><p className={cn('text-sm', descriptionClass)}>{copy('supportHint')}</p></> : null}\n </SidebarFeatureCard>\n {(type === 'meeting' || type === 'progress' || type === 'link') ? <div id={id} tabIndex={-1}>{details ? <div data-slot=\"sidebar-feature-details\" className=\"mt-3 rounded-md border border-border p-3 text-sm\"><p className=\"font-medium\">{copy('localDestination')}</p><p className=\"mt-1 text-muted-foreground\">{copy(type === 'meeting' ? 'meetingTime' : 'plansHint')}</p></div> : null}</div> : null}\n </div>\n}\n\nexport function SidebarFeatureExample({ type = 'meeting' }: { type?: FeatureType }) {\n return <div className=\"flex w-full flex-wrap items-start gap-6\">{(['stroke', 'gray', 'primary', 'neutral'] as const).map(appearance => <FeatureCard key={appearance} type={type} appearance={appearance} />)}</div>\n}\n\nexport function SidebarItemsExample() {\n const t = useT()\n const [selected, setSelected] = React.useState([0, 0])\n return <div className=\"flex flex-wrap items-start gap-6\">{[false, true].map((collapsed, group) => <div key={String(collapsed)} className={collapsed ? 'w-9' : 'w-58'}>\n {['dashboard', 'calendar', 'projects'].map((key, index) => <SidebarItem key={key} collapsed={collapsed} icon={React.createElement(navIcons[index], { className: 'size-5' })} active={selected[group] === index} trailing={<ChevronRight className=\"size-5\" />} onClick={() => setSelected(current => current.map((value, currentGroup) => currentGroup === group ? index : value))}>{t(`design_system.gallery.examples.shell.${key}`)}</SidebarItem>)}\n <output className=\"sr-only\">{t(`design_system.gallery.examples.shell.${['dashboard', 'calendar', 'projects'][selected[group]]}`)}</output>\n </div>)}</div>\n}\n\nexport function SidebarIdentityExample() {\n const [product, setProduct] = React.useState<Product>('hr')\n return <div className=\"flex flex-wrap items-start gap-6\">{[false, true].map(collapsed => <div key={String(collapsed)} className={collapsed ? 'w-16' : 'w-62'}><WorkspaceIdentity collapsed={collapsed} product={product} onProductChange={setProduct} /><WorkspaceIdentity collapsed={collapsed} product={product} profile /></div>)}</div>\n}\n\nexport function SidebarExample({ product = 'hr', collapsed = false, feature = false }: { product?: Product; collapsed?: boolean; feature?: boolean }) {\n const t = useT()\n const [compact, setCompact] = React.useState(collapsed)\n const [workspace, setWorkspace] = React.useState(product)\n const [active, setActive] = React.useState('dashboard')\n const copy = (key: string) => t(`design_system.gallery.examples.shell.${key}`)\n return <div className=\"flex w-full flex-wrap items-start gap-6\">\n <Sidebar collapsed={compact} className=\"max-w-full\" aria-label={copy('sidebar')}>\n <SidebarHeader className=\"relative after:absolute after:inset-x-5 after:bottom-0 after:border-b after:border-border after:content-['']\"><WorkspaceIdentity collapsed={compact} product={workspace} onProductChange={value => { setWorkspace(value); setActive('dashboard') }} /></SidebarHeader>\n <SidebarContent>\n <nav aria-label={copy('main')} className=\"w-full\"><p className={cn('mb-3 px-1 text-xs uppercase text-muted-foreground', compact && 'sr-only')}>{copy('main')}</p><div className=\"flex flex-col gap-1\">\n {navItems[workspace].map((key, index) => <SidebarItem key={key} icon={React.createElement(navIcons[index], { className: 'size-5' })} active={active === key} trailing={active === key ? <ChevronRight className=\"size-5\" /> : undefined} className={active === key ? cn(\"before:absolute before:top-2 before:h-5 before:w-1 before:rounded-r before:bg-foreground before:content-['']\", compact ? 'before:-left-5.5' : 'before:-left-5') : undefined} onClick={() => setActive(key)}>{copy(key)}</SidebarItem>)}\n </div></nav>\n {!compact && workspace === 'hr' && !feature ? <nav aria-label={copy('favorites')}><p className=\"mb-3 px-1 text-xs uppercase text-muted-foreground\">{copy('favorites')}</p>{['Loom Mobile App', 'Monday Redesign', 'Udemy Courses'].map((name, index) => <SidebarItem key={name} icon={<Folder className=\"size-5\" />} active={active === name} trailing={<span className=\"text-xs\">⌘{index + 1}</span>} onClick={() => setActive(name)}>{name}</SidebarItem>)}</nav> : null}\n <nav aria-label={copy('support')} className=\"mt-auto w-full\">{['settings', 'support'].map(key => <SidebarItem key={key} icon={key === 'settings' ? <Settings /> : <Headphones />} active={active === key} onClick={() => setActive(key)}>{copy(key)}</SidebarItem>)}</nav>\n {feature && !compact ? <FeatureCard type={workspace === 'hr' ? 'meeting' : 'progress'} appearance=\"stroke\" /> : null}\n </SidebarContent>\n <SidebarFooter><WorkspaceIdentity collapsed={compact} product={workspace} profile /></SidebarFooter>\n </Sidebar>\n <div className=\"min-w-0 flex-1 basis-64 space-y-4 py-5\"><IconButton variant=\"outline\" aria-label={copy(compact ? 'expand' : 'collapse')} aria-expanded={!compact} onClick={() => setCompact(value => !value)}>{compact ? <PanelLeftOpen /> : <PanelLeftClose />}</IconButton><h3 className=\"text-lg font-medium\">{navItems[workspace].some(key => key === active) || ['settings', 'support'].includes(active) ? copy(active) : active}</h3><p className=\"text-sm text-muted-foreground\">{copy('previewHint')}</p></div>\n </div>\n}\n\nexport function TopbarExample({ product = 'hr', icons = false }: { product?: Product; icons?: boolean }) {\n const t = useT()\n const [active, setActive] = React.useState('dashboard')\n const [query, setQuery] = React.useState('')\n const copy = (key: string) => t(`design_system.gallery.examples.shell.${key}`)\n const items = product === 'hr' ? ['dashboard', 'calendar', 'timeOff', 'projects', 'teams', 'settings'] : navItems.finance\n return <div className=\"w-full overflow-x-auto\"><Topbar className=\"w-360 flex-nowrap\">\n <img src={navigationBrandArtwork[product === 'hr' ? 'synergyOriginal' : 'apexOriginal']} alt={product === 'hr' ? 'Synergy' : 'Apex'} className=\"size-10\" />\n <nav aria-label={copy('topbar')} className=\"flex min-w-0 flex-1 items-center gap-1\">{items.filter(key => copy(key).toLocaleLowerCase().includes(query.toLocaleLowerCase())).map((key, index) => <SidebarItem key={key} icon={icons ? React.createElement(navIcons[index], { className: 'size-5' }) : null} active={active === key} className={cn('w-auto flex-1 px-2', !icons && '[&>span:first-child]:hidden')} onClick={() => setActive(key)}>{copy(key)}</SidebarItem>)}<Popover><PopoverTrigger asChild><Button type=\"button\" variant=\"ghost\" className=\"h-9 gap-2 px-3\">{copy('others')}<ChevronDown className=\"size-5\" /></Button></PopoverTrigger><PopoverContent className=\"p-2\">{['settings', 'support'].map(key => <Button key={key} type=\"button\" variant=\"ghost\" onClick={() => setActive(key)}>{copy(key)}</Button>)}</PopoverContent></Popover></nav>\n <SearchInput value={query} onChange={setQuery} className=\"w-58\" aria-label={copy('filterNavigation')} />\n <Popover><PopoverTrigger asChild><IconButton variant=\"ghost\" className=\"size-10 rounded-lg data-[state=open]:bg-muted\" aria-label={copy('notifications')}><Bell className=\"size-5\" /></IconButton></PopoverTrigger><PopoverContent className=\"p-4 text-sm\">{copy('noNotifications')}</PopoverContent></Popover>\n <Popover><PopoverTrigger asChild><Button type=\"button\" variant=\"ghost\" className=\"h-10 gap-1.5 rounded-lg py-1 pr-2 pl-1 data-[state=open]:bg-muted\"><ShellAvatar size={32} product={product} /><span>{product === 'hr' ? 'Sophia' : 'Arthur'}</span><ChevronDown className=\"size-5\" /></Button></PopoverTrigger><PopoverContent className=\"p-4 text-sm\">{copy('localProfile')}</PopoverContent></Popover>\n </Topbar><output className=\"sr-only\">{copy(active)}</output></div>\n}\n\nexport function NavigationBrandsExample() {\n return <div className=\"flex flex-wrap gap-6\">{Object.entries(navigationBrandArtwork).filter(([key]) => key !== 'loom' && !key.endsWith('Avatar')).map(([key, src]) => <figure key={key} className=\"space-y-2\"><div className={cn('flex size-20 items-center justify-center rounded-lg', key.endsWith('White') ? 'bg-status-info-solid' : key.endsWith('Black') ? 'bg-accent-indigo-foreground' : 'bg-background')}><img src={src} alt={key.startsWith('synergy') ? 'Synergy' : 'Apex'} className=\"size-10\" /></div><figcaption className=\"text-xs text-muted-foreground\">{key}</figcaption></figure>)}</div>\n}\n"
3
+ export const shellExampleCode = (expression: string) => source + "\n" + expression + "\n"
@@ -0,0 +1,183 @@
1
+ import * as React from 'react'
2
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
3
+ import { cn } from '@open-mercato/shared/lib/utils'
4
+ import { BadgeCheck, Bell, CalendarDays, ChevronDown, ChevronRight, ChevronsUpDown, Cloud, CreditCard, Download, FileText, Folder, Gift, Headphones, Home, Layers, PanelLeftClose, PanelLeftOpen, Pause, Play, Plus, Search, Settings, UserRound, Users, X } from 'lucide-react'
5
+ import { PageHeader } from '@open-mercato/ui/backend/Page'
6
+ import { Avatar, AvatarStack } from '@open-mercato/ui/primitives/avatar'
7
+ import { Button } from '@open-mercato/ui/primitives/button'
8
+ import { CompactButton } from '@open-mercato/ui/primitives/compact-button'
9
+ import { IconButton } from '@open-mercato/ui/primitives/icon-button'
10
+ import { LinkButton } from '@open-mercato/ui/primitives/link-button'
11
+ import { Input } from '@open-mercato/ui/primitives/input'
12
+ import { Checkbox } from '@open-mercato/ui/primitives/checkbox'
13
+ import { SearchInput } from '@open-mercato/ui/primitives/search-input'
14
+ import { Progress } from '@open-mercato/ui/primitives/progress'
15
+ import { Popover, PopoverContent, PopoverTrigger } from '@open-mercato/ui/primitives/popover'
16
+ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'
17
+ import { Sidebar, SidebarContent, SidebarFeatureCard, SidebarFooter, SidebarHeader, SidebarIdentity, SidebarItem, Topbar } from '@open-mercato/ui/primitives/sidebar'
18
+ import { navigationBrandArtwork } from '@open-mercato/ui/assets/navigation-brand-artwork'
19
+ import avatarIllustration from '../assets/avatar-illustration.png'
20
+ import avatarMemoji from '../assets/avatar-memoji.png'
21
+ import avatarPhoto from '../assets/avatar-photo.png'
22
+
23
+ const avatarImages = [avatarIllustration, avatarMemoji, avatarPhoto].map(image => typeof image === 'string' ? image : image.src)
24
+ type Product = 'hr' | 'finance'
25
+ type HeaderType = 'basic' | 'avatar' | 'icon' | 'brand' | 'company'
26
+ type FeatureType = 'meeting' | 'progress' | 'link' | 'gift' | 'storage' | 'support'
27
+ type FeatureAppearance = 'stroke' | 'gray' | 'primary' | 'neutral'
28
+ const navItems = {
29
+ hr: ['dashboard', 'calendar', 'timeOff', 'projects', 'teams', 'integrations', 'benefits', 'documents'],
30
+ finance: ['dashboard', 'cards', 'transfer', 'transactions', 'payments', 'exchange'],
31
+ } as const
32
+ const navIcons = [Home, CalendarDays, CreditCard, Folder, Users, Layers, Gift, FileText]
33
+
34
+ function ShellAvatar({ size = 40, product = 'hr' }: { size?: 24 | 32 | 40 | 48; product?: Product }) {
35
+ return <Avatar src={product === 'hr' ? navigationBrandArtwork.sophiaAvatar : navigationBrandArtwork.arthurAvatar} label={product === 'hr' ? 'Sophia Williams' : 'Arthur Taylor'} size={size} />
36
+ }
37
+
38
+ function WorkspaceIdentity({ product, onProductChange, collapsed = false, profile = false }: { product: Product; onProductChange?: (product: Product) => void; collapsed?: boolean; profile?: boolean }) {
39
+ const t = useT()
40
+ const [open, setOpen] = React.useState(false)
41
+ const name = product === 'hr' ? 'Synergy' : 'Apex'
42
+ return <Popover open={open} onOpenChange={setOpen}>
43
+ <PopoverTrigger asChild>
44
+ <SidebarIdentity collapsed={collapsed} label={profile ? product === 'hr' ? 'Sophia Williams' : 'Arthur Taylor' : name}
45
+ description={profile ? product === 'hr' ? 'sophia@example.com' : 'arthur@example.com' : t(`design_system.gallery.examples.shell.${product}`)}
46
+ leading={profile ? <ShellAvatar product={product} /> : <img src={navigationBrandArtwork[product === 'hr' ? 'synergyOriginal' : 'apexOriginal']} alt="" className="size-10" />}
47
+ badge={profile ? <BadgeCheck className="size-3.5 fill-status-info-icon stroke-status-info-solid-foreground" aria-hidden="true" /> : undefined}
48
+ trailing={profile ? <ChevronRight className="size-5" /> : <span className="flex size-6 items-center justify-center rounded-sm border border-border bg-background shadow-xs"><ChevronsUpDown className="size-5" /></span>} />
49
+ </PopoverTrigger>
50
+ <PopoverContent className="w-64 p-3">
51
+ {profile ? <p className="text-sm">{t('design_system.gallery.examples.shell.localProfile')}</p> : <div className="flex flex-col gap-1">
52
+ {(['hr', 'finance'] as const).map(value => <Button key={value} type="button" variant="ghost" aria-pressed={product === value} onClick={() => { onProductChange?.(value); setOpen(false) }}>{value === 'hr' ? 'Synergy' : 'Apex'}</Button>)}
53
+ </div>}
54
+ </PopoverContent>
55
+ </Popover>
56
+ }
57
+
58
+ export function PageHeaderExample({ appearance = 'page', type = 'basic', configurable = false }: { appearance?: 'page' | 'section'; type?: HeaderType; configurable?: boolean }) {
59
+ const t = useT()
60
+ const [options, setOptions] = React.useState({ divider: true, search: true, notification: true, actions: true, second: true, dropdown: false })
61
+ const [query, setQuery] = React.useState('')
62
+ const [dialog, setDialog] = React.useState(false)
63
+ const [draft, setDraft] = React.useState('')
64
+ const [saved, setSaved] = React.useState('')
65
+ const [period, setPeriod] = React.useState('month')
66
+ const [workspace, setWorkspace] = React.useState<Product>('hr')
67
+ const submit = () => { if (draft.trim()) { setSaved(draft.trim()); setDialog(false) } }
68
+ const copy = (key: string) => t(`design_system.gallery.examples.shell.${key}`)
69
+ const leading = type === 'avatar' ? <ShellAvatar size={48} />
70
+ : type === 'icon' ? <span className="flex size-12 items-center justify-center rounded-full border border-border"><UserRound className="size-6" aria-hidden="true" /></span>
71
+ : type === 'brand' ? <img src={navigationBrandArtwork.loom} alt="Loom" className="size-12" />
72
+ : type === 'company' ? <img src={navigationBrandArtwork.apexOriginal} alt="Apex" className="size-12" /> : undefined
73
+ const periodMenu = <Popover><PopoverTrigger asChild><Button type="button" variant="outline" size="lg"><CalendarDays className="size-5" />{copy(period)}<ChevronDown className="size-5" /></Button></PopoverTrigger><PopoverContent className="p-2">{['month', 'year'].map(value => <Button key={value} type="button" variant="ghost" aria-pressed={period === value} onClick={() => setPeriod(value)}>{copy(value)}</Button>)}</PopoverContent></Popover>
74
+ const titleAction = options.dropdown && appearance === 'section' ? <Popover><PopoverTrigger asChild><CompactButton fullRadius aria-label={copy('switchWorkspace')}><ChevronDown /></CompactButton></PopoverTrigger><PopoverContent className="p-3">{(['hr', 'finance'] as const).map(value => <Button key={value} type="button" variant="ghost" onClick={() => setWorkspace(value)}>{copy(value)}</Button>)}</PopoverContent></Popover> : undefined
75
+ return <div className="w-full">
76
+ {configurable ? <div className="mb-4 flex flex-wrap gap-4">{(Object.keys(options) as Array<keyof typeof options>).map(key => <label key={key} className="flex items-center gap-2 text-sm"><Checkbox checked={options[key]} onCheckedChange={checked => setOptions(current => ({ ...current, [key]: checked === true }))} />{copy(key)}</label>)}</div> : null}
77
+ <PageHeader appearance={appearance} leading={leading} divider={options.divider} title={configurable ? copy(workspace) : copy('title')} description={copy('description')} titleAction={titleAction}
78
+ actions={<>
79
+ {options.search ? <Popover><PopoverTrigger asChild><IconButton variant="ghost" className="size-10 rounded-lg data-[state=open]:bg-muted" aria-label={copy('search')}><Search className="size-5" /></IconButton></PopoverTrigger><PopoverContent className="p-3"><SearchInput value={query} onChange={setQuery} aria-label={copy('search')} /><p className="mt-2 text-sm" role="status">{query ? t('design_system.gallery.examples.shell.localQuery', { query }) : copy('searchHint')}</p></PopoverContent></Popover> : null}
80
+ {options.notification && appearance === 'page' ? <Popover><PopoverTrigger asChild><IconButton variant="ghost" className="size-10 rounded-lg data-[state=open]:bg-muted" aria-label={copy('notifications')}><Bell className="size-5" /></IconButton></PopoverTrigger><PopoverContent className="p-4 text-sm">{copy('noNotifications')}</PopoverContent></Popover> : null}
81
+ {options.dropdown && appearance === 'page' ? periodMenu : null}
82
+ {options.actions ? <>
83
+ {options.second ? appearance === 'page' ? <Button type="button" variant="outline" size="lg" onClick={() => { setDraft(''); setDialog(true) }}><CalendarDays className="size-5" />{copy('schedule')}</Button> : <Button asChild variant="outline" size="lg"><a download="page-header-example.json" href={`data:application/json;charset=utf-8,${encodeURIComponent(JSON.stringify({ title: copy('title'), description: copy('description') }, null, 2))}`}><Download className="size-5" />{copy('export')}</a></Button> : null}
84
+ <Button type="button" variant="primary-filled" size="lg" onClick={() => { setDraft(''); setDialog(true) }}><Plus className="size-5" />{copy(appearance === 'page' ? 'create' : 'invite')}</Button>
85
+ </> : null}
86
+ </>} />
87
+ {saved ? <output className="block px-8 pt-3 text-sm">{t('design_system.gallery.examples.shell.savedDraft', { title: saved })}</output> : null}
88
+ <Dialog open={dialog} onOpenChange={setDialog}><DialogContent onKeyDown={event => { if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') { event.preventDefault(); submit() } }}>
89
+ <DialogHeader><DialogTitle>{copy('localDraft')}</DialogTitle><DialogDescription>{copy('localDraftHint')}</DialogDescription></DialogHeader>
90
+ <Input value={draft} onChange={event => setDraft(event.target.value)} aria-label={copy('draftTitle')} autoFocus />
91
+ <DialogFooter><Button type="button" variant="outline" onClick={() => setDialog(false)}>{copy('cancel')}</Button><Button type="button" disabled={!draft.trim()} onClick={submit}>{copy('saveDraft')}</Button></DialogFooter>
92
+ </DialogContent></Dialog>
93
+ </div>
94
+ }
95
+
96
+ function FeatureCard({ type, appearance }: { type: FeatureType; appearance: FeatureAppearance }) {
97
+ const t = useT()
98
+ const [dismissed, setDismissed] = React.useState(false)
99
+ const [paused, setPaused] = React.useState(true)
100
+ const [details, setDetails] = React.useState(false)
101
+ const id = React.useId()
102
+ const copy = (key: string) => t(`design_system.gallery.examples.shell.${key}`)
103
+ const inverse = appearance === 'primary' || appearance === 'neutral'
104
+ const descriptionClass = inverse ? 'text-current' : 'text-muted-foreground'
105
+ const link = (key: string) => <LinkButton asChild variant="modifiable" underline="always" className={type === 'link' ? undefined : 'self-start'}><a href={`#${id}`} onClick={() => setDetails(true)}>{copy(key)}{type === 'meeting' ? <ChevronRight className="size-5" /> : null}</a></LinkButton>
106
+ if (dismissed) return <div className="flex min-h-27 w-58 items-center justify-center rounded-lg border border-dashed border-border"><Button type="button" variant="outline" onClick={() => setDismissed(false)}>{copy('restoreCard')}</Button></div>
107
+ return <div className="w-58 max-w-full">
108
+ <SidebarFeatureCard appearance={appearance} className={cn('flex flex-col', type === 'gift' ? 'flex-row items-center gap-3 p-3' : type === 'link' || type === 'support' ? 'gap-3' : 'gap-4', type === 'link' && 'items-center text-center', type === 'storage' && 'px-2 pt-4 pb-2')}>
109
+ {(type === 'meeting' || type === 'support') ? <CompactButton appearance="modifiable" className="absolute top-3 right-3 text-current" aria-label={copy('dismissCard')} onClick={() => setDismissed(true)}><X /></CompactButton> : null}
110
+ {type === 'meeting' ? <>
111
+ <div data-slot="sidebar-meeting-avatars" className="flex w-fit items-center gap-1.5 rounded-full bg-background p-0.5 pr-2 text-foreground ring-1 ring-inset ring-border"><AvatarStack size={24} max={3} className="[&_[data-slot=avatar-stack-item]+[data-slot=avatar-stack-item]]:-ml-0.5">{avatarImages.map((src, index) => <Avatar key={`meeting-avatar-${index}`} label={String(index + 1)} src={src} size={24} />)}</AvatarStack><span className="w-4 text-xs leading-4 text-muted-foreground">+4</span></div>
112
+ <div className="flex flex-col gap-1"><p className="text-sm font-medium">{copy('meeting')}</p><p className={cn('text-xs', descriptionClass)}>{copy('meetingTime')}</p></div>{link('join')}
113
+ </> : null}
114
+ {type === 'progress' ? <><div className="flex flex-col gap-1"><p className="text-sm font-medium">{copy('capacity')}</p><p className={cn('text-xs', descriptionClass)}>{copy('capacityHint')}</p></div><Progress value={90} size="md" aria-label={copy('capacity')} className={inverse ? 'bg-current/20' : undefined} fillClassName={inverse ? 'bg-current' : undefined} />{link('upgrade')}</> : null}
115
+ {(type === 'link' || type === 'gift') ? <span className={cn('flex size-10 shrink-0 items-center justify-center rounded-full', inverse ? 'bg-background text-status-info-icon' : 'bg-status-info-bg text-status-info-icon')}>{type === 'link' ? <Cloud className="size-5" /> : <Gift className="size-5" />}</span> : null}
116
+ {type === 'link' ? <><p className={cn('text-sm', descriptionClass)}>{copy('plansHint')}</p>{link('plans')}</> : null}
117
+ {type === 'gift' ? <div className="flex min-w-0 flex-col gap-1"><p className="text-sm font-medium">{copy('gift')}</p><p className={cn('text-xs', descriptionClass)}>{copy('giftHint')}</p></div> : null}
118
+ {type === 'storage' ? <><div className="px-2"><div className="mb-1.5 flex items-center justify-between text-sm"><span>{copy('storage')}</span><span className="text-xs">80%</span></div><Progress value={80} size="md" aria-label={copy('storage')} className={inverse ? 'bg-current/20' : undefined} fillClassName={inverse ? 'bg-current' : undefined} /><p className={cn('mt-1.5 text-xs', descriptionClass)}>{copy('storageHint')}</p></div><Button type="button" variant="ghost" className="h-8 w-full justify-start gap-1 rounded-md bg-background px-2.5 py-1.5 text-xs text-foreground hover:bg-muted" aria-pressed={!paused} onClick={() => setPaused(value => !value)}>{copy('sync')}<span className="flex-1 text-left text-muted-foreground">{copy(paused ? 'paused' : 'running')}</span>{paused ? <Play className="size-5" /> : <Pause className="size-5" />}</Button></> : null}
119
+ {type === 'support' ? <><div className="flex items-center gap-2.5 pr-4"><Headphones className="size-6" /><p className="text-base">{copy('supportTitle')}</p></div><p className={cn('text-sm', descriptionClass)}>{copy('supportHint')}</p></> : null}
120
+ </SidebarFeatureCard>
121
+ {(type === 'meeting' || type === 'progress' || type === 'link') ? <div id={id} tabIndex={-1}>{details ? <div data-slot="sidebar-feature-details" className="mt-3 rounded-md border border-border p-3 text-sm"><p className="font-medium">{copy('localDestination')}</p><p className="mt-1 text-muted-foreground">{copy(type === 'meeting' ? 'meetingTime' : 'plansHint')}</p></div> : null}</div> : null}
122
+ </div>
123
+ }
124
+
125
+ export function SidebarFeatureExample({ type = 'meeting' }: { type?: FeatureType }) {
126
+ return <div className="flex w-full flex-wrap items-start gap-6">{(['stroke', 'gray', 'primary', 'neutral'] as const).map(appearance => <FeatureCard key={appearance} type={type} appearance={appearance} />)}</div>
127
+ }
128
+
129
+ export function SidebarItemsExample() {
130
+ const t = useT()
131
+ const [selected, setSelected] = React.useState([0, 0])
132
+ return <div className="flex flex-wrap items-start gap-6">{[false, true].map((collapsed, group) => <div key={String(collapsed)} className={collapsed ? 'w-9' : 'w-58'}>
133
+ {['dashboard', 'calendar', 'projects'].map((key, index) => <SidebarItem key={key} collapsed={collapsed} icon={React.createElement(navIcons[index], { className: 'size-5' })} active={selected[group] === index} trailing={<ChevronRight className="size-5" />} onClick={() => setSelected(current => current.map((value, currentGroup) => currentGroup === group ? index : value))}>{t(`design_system.gallery.examples.shell.${key}`)}</SidebarItem>)}
134
+ <output className="sr-only">{t(`design_system.gallery.examples.shell.${['dashboard', 'calendar', 'projects'][selected[group]]}`)}</output>
135
+ </div>)}</div>
136
+ }
137
+
138
+ export function SidebarIdentityExample() {
139
+ const [product, setProduct] = React.useState<Product>('hr')
140
+ return <div className="flex flex-wrap items-start gap-6">{[false, true].map(collapsed => <div key={String(collapsed)} className={collapsed ? 'w-16' : 'w-62'}><WorkspaceIdentity collapsed={collapsed} product={product} onProductChange={setProduct} /><WorkspaceIdentity collapsed={collapsed} product={product} profile /></div>)}</div>
141
+ }
142
+
143
+ export function SidebarExample({ product = 'hr', collapsed = false, feature = false }: { product?: Product; collapsed?: boolean; feature?: boolean }) {
144
+ const t = useT()
145
+ const [compact, setCompact] = React.useState(collapsed)
146
+ const [workspace, setWorkspace] = React.useState(product)
147
+ const [active, setActive] = React.useState('dashboard')
148
+ const copy = (key: string) => t(`design_system.gallery.examples.shell.${key}`)
149
+ return <div className="flex w-full flex-wrap items-start gap-6">
150
+ <Sidebar collapsed={compact} className="max-w-full" aria-label={copy('sidebar')}>
151
+ <SidebarHeader className="relative after:absolute after:inset-x-5 after:bottom-0 after:border-b after:border-border after:content-['']"><WorkspaceIdentity collapsed={compact} product={workspace} onProductChange={value => { setWorkspace(value); setActive('dashboard') }} /></SidebarHeader>
152
+ <SidebarContent>
153
+ <nav aria-label={copy('main')} className="w-full"><p className={cn('mb-3 px-1 text-xs uppercase text-muted-foreground', compact && 'sr-only')}>{copy('main')}</p><div className="flex flex-col gap-1">
154
+ {navItems[workspace].map((key, index) => <SidebarItem key={key} icon={React.createElement(navIcons[index], { className: 'size-5' })} active={active === key} trailing={active === key ? <ChevronRight className="size-5" /> : undefined} className={active === key ? cn("before:absolute before:top-2 before:h-5 before:w-1 before:rounded-r before:bg-foreground before:content-['']", compact ? 'before:-left-5.5' : 'before:-left-5') : undefined} onClick={() => setActive(key)}>{copy(key)}</SidebarItem>)}
155
+ </div></nav>
156
+ {!compact && workspace === 'hr' && !feature ? <nav aria-label={copy('favorites')}><p className="mb-3 px-1 text-xs uppercase text-muted-foreground">{copy('favorites')}</p>{['Loom Mobile App', 'Monday Redesign', 'Udemy Courses'].map((name, index) => <SidebarItem key={name} icon={<Folder className="size-5" />} active={active === name} trailing={<span className="text-xs">⌘{index + 1}</span>} onClick={() => setActive(name)}>{name}</SidebarItem>)}</nav> : null}
157
+ <nav aria-label={copy('support')} className="mt-auto w-full">{['settings', 'support'].map(key => <SidebarItem key={key} icon={key === 'settings' ? <Settings /> : <Headphones />} active={active === key} onClick={() => setActive(key)}>{copy(key)}</SidebarItem>)}</nav>
158
+ {feature && !compact ? <FeatureCard type={workspace === 'hr' ? 'meeting' : 'progress'} appearance="stroke" /> : null}
159
+ </SidebarContent>
160
+ <SidebarFooter><WorkspaceIdentity collapsed={compact} product={workspace} profile /></SidebarFooter>
161
+ </Sidebar>
162
+ <div className="min-w-0 flex-1 basis-64 space-y-4 py-5"><IconButton variant="outline" aria-label={copy(compact ? 'expand' : 'collapse')} aria-expanded={!compact} onClick={() => setCompact(value => !value)}>{compact ? <PanelLeftOpen /> : <PanelLeftClose />}</IconButton><h3 className="text-lg font-medium">{navItems[workspace].some(key => key === active) || ['settings', 'support'].includes(active) ? copy(active) : active}</h3><p className="text-sm text-muted-foreground">{copy('previewHint')}</p></div>
163
+ </div>
164
+ }
165
+
166
+ export function TopbarExample({ product = 'hr', icons = false }: { product?: Product; icons?: boolean }) {
167
+ const t = useT()
168
+ const [active, setActive] = React.useState('dashboard')
169
+ const [query, setQuery] = React.useState('')
170
+ const copy = (key: string) => t(`design_system.gallery.examples.shell.${key}`)
171
+ const items = product === 'hr' ? ['dashboard', 'calendar', 'timeOff', 'projects', 'teams', 'settings'] : navItems.finance
172
+ return <div className="w-full overflow-x-auto"><Topbar className="w-360 flex-nowrap">
173
+ <img src={navigationBrandArtwork[product === 'hr' ? 'synergyOriginal' : 'apexOriginal']} alt={product === 'hr' ? 'Synergy' : 'Apex'} className="size-10" />
174
+ <nav aria-label={copy('topbar')} className="flex min-w-0 flex-1 items-center gap-1">{items.filter(key => copy(key).toLocaleLowerCase().includes(query.toLocaleLowerCase())).map((key, index) => <SidebarItem key={key} icon={icons ? React.createElement(navIcons[index], { className: 'size-5' }) : null} active={active === key} className={cn('w-auto flex-1 px-2', !icons && '[&>span:first-child]:hidden')} onClick={() => setActive(key)}>{copy(key)}</SidebarItem>)}<Popover><PopoverTrigger asChild><Button type="button" variant="ghost" className="h-9 gap-2 px-3">{copy('others')}<ChevronDown className="size-5" /></Button></PopoverTrigger><PopoverContent className="p-2">{['settings', 'support'].map(key => <Button key={key} type="button" variant="ghost" onClick={() => setActive(key)}>{copy(key)}</Button>)}</PopoverContent></Popover></nav>
175
+ <SearchInput value={query} onChange={setQuery} className="w-58" aria-label={copy('filterNavigation')} />
176
+ <Popover><PopoverTrigger asChild><IconButton variant="ghost" className="size-10 rounded-lg data-[state=open]:bg-muted" aria-label={copy('notifications')}><Bell className="size-5" /></IconButton></PopoverTrigger><PopoverContent className="p-4 text-sm">{copy('noNotifications')}</PopoverContent></Popover>
177
+ <Popover><PopoverTrigger asChild><Button type="button" variant="ghost" className="h-10 gap-1.5 rounded-lg py-1 pr-2 pl-1 data-[state=open]:bg-muted"><ShellAvatar size={32} product={product} /><span>{product === 'hr' ? 'Sophia' : 'Arthur'}</span><ChevronDown className="size-5" /></Button></PopoverTrigger><PopoverContent className="p-4 text-sm">{copy('localProfile')}</PopoverContent></Popover>
178
+ </Topbar><output className="sr-only">{copy(active)}</output></div>
179
+ }
180
+
181
+ export function NavigationBrandsExample() {
182
+ return <div className="flex flex-wrap gap-6">{Object.entries(navigationBrandArtwork).filter(([key]) => key !== 'loom' && !key.endsWith('Avatar')).map(([key, src]) => <figure key={key} className="space-y-2"><div className={cn('flex size-20 items-center justify-center rounded-lg', key.endsWith('White') ? 'bg-status-info-solid' : key.endsWith('Black') ? 'bg-accent-indigo-foreground' : 'bg-background')}><img src={src} alt={key.startsWith('synergy') ? 'Synergy' : 'Apex'} className="size-10" /></div><figcaption className="text-xs text-muted-foreground">{key}</figcaption></figure>)}</div>
183
+ }
@@ -0,0 +1,88 @@
1
+ import * as React from 'react'
2
+ import { cn } from '@open-mercato/shared/lib/utils'
3
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
4
+ import { loadSourceArtwork, type SourceArtwork, type SourceArtworkGroup } from '@open-mercato/ui/assets/source-artwork'
5
+ import { Button } from '@open-mercato/ui/primitives/button'
6
+ import { SearchInput } from '@open-mercato/ui/primitives/search-input'
7
+ import { Pagination } from '@open-mercato/ui/primitives/pagination'
8
+ import { EmptyState } from '@open-mercato/ui/primitives/empty-state'
9
+ import { Popover, PopoverContent, PopoverTrigger } from '@open-mercato/ui/primitives/popover'
10
+ import { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'
11
+
12
+ const pageSize = 48
13
+
14
+ function ArtworkTile({ asset, group, duplicate }: { asset: SourceArtwork; group: SourceArtworkGroup; duplicate: boolean }) {
15
+ const t = useT()
16
+ const [notice, setNotice] = React.useState('')
17
+ const illustration = group === 'thumbnails'
18
+ const name = illustration ? t(`design_system.gallery.thumbnailNames.${asset.name}`, asset.name) : asset.name
19
+ const category = illustration ? t(`design_system.gallery.thumbnailCategories.${asset.category}`, asset.category) : asset.category
20
+ const label = duplicate ? `${name} (${illustration ? category : asset.id})` : name
21
+ async function copy() {
22
+ const code = `import { loadSourceArtwork } from '@open-mercato/ui/assets/source-artwork'\n\nconst assets = await loadSourceArtwork('${group}')\nconst asset = assets.find(item => item.id === '${asset.id}')\n\n<img src={asset?.src} width={asset?.width} height={asset?.height} alt="" />`
23
+ try { await navigator.clipboard.writeText(code); setNotice(t('design_system.gallery.sourceIcons.copied')) }
24
+ catch { setNotice(t('design_system.gallery.copyFailed')) }
25
+ }
26
+ return <Popover onOpenChange={() => setNotice('')}>
27
+ <PopoverTrigger asChild>
28
+ <Button type="button" variant="ghost" data-source-node={asset.id} aria-label={label} className="h-auto min-h-36 min-w-0 flex-col gap-3 whitespace-normal rounded-lg border border-border bg-background p-3">
29
+ <span className={cn("flex w-full items-center justify-center rounded-md bg-muted p-2", illustration ? "h-40" : "h-20")}><img src={asset.src} width={asset.width} height={asset.height} alt="" loading="lazy" className="max-h-full max-w-full object-contain drop-shadow-sm" /></span>
30
+ <span className="max-w-full break-words text-center text-xs font-normal leading-4">{name}</span>
31
+ </Button>
32
+ </PopoverTrigger>
33
+ <PopoverContent className={cn("space-y-4 p-4", illustration ? "w-80" : "w-72")} aria-label={label}>
34
+ <div className={cn("flex items-center justify-center rounded-md bg-status-neutral-solid p-4", illustration ? "h-64" : "h-32")}><img src={asset.src} width={asset.width} height={asset.height} alt="" className="max-h-full max-w-full object-contain" /></div>
35
+ <div><p className="break-words font-medium">{name}</p><p className="text-xs text-muted-foreground">{category} · {asset.width} × {asset.height}</p></div>
36
+ <Button type="button" size="sm" variant="outline" onClick={copy}>{t('design_system.gallery.sourceIcons.copy')}</Button>
37
+ {notice ? <p role="status" className="text-xs text-muted-foreground">{notice}</p> : null}
38
+ </PopoverContent>
39
+ </Popover>
40
+ }
41
+
42
+ export function SourceArtworkCatalogue({ group, collectionControl }: { group: SourceArtworkGroup; collectionControl?: React.ReactNode }) {
43
+ const t = useT()
44
+ const [assets, setAssets] = React.useState<readonly SourceArtwork[] | null>(null)
45
+ const [failed, setFailed] = React.useState(false)
46
+ const [attempt, setAttempt] = React.useState(0)
47
+ const [query, setQuery] = React.useState('')
48
+ const [page, setPage] = React.useState(1)
49
+
50
+ React.useEffect(() => {
51
+ let active = true
52
+ setAssets(null)
53
+ setFailed(false)
54
+ setPage(1)
55
+ setQuery('')
56
+ loadSourceArtwork(group).then(items => {
57
+ if (active) setAssets(items)
58
+ }).catch(() => { if (active) setFailed(true) })
59
+ return () => { active = false }
60
+ }, [group, attempt])
61
+
62
+ if (failed) return <div className="space-y-5">{collectionControl}<ErrorMessage label={t('design_system.gallery.loadFailed')} action={<Button type="button" variant="outline" onClick={() => setAttempt(value => value + 1)}>{t('design_system.gallery.retry')}</Button>} /></div>
63
+ if (!assets) return <div className="space-y-5">{collectionControl}<LoadingMessage label={t('design_system.gallery.sourceArtwork.loading')} /></div>
64
+ const needle = query.trim().toLowerCase()
65
+ const nameCounts = new Map<string, number>()
66
+ for (const asset of assets) nameCounts.set(asset.name, (nameCounts.get(asset.name) ?? 0) + 1)
67
+ const filtered = assets.filter(asset => {
68
+ const translated = group === 'thumbnails' ? `${t(`design_system.gallery.thumbnailNames.${asset.name}`, asset.name)} ${t(`design_system.gallery.thumbnailCategories.${asset.category}`, asset.category)}` : ''
69
+ return `${asset.name} ${asset.category} ${translated}`.toLowerCase().includes(needle)
70
+ })
71
+ const visible = filtered.slice((page - 1) * pageSize, page * pageSize)
72
+
73
+ return <div className="w-full min-w-0 space-y-5" data-example="source-artwork-catalogue" data-collection={group}>
74
+ <div className="flex flex-wrap items-center gap-4">
75
+ {collectionControl}
76
+ <SearchInput value={query} onChange={value => { setQuery(value); setPage(1) }} className="w-80 max-w-full" aria-label={t('design_system.gallery.sourceArtwork.search')} placeholder={t('design_system.gallery.sourceArtwork.search')} />
77
+ <p role="status" className="text-sm text-muted-foreground">{t('design_system.gallery.sourceArtwork.count', { visible: filtered.length, total: assets.length })}</p>
78
+ </div>
79
+ {visible.length ? <div className={cn("grid gap-3", group === "thumbnails" ? "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3" : "grid-cols-2 sm:grid-cols-3 lg:grid-cols-5")}>
80
+ {visible.map(asset => <ArtworkTile key={asset.id} asset={asset} group={group} duplicate={(nameCounts.get(asset.name) ?? 0) > 1} />)}
81
+ </div> : <EmptyState title={t('design_system.gallery.noResults')} />}
82
+ {filtered.length > pageSize ? <Pagination page={page} pageSize={pageSize} total={filtered.length} showPageSize={false} onPageChange={setPage} aria-label={t('design_system.gallery.sourceArtwork.pages')} /> : null}
83
+ </div>
84
+ }
85
+
86
+ export function sourceArtworkCode(group: SourceArtworkGroup) {
87
+ return `import { loadSourceArtwork } from '@open-mercato/ui/assets/source-artwork'\n\nconst artwork = await loadSourceArtwork('${group}')\n\n<div className="flex flex-wrap gap-4">\n {artwork.slice(0, 24).map(asset => <img key={asset.id} src={asset.src} width={asset.width} height={asset.height} alt={asset.name} />)}\n</div>`
88
+ }