@open-mercato/core 0.7.1-develop.7203.1.9396450b78 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/build.mjs +11 -0
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js +23 -0
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies/[id]/page.js +2 -1
- package/dist/modules/customers/backend/customers/companies/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +2 -1
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people/[id]/page.js +2 -2
- package/dist/modules/customers/backend/customers/people/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +2 -1
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/lib/detailHelpers.js +4 -0
- package/dist/modules/customers/lib/detailHelpers.js.map +2 -2
- package/dist/modules/customers/lib/todoCompatibility.js +5 -0
- package/dist/modules/customers/lib/todoCompatibility.js.map +2 -2
- package/dist/modules/design_system/backend/design-system/page.meta.js +3 -2
- package/dist/modules/design_system/backend/design-system/page.meta.js.map +2 -2
- package/dist/modules/design_system/gallery/assets/ai-prompt-image.png +0 -0
- package/dist/modules/design_system/gallery/assets/avatar-illustration.png +0 -0
- package/dist/modules/design_system/gallery/assets/avatar-memoji.png +0 -0
- package/dist/modules/design_system/gallery/assets/avatar-photo.png +0 -0
- package/dist/modules/design_system/gallery/assets/blog-photo-0.png +0 -0
- package/dist/modules/design_system/gallery/assets/contact-support-portrait.png +0 -0
- package/dist/modules/design_system/gallery/assets/crypto-ada.png +0 -0
- package/dist/modules/design_system/gallery/assets/crypto-avax.png +0 -0
- package/dist/modules/design_system/gallery/assets/crypto-bnb.png +0 -0
- package/dist/modules/design_system/gallery/assets/crypto-brand.png +0 -0
- package/dist/modules/design_system/gallery/assets/crypto-btc.png +0 -0
- package/dist/modules/design_system/gallery/assets/crypto-clock.svg +3 -0
- package/dist/modules/design_system/gallery/assets/crypto-crown.svg +3 -0
- package/dist/modules/design_system/gallery/assets/crypto-dot.png +0 -0
- package/dist/modules/design_system/gallery/assets/crypto-eth.png +0 -0
- package/dist/modules/design_system/gallery/assets/crypto-hbar.png +0 -0
- package/dist/modules/design_system/gallery/assets/crypto-history.svg +3 -0
- package/dist/modules/design_system/gallery/assets/crypto-link.svg +3 -0
- package/dist/modules/design_system/gallery/assets/crypto-menu.svg +3 -0
- package/dist/modules/design_system/gallery/assets/crypto-metamask.png +0 -0
- package/dist/modules/design_system/gallery/assets/crypto-more.svg +3 -0
- package/dist/modules/design_system/gallery/assets/crypto-phantom.png +0 -0
- package/dist/modules/design_system/gallery/assets/crypto-rewards.svg +3 -0
- package/dist/modules/design_system/gallery/assets/crypto-sad-face.png +0 -0
- package/dist/modules/design_system/gallery/assets/crypto-sol.png +0 -0
- package/dist/modules/design_system/gallery/assets/crypto-star.svg +3 -0
- package/dist/modules/design_system/gallery/assets/crypto-status-cancelled.svg +3 -0
- package/dist/modules/design_system/gallery/assets/crypto-status-pending.svg +3 -0
- package/dist/modules/design_system/gallery/assets/crypto-status-success.svg +3 -0
- package/dist/modules/design_system/gallery/assets/crypto-status-warning.svg +3 -0
- package/dist/modules/design_system/gallery/assets/crypto-trx.png +0 -0
- package/dist/modules/design_system/gallery/assets/crypto-usdc.png +0 -0
- package/dist/modules/design_system/gallery/assets/crypto-usdt.png +0 -0
- package/dist/modules/design_system/gallery/assets/crypto-walletconnect.png +0 -0
- package/dist/modules/design_system/gallery/assets/crypto-wei.png +0 -0
- package/dist/modules/design_system/gallery/assets/crypto-xrp.png +0 -0
- package/dist/modules/design_system/gallery/assets/cryptocurrency-artwork.js +67 -0
- package/dist/modules/design_system/gallery/assets/cryptocurrency-artwork.js.map +7 -0
- package/dist/modules/design_system/gallery/assets/github-logo.png +0 -0
- package/dist/modules/design_system/gallery/assets/images.d.js +1 -0
- package/dist/modules/design_system/gallery/assets/images.d.js.map +7 -0
- package/dist/modules/design_system/gallery/assets/input-european-union.svg +18 -0
- package/dist/modules/design_system/gallery/assets/landing-teams-01.png +0 -0
- package/dist/modules/design_system/gallery/assets/marketing-artwork.js +83 -0
- package/dist/modules/design_system/gallery/assets/marketing-artwork.js.map +7 -0
- package/dist/modules/design_system/gallery/assets/menu-apex.svg +18 -0
- package/dist/modules/design_system/gallery/assets/menu-mastercard.svg +7 -0
- package/dist/modules/design_system/gallery/assets/menu-spotify.svg +4 -0
- package/dist/modules/design_system/gallery/assets/menu-united-states.svg +16 -0
- package/dist/modules/design_system/gallery/assets/select-google.png +0 -0
- package/dist/modules/design_system/gallery/assets/select-poland.png +0 -0
- package/dist/modules/design_system/gallery/assets/selection-artwork.js +5 -0
- package/dist/modules/design_system/gallery/assets/selection-artwork.js.map +7 -0
- package/dist/modules/design_system/gallery/assets/social-apple-filled.svg +4 -0
- package/dist/modules/design_system/gallery/assets/social-apple-stroke.svg +4 -0
- package/dist/modules/design_system/gallery/assets/social-dropbox-filled.svg +5 -0
- package/dist/modules/design_system/gallery/assets/social-dropbox-stroke.svg +5 -0
- package/dist/modules/design_system/gallery/assets/social-facebook-filled.svg +4 -0
- package/dist/modules/design_system/gallery/assets/social-facebook-stroke.svg +10 -0
- package/dist/modules/design_system/gallery/assets/social-github-filled.svg +4 -0
- package/dist/modules/design_system/gallery/assets/social-github-stroke.svg +4 -0
- package/dist/modules/design_system/gallery/assets/social-google-filled.svg +7 -0
- package/dist/modules/design_system/gallery/assets/social-google-stroke.svg +7 -0
- package/dist/modules/design_system/gallery/assets/social-linkedin-filled.svg +4 -0
- package/dist/modules/design_system/gallery/assets/social-linkedin-stroke.svg +4 -0
- package/dist/modules/design_system/gallery/assets/social-x-filled.svg +4 -0
- package/dist/modules/design_system/gallery/assets/social-x-stroke.svg +4 -0
- package/dist/modules/design_system/gallery/assets/style-studio-product.png +0 -0
- package/dist/modules/design_system/gallery/assets/upload-images.js +8 -0
- package/dist/modules/design_system/gallery/assets/upload-images.js.map +7 -0
- package/dist/modules/design_system/gallery/components/BrandStylePreview.js +74 -0
- package/dist/modules/design_system/gallery/components/BrandStylePreview.js.map +7 -0
- package/dist/modules/design_system/gallery/components/CodeSnippet.js +31 -31
- package/dist/modules/design_system/gallery/components/CodeSnippet.js.map +2 -2
- package/dist/modules/design_system/gallery/components/ColorPlayground.js +408 -0
- package/dist/modules/design_system/gallery/components/ColorPlayground.js.map +7 -0
- package/dist/modules/design_system/gallery/components/ColorStudioCompositions.js +166 -0
- package/dist/modules/design_system/gallery/components/ColorStudioCompositions.js.map +7 -0
- package/dist/modules/design_system/gallery/components/ColorStudioSpecimen.js +182 -0
- package/dist/modules/design_system/gallery/components/ColorStudioSpecimen.js.map +7 -0
- package/dist/modules/design_system/gallery/components/CompactEntrySpecimen.js +80 -0
- package/dist/modules/design_system/gallery/components/CompactEntrySpecimen.js.map +7 -0
- package/dist/modules/design_system/gallery/components/ComponentIndex.js +42 -0
- package/dist/modules/design_system/gallery/components/ComponentIndex.js.map +7 -0
- package/dist/modules/design_system/gallery/components/DesignSystemHome.js +69 -0
- package/dist/modules/design_system/gallery/components/DesignSystemHome.js.map +7 -0
- package/dist/modules/design_system/gallery/components/EntryCard.js +47 -41
- package/dist/modules/design_system/gallery/components/EntryCard.js.map +2 -2
- package/dist/modules/design_system/gallery/components/EntryHeader.js +68 -0
- package/dist/modules/design_system/gallery/components/EntryHeader.js.map +7 -0
- package/dist/modules/design_system/gallery/components/FamilyEntryPreview.js +19 -0
- package/dist/modules/design_system/gallery/components/FamilyEntryPreview.js.map +7 -0
- package/dist/modules/design_system/gallery/components/FoundationGuide.js +46 -0
- package/dist/modules/design_system/gallery/components/FoundationGuide.js.map +7 -0
- package/dist/modules/design_system/gallery/components/GalleryLink.js +21 -0
- package/dist/modules/design_system/gallery/components/GalleryLink.js.map +7 -0
- package/dist/modules/design_system/gallery/components/GalleryNavigation.js +82 -0
- package/dist/modules/design_system/gallery/components/GalleryNavigation.js.map +7 -0
- package/dist/modules/design_system/gallery/components/GalleryShell.js +147 -67
- package/dist/modules/design_system/gallery/components/GalleryShell.js.map +3 -3
- package/dist/modules/design_system/gallery/components/IconsBrowser.js +33 -0
- package/dist/modules/design_system/gallery/components/IconsBrowser.js.map +7 -0
- package/dist/modules/design_system/gallery/components/SourceLibrary.js +93 -0
- package/dist/modules/design_system/gallery/components/SourceLibrary.js.map +7 -0
- package/dist/modules/design_system/gallery/components/StyleAgents.js +139 -0
- package/dist/modules/design_system/gallery/components/StyleAgents.js.map +7 -0
- package/dist/modules/design_system/gallery/components/ThemeTokenReference.js +101 -0
- package/dist/modules/design_system/gallery/components/ThemeTokenReference.js.map +7 -0
- package/dist/modules/design_system/gallery/components/VariantPreview.js +2 -2
- package/dist/modules/design_system/gallery/components/VariantPreview.js.map +2 -2
- package/dist/modules/design_system/gallery/components/colorPreview.js +58 -0
- package/dist/modules/design_system/gallery/components/colorPreview.js.map +7 -0
- package/dist/modules/design_system/gallery/components/colorStudio.js +218 -0
- package/dist/modules/design_system/gallery/components/colorStudio.js.map +7 -0
- package/dist/modules/design_system/gallery/components/styleAgentBranding.js +46 -0
- package/dist/modules/design_system/gallery/components/styleAgentBranding.js.map +7 -0
- package/dist/modules/design_system/gallery/components/themeTokens.js +32 -0
- package/dist/modules/design_system/gallery/components/themeTokens.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/ai-product-code.generated.js +194 -0
- package/dist/modules/design_system/gallery/demos/ai-product-code.generated.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/ai-product.js +423 -0
- package/dist/modules/design_system/gallery/demos/ai-product.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/buttons.js +289 -0
- package/dist/modules/design_system/gallery/demos/buttons.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/cryptocurrency.js +1708 -0
- package/dist/modules/design_system/gallery/demos/cryptocurrency.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/feeds-code.generated.js +92 -0
- package/dist/modules/design_system/gallery/demos/feeds-code.generated.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/feeds.js +148 -0
- package/dist/modules/design_system/gallery/demos/feeds.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/file-upload.js +72 -0
- package/dist/modules/design_system/gallery/demos/file-upload.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/foundation-reference-data.js +4875 -0
- package/dist/modules/design_system/gallery/demos/foundation-reference-data.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/foundation-references.js +188 -0
- package/dist/modules/design_system/gallery/demos/foundation-references.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/key-components-code.generated.js +6 -0
- package/dist/modules/design_system/gallery/demos/key-components-code.generated.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/key-components.js +127 -0
- package/dist/modules/design_system/gallery/demos/key-components.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/marketing-controls-code.generated.js +98 -0
- package/dist/modules/design_system/gallery/demos/marketing-controls-code.generated.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/marketing-controls.js +134 -0
- package/dist/modules/design_system/gallery/demos/marketing-controls.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/marketing-widgets-code.generated.js +195 -0
- package/dist/modules/design_system/gallery/demos/marketing-widgets-code.generated.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/marketing-widgets.js +586 -0
- package/dist/modules/design_system/gallery/demos/marketing-widgets.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/menu-fixtures.js +40 -0
- package/dist/modules/design_system/gallery/demos/menu-fixtures.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/menu-sources.js +295 -0
- package/dist/modules/design_system/gallery/demos/menu-sources.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/menus.js +150 -0
- package/dist/modules/design_system/gallery/demos/menus.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/rating-reviews.js +53 -0
- package/dist/modules/design_system/gallery/demos/rating-reviews.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/registered-icons.js +104 -0
- package/dist/modules/design_system/gallery/demos/registered-icons.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/selection-cards.js +99 -0
- package/dist/modules/design_system/gallery/demos/selection-cards.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/shell-code.generated.js +189 -0
- package/dist/modules/design_system/gallery/demos/shell-code.generated.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/shell.js +330 -0
- package/dist/modules/design_system/gallery/demos/shell.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/source-artwork.js +120 -0
- package/dist/modules/design_system/gallery/demos/source-artwork.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/source-filters-code.generated.js +125 -0
- package/dist/modules/design_system/gallery/demos/source-filters-code.generated.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/source-filters.js +185 -0
- package/dist/modules/design_system/gallery/demos/source-filters.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/source-icons.js +69 -0
- package/dist/modules/design_system/gallery/demos/source-icons.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/table-cells-code.generated.js +150 -0
- package/dist/modules/design_system/gallery/demos/table-cells-code.generated.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/table-cells.js +181 -0
- package/dist/modules/design_system/gallery/demos/table-cells.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/text-inputs.js +113 -0
- package/dist/modules/design_system/gallery/demos/text-inputs.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/time-picker-code.generated.js +41 -0
- package/dist/modules/design_system/gallery/demos/time-picker-code.generated.js.map +7 -0
- package/dist/modules/design_system/gallery/demos/time-picker.js +41 -0
- package/dist/modules/design_system/gallery/demos/time-picker.js.map +7 -0
- package/dist/modules/design_system/gallery/entries/banners.js +123 -60
- package/dist/modules/design_system/gallery/entries/banners.js.map +2 -2
- package/dist/modules/design_system/gallery/entries/buttons-snippets.js +433 -0
- package/dist/modules/design_system/gallery/entries/buttons-snippets.js.map +7 -0
- package/dist/modules/design_system/gallery/entries/buttons.js +127 -148
- package/dist/modules/design_system/gallery/entries/buttons.js.map +2 -2
- package/dist/modules/design_system/gallery/entries/charts.js +532 -186
- package/dist/modules/design_system/gallery/entries/charts.js.map +2 -2
- package/dist/modules/design_system/gallery/entries/dates.js +191 -3
- package/dist/modules/design_system/gallery/entries/dates.js.map +2 -2
- package/dist/modules/design_system/gallery/entries/detail.js +118 -77
- package/dist/modules/design_system/gallery/entries/detail.js.map +2 -2
- package/dist/modules/design_system/gallery/entries/display.js +1449 -240
- package/dist/modules/design_system/gallery/entries/display.js.map +2 -2
- package/dist/modules/design_system/gallery/entries/feedback.js +1058 -132
- package/dist/modules/design_system/gallery/entries/feedback.js.map +2 -2
- package/dist/modules/design_system/gallery/entries/filters.js +231 -103
- package/dist/modules/design_system/gallery/entries/filters.js.map +2 -2
- package/dist/modules/design_system/gallery/entries/foundations.js +368 -57
- package/dist/modules/design_system/gallery/entries/foundations.js.map +2 -2
- package/dist/modules/design_system/gallery/entries/icons.js +29 -91
- package/dist/modules/design_system/gallery/entries/icons.js.map +2 -2
- package/dist/modules/design_system/gallery/entries/inputs-snippets.js +154 -0
- package/dist/modules/design_system/gallery/entries/inputs-snippets.js.map +7 -0
- package/dist/modules/design_system/gallery/entries/inputs.js +2043 -306
- package/dist/modules/design_system/gallery/entries/inputs.js.map +2 -2
- package/dist/modules/design_system/gallery/entries/library.js +20 -0
- package/dist/modules/design_system/gallery/entries/library.js.map +7 -0
- package/dist/modules/design_system/gallery/entries/messages.js +121 -103
- package/dist/modules/design_system/gallery/entries/messages.js.map +2 -2
- package/dist/modules/design_system/gallery/entries/navigation.js +676 -103
- package/dist/modules/design_system/gallery/entries/navigation.js.map +2 -2
- package/dist/modules/design_system/gallery/entries/notifications.js +131 -78
- package/dist/modules/design_system/gallery/entries/notifications.js.map +2 -2
- package/dist/modules/design_system/gallery/entries/overlays.js +611 -203
- package/dist/modules/design_system/gallery/entries/overlays.js.map +2 -2
- package/dist/modules/design_system/gallery/entries/scaffolding.js +1175 -120
- package/dist/modules/design_system/gallery/entries/scaffolding.js.map +2 -2
- package/dist/modules/design_system/gallery/entries/schedule.js +79 -54
- package/dist/modules/design_system/gallery/entries/schedule.js.map +2 -2
- package/dist/modules/design_system/gallery/presentation.js +108 -0
- package/dist/modules/design_system/gallery/presentation.js.map +7 -0
- package/dist/modules/design_system/gallery/registry.js +8 -5
- package/dist/modules/design_system/gallery/registry.js.map +2 -2
- package/dist/modules/design_system/gallery/source-library.generated.js +5 -0
- package/dist/modules/design_system/gallery/source-library.generated.js.map +7 -0
- package/dist/modules/design_system/i18n/de.json +7167 -0
- package/dist/modules/design_system/i18n/en.json +7167 -0
- package/dist/modules/design_system/i18n/es.json +7167 -0
- package/dist/modules/design_system/i18n/ko.json +7167 -0
- package/dist/modules/design_system/i18n/pl.json +7167 -0
- package/dist/modules/push_notifications/lib/queue.js +1 -31
- package/dist/modules/push_notifications/lib/queue.js.map +2 -2
- package/jest.config.cjs +1 -0
- package/jest.mocks/image.js +1 -0
- package/package.json +8 -9
- package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +50 -0
- package/src/modules/customers/backend/customers/companies/[id]/page.tsx +2 -1
- package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +2 -1
- package/src/modules/customers/backend/customers/people/[id]/page.tsx +2 -2
- package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +2 -1
- package/src/modules/customers/lib/detailHelpers.ts +4 -0
- package/src/modules/customers/lib/todoCompatibility.ts +10 -0
- package/src/modules/design_system/backend/design-system/page.meta.ts +3 -2
- package/src/modules/design_system/gallery/assets/README.md +30 -0
- package/src/modules/design_system/gallery/assets/ai-prompt-image.png +0 -0
- package/src/modules/design_system/gallery/assets/avatar-illustration.png +0 -0
- package/src/modules/design_system/gallery/assets/avatar-memoji.png +0 -0
- package/src/modules/design_system/gallery/assets/avatar-photo.png +0 -0
- package/src/modules/design_system/gallery/assets/blog-photo-0.png +0 -0
- package/src/modules/design_system/gallery/assets/contact-support-portrait.png +0 -0
- package/src/modules/design_system/gallery/assets/crypto-ada.png +0 -0
- package/src/modules/design_system/gallery/assets/crypto-avax.png +0 -0
- package/src/modules/design_system/gallery/assets/crypto-bnb.png +0 -0
- package/src/modules/design_system/gallery/assets/crypto-brand.png +0 -0
- package/src/modules/design_system/gallery/assets/crypto-btc.png +0 -0
- package/src/modules/design_system/gallery/assets/crypto-clock.svg +3 -0
- package/src/modules/design_system/gallery/assets/crypto-crown.svg +3 -0
- package/src/modules/design_system/gallery/assets/crypto-dot.png +0 -0
- package/src/modules/design_system/gallery/assets/crypto-eth.png +0 -0
- package/src/modules/design_system/gallery/assets/crypto-hbar.png +0 -0
- package/src/modules/design_system/gallery/assets/crypto-history.svg +3 -0
- package/src/modules/design_system/gallery/assets/crypto-link.svg +3 -0
- package/src/modules/design_system/gallery/assets/crypto-menu.svg +3 -0
- package/src/modules/design_system/gallery/assets/crypto-metamask.png +0 -0
- package/src/modules/design_system/gallery/assets/crypto-more.svg +3 -0
- package/src/modules/design_system/gallery/assets/crypto-phantom.png +0 -0
- package/src/modules/design_system/gallery/assets/crypto-rewards.svg +3 -0
- package/src/modules/design_system/gallery/assets/crypto-sad-face.png +0 -0
- package/src/modules/design_system/gallery/assets/crypto-sol.png +0 -0
- package/src/modules/design_system/gallery/assets/crypto-star.svg +3 -0
- package/src/modules/design_system/gallery/assets/crypto-status-cancelled.svg +3 -0
- package/src/modules/design_system/gallery/assets/crypto-status-pending.svg +3 -0
- package/src/modules/design_system/gallery/assets/crypto-status-success.svg +3 -0
- package/src/modules/design_system/gallery/assets/crypto-status-warning.svg +3 -0
- package/src/modules/design_system/gallery/assets/crypto-trx.png +0 -0
- package/src/modules/design_system/gallery/assets/crypto-usdc.png +0 -0
- package/src/modules/design_system/gallery/assets/crypto-usdt.png +0 -0
- package/src/modules/design_system/gallery/assets/crypto-walletconnect.png +0 -0
- package/src/modules/design_system/gallery/assets/crypto-wei.png +0 -0
- package/src/modules/design_system/gallery/assets/crypto-xrp.png +0 -0
- package/src/modules/design_system/gallery/assets/cryptocurrency-artwork.ts +63 -0
- package/src/modules/design_system/gallery/assets/github-logo.png +0 -0
- package/src/modules/design_system/gallery/assets/images.d.ts +9 -0
- package/src/modules/design_system/gallery/assets/input-european-union.svg +18 -0
- package/src/modules/design_system/gallery/assets/landing-teams-01.png +0 -0
- package/src/modules/design_system/gallery/assets/marketing-artwork.ts +79 -0
- package/src/modules/design_system/gallery/assets/menu-apex.svg +18 -0
- package/src/modules/design_system/gallery/assets/menu-mastercard.svg +7 -0
- package/src/modules/design_system/gallery/assets/menu-spotify.svg +4 -0
- package/src/modules/design_system/gallery/assets/menu-united-states.svg +16 -0
- package/src/modules/design_system/gallery/assets/select-google.png +0 -0
- package/src/modules/design_system/gallery/assets/select-poland.png +0 -0
- package/src/modules/design_system/gallery/assets/selection-artwork.ts +1 -0
- package/src/modules/design_system/gallery/assets/social-apple-filled.svg +4 -0
- package/src/modules/design_system/gallery/assets/social-apple-stroke.svg +4 -0
- package/src/modules/design_system/gallery/assets/social-dropbox-filled.svg +5 -0
- package/src/modules/design_system/gallery/assets/social-dropbox-stroke.svg +5 -0
- package/src/modules/design_system/gallery/assets/social-facebook-filled.svg +4 -0
- package/src/modules/design_system/gallery/assets/social-facebook-stroke.svg +10 -0
- package/src/modules/design_system/gallery/assets/social-github-filled.svg +4 -0
- package/src/modules/design_system/gallery/assets/social-github-stroke.svg +4 -0
- package/src/modules/design_system/gallery/assets/social-google-filled.svg +7 -0
- package/src/modules/design_system/gallery/assets/social-google-stroke.svg +7 -0
- package/src/modules/design_system/gallery/assets/social-linkedin-filled.svg +4 -0
- package/src/modules/design_system/gallery/assets/social-linkedin-stroke.svg +4 -0
- package/src/modules/design_system/gallery/assets/social-x-filled.svg +4 -0
- package/src/modules/design_system/gallery/assets/social-x-stroke.svg +4 -0
- package/src/modules/design_system/gallery/assets/style-studio-product.png +0 -0
- package/src/modules/design_system/gallery/assets/upload-images.ts +5 -0
- package/src/modules/design_system/gallery/components/BrandStylePreview.tsx +94 -0
- package/src/modules/design_system/gallery/components/CodeSnippet.tsx +34 -34
- package/src/modules/design_system/gallery/components/ColorPlayground.tsx +324 -0
- package/src/modules/design_system/gallery/components/ColorStudioCompositions.tsx +96 -0
- package/src/modules/design_system/gallery/components/ColorStudioSpecimen.tsx +164 -0
- package/src/modules/design_system/gallery/components/CompactEntrySpecimen.tsx +65 -0
- package/src/modules/design_system/gallery/components/ComponentIndex.tsx +47 -0
- package/src/modules/design_system/gallery/components/DesignSystemHome.tsx +66 -0
- package/src/modules/design_system/gallery/components/EntryCard.tsx +52 -37
- package/src/modules/design_system/gallery/components/EntryHeader.tsx +80 -0
- package/src/modules/design_system/gallery/components/FamilyEntryPreview.tsx +22 -0
- package/src/modules/design_system/gallery/components/FoundationGuide.tsx +43 -0
- package/src/modules/design_system/gallery/components/GalleryLink.tsx +19 -0
- package/src/modules/design_system/gallery/components/GalleryNavigation.tsx +86 -0
- package/src/modules/design_system/gallery/components/GalleryShell.tsx +149 -63
- package/src/modules/design_system/gallery/components/IconsBrowser.tsx +40 -0
- package/src/modules/design_system/gallery/components/SourceLibrary.tsx +90 -0
- package/src/modules/design_system/gallery/components/StyleAgents.tsx +115 -0
- package/src/modules/design_system/gallery/components/ThemeTokenReference.tsx +76 -0
- package/src/modules/design_system/gallery/components/VariantPreview.tsx +2 -2
- package/src/modules/design_system/gallery/components/colorPreview.ts +63 -0
- package/src/modules/design_system/gallery/components/colorStudio.ts +260 -0
- package/src/modules/design_system/gallery/components/styleAgentBranding.ts +42 -0
- package/src/modules/design_system/gallery/components/themeTokens.ts +26 -0
- package/src/modules/design_system/gallery/demos/ai-product-code.generated.ts +3 -0
- package/src/modules/design_system/gallery/demos/ai-product.tsx +188 -0
- package/src/modules/design_system/gallery/demos/buttons.tsx +331 -0
- package/src/modules/design_system/gallery/demos/cryptocurrency.tsx +1943 -0
- package/src/modules/design_system/gallery/demos/feeds-code.generated.ts +4 -0
- package/src/modules/design_system/gallery/demos/feeds.tsx +82 -0
- package/src/modules/design_system/gallery/demos/file-upload.tsx +61 -0
- package/src/modules/design_system/gallery/demos/foundation-reference-data.ts +4872 -0
- package/src/modules/design_system/gallery/demos/foundation-references.tsx +140 -0
- package/src/modules/design_system/gallery/demos/key-components-code.generated.ts +3 -0
- package/src/modules/design_system/gallery/demos/key-components.tsx +106 -0
- package/src/modules/design_system/gallery/demos/marketing-controls-code.generated.ts +3 -0
- package/src/modules/design_system/gallery/demos/marketing-controls.tsx +92 -0
- package/src/modules/design_system/gallery/demos/marketing-widgets-code.generated.ts +3 -0
- package/src/modules/design_system/gallery/demos/marketing-widgets.tsx +189 -0
- package/src/modules/design_system/gallery/demos/menu-fixtures.tsx +48 -0
- package/src/modules/design_system/gallery/demos/menu-sources.ts +5 -0
- package/src/modules/design_system/gallery/demos/menus.tsx +122 -0
- package/src/modules/design_system/gallery/demos/rating-reviews.tsx +44 -0
- package/src/modules/design_system/gallery/demos/registered-icons.tsx +117 -0
- package/src/modules/design_system/gallery/demos/selection-cards.tsx +97 -0
- package/src/modules/design_system/gallery/demos/shell-code.generated.ts +3 -0
- package/src/modules/design_system/gallery/demos/shell.tsx +183 -0
- package/src/modules/design_system/gallery/demos/source-artwork.tsx +88 -0
- package/src/modules/design_system/gallery/demos/source-filters-code.generated.ts +3 -0
- package/src/modules/design_system/gallery/demos/source-filters.tsx +119 -0
- package/src/modules/design_system/gallery/demos/source-icons.tsx +62 -0
- package/src/modules/design_system/gallery/demos/table-cells-code.generated.ts +3 -0
- package/src/modules/design_system/gallery/demos/table-cells.tsx +144 -0
- package/src/modules/design_system/gallery/demos/text-inputs.tsx +90 -0
- package/src/modules/design_system/gallery/demos/time-picker-code.generated.ts +5 -0
- package/src/modules/design_system/gallery/demos/time-picker.tsx +31 -0
- package/src/modules/design_system/gallery/entries/banners.tsx +143 -94
- package/src/modules/design_system/gallery/entries/buttons-snippets.ts +432 -0
- package/src/modules/design_system/gallery/entries/buttons.tsx +125 -196
- package/src/modules/design_system/gallery/entries/charts.tsx +545 -233
- package/src/modules/design_system/gallery/entries/dates.tsx +171 -3
- package/src/modules/design_system/gallery/entries/detail.tsx +142 -115
- package/src/modules/design_system/gallery/entries/display.tsx +1691 -319
- package/src/modules/design_system/gallery/entries/feedback.tsx +1082 -169
- package/src/modules/design_system/gallery/entries/filters.tsx +238 -143
- package/src/modules/design_system/gallery/entries/foundations.tsx +404 -61
- package/src/modules/design_system/gallery/entries/icons.tsx +31 -117
- package/src/modules/design_system/gallery/entries/inputs-snippets.ts +156 -0
- package/src/modules/design_system/gallery/entries/inputs.tsx +2146 -503
- package/src/modules/design_system/gallery/entries/library.tsx +16 -0
- package/src/modules/design_system/gallery/entries/messages.tsx +114 -106
- package/src/modules/design_system/gallery/entries/navigation.tsx +782 -192
- package/src/modules/design_system/gallery/entries/notifications.tsx +145 -120
- package/src/modules/design_system/gallery/entries/overlays.tsx +696 -280
- package/src/modules/design_system/gallery/entries/scaffolding.tsx +1192 -158
- package/src/modules/design_system/gallery/entries/schedule.tsx +72 -58
- package/src/modules/design_system/gallery/presentation.ts +106 -0
- package/src/modules/design_system/gallery/registry.ts +8 -6
- package/src/modules/design_system/gallery/source-library.generated.ts +2 -0
- package/src/modules/design_system/i18n/de.json +7112 -3
- package/src/modules/design_system/i18n/en.json +7112 -3
- package/src/modules/design_system/i18n/es.json +7112 -3
- package/src/modules/design_system/i18n/ko.json +7112 -3
- package/src/modules/design_system/i18n/pl.json +7112 -3
- package/src/modules/push_notifications/AGENTS.md +9 -5
- package/src/modules/push_notifications/lib/queue.ts +15 -44
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
5
|
+
import { Button } from '@open-mercato/ui/primitives/button'
|
|
6
|
+
import { SearchInput } from '@open-mercato/ui/primitives/search-input'
|
|
7
|
+
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
8
|
+
import { colors, grid, radius, shadows, typography } from './foundation-reference-data'
|
|
9
|
+
|
|
10
|
+
function ReferenceNote({ message }: { message: string }) {
|
|
11
|
+
const t = useT()
|
|
12
|
+
return <p className="max-w-3xl text-sm leading-relaxed text-muted-foreground">{t(`design_system.foundations.reference.${message}`)}</p>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function SourceTypography() {
|
|
16
|
+
const t = useT()
|
|
17
|
+
return <div className="space-y-6">
|
|
18
|
+
<ReferenceNote message="typographyNote" />
|
|
19
|
+
<div className="divide-y divide-border">
|
|
20
|
+
{typography.map(item => <article key={item.id} className="min-w-0 space-y-4 py-6" data-foundation="typography">
|
|
21
|
+
<div className="flex flex-wrap items-baseline justify-between gap-3">
|
|
22
|
+
<h4 className="text-sm font-medium">{t(`design_system.foundations.reference.type.${item.id}`)}</h4>
|
|
23
|
+
<dl className="flex flex-wrap gap-x-5 gap-y-2 text-xs text-muted-foreground">
|
|
24
|
+
<div><dt className="inline">{t('design_system.foundations.reference.sizeHeight')} </dt><dd className="inline font-mono">{item.size}/{item.lineHeight} px</dd></div>
|
|
25
|
+
<div><dt className="inline">{t('design_system.foundations.reference.weight')} </dt><dd className="inline font-mono">{item.weight}</dd></div>
|
|
26
|
+
<div><dt className="inline">{t('design_system.foundations.reference.tracking')} </dt><dd className="inline font-mono">{item.tracking}%</dd></div>
|
|
27
|
+
</dl>
|
|
28
|
+
</div>
|
|
29
|
+
<p className="break-words font-sans" style={{ fontSize: item.size, lineHeight: `${item.lineHeight}px`, fontWeight: item.weight, letterSpacing: `${item.tracking / 100}em`, textTransform: item.name.toLowerCase().startsWith('subhead') ? 'uppercase' : undefined }}>{t('design_system.gallery.samples.typeSample')}</p>
|
|
30
|
+
</article>)}
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function SourceRadius() {
|
|
36
|
+
const t = useT()
|
|
37
|
+
return <div className="space-y-6">
|
|
38
|
+
<ReferenceNote message="radiusNote" />
|
|
39
|
+
<div className="grid grid-cols-2 gap-x-6 gap-y-8 sm:grid-cols-3 xl:grid-cols-4">
|
|
40
|
+
{radius.map(item => <article key={item.id} className="space-y-3" data-foundation="radius">
|
|
41
|
+
<div className="h-24 border-2 border-foreground bg-muted/40" style={{ borderRadius: item.pixels }} aria-hidden />
|
|
42
|
+
<div className="flex flex-wrap justify-between gap-2 text-sm"><code>{item.name}</code><span className="text-muted-foreground">{item.pixels} px</span></div>
|
|
43
|
+
<p className="text-xs text-muted-foreground">{item.applicationClassName ?? t('design_system.foundations.reference.sourceOnly')}</p>
|
|
44
|
+
</article>)}
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function SourceShadows() {
|
|
50
|
+
const t = useT()
|
|
51
|
+
return <div className="space-y-6">
|
|
52
|
+
<ReferenceNote message="shadowNote" />
|
|
53
|
+
<div className="grid gap-6 sm:grid-cols-2 xl:grid-cols-3">
|
|
54
|
+
{shadows.map(item => <article key={item.id} className="min-w-0 space-y-4 rounded-lg border border-border p-5" data-foundation="shadow">
|
|
55
|
+
<div className="flex h-40 items-center justify-center overflow-hidden rounded-md" style={{ backgroundColor: '#ffffff' }}>
|
|
56
|
+
<div className="h-20 w-28 rounded-lg" style={{ backgroundColor: '#ffffff', boxShadow: item.css }} aria-hidden />
|
|
57
|
+
</div>
|
|
58
|
+
<h4 className="text-sm font-medium">{t(`design_system.foundations.reference.shadow.${item.id}`)}</h4>
|
|
59
|
+
<code className="block break-words text-xs text-muted-foreground">{item.name}</code>
|
|
60
|
+
<section className="text-xs">
|
|
61
|
+
<h5 className="text-xs font-medium">{t('design_system.foundations.reference.shadowLayers')}</h5>
|
|
62
|
+
<code className="mt-3 block break-words leading-relaxed text-muted-foreground">{item.css}</code>
|
|
63
|
+
</section>
|
|
64
|
+
</article>)}
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function SourceGrids() {
|
|
70
|
+
const t = useT()
|
|
71
|
+
return <div className="space-y-8">
|
|
72
|
+
<ReferenceNote message="gridNote" />
|
|
73
|
+
{grid.map(item => <article key={item.id} className="space-y-4" data-foundation="grid">
|
|
74
|
+
<h4 className="text-base font-medium">{t(`design_system.foundations.reference.grid.${item.id}`)}</h4>
|
|
75
|
+
<div role="img" aria-label={t(`design_system.foundations.reference.grid.${item.id}`)} className="relative flex h-36 overflow-hidden rounded-lg border border-border bg-background sm:h-48">
|
|
76
|
+
{item.sidebar > 0 ? <div className="h-full shrink-0 bg-primary" style={{ width: `${item.sidebar / item.width * 100}%` }} /> : null}
|
|
77
|
+
{item.submenu > 0 ? <div className="h-full shrink-0 bg-muted" style={{ width: `${item.submenu / item.width * 100}%` }} /> : null}
|
|
78
|
+
<div className="flex h-full min-w-0 flex-1" style={{ paddingInline: `${item.safe / item.width * 100}%`, gap: `${item.gutter / (item.width - item.sidebar - item.submenu - item.safe * 2) * 100}%` }}>
|
|
79
|
+
{Array.from({ length: 12 }, (_, index) => <div key={index} className="h-full min-w-0 flex-1 bg-primary/15" />)}
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
<dl className="grid grid-cols-2 gap-4 text-sm sm:grid-cols-4">
|
|
83
|
+
<div><dt className="text-xs text-muted-foreground">{t('design_system.figmaFoundations.columns')}</dt><dd className="mt-1 font-mono">12 × {item.column} px</dd></div>
|
|
84
|
+
<div><dt className="text-xs text-muted-foreground">{t('design_system.figmaFoundations.gutter')}</dt><dd className="mt-1 font-mono">{item.gutter} px</dd></div>
|
|
85
|
+
<div><dt className="text-xs text-muted-foreground">{t('design_system.figmaFoundations.safeArea')}</dt><dd className="mt-1 font-mono">{item.safe} px</dd></div>
|
|
86
|
+
<div><dt className="text-xs text-muted-foreground">{t('design_system.figmaFoundations.sidebar')}</dt><dd className="mt-1 font-mono">{item.sidebar}{item.submenu ? ` + ${item.submenu}` : ''} px</dd></div>
|
|
87
|
+
</dl>
|
|
88
|
+
</article>)}
|
|
89
|
+
</div>
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function colorGroup(item: typeof colors[number]) {
|
|
93
|
+
if (item.mode) return item.mode
|
|
94
|
+
if (item.kind.startsWith('Color/')) return 'brand'
|
|
95
|
+
if (item.name.includes(' [')) return item.name.split(' [')[0].toLowerCase()
|
|
96
|
+
return item.name.split('-')[0]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function SourceSwatch({ item }: { item: typeof colors[number] }) {
|
|
100
|
+
const t = useT()
|
|
101
|
+
const value = item.opacity < 1 ? `${item.hex}${Math.round(item.opacity * 255).toString(16).padStart(2, '0')}` : item.hex
|
|
102
|
+
const copy = async () => {
|
|
103
|
+
try {
|
|
104
|
+
await navigator.clipboard.writeText(value)
|
|
105
|
+
flash(t('design_system.gallery.tokenCopied', { token: value }), 'success')
|
|
106
|
+
} catch {
|
|
107
|
+
flash(t('design_system.gallery.tokenCopyFailed'), 'error')
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return <Button type="button" variant="ghost" onClick={copy} title={t('design_system.gallery.copyToken', { token: value })} className="h-auto min-w-0 flex-col items-stretch justify-start gap-2 whitespace-normal rounded-md p-1.5 text-left" data-foundation="color">
|
|
111
|
+
<span aria-hidden className="block h-16 shrink-0 overflow-hidden rounded-md border border-border bg-background"><span className="block h-full" style={{ backgroundColor: item.hex, opacity: item.opacity }} /></span>
|
|
112
|
+
<span className="break-words text-xs font-medium">{item.role || item.name}</span>
|
|
113
|
+
<span className="font-mono text-xs text-muted-foreground">{value.toUpperCase()}</span>
|
|
114
|
+
{item.mismatch ? <span className="text-overline leading-normal text-muted-foreground">{t('design_system.foundations.reference.sourceCaption', { value: item.captions.join(', ') })}</span> : null}
|
|
115
|
+
</Button>
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function SourceColors() {
|
|
119
|
+
const t = useT()
|
|
120
|
+
const [query, setQuery] = React.useState('')
|
|
121
|
+
const needle = query.trim().toLocaleLowerCase()
|
|
122
|
+
const grouped = new Map<string, Array<typeof colors[number]>>()
|
|
123
|
+
for (const item of colors) {
|
|
124
|
+
const group = colorGroup(item)
|
|
125
|
+
const label = t(`design_system.foundations.reference.colorGroup.${group}`, group)
|
|
126
|
+
if (needle && !`${label} ${item.name} ${item.role ?? ''} ${item.hex} ${item.captions.join(' ')}`.toLocaleLowerCase().includes(needle)) continue
|
|
127
|
+
const items = grouped.get(group) ?? []
|
|
128
|
+
items.push(item)
|
|
129
|
+
grouped.set(group, items)
|
|
130
|
+
}
|
|
131
|
+
return <div className="space-y-8">
|
|
132
|
+
<ReferenceNote message="colorNote" />
|
|
133
|
+
<SearchInput value={query} onChange={setQuery} aria-label={t('design_system.figmaFoundations.filterColors')} placeholder={t('design_system.figmaFoundations.filterColors')} className="max-w-xl" />
|
|
134
|
+
{Array.from(grouped, ([group, items]) => <section key={group} className="space-y-3">
|
|
135
|
+
<h4 className="text-sm font-medium">{t(`design_system.foundations.reference.colorGroup.${group}`, group)}</h4>
|
|
136
|
+
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4 xl:grid-cols-6">{items.map(item => <SourceSwatch key={item.id} item={item} />)}</div>
|
|
137
|
+
</section>)}
|
|
138
|
+
{grouped.size === 0 ? <p className="text-sm text-muted-foreground">{t('design_system.figmaFoundations.emptyResults')}</p> : null}
|
|
139
|
+
</div>
|
|
140
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// Generated by yarn storybook:generate from key-components.tsx.
|
|
2
|
+
const source = "import * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { FieldLabel } from '@open-mercato/ui/primitives/label'\nimport { HintText } from '@open-mercato/ui/primitives/hint-text'\nimport { FormField } from '@open-mercato/ui/primitives/form-field'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { CompactButton } from '@open-mercato/ui/primitives/compact-button'\nimport { LinkButton } from '@open-mercato/ui/primitives/link-button'\nimport { SimpleTooltip } from '@open-mercato/ui/primitives/tooltip'\nimport { CheckboxField } from '@open-mercato/ui/primitives/checkbox-field'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { ContentLabel } from '@open-mercato/ui/primitives/content-label'\nimport { ContentCard } from '@open-mercato/ui/primitives/content-card'\nimport { KeyIcon, KeyIconGlyph, type KeyIconColor } from '@open-mercato/ui/primitives/key-icon'\nimport { PaymentIcon, type PaymentIconCategory } from '@open-mercato/ui/primitives/payment-icon'\nimport { ChartLegend, ChartLegendDot, type ChartLegendColor } from '@open-mercato/ui/primitives/chart-legend'\nimport { navigationBrandArtwork } from '@open-mercato/ui/assets/navigation-brand-artwork'\nimport mastercard from '../assets/menu-mastercard.svg'\nimport spotify from '../assets/menu-spotify.svg'\nimport avatar from '../assets/avatar-photo.png'\n\nconst imageSource = (asset: string | { readonly src: string }) => typeof asset === 'string' ? asset : asset.src\nconst prefix = 'design_system.gallery.examples.keyComponents.'\ntype ContentKind = 'basic' | 'icon' | 'avatar' | 'provider' | 'brand' | 'company'\n\nfunction ContentLeading({ kind, size = 40 }: { kind: ContentKind; size?: 40 | 48 }) {\n if (kind === 'basic') return null\n if (kind === 'icon') return <KeyIcon size={size}><KeyIconGlyph /></KeyIcon>\n if (kind === 'avatar') return <Avatar label=\"James Brown\" size={size} src={imageSource(avatar)} />\n if (kind === 'provider') return <img alt=\"\" src={imageSource(mastercard)} className=\"h-6 w-8 object-contain\" />\n return <img alt=\"\" src={kind === 'company' ? navigationBrandArtwork.synergyOriginal : imageSource(spotify)} className={size === 40 ? 'size-10 object-contain' : 'size-12 object-contain'} />\n}\n\nexport function FieldLabelExample({ disabled = false, kind = 'all' }: { disabled?: boolean; kind?: 'all' | 'required' | 'optional' | 'information' | 'action' }) {\n const t = useT()\n const inputId = React.useId()\n const helpId = React.useId()\n const [help, setHelp] = React.useState(false)\n const information = <SimpleTooltip content={t(`${prefix}helpText`)}><CompactButton type=\"button\" size={20} appearance=\"ghost\" disabled={disabled} aria-label={t(`${prefix}information`)}><KeyIconGlyph name=\"labelInformation\" /></CompactButton></SimpleTooltip>\n const action = <LinkButton type=\"button\" size=\"sm\" variant=\"gray\" disabled={disabled} className=\"disabled:text-text-disabled disabled:opacity-100\" aria-expanded={help} aria-controls={helpId} onClick={() => setHelp(value => !value)}>{t(`${prefix}help`)}</LinkButton>\n return <div className=\"grid w-80 max-w-full gap-2\">\n <FieldLabel htmlFor={inputId} disabled={disabled} required={kind === 'required'} sublabel={kind === 'all' || kind === 'optional' ? t(`${prefix}optional`) : undefined} information={kind === 'all' || kind === 'information' ? information : undefined} action={kind === 'all' || kind === 'action' ? action : undefined}>{t(`${prefix}label`)}</FieldLabel>\n <Input id={inputId} disabled={disabled} aria-required={kind === 'required'} aria-describedby={help ? helpId : undefined} />\n <p id={helpId} hidden={!help} className=\"text-xs text-muted-foreground\">{t(`${prefix}helpText`)}</p>\n </div>\n}\n\nexport function FormFieldLabelExample() {\n const t = useT()\n return <div className=\"grid w-80 max-w-full gap-5\">{[false, true].map(disabled => <FormField key={String(disabled)} sourceLabel label={t(`${prefix}label`)} labelSublabel={t(`${prefix}optional`)} disabled={disabled} description={t(`${prefix}hint`)}><Input /></FormField>)}</div>\n}\n\nexport function HintTextExample({ state = 'default', icon = true }: { state?: 'default' | 'error' | 'disabled'; icon?: boolean }) {\n const t = useT()\n return <HintText state={state} leading={icon ? <KeyIconGlyph name=\"information\" /> : undefined}>{t(`${prefix}hint`)}</HintText>\n}\n\nexport function ContentLabelExample({ kind = 'basic', size = 40, configurable = false }: { kind?: Exclude<ContentKind, 'provider'>; size?: 40 | 48; configurable?: boolean }) {\n const t = useT()\n const [sublabel, setSublabel] = React.useState(true)\n const [badge, setBadge] = React.useState(false)\n const [toggle, setToggle] = React.useState(false)\n const [enabled, setEnabled] = React.useState(false)\n return <div className=\"grid w-full gap-5\">\n {configurable ? <div className=\"flex flex-wrap gap-4\"><CheckboxField label={t(`${prefix}sublabelControl`)} checked={sublabel} onCheckedChange={checked => setSublabel(checked === true)} /><CheckboxField label={t(`${prefix}badgeControl`)} checked={badge} onCheckedChange={checked => setBadge(checked === true)} /><CheckboxField label={t(`${prefix}toggleControl`)} checked={toggle} onCheckedChange={checked => setToggle(checked === true)} /></div> : null}\n <ContentLabel size={size} label={t(`${prefix}label`)} description={t(`${prefix}description`)} sublabel={sublabel ? t(`${prefix}sublabel`) : undefined} leading={kind === 'basic' ? undefined : <ContentLeading kind={kind} size={size} />} badge={badge ? <Badge size={20} tone=\"info\" appearance=\"light\">{t(`${prefix}new`)}</Badge> : undefined} trailing={toggle ? <Switch aria-label={t(`${prefix}enabled`)} checked={enabled} onCheckedChange={setEnabled} /> : undefined} className={configurable ? 'w-full max-w-md' : 'w-75 max-w-full'} />\n {toggle ? <output className=\"text-xs text-muted-foreground\">{t(`${prefix}${enabled ? 'enabled' : 'disabled'}`)}</output> : null}\n </div>\n}\n\nexport function ContentCardExample({ kind = 'basic', disabled = false }: { kind?: ContentKind; disabled?: boolean }) {\n const t = useT()\n const [visible, setVisible] = React.useState(true)\n return <div className=\"grid w-90 max-w-full gap-3\">\n {visible ? <ContentCard label={t(`${prefix}label`)} description={t(`${prefix}description`)} sublabel={t(`${prefix}sublabel`)} leading={kind === 'basic' ? undefined : <ContentLeading kind={kind} />} badge={<Badge size={16} tone=\"info\" appearance=\"light\">{t(`${prefix}new`)}</Badge>} onDismiss={() => setVisible(false)} dismissLabel={t(`${prefix}dismiss`)} disabled={disabled} /> : <><output className=\"text-sm text-muted-foreground\">{t(`${prefix}dismissed`)}</output><Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => setVisible(true)}>{t(`${prefix}restore`)}</Button></>}\n </div>\n}\n\nconst keyColors: KeyIconColor[] = ['gray', 'blue', 'orange', 'red', 'green', 'yellow', 'purple', 'pink', 'teal']\nconst legendColors: ChartLegendColor[] = ['gray', 'light-gray', 'blue', 'orange', 'red', 'green', 'yellow', 'purple', 'sky', 'pink', 'teal']\n\nexport function KeyIconExamples({ appearance = 'stroke' }: { appearance?: 'stroke' | 'lighter' }) {\n const t = useT()\n return <div className=\"grid w-full gap-4\"><p className=\"max-w-xl text-sm text-muted-foreground\">{t(`${prefix}paletteNote`)}</p>{keyColors.map(color => <div key={color} className=\"flex flex-wrap items-center gap-4\"><span className=\"w-20 text-xs text-muted-foreground\">{t(`${prefix}color.${color}`)}</span>{([64, 56, 48, 40, 32] as const).map(size => <KeyIcon key={size} color={color} appearance={appearance} size={size} aria-label={`${t(`${prefix}color.${color}`)} ${size}`}><KeyIconGlyph /></KeyIcon>)}</div>)}</div>\n}\n\nexport function PaymentIconExamples() {\n const t = useT()\n return <div className=\"grid grid-cols-2 gap-6 sm:grid-cols-4\">{(['water', 'gas', 'electricity', 'donate', 'internet', 'phone', 'rent', 'tax'] as PaymentIconCategory[]).map(category => <div key={category} className=\"flex items-center gap-3\"><PaymentIcon category={category} /><span className=\"text-sm text-foreground\">{t(`${prefix}payment.${category}`)}</span></div>)}</div>\n}\n\nexport function ChartLegendExamples({ interactive = false }: { interactive?: boolean }) {\n const t = useT()\n const [hidden, setHidden] = React.useState<ChartLegendColor[]>([])\n return <div className=\"grid w-full gap-5\"><div className=\"flex flex-wrap gap-5\">{legendColors.map(color => interactive ? <Button key={color} type=\"button\" variant=\"ghost\" size=\"sm\" aria-pressed={!hidden.includes(color)} onClick={() => setHidden(items => items.includes(color) ? items.filter(item => item !== color) : [...items, color])}><ChartLegend color={color} disabled={hidden.includes(color)}>{t(`${prefix}color.${color}`)}</ChartLegend></Button> : <ChartLegend key={color} color={color}>{t(`${prefix}color.${color}`)}</ChartLegend>)}{!interactive ? <ChartLegend disabled>{t(`${prefix}disabled`)}</ChartLegend> : null}</div>\n {interactive ? <div className=\"flex flex-wrap gap-3\" role=\"list\" aria-label={t(`${prefix}visibleSeries`)}>{legendColors.filter(color => !hidden.includes(color)).map(color => <div key={color} role=\"listitem\" className=\"rounded-md border border-border p-3\"><ChartLegend color={color}>{t(`${prefix}color.${color}`)}</ChartLegend></div>)}</div> : null}\n </div>\n}\n\nexport function ChartLegendDotExamples({ size = 16 }: { size?: 16 | 20 }) {\n const t = useT()\n return <div className=\"flex flex-wrap items-center gap-5\">{legendColors.map(color => <div key={color} className=\"flex items-center gap-1\"><ChartLegendDot color={color} size={size} /><span className=\"text-xs text-muted-foreground\">{t(`${prefix}color.${color}`)}</span></div>)}</div>\n}\n"
|
|
3
|
+
export const keyComponentExampleCode = (expression: string) => source + "\n" + expression + "\n"
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
3
|
+
import { FieldLabel } from '@open-mercato/ui/primitives/label'
|
|
4
|
+
import { HintText } from '@open-mercato/ui/primitives/hint-text'
|
|
5
|
+
import { FormField } from '@open-mercato/ui/primitives/form-field'
|
|
6
|
+
import { Input } from '@open-mercato/ui/primitives/input'
|
|
7
|
+
import { Button } from '@open-mercato/ui/primitives/button'
|
|
8
|
+
import { CompactButton } from '@open-mercato/ui/primitives/compact-button'
|
|
9
|
+
import { LinkButton } from '@open-mercato/ui/primitives/link-button'
|
|
10
|
+
import { SimpleTooltip } from '@open-mercato/ui/primitives/tooltip'
|
|
11
|
+
import { CheckboxField } from '@open-mercato/ui/primitives/checkbox-field'
|
|
12
|
+
import { Avatar } from '@open-mercato/ui/primitives/avatar'
|
|
13
|
+
import { Badge } from '@open-mercato/ui/primitives/badge'
|
|
14
|
+
import { Switch } from '@open-mercato/ui/primitives/switch'
|
|
15
|
+
import { ContentLabel } from '@open-mercato/ui/primitives/content-label'
|
|
16
|
+
import { ContentCard } from '@open-mercato/ui/primitives/content-card'
|
|
17
|
+
import { KeyIcon, KeyIconGlyph, type KeyIconColor } from '@open-mercato/ui/primitives/key-icon'
|
|
18
|
+
import { PaymentIcon, type PaymentIconCategory } from '@open-mercato/ui/primitives/payment-icon'
|
|
19
|
+
import { ChartLegend, ChartLegendDot, type ChartLegendColor } from '@open-mercato/ui/primitives/chart-legend'
|
|
20
|
+
import { navigationBrandArtwork } from '@open-mercato/ui/assets/navigation-brand-artwork'
|
|
21
|
+
import mastercard from '../assets/menu-mastercard.svg'
|
|
22
|
+
import spotify from '../assets/menu-spotify.svg'
|
|
23
|
+
import avatar from '../assets/avatar-photo.png'
|
|
24
|
+
|
|
25
|
+
const imageSource = (asset: string | { readonly src: string }) => typeof asset === 'string' ? asset : asset.src
|
|
26
|
+
const prefix = 'design_system.gallery.examples.keyComponents.'
|
|
27
|
+
type ContentKind = 'basic' | 'icon' | 'avatar' | 'provider' | 'brand' | 'company'
|
|
28
|
+
|
|
29
|
+
function ContentLeading({ kind, size = 40 }: { kind: ContentKind; size?: 40 | 48 }) {
|
|
30
|
+
if (kind === 'basic') return null
|
|
31
|
+
if (kind === 'icon') return <KeyIcon size={size}><KeyIconGlyph /></KeyIcon>
|
|
32
|
+
if (kind === 'avatar') return <Avatar label="James Brown" size={size} src={imageSource(avatar)} />
|
|
33
|
+
if (kind === 'provider') return <img alt="" src={imageSource(mastercard)} className="h-6 w-8 object-contain" />
|
|
34
|
+
return <img alt="" src={kind === 'company' ? navigationBrandArtwork.synergyOriginal : imageSource(spotify)} className={size === 40 ? 'size-10 object-contain' : 'size-12 object-contain'} />
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function FieldLabelExample({ disabled = false, kind = 'all' }: { disabled?: boolean; kind?: 'all' | 'required' | 'optional' | 'information' | 'action' }) {
|
|
38
|
+
const t = useT()
|
|
39
|
+
const inputId = React.useId()
|
|
40
|
+
const helpId = React.useId()
|
|
41
|
+
const [help, setHelp] = React.useState(false)
|
|
42
|
+
const information = <SimpleTooltip content={t(`${prefix}helpText`)}><CompactButton type="button" size={20} appearance="ghost" disabled={disabled} aria-label={t(`${prefix}information`)}><KeyIconGlyph name="labelInformation" /></CompactButton></SimpleTooltip>
|
|
43
|
+
const action = <LinkButton type="button" size="sm" variant="gray" disabled={disabled} className="disabled:text-text-disabled disabled:opacity-100" aria-expanded={help} aria-controls={helpId} onClick={() => setHelp(value => !value)}>{t(`${prefix}help`)}</LinkButton>
|
|
44
|
+
return <div className="grid w-80 max-w-full gap-2">
|
|
45
|
+
<FieldLabel htmlFor={inputId} disabled={disabled} required={kind === 'required'} sublabel={kind === 'all' || kind === 'optional' ? t(`${prefix}optional`) : undefined} information={kind === 'all' || kind === 'information' ? information : undefined} action={kind === 'all' || kind === 'action' ? action : undefined}>{t(`${prefix}label`)}</FieldLabel>
|
|
46
|
+
<Input id={inputId} disabled={disabled} aria-required={kind === 'required'} aria-describedby={help ? helpId : undefined} />
|
|
47
|
+
<p id={helpId} hidden={!help} className="text-xs text-muted-foreground">{t(`${prefix}helpText`)}</p>
|
|
48
|
+
</div>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function FormFieldLabelExample() {
|
|
52
|
+
const t = useT()
|
|
53
|
+
return <div className="grid w-80 max-w-full gap-5">{[false, true].map(disabled => <FormField key={String(disabled)} sourceLabel label={t(`${prefix}label`)} labelSublabel={t(`${prefix}optional`)} disabled={disabled} description={t(`${prefix}hint`)}><Input /></FormField>)}</div>
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function HintTextExample({ state = 'default', icon = true }: { state?: 'default' | 'error' | 'disabled'; icon?: boolean }) {
|
|
57
|
+
const t = useT()
|
|
58
|
+
return <HintText state={state} leading={icon ? <KeyIconGlyph name="information" /> : undefined}>{t(`${prefix}hint`)}</HintText>
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function ContentLabelExample({ kind = 'basic', size = 40, configurable = false }: { kind?: Exclude<ContentKind, 'provider'>; size?: 40 | 48; configurable?: boolean }) {
|
|
62
|
+
const t = useT()
|
|
63
|
+
const [sublabel, setSublabel] = React.useState(true)
|
|
64
|
+
const [badge, setBadge] = React.useState(false)
|
|
65
|
+
const [toggle, setToggle] = React.useState(false)
|
|
66
|
+
const [enabled, setEnabled] = React.useState(false)
|
|
67
|
+
return <div className="grid w-full gap-5">
|
|
68
|
+
{configurable ? <div className="flex flex-wrap gap-4"><CheckboxField label={t(`${prefix}sublabelControl`)} checked={sublabel} onCheckedChange={checked => setSublabel(checked === true)} /><CheckboxField label={t(`${prefix}badgeControl`)} checked={badge} onCheckedChange={checked => setBadge(checked === true)} /><CheckboxField label={t(`${prefix}toggleControl`)} checked={toggle} onCheckedChange={checked => setToggle(checked === true)} /></div> : null}
|
|
69
|
+
<ContentLabel size={size} label={t(`${prefix}label`)} description={t(`${prefix}description`)} sublabel={sublabel ? t(`${prefix}sublabel`) : undefined} leading={kind === 'basic' ? undefined : <ContentLeading kind={kind} size={size} />} badge={badge ? <Badge size={20} tone="info" appearance="light">{t(`${prefix}new`)}</Badge> : undefined} trailing={toggle ? <Switch aria-label={t(`${prefix}enabled`)} checked={enabled} onCheckedChange={setEnabled} /> : undefined} className={configurable ? 'w-full max-w-md' : 'w-75 max-w-full'} />
|
|
70
|
+
{toggle ? <output className="text-xs text-muted-foreground">{t(`${prefix}${enabled ? 'enabled' : 'disabled'}`)}</output> : null}
|
|
71
|
+
</div>
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function ContentCardExample({ kind = 'basic', disabled = false }: { kind?: ContentKind; disabled?: boolean }) {
|
|
75
|
+
const t = useT()
|
|
76
|
+
const [visible, setVisible] = React.useState(true)
|
|
77
|
+
return <div className="grid w-90 max-w-full gap-3">
|
|
78
|
+
{visible ? <ContentCard label={t(`${prefix}label`)} description={t(`${prefix}description`)} sublabel={t(`${prefix}sublabel`)} leading={kind === 'basic' ? undefined : <ContentLeading kind={kind} />} badge={<Badge size={16} tone="info" appearance="light">{t(`${prefix}new`)}</Badge>} onDismiss={() => setVisible(false)} dismissLabel={t(`${prefix}dismiss`)} disabled={disabled} /> : <><output className="text-sm text-muted-foreground">{t(`${prefix}dismissed`)}</output><Button type="button" variant="outline" size="sm" onClick={() => setVisible(true)}>{t(`${prefix}restore`)}</Button></>}
|
|
79
|
+
</div>
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const keyColors: KeyIconColor[] = ['gray', 'blue', 'orange', 'red', 'green', 'yellow', 'purple', 'pink', 'teal']
|
|
83
|
+
const legendColors: ChartLegendColor[] = ['gray', 'light-gray', 'blue', 'orange', 'red', 'green', 'yellow', 'purple', 'sky', 'pink', 'teal']
|
|
84
|
+
|
|
85
|
+
export function KeyIconExamples({ appearance = 'stroke' }: { appearance?: 'stroke' | 'lighter' }) {
|
|
86
|
+
const t = useT()
|
|
87
|
+
return <div className="grid w-full gap-4"><p className="max-w-xl text-sm text-muted-foreground">{t(`${prefix}paletteNote`)}</p>{keyColors.map(color => <div key={color} className="flex flex-wrap items-center gap-4"><span className="w-20 text-xs text-muted-foreground">{t(`${prefix}color.${color}`)}</span>{([64, 56, 48, 40, 32] as const).map(size => <KeyIcon key={size} color={color} appearance={appearance} size={size} aria-label={`${t(`${prefix}color.${color}`)} ${size}`}><KeyIconGlyph /></KeyIcon>)}</div>)}</div>
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function PaymentIconExamples() {
|
|
91
|
+
const t = useT()
|
|
92
|
+
return <div className="grid grid-cols-2 gap-6 sm:grid-cols-4">{(['water', 'gas', 'electricity', 'donate', 'internet', 'phone', 'rent', 'tax'] as PaymentIconCategory[]).map(category => <div key={category} className="flex items-center gap-3"><PaymentIcon category={category} /><span className="text-sm text-foreground">{t(`${prefix}payment.${category}`)}</span></div>)}</div>
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function ChartLegendExamples({ interactive = false }: { interactive?: boolean }) {
|
|
96
|
+
const t = useT()
|
|
97
|
+
const [hidden, setHidden] = React.useState<ChartLegendColor[]>([])
|
|
98
|
+
return <div className="grid w-full gap-5"><div className="flex flex-wrap gap-5">{legendColors.map(color => interactive ? <Button key={color} type="button" variant="ghost" size="sm" aria-pressed={!hidden.includes(color)} onClick={() => setHidden(items => items.includes(color) ? items.filter(item => item !== color) : [...items, color])}><ChartLegend color={color} disabled={hidden.includes(color)}>{t(`${prefix}color.${color}`)}</ChartLegend></Button> : <ChartLegend key={color} color={color}>{t(`${prefix}color.${color}`)}</ChartLegend>)}{!interactive ? <ChartLegend disabled>{t(`${prefix}disabled`)}</ChartLegend> : null}</div>
|
|
99
|
+
{interactive ? <div className="flex flex-wrap gap-3" role="list" aria-label={t(`${prefix}visibleSeries`)}>{legendColors.filter(color => !hidden.includes(color)).map(color => <div key={color} role="listitem" className="rounded-md border border-border p-3"><ChartLegend color={color}>{t(`${prefix}color.${color}`)}</ChartLegend></div>)}</div> : null}
|
|
100
|
+
</div>
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function ChartLegendDotExamples({ size = 16 }: { size?: 16 | 20 }) {
|
|
104
|
+
const t = useT()
|
|
105
|
+
return <div className="flex flex-wrap items-center gap-5">{legendColors.map(color => <div key={color} className="flex items-center gap-1"><ChartLegendDot color={color} size={size} /><span className="text-xs text-muted-foreground">{t(`${prefix}color.${color}`)}</span></div>)}</div>
|
|
106
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// Generated by yarn storybook:generate from marketing-controls.tsx.
|
|
2
|
+
const source = "import * as React from 'react'\nimport { CheckCircle2, ChevronRight, Info, Pencil, UserRound } from 'lucide-react'\nimport { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { InlineInput } from '@open-mercato/ui/primitives/inline-input'\nimport { InlineSelectTrigger } from '@open-mercato/ui/primitives/inline-select'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@open-mercato/ui/primitives/select'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@open-mercato/ui/primitives/tooltip'\nimport { marketingProducts } from '../assets/marketing-artwork'\n\nexport type MarketingControlState = 'default' | 'hover' | 'active' | 'filled' | 'placeholder' | 'focus' | 'disabled' | 'complete' | 'selected'\nexport type MarketingControlKind = 'step-item' | 'select' | 'input' | 'textarea' | 'amount' | 'tab-menu' | 'inline-input' | 'inline-select' | 'chart-tooltip' | 'selected-button' | 'product-image'\ntype ControlProps = { kind: MarketingControlKind; state?: MarketingControlState; size?: 'medium' | 'large'; side?: 'right' | 'left' | 'bottom' | 'top'; product?: number }\nconst key = (name: string) => `design_system.gallery.samples.marketing.${name}`\nconst focusStyle = 'focus-within:border-accent-orange-border focus-within:shadow-none'\n\nfunction UnderlineField({ kind, state = 'default', size = 'medium' }: ControlProps) {\n const t = useT()\n const id = React.useId()\n const large = size === 'large' || kind === 'amount'\n const label = t(key(kind === 'textarea' ? 'description' : kind === 'amount' ? 'productPricing' : kind === 'select' ? 'category' : 'productName'))\n const placeholder = t(key(kind === 'textarea' ? 'productDescriptionPlaceholder' : kind === 'select' ? 'categoryPlaceholder' : 'productNamePlaceholder'))\n const [value, setValue] = React.useState(state === 'filled' ? kind === 'select' ? 'accessories' : kind === 'amount' ? '800' : t(key(kind === 'textarea' ? 'productDescription' : 'productNameExample')) : '')\n const active = state === 'active'\n return <div data-slot=\"marketing-control\" data-kind={kind} className={cn('relative flex w-93 max-w-full flex-col gap-1.5 pb-3.5 after:absolute after:inset-x-0 after:bottom-0 after:h-px after:bg-border focus-within:after:h-0.5 focus-within:after:bg-accent-orange-border', large ? 'h-20' : 'h-16', active && 'after:h-0.5 after:bg-accent-orange-border')}>\n <Label htmlFor={id} className=\"flex h-5 shrink-0 items-center gap-1 text-sm font-medium text-muted-foreground\">{label}<Info aria-hidden=\"true\" className=\"size-3 text-text-disabled\" /></Label>\n {kind === 'select' ? <Select value={value} onValueChange={setValue}><SelectTrigger id={id} aria-label={label} className={cn('h-6 rounded-none border-0 bg-transparent p-0 text-lg font-medium shadow-none hover:bg-transparent focus:border-0 focus:shadow-none', state === 'hover' && 'text-muted-foreground', active && '[&_svg]:text-accent-orange-text')}><SelectValue placeholder={placeholder} /></SelectTrigger><SelectContent>{['accessories','computers','audio'].map(option => <SelectItem key={option} value={option}>{t(key(option))}</SelectItem>)}</SelectContent></Select> : kind === 'textarea' ? <Textarea id={id} aria-label={label} rows={1} value={value} onChange={event => setValue(event.target.value)} placeholder={placeholder} autoFocus={active} className={cn('h-6 min-h-6 resize-none rounded-none border-0 bg-transparent p-0 text-lg font-medium leading-6 shadow-none hover:bg-transparent focus-visible:shadow-none', state === 'hover' && 'placeholder:text-muted-foreground')} /> : <Input id={id} aria-label={label} value={value} onChange={event => setValue(event.target.value)} type={kind === 'amount' ? 'number' : 'text'} min={kind === 'amount' ? 0 : undefined} step={kind === 'amount' ? '0.01' : undefined} autoFocus={active} placeholder={kind === 'amount' ? '0.00' : placeholder} leading={kind === 'amount' ? <span aria-hidden=\"true\" className={cn('text-title-4 font-medium', !value && !active && 'text-text-disabled')}>$</span> : undefined} className={cn('rounded-none border-0 bg-transparent p-0 shadow-none hover:bg-transparent focus-within:shadow-none', large ? 'h-10' : 'h-6')} inputClassName={cn('font-medium placeholder:text-text-disabled', large ? 'text-title-4 leading-10' : 'text-lg leading-6', state === 'hover' && 'placeholder:text-muted-foreground')} />}\n </div>\n}\n\nfunction InlineEditor({ state = 'placeholder' }: { state?: MarketingControlState }) {\n const t = useT()\n const [value, setValue] = React.useState(state === 'filled' ? 'James Brown' : '')\n const [draft, setDraft] = React.useState(value)\n const [editing, setEditing] = React.useState(state === 'focus')\n const disabled = state === 'disabled'\n const save = () => { setValue(draft.trim()); setEditing(false) }\n const cancel = () => { setDraft(value); setEditing(false) }\n return <div data-slot=\"marketing-control\" data-kind=\"inline-input\" className=\"w-78 max-w-full\">\n {editing ? <InlineInput autoFocus value={draft} onChange={event => setDraft(event.target.value)} aria-label={t(key('name'))} placeholder={t(key('namePlaceholder'))} className={cn('h-10 gap-2 rounded-lg border-accent-orange-border bg-background px-3', focusStyle)} onKeyDown={event => { if (event.key === 'Escape') { event.preventDefault(); cancel() } if (event.key === 'Enter') { event.preventDefault(); save() } }} trailing={<div className=\"flex gap-2\"><Button variant=\"ghost\" className=\"h-5 p-0 text-sm font-normal\" onClick={cancel}>{t(key('cancel'))}</Button><Button variant=\"ghost\" className=\"h-5 p-0 text-sm font-normal text-accent-orange-text\" onClick={save}>{t(key('save'))}</Button></div>} /> : <Button disabled={disabled} variant=\"ghost\" onClick={() => setEditing(true)} className={cn('h-10 w-full justify-between rounded-lg px-3 text-sm font-normal', state === 'hover' && 'bg-muted', !value && 'text-muted-foreground')}><span className=\"truncate\">{value || t(key('namePlaceholder'))}</span><Pencil aria-hidden=\"true\" className=\"size-4 shrink-0 text-text-disabled\" /></Button>}\n </div>\n}\n\nfunction InlineLanguage({ state = 'placeholder' }: { state?: MarketingControlState }) {\n const t = useT()\n const [value, setValue] = React.useState(state === 'filled' ? 'en' : '')\n const [open, setOpen] = React.useState(false)\n return <div data-slot=\"marketing-control\" data-kind=\"inline-select\" className=\"w-64 max-w-full\"><Select value={value} onValueChange={setValue} open={open} onOpenChange={setOpen} disabled={state === 'disabled'}><InlineSelectTrigger aria-label={t(key('language'))} className={cn('h-10 rounded-lg px-3 focus:border-accent-orange-border focus:shadow-none', state === 'hover' && 'bg-muted', state === 'focus' && 'border-accent-orange-border')}><SelectValue placeholder={t(key('languagePlaceholder'))} /></InlineSelectTrigger><SelectContent>{['en','pl','de','es','ko'].map(language => <SelectItem key={language} value={language}>{t(key(`language${language}`))}</SelectItem>)}</SelectContent></Select></div>\n}\n\nfunction StepItem({ state = 'default' }: { state?: MarketingControlState }) {\n const t = useT()\n const [selected, setSelected] = React.useState(state === 'active')\n return <Button data-slot=\"marketing-control\" data-kind=\"step-item\" variant=\"ghost\" aria-pressed={selected} onClick={() => setSelected(current => !current)} className={cn('h-11 w-45 max-w-full flex-col items-start justify-start gap-1 rounded-none border-r-2 border-transparent p-0 pr-6 text-sm font-normal hover:bg-transparent', selected && 'border-r-accent-orange')}><span className={cn('flex h-5 items-center gap-1', selected ? 'text-accent-orange-text' : 'text-muted-foreground')}>{t(key('step'), undefined, { current: 1, total: 5 })}{state === 'complete' ? <CheckCircle2 aria-hidden=\"true\" className=\"size-3.5 fill-status-success-icon text-status-success-foreground\" /> : null}</span><span className=\"h-5\">{t(key('generalInformation'))}</span></Button>\n}\n\nfunction SelectedAmount({ state = 'default' }: { state?: MarketingControlState }) {\n const locale = useLocale()\n const [selected,setSelected] = React.useState(state === 'selected')\n return <Button data-slot=\"marketing-control\" data-kind=\"selected-button\" aria-pressed={selected} disabled={state === 'disabled'} variant={selected ? 'default' : 'outline'} onClick={() => setSelected(current => !current)} className={cn('h-7 gap-1.5 rounded-md border border-border px-2.5 py-1 font-normal', state === 'hover' && 'bg-muted', selected && 'border-transparent bg-accent-orange/10 text-accent-orange-text hover:bg-accent-orange/20')}>\n {selected ? <CheckCircle2 aria-hidden=\"true\" className=\"size-3.5\" /> : null}{new Intl.NumberFormat(locale).format(800)}\n </Button>\n}\n\nfunction MenuItem({ state = 'default' }: { state?: MarketingControlState }) {\n const t = useT()\n const [selected,setSelected] = React.useState(state === 'active')\n return <Button data-slot=\"marketing-control\" data-kind=\"tab-menu\" variant=\"ghost\" aria-pressed={selected} onClick={() => setSelected(current => !current)} className={cn('h-9 w-56 max-w-full justify-start gap-2 rounded-lg p-2 text-sm font-medium', (state === 'hover' || selected) && 'bg-muted')}><UserRound aria-hidden=\"true\" className={cn('size-5', selected ? 'text-accent-orange-text' : 'text-muted-foreground')} /><span className=\"flex-1 text-left\">{t(key('accountSettings'))}</span><ChevronRight aria-hidden=\"true\" className=\"size-4 text-muted-foreground\" /></Button>\n}\n\nfunction ChartTooltipExample({ side = 'right' }: { side?: 'right' | 'left' | 'bottom' | 'top' }) {\n const t = useT()\n const locale = useLocale()\n const [open,setOpen] = React.useState(true)\n return <div data-slot=\"marketing-control\" data-kind=\"chart-tooltip\" className=\"flex h-40 w-80 max-w-full items-center justify-center\"><TooltipProvider delayDuration={0}><Tooltip open={open} onOpenChange={setOpen}><Button asChild variant=\"ghost\" size=\"sm\"><TooltipTrigger>{t(key('chartPoint'))}</TooltipTrigger></Button><TooltipContent side={side === 'right' ? 'left' : side === 'left' ? 'right' : side === 'bottom' ? 'top' : 'bottom'} variant=\"light\" className=\"h-16 w-39.5 rounded-lg p-3 shadow-lg\"><p className=\"text-xs leading-4 text-muted-foreground\">{new Intl.DateTimeFormat(locale,{weekday:'short',month:'short',day:'numeric',timeZone:'UTC'}).format(new Date('2026-01-08T00:00:00Z'))}</p><div className=\"mt-1 flex items-center gap-1.5\"><span className=\"text-lg font-medium leading-6\">{new Intl.NumberFormat(locale).format(3484)}</span><span className=\"text-xs text-status-success-icon\">+7.1%</span><span className=\"text-xs text-muted-foreground\">{t(key('versusPrevious'))}</span></div></TooltipContent></Tooltip></TooltipProvider></div>\n}\n\nexport function MarketingControlDemo(props: ControlProps) {\n if (['input','select','textarea','amount'].includes(props.kind)) return <UnderlineField {...props} />\n if (props.kind === 'inline-input') return <InlineEditor state={props.state} />\n if (props.kind === 'inline-select') return <InlineLanguage state={props.state} />\n if (props.kind === 'step-item') return <StepItem state={props.state} />\n if (props.kind === 'selected-button') return <SelectedAmount state={props.state} />\n if (props.kind === 'tab-menu') return <MenuItem state={props.state} />\n if (props.kind === 'chart-tooltip') return <ChartTooltipExample side={props.side} />\n const product = marketingProducts[props.product ?? 0]\n return <img data-slot=\"marketing-control\" data-kind=\"product-image\" src={product.src} alt={product.name} className=\"size-50 max-w-full object-contain\" />\n}\n"
|
|
3
|
+
export const marketingControlExampleCode = (expression: string) => source + "\n" + expression + "\n"
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { CheckCircle2, ChevronRight, Info, Pencil, UserRound } from 'lucide-react'
|
|
3
|
+
import { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'
|
|
4
|
+
import { cn } from '@open-mercato/shared/lib/utils'
|
|
5
|
+
import { Button } from '@open-mercato/ui/primitives/button'
|
|
6
|
+
import { Input } from '@open-mercato/ui/primitives/input'
|
|
7
|
+
import { InlineInput } from '@open-mercato/ui/primitives/inline-input'
|
|
8
|
+
import { InlineSelectTrigger } from '@open-mercato/ui/primitives/inline-select'
|
|
9
|
+
import { Label } from '@open-mercato/ui/primitives/label'
|
|
10
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@open-mercato/ui/primitives/select'
|
|
11
|
+
import { Textarea } from '@open-mercato/ui/primitives/textarea'
|
|
12
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@open-mercato/ui/primitives/tooltip'
|
|
13
|
+
import { marketingProducts } from '../assets/marketing-artwork'
|
|
14
|
+
|
|
15
|
+
export type MarketingControlState = 'default' | 'hover' | 'active' | 'filled' | 'placeholder' | 'focus' | 'disabled' | 'complete' | 'selected'
|
|
16
|
+
export type MarketingControlKind = 'step-item' | 'select' | 'input' | 'textarea' | 'amount' | 'tab-menu' | 'inline-input' | 'inline-select' | 'chart-tooltip' | 'selected-button' | 'product-image'
|
|
17
|
+
type ControlProps = { kind: MarketingControlKind; state?: MarketingControlState; size?: 'medium' | 'large'; side?: 'right' | 'left' | 'bottom' | 'top'; product?: number }
|
|
18
|
+
const key = (name: string) => `design_system.gallery.samples.marketing.${name}`
|
|
19
|
+
const focusStyle = 'focus-within:border-accent-orange-border focus-within:shadow-none'
|
|
20
|
+
|
|
21
|
+
function UnderlineField({ kind, state = 'default', size = 'medium' }: ControlProps) {
|
|
22
|
+
const t = useT()
|
|
23
|
+
const id = React.useId()
|
|
24
|
+
const large = size === 'large' || kind === 'amount'
|
|
25
|
+
const label = t(key(kind === 'textarea' ? 'description' : kind === 'amount' ? 'productPricing' : kind === 'select' ? 'category' : 'productName'))
|
|
26
|
+
const placeholder = t(key(kind === 'textarea' ? 'productDescriptionPlaceholder' : kind === 'select' ? 'categoryPlaceholder' : 'productNamePlaceholder'))
|
|
27
|
+
const [value, setValue] = React.useState(state === 'filled' ? kind === 'select' ? 'accessories' : kind === 'amount' ? '800' : t(key(kind === 'textarea' ? 'productDescription' : 'productNameExample')) : '')
|
|
28
|
+
const active = state === 'active'
|
|
29
|
+
return <div data-slot="marketing-control" data-kind={kind} className={cn('relative flex w-93 max-w-full flex-col gap-1.5 pb-3.5 after:absolute after:inset-x-0 after:bottom-0 after:h-px after:bg-border focus-within:after:h-0.5 focus-within:after:bg-accent-orange-border', large ? 'h-20' : 'h-16', active && 'after:h-0.5 after:bg-accent-orange-border')}>
|
|
30
|
+
<Label htmlFor={id} className="flex h-5 shrink-0 items-center gap-1 text-sm font-medium text-muted-foreground">{label}<Info aria-hidden="true" className="size-3 text-text-disabled" /></Label>
|
|
31
|
+
{kind === 'select' ? <Select value={value} onValueChange={setValue}><SelectTrigger id={id} aria-label={label} className={cn('h-6 rounded-none border-0 bg-transparent p-0 text-lg font-medium shadow-none hover:bg-transparent focus:border-0 focus:shadow-none', state === 'hover' && 'text-muted-foreground', active && '[&_svg]:text-accent-orange-text')}><SelectValue placeholder={placeholder} /></SelectTrigger><SelectContent>{['accessories','computers','audio'].map(option => <SelectItem key={option} value={option}>{t(key(option))}</SelectItem>)}</SelectContent></Select> : kind === 'textarea' ? <Textarea id={id} aria-label={label} rows={1} value={value} onChange={event => setValue(event.target.value)} placeholder={placeholder} autoFocus={active} className={cn('h-6 min-h-6 resize-none rounded-none border-0 bg-transparent p-0 text-lg font-medium leading-6 shadow-none hover:bg-transparent focus-visible:shadow-none', state === 'hover' && 'placeholder:text-muted-foreground')} /> : <Input id={id} aria-label={label} value={value} onChange={event => setValue(event.target.value)} type={kind === 'amount' ? 'number' : 'text'} min={kind === 'amount' ? 0 : undefined} step={kind === 'amount' ? '0.01' : undefined} autoFocus={active} placeholder={kind === 'amount' ? '0.00' : placeholder} leading={kind === 'amount' ? <span aria-hidden="true" className={cn('text-title-4 font-medium', !value && !active && 'text-text-disabled')}>$</span> : undefined} className={cn('rounded-none border-0 bg-transparent p-0 shadow-none hover:bg-transparent focus-within:shadow-none', large ? 'h-10' : 'h-6')} inputClassName={cn('font-medium placeholder:text-text-disabled', large ? 'text-title-4 leading-10' : 'text-lg leading-6', state === 'hover' && 'placeholder:text-muted-foreground')} />}
|
|
32
|
+
</div>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function InlineEditor({ state = 'placeholder' }: { state?: MarketingControlState }) {
|
|
36
|
+
const t = useT()
|
|
37
|
+
const [value, setValue] = React.useState(state === 'filled' ? 'James Brown' : '')
|
|
38
|
+
const [draft, setDraft] = React.useState(value)
|
|
39
|
+
const [editing, setEditing] = React.useState(state === 'focus')
|
|
40
|
+
const disabled = state === 'disabled'
|
|
41
|
+
const save = () => { setValue(draft.trim()); setEditing(false) }
|
|
42
|
+
const cancel = () => { setDraft(value); setEditing(false) }
|
|
43
|
+
return <div data-slot="marketing-control" data-kind="inline-input" className="w-78 max-w-full">
|
|
44
|
+
{editing ? <InlineInput autoFocus value={draft} onChange={event => setDraft(event.target.value)} aria-label={t(key('name'))} placeholder={t(key('namePlaceholder'))} className={cn('h-10 gap-2 rounded-lg border-accent-orange-border bg-background px-3', focusStyle)} onKeyDown={event => { if (event.key === 'Escape') { event.preventDefault(); cancel() } if (event.key === 'Enter') { event.preventDefault(); save() } }} trailing={<div className="flex gap-2"><Button variant="ghost" className="h-5 p-0 text-sm font-normal" onClick={cancel}>{t(key('cancel'))}</Button><Button variant="ghost" className="h-5 p-0 text-sm font-normal text-accent-orange-text" onClick={save}>{t(key('save'))}</Button></div>} /> : <Button disabled={disabled} variant="ghost" onClick={() => setEditing(true)} className={cn('h-10 w-full justify-between rounded-lg px-3 text-sm font-normal', state === 'hover' && 'bg-muted', !value && 'text-muted-foreground')}><span className="truncate">{value || t(key('namePlaceholder'))}</span><Pencil aria-hidden="true" className="size-4 shrink-0 text-text-disabled" /></Button>}
|
|
45
|
+
</div>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function InlineLanguage({ state = 'placeholder' }: { state?: MarketingControlState }) {
|
|
49
|
+
const t = useT()
|
|
50
|
+
const [value, setValue] = React.useState(state === 'filled' ? 'en' : '')
|
|
51
|
+
const [open, setOpen] = React.useState(false)
|
|
52
|
+
return <div data-slot="marketing-control" data-kind="inline-select" className="w-64 max-w-full"><Select value={value} onValueChange={setValue} open={open} onOpenChange={setOpen} disabled={state === 'disabled'}><InlineSelectTrigger aria-label={t(key('language'))} className={cn('h-10 rounded-lg px-3 focus:border-accent-orange-border focus:shadow-none', state === 'hover' && 'bg-muted', state === 'focus' && 'border-accent-orange-border')}><SelectValue placeholder={t(key('languagePlaceholder'))} /></InlineSelectTrigger><SelectContent>{['en','pl','de','es','ko'].map(language => <SelectItem key={language} value={language}>{t(key(`language${language}`))}</SelectItem>)}</SelectContent></Select></div>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function StepItem({ state = 'default' }: { state?: MarketingControlState }) {
|
|
56
|
+
const t = useT()
|
|
57
|
+
const [selected, setSelected] = React.useState(state === 'active')
|
|
58
|
+
return <Button data-slot="marketing-control" data-kind="step-item" variant="ghost" aria-pressed={selected} onClick={() => setSelected(current => !current)} className={cn('h-11 w-45 max-w-full flex-col items-start justify-start gap-1 rounded-none border-r-2 border-transparent p-0 pr-6 text-sm font-normal hover:bg-transparent', selected && 'border-r-accent-orange')}><span className={cn('flex h-5 items-center gap-1', selected ? 'text-accent-orange-text' : 'text-muted-foreground')}>{t(key('step'), undefined, { current: 1, total: 5 })}{state === 'complete' ? <CheckCircle2 aria-hidden="true" className="size-3.5 fill-status-success-icon text-status-success-foreground" /> : null}</span><span className="h-5">{t(key('generalInformation'))}</span></Button>
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function SelectedAmount({ state = 'default' }: { state?: MarketingControlState }) {
|
|
62
|
+
const locale = useLocale()
|
|
63
|
+
const [selected,setSelected] = React.useState(state === 'selected')
|
|
64
|
+
return <Button data-slot="marketing-control" data-kind="selected-button" aria-pressed={selected} disabled={state === 'disabled'} variant={selected ? 'default' : 'outline'} onClick={() => setSelected(current => !current)} className={cn('h-7 gap-1.5 rounded-md border border-border px-2.5 py-1 font-normal', state === 'hover' && 'bg-muted', selected && 'border-transparent bg-accent-orange/10 text-accent-orange-text hover:bg-accent-orange/20')}>
|
|
65
|
+
{selected ? <CheckCircle2 aria-hidden="true" className="size-3.5" /> : null}{new Intl.NumberFormat(locale).format(800)}
|
|
66
|
+
</Button>
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function MenuItem({ state = 'default' }: { state?: MarketingControlState }) {
|
|
70
|
+
const t = useT()
|
|
71
|
+
const [selected,setSelected] = React.useState(state === 'active')
|
|
72
|
+
return <Button data-slot="marketing-control" data-kind="tab-menu" variant="ghost" aria-pressed={selected} onClick={() => setSelected(current => !current)} className={cn('h-9 w-56 max-w-full justify-start gap-2 rounded-lg p-2 text-sm font-medium', (state === 'hover' || selected) && 'bg-muted')}><UserRound aria-hidden="true" className={cn('size-5', selected ? 'text-accent-orange-text' : 'text-muted-foreground')} /><span className="flex-1 text-left">{t(key('accountSettings'))}</span><ChevronRight aria-hidden="true" className="size-4 text-muted-foreground" /></Button>
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function ChartTooltipExample({ side = 'right' }: { side?: 'right' | 'left' | 'bottom' | 'top' }) {
|
|
76
|
+
const t = useT()
|
|
77
|
+
const locale = useLocale()
|
|
78
|
+
const [open,setOpen] = React.useState(true)
|
|
79
|
+
return <div data-slot="marketing-control" data-kind="chart-tooltip" className="flex h-40 w-80 max-w-full items-center justify-center"><TooltipProvider delayDuration={0}><Tooltip open={open} onOpenChange={setOpen}><Button asChild variant="ghost" size="sm"><TooltipTrigger>{t(key('chartPoint'))}</TooltipTrigger></Button><TooltipContent side={side === 'right' ? 'left' : side === 'left' ? 'right' : side === 'bottom' ? 'top' : 'bottom'} variant="light" className="h-16 w-39.5 rounded-lg p-3 shadow-lg"><p className="text-xs leading-4 text-muted-foreground">{new Intl.DateTimeFormat(locale,{weekday:'short',month:'short',day:'numeric',timeZone:'UTC'}).format(new Date('2026-01-08T00:00:00Z'))}</p><div className="mt-1 flex items-center gap-1.5"><span className="text-lg font-medium leading-6">{new Intl.NumberFormat(locale).format(3484)}</span><span className="text-xs text-status-success-icon">+7.1%</span><span className="text-xs text-muted-foreground">{t(key('versusPrevious'))}</span></div></TooltipContent></Tooltip></TooltipProvider></div>
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function MarketingControlDemo(props: ControlProps) {
|
|
83
|
+
if (['input','select','textarea','amount'].includes(props.kind)) return <UnderlineField {...props} />
|
|
84
|
+
if (props.kind === 'inline-input') return <InlineEditor state={props.state} />
|
|
85
|
+
if (props.kind === 'inline-select') return <InlineLanguage state={props.state} />
|
|
86
|
+
if (props.kind === 'step-item') return <StepItem state={props.state} />
|
|
87
|
+
if (props.kind === 'selected-button') return <SelectedAmount state={props.state} />
|
|
88
|
+
if (props.kind === 'tab-menu') return <MenuItem state={props.state} />
|
|
89
|
+
if (props.kind === 'chart-tooltip') return <ChartTooltipExample side={props.side} />
|
|
90
|
+
const product = marketingProducts[props.product ?? 0]
|
|
91
|
+
return <img data-slot="marketing-control" data-kind="product-image" src={product.src} alt={product.name} className="size-50 max-w-full object-contain" />
|
|
92
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
// Generated by yarn storybook:generate from marketing-widgets.tsx.
|
|
2
|
+
const source = "import * as React from 'react'\nimport { Area, AreaChart, Bar, BarChart, CartesianGrid, Cell, Line, LineChart, Pie, PieChart, PolarAngleAxis, PolarGrid, Radar, RadarChart, ReferenceLine, ResponsiveContainer, Tooltip, XAxis } from 'recharts'\nimport { ArrowDownLeft, ArrowUp, ArrowUpRight, CheckCircle2, ChevronLeft, ChevronRight, Clock3, Code, Info, Minus, Plus, Store, Target, UserRound } from 'lucide-react'\nimport { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { ChartContainer, ChartTooltipContent } from '@open-mercato/ui/backend/charts'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Card } from '@open-mercato/ui/primitives/card'\nimport { CompactButton } from '@open-mercato/ui/primitives/compact-button'\nimport { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@open-mercato/ui/primitives/dialog'\nimport { ScrollArea } from '@open-mercato/ui/primitives/scroll-area'\nimport { SearchInput } from '@open-mercato/ui/primitives/search-input'\nimport { SegmentedControl, SegmentedControlItem } from '@open-mercato/ui/primitives/segmented-control'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@open-mercato/ui/primitives/select'\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'\nimport { marketingBrands, marketingProducts, marketingWorldMap, marketingActivityIcons } from '../assets/marketing-artwork'\n\nexport type MarketingWidgetKind = 'total-sales' | 'total-visitors' | 'conversion-rate' | 'user-retention' | 'weekly-visitors' | 'visitor-channels' | 'geography' | 'realtime-visitors' | 'marketing-channels' | 'product-performance' | 'shipping-tracking' | 'my-products' | 'product-categories' | 'customer-segments' | 'sales-channels' | 'campaign-data' | 'support-analytics' | 'recent-activities'\nconst key = (name: string) => `design_system.gallery.samples.marketing.${name}`\nconst chartColor = 'var(--chart-orange)'\nconst palette = ['var(--chart-orange)','var(--chart-amber)','var(--chart-teal)','var(--chart-cyan)','var(--chart-violet)','var(--chart-indigo)']\nfunction useMarketing() {\n const t = useT()\n const locale = useLocale()\n const number = (value: number, digits = 0) => new Intl.NumberFormat(locale,{maximumFractionDigits:digits,minimumFractionDigits:digits}).format(value)\n const money = (value: number) => new Intl.NumberFormat(locale,{style:'currency',currency:'USD',maximumFractionDigits:value % 1 ? 2 : 0}).format(value)\n const date = (value: string, options: Intl.DateTimeFormatOptions = {month:'short',day:'numeric',year:'numeric'}) => new Intl.DateTimeFormat(locale,{...options,timeZone:'UTC'}).format(new Date(value))\n return { t, locale, number, money, date }\n}\nfunction Trend({ value, badge = false, previous = false }: { value: number; badge?: boolean; previous?: boolean }) {\n const { t, number } = useMarketing()\n const label = `${value > 0 ? '+' : ''}${number(value,1)}%`\n return <span className=\"inline-flex items-center gap-1 whitespace-nowrap text-sm\">{badge ? <Badge appearance=\"light\" tone={value < 0 ? 'error' : 'success'} size={20}>{label}</Badge> : <span className={value < 0 ? 'text-status-error-icon' : 'text-status-success-icon'}>{label}</span>}{previous ? <span className=\"text-muted-foreground\">{t(key('versusWeek'))}</span> : null}</span>\n}\nfunction MarketingFrame({ children, compact = false, tall = false, flush = false, className }: { children: React.ReactNode; compact?: boolean; tall?: boolean; flush?: boolean; className?: string }) {\n return <Card data-slot=\"marketing-widget\" className={cn('relative w-88 max-w-full overflow-hidden rounded-xl border-0 bg-background ring-1 ring-inset ring-border shadow-xs',tall ? 'h-196' : compact ? 'h-44.5' : 'h-95',flush ? 'gap-0 p-0' : 'gap-5 p-5',className)}>{children}</Card>\n}\nfunction Header({ title, value, action, children, info = false, className }: { title: string; value?: React.ReactNode; action?: React.ReactNode; children?: React.ReactNode; info?: boolean; className?: string }) {\n return <div className={cn('flex h-14 shrink-0 items-start gap-2',className)}><div className=\"min-w-0 flex-1\"><h3 className=\"flex items-center gap-1 text-sm font-medium leading-5 text-muted-foreground\">{title}{info ? <Info aria-hidden=\"true\" className=\"size-3 shrink-0 text-text-disabled\" /> : null}</h3><div className=\"mt-1 flex items-center gap-2\"><span className=\"whitespace-nowrap text-2xl font-medium leading-8 tracking-tight\">{value}</span>{children}</div></div>{action}</div>\n}\nfunction Details({ title, children, label, className }: { title: string; children: React.ReactNode; label?: string; className?: string }) {\n const {t} = useMarketing()\n const [open,setOpen] = React.useState(false)\n return <Dialog open={open} onOpenChange={setOpen}><Button asChild variant=\"outline\" className={cn('h-7 shrink-0 rounded-lg px-2.5 text-sm font-normal',className)}><DialogTrigger>{label ?? t(key('details'))}</DialogTrigger></Button><DialogContent onKeyDownCapture={event=>{if(event.key==='Enter'&&(event.ctrlKey||event.metaKey)){event.preventDefault();setOpen(false)}}}><DialogHeader><DialogTitle>{title}</DialogTitle><DialogDescription>{t(key('localData'))}</DialogDescription></DialogHeader><ScrollArea className=\"max-h-96\" scrollbarSize=\"sm\">{children}</ScrollArea><DialogFooter><Button variant=\"outline\" onClick={()=>setOpen(false)}>{t(key('close'))}</Button></DialogFooter></DialogContent></Dialog>\n}\nfunction Period({ value, onChange, compact = false }: { value: string; onChange: (value: string)=>void; compact?: boolean }) {\n const {t}=useMarketing()\n return compact ? <Select value={value} onValueChange={onChange}><SelectTrigger aria-label={t(key('period'))} className=\"h-7 w-auto gap-2 rounded-lg px-2.5 text-sm\"><SelectValue /></SelectTrigger><SelectContent>{['weekly','monthly','yearly'].map(option=><SelectItem key={option} value={option}>{t(key(option))}</SelectItem>)}</SelectContent></Select> : <SegmentedControl value={value} onValueChange={onChange} aria-label={t(key('period'))} className=\"h-6 w-full shrink-0 rounded-sm bg-background p-0\">{['1d','1w','1m','3m','1y'].map(option=><SegmentedControlItem key={option} value={option} className=\"h-6 flex-1 rounded-none border-r border-border p-0 text-xs last:border-0 data-[state=checked]:bg-muted data-[state=checked]:font-medium data-[state=checked]:shadow-none\">{t(key(`period${option}`))}</SegmentedControlItem>)}</SegmentedControl>\n}\nfunction Choices({ values, value, onChange, label }: { values: string[]; value: string; onChange: (value:string)=>void; label: string }) {\n const {t}=useMarketing()\n return <SegmentedControl value={value} onValueChange={onChange} aria-label={label} className=\"h-7 max-w-full gap-2 rounded-none border-0 bg-transparent p-0\">{values.map(option=><SegmentedControlItem key={option} value={option} className=\"h-7 rounded-lg bg-muted px-2.5 text-sm font-normal data-[state=checked]:bg-accent-orange/10 data-[state=checked]:font-normal data-[state=checked]:text-accent-orange-text data-[state=checked]:shadow-none data-[state=unchecked]:bg-muted\">{t(key(option))}</SegmentedControlItem>)}</SegmentedControl>\n}\nfunction DataList({ rows }: { rows: { label: string; value: React.ReactNode; detail?: React.ReactNode; icon?: React.ReactNode }[] }) {\n return <div className=\"flex flex-col gap-4\">{rows.map(row=><div key={row.label} className=\"flex min-h-5 items-center gap-2 text-sm text-muted-foreground\">{row.icon}<span className=\"min-w-0 flex-1\">{row.label}</span><span>{row.value}</span>{row.detail ? <span className=\"ml-3 flex w-18 items-center justify-end gap-1\">{row.detail}</span> : null}</div>)}</div>\n}\nconst salesSeries = [28,14,33,11,57,26,57,7,42,39,24,44,31,36,15,25,13,21,41,46,15,30,24,44,18]\nfunction TotalSales() {\n const {t,money}=useMarketing()\n const [period,setPeriod]=React.useState('1w')\n const factor = {'1d':0.4,'1w':1,'1m':2,'3m':3,'1y':7}[period] ?? 1\n const rows=[{label:t(key('onlineStore')),value:money(52.12*factor),icon:<Store aria-hidden=\"true\" className=\"size-5\" />,delta:4.5},{label:'Facebook',value:money(38.45*factor),icon:<img src={marketingBrands.facebook} alt=\"\" className=\"size-5\" />,delta:-2.8},{label:'Instagram',value:money(37.75*factor),icon:<img src={marketingBrands.instagram} alt=\"\" className=\"size-5\" />,delta:3.2}].map(row=>({...row,detail:<><ArrowUp aria-hidden=\"true\" className={cn('size-4',row.delta < 0 ? 'rotate-180 text-status-error-icon':'text-status-success-icon')} />{row.delta>0?'+':''}{row.delta}%</>}))\n return <MarketingFrame><Header title={t(key('totalSales'))} value={money(128.32*factor)} action={<Details title={t(key('totalSales'))} label={t(key('report'))}><DataList rows={rows} /></Details>}><Trend value={2} badge /></Header><Period value={period} onChange={setPeriod}/><ChartContainer config={{value:{label:t(key('sales')),color:chartColor}}} className=\"h-27 min-h-0 w-full shrink-0\" aria-label={t(key('totalSales'))}><ResponsiveContainer width=\"100%\" height=\"100%\"><LineChart data={salesSeries.map((value,index)=>({index,value:value*factor}))} margin={{top:0,right:0,bottom:0,left:0}}><CartesianGrid stroke=\"var(--border)\" strokeDasharray=\"3 3\"/><Tooltip content={<ChartTooltipContent/>}/><Line dataKey=\"value\" type=\"linear\" stroke={chartColor} strokeWidth={1.5} dot={false} isAnimationActive={false}/></LineChart></ResponsiveContainer></ChartContainer><DataList rows={rows}/></MarketingFrame>\n}\nfunction TotalVisitors() {\n const {t,number}=useMarketing()\n const values=[{key:'desktop',value:27,delta:-3.2,color:'bg-chart-amber'},{key:'tablet',value:12,delta:-6.4,color:'bg-chart-cyan'},{key:'mobile',value:61,delta:0.8,color:'bg-chart-violet'}]\n return <MarketingFrame><Header title={t(key('totalVisitors'))} value={number(237456)}><Trend value={-1.4} badge/></Header><div className=\"flex min-h-0 flex-1 border-t border-border pt-5\">{values.map(item=><div key={item.key} className=\"flex min-w-0 flex-1 flex-col border-r border-dashed border-border px-4 first:pl-0 last:border-0 last:pr-0\"><p className=\"text-sm text-muted-foreground\">{t(key(item.key))}</p><p className=\"mt-1 text-xl font-medium\">{item.value}%</p><span className=\"mt-auto flex items-center gap-1 text-sm text-muted-foreground\">{item.delta>0?'+':''}{number(item.delta,1)}%{item.delta<0?<ArrowDownLeft aria-hidden=\"true\" className=\"size-3 text-status-error-icon\"/>:<ArrowUpRight aria-hidden=\"true\" className=\"size-3 text-status-success-icon\"/>}</span><span className={cn('mt-4 h-2 w-full rounded-sm',item.color)}/></div>)}</div></MarketingFrame>\n}\nfunction ConversionRate() {\n const {t,number}=useMarketing()\n const rows=[['addedToCart',3842,1.8],['reachedCheckout',1256,-1.2],['purchased',649,2.4]] as const\n const list=<DataList rows={rows.map(([label,value,delta])=>({label:t(key(label)),value:number(value),detail:<Trend value={delta}/>}))}/>\n const series=[12,9,6,12,3,8,7,6,23,19,28,3,14,4].map((value,index)=>({month:index,value,previous:value+8+(index%3)*5}))\n return <MarketingFrame><Header title={t(key('conversionRate'))} value={`${number(16.9,1)}%`} action={<Details title={t(key('conversionRate'))}>{list}</Details>}><Trend value={2.1} badge/></Header><div className=\"border-t border-border pt-5\">{list}</div><ChartContainer config={{value:{label:t(key('purchased')),color:chartColor},previous:{label:t(key('previousPeriod')),color:'var(--chart-orange)'}}} className=\"min-h-0 flex-1\" aria-label={t(key('conversionRate'))}><ResponsiveContainer width=\"100%\" height=\"100%\"><AreaChart data={series} margin={{top:0,right:0,left:0,bottom:0}}><CartesianGrid stroke=\"var(--border)\"/><Tooltip content={<ChartTooltipContent/>}/><Area dataKey=\"previous\" stroke=\"none\" fill={chartColor} fillOpacity={0.18} isAnimationActive={false}/><Area dataKey=\"value\" stroke=\"none\" fill={chartColor} fillOpacity={1} isAnimationActive={false}/></AreaChart></ResponsiveContainer></ChartContainer><div className=\"-mt-3 flex justify-between text-overline text-muted-foreground\">{['feb','mar','apr','may','jun','jul'].map(month=><span key={month}>{t(key(month))}</span>)}</div></MarketingFrame>\n}\nfunction Retention() {\n const {t,number}=useMarketing()\n const [selected,setSelected]=React.useState<[number,number]|null>(null)\n const cells=Array.from({length:12},(_,row)=>Array.from({length:12-row},(_,column)=>({row,column,value:Math.max(8,100-row*6-column*4+(row*column%5)*5)}))).flat()\n return <MarketingFrame><Header title={t(key('retention'))} value=\"24%\" action={<Details title={t(key('retention'))}><DataList rows={cells.filter(cell=>cell.column===0).map(cell=>({label:t(key('month'),undefined,{month:cell.row+1}),value:`${cell.value}%`}))}/></Details>}><Trend value={2} badge/></Header><div className=\"grid h-48 shrink-0 grid-cols-12 grid-rows-12 gap-0.5\" role=\"group\" aria-label={t(key('retentionCohorts'))}>{cells.map(cell=><button key={`${cell.row}-${cell.column}`} type=\"button\" aria-label={t(key('cohortCell'),undefined,{cohort:cell.row+1,month:cell.column+1,value:cell.value})} aria-pressed={selected?.[0]===cell.row&&selected?.[1]===cell.column} className=\"min-h-0 min-w-0 bg-chart-orange outline-none focus-visible:ring-2 focus-visible:ring-foreground focus-visible:ring-offset-1\" style={{gridRow:cell.row+1,gridColumn:cell.column+1,opacity:cell.value/100}} onClick={()=>setSelected([cell.row,cell.column])}/>)}</div><div className=\"-mt-3 grid grid-cols-12 text-center text-overline text-muted-foreground\">{Array.from({length:12},(_,index)=><span key={index}>{index+1}</span>)}</div><p aria-live=\"polite\" className=\"mt-auto flex h-7 items-center gap-2 rounded-lg border border-border px-2 text-xs text-muted-foreground\"><Info aria-hidden=\"true\" className=\"size-3 shrink-0\"/><span className=\"truncate\">{selected?t(key('cohortCell'),undefined,{cohort:selected[0]+1,month:selected[1]+1,value:number(cells.find(cell=>cell.row===selected[0]&&cell.column===selected[1])?.value??0)}):t(key('retentionUpdated'))}</span></p></MarketingFrame>\n}\nfunction WeeklyVisitors() {\n const {t,number}=useMarketing()\n const values=['happiness','sadness','anger','fear','surprise','disgust','love','excitement']\n const [series,setSeries]=React.useState({fresh:true,returning:true})\n return <MarketingFrame className=\"gap-4\"><Header title={t(key('weeklyVisitors'))} value={number(16008)}><Trend value={1.1} badge/></Header><div className=\"flex h-7 shrink-0 rounded-lg border border-border\">{(['fresh','returning'] as const).map((item,index)=><Button key={item} variant=\"ghost\" aria-pressed={series[item]} onClick={()=>setSeries(current=>({...current,[item]:!current[item]}))} className=\"h-7 min-w-0 flex-1 gap-2 p-0 text-xs font-normal\"><span className={cn('size-2 rounded-full',index?'bg-chart-emerald':'bg-chart-orange',!series[item]&&'opacity-30')}/>{t(key(item==='fresh'?'newVisitors':'returningVisitors'))}</Button>)}</div><ChartContainer config={{fresh:{label:t(key('newVisitors')),color:chartColor},returning:{label:t(key('returningVisitors')),color:'var(--chart-emerald)'}}} className=\"min-h-0 flex-1\" aria-label={t(key('weeklyVisitors'))}><ResponsiveContainer width=\"100%\" height=\"100%\"><RadarChart data={values.map((label,index)=>({label:t(key(label)),fresh:[76,39,84,72,40,70,74,32][index],returning:[43,75,39,40,0,54,39,75][index]}))} outerRadius=\"62%\"><PolarGrid stroke=\"var(--border)\"/><PolarAngleAxis dataKey=\"label\" tick={{fontSize:11,fill:'var(--muted-foreground)'}}/>{series.fresh?<Radar name={t(key('newVisitors'))} dataKey=\"fresh\" stroke={chartColor} fill={chartColor} fillOpacity={0.08} dot={{r:2}} isAnimationActive={false}/>:null}{series.returning?<Radar name={t(key('returningVisitors'))} dataKey=\"returning\" stroke=\"var(--chart-emerald)\" fill=\"var(--chart-emerald)\" fillOpacity={0.08} dot={{r:2}} isAnimationActive={false}/>:null}<Tooltip content={<ChartTooltipContent/>}/></RadarChart></ResponsiveContainer></ChartContainer></MarketingFrame>\n}\nfunction ChannelRows({ marketing = false }: { marketing?: boolean }) {\n const {t,money,number}=useMarketing()\n const names=marketing?['acquisition','conversion','roi']:['Facebook','Instagram','Google']\n return <div className=\"flex flex-col gap-4\"><div className=\"flex items-center text-xs text-muted-foreground\"><span className=\"flex-1\">{t(key('channels'))}</span><span className=\"w-22\">{t(key(marketing?'metric':'percent'))}</span><span className=\"w-12\">{t(key('total'))}</span></div>{names.map((name,index)=><div key={name} className=\"flex h-5 items-center gap-2 text-sm text-muted-foreground\">{marketing ? index===0?<UserRound aria-hidden=\"true\" className=\"size-5\"/>:index===1?<Clock3 aria-hidden=\"true\" className=\"size-5\"/>:<Target aria-hidden=\"true\" className=\"size-5\"/>:<img src={marketingBrands[name.toLowerCase() as 'facebook'|'instagram'|'google']} alt=\"\" className=\"size-5\"/>}<span className=\"min-w-0 flex-1\">{marketing?t(key(name)):name}</span><span className=\"w-18\">{marketing?[money(38.25),t(key('days'),undefined,{count:4.2}),'324%'][index]:`${[28,23,32][index]}%`}</span><span className=\"w-12 text-right\">{marketing?<Trend value={[5.2,3.8,4.5][index]}/>:number([6958,5716,7952][index])}</span></div>)}</div>\n}\nfunction Channels({ marketing = false }: { marketing?: boolean }) {\n const {t}=useMarketing()\n const title=t(key(marketing?'marketingChannels':'visitorChannels'))\n return <MarketingFrame><Header title={title} info={marketing} value={marketing?'82%':'78%'} action={marketing?<Details title={title}><ChannelRows marketing/></Details>:undefined}><Trend value={marketing?2.1:-0.4} badge={!marketing} previous={marketing}/></Header><div className=\"space-y-4\"><div className=\"flex h-2 gap-1\">{[41,41,16].map((width,index)=><span key={index} className=\"rounded-sm\" style={{width:`${width}%`,background:marketing?palette[index]:['var(--chart-amber)','var(--chart-cyan)','var(--chart-violet)'][index]}}/>)}</div><div className=\"flex justify-between gap-1 text-xs text-muted-foreground\">{(marketing?['organicSearch','socialMedia','direct']:['organic','referral','direct']).map((name,index)=><span key={name} className=\"flex items-center gap-2\"><span className=\"size-2 shrink-0 rounded-full\" style={{background:marketing?palette[index]:['var(--chart-amber)','var(--chart-cyan)','var(--chart-violet)'][index]}}/>{t(key(name))}</span>)}</div></div><div className={cn('border-t border-border pt-5',marketing&&'border-dashed')}><ChannelRows marketing={marketing}/></div><Details title={title} label={t(key('viewReports'))} className=\"mt-auto h-8 w-full\"><ChannelRows marketing={marketing}/></Details></MarketingFrame>\n}\nconst mapPoints=[{key:'greenland',x:161,y:7,visitors:1032},{key:'usa',x:80,y:69,visitors:8530},{key:'brazil',x:129,y:126,visitors:3240},{key:'turkey',x:222,y:110,visitors:6504},{key:'china',x:276,y:57,visitors:9322}]\nfunction Geography() {\n const {t,number}=useMarketing()\n const [selected,setSelected]=React.useState('turkey')\n const [zoom,setZoom]=React.useState(1)\n const place=mapPoints.find(point=>point.key===selected)??mapPoints[3]\n return <MarketingFrame className=\"gap-4\"><Header title={t(key('geography'))} value={number(place.visitors)} action={<Details title={t(key('geography'))}><DataList rows={mapPoints.map(point=>({label:t(key(point.key)),value:number(point.visitors)}))}/></Details>}><span className=\"flex h-6 items-center gap-1 rounded-sm border border-border px-1 text-xs text-muted-foreground\">{selected==='turkey'?<img src={marketingBrands.turkey} alt=\"\" className=\"size-4\"/>:null}{t(key(selected))}</span></Header><div className=\"flex h-7 shrink-0 items-center justify-around rounded-lg border border-border text-xs text-muted-foreground\">{['men','women','other'].map((item,index)=><span key={item}>{t(key(item))} <span className=\"text-foreground\">{[32,60,8][index]}%</span></span>)}</div><div data-slot=\"marketing-map\" className=\"relative h-56 w-full shrink-0 overflow-hidden\"><div className=\"absolute inset-0 origin-center transition-transform\" style={{transform:`scale(${zoom})`}}><span aria-hidden=\"true\" className=\"absolute left-0 top-0 h-56 w-83 bg-text-disabled\" style={{maskImage:`url(\"${marketingWorldMap}\")`,WebkitMaskImage:`url(\"${marketingWorldMap}\")`,maskSize:'100% 100%',WebkitMaskSize:'100% 100%'}}/>{mapPoints.map(point=><button key={point.key} type=\"button\" aria-label={t(key(point.key))} aria-pressed={selected===point.key} onClick={()=>setSelected(point.key)} style={{left:`${point.x/312*100}%`,top:`${point.y/224*100}%`}} className={cn('absolute flex size-4 items-center justify-center rounded-full bg-background ring-1 ring-inset ring-border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground',selected===point.key&&'bg-accent-orange ring-4 ring-accent-orange/20')}><span className={cn('size-2 rounded-full',selected===point.key?'bg-accent-orange-foreground':'bg-text-disabled')}/></button>)}</div><div className=\"absolute bottom-0 left-0 flex flex-col gap-2\"><CompactButton aria-label={t(key('zoomIn'))} disabled={zoom>=2} onClick={()=>setZoom(current=>Math.min(2,current+0.25))}><Plus aria-hidden=\"true\"/></CompactButton><CompactButton aria-label={t(key('zoomOut'))} disabled={zoom<=1} onClick={()=>setZoom(current=>Math.max(1,current-0.25))}><Minus aria-hidden=\"true\"/></CompactButton></div><CompactButton className=\"absolute bottom-0 right-0\" aria-label={t(key('resetMap'))} onClick={()=>{setZoom(1);setSelected('turkey')}}><Code aria-hidden=\"true\"/></CompactButton></div></MarketingFrame>\n}\nfunction RealtimeVisitors() {\n const {t,number}=useMarketing()\n return <MarketingFrame><Header title={t(key('realtimeVisitors'))} value={t(key('millions'),undefined,{value:number(32.6,1)})}><Trend value={8.4} badge/></Header><div className=\"relative h-39 shrink-0\" aria-label={t(key('regionalVisitors'))}><div className=\"absolute left-1/2 top-1/2 -ml-10.5 -mt-9.5 flex size-20 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full bg-chart-emerald/25 text-xl text-status-success-foreground\">20%</div><div className=\"absolute left-1/2 top-1/2 ml-10 mt-1 flex size-33 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full bg-chart-orange/25 text-title-4 font-medium text-accent-orange-text\">48%</div><div className=\"absolute left-1/2 top-1/2 -ml-14.5 mt-7.5 flex size-24 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-full bg-chart-amber/25 text-2xl font-medium text-status-warning-foreground\">32%</div></div><DataList rows={['europe','asia','americas'].map((region,index)=>({label:t(key(region)),value:t(key('millions'),undefined,{value:number([15.8,10.2,6.6][index],1)}),icon:<span className=\"ml-1 size-2 rounded-full\" style={{background:[palette[0],palette[1],'var(--chart-emerald)'][index]}}/>,detail:<><ArrowUpRight aria-hidden=\"true\" className={cn('size-4',index===1?'rotate-90 text-status-error-icon':'text-status-success-icon')}/>+{number([4.7,6.2,3.8][index],1)}%</>}))}/></MarketingFrame>\n}\nfunction ProductPerformance() {\n const {t,number}=useMarketing()\n const [period,setPeriod]=React.useState('1w')\n const factor=period==='1w'?1:period==='1d'?0.6:0.85\n return <MarketingFrame><Header title={t(key('productPerformance'))} value={`${number(22.8*factor,1)}%`}><Trend value={8.4} badge/></Header><Period value={period} onChange={setPeriod}/><div className=\"flex h-39.5 shrink-0 items-end gap-2.5\" aria-label={t(key('productPerformance'))}>{[50,80,100,60,40].map((value,index)=><div key={index} className=\"flex min-w-0 flex-1 flex-col items-center justify-between rounded-lg bg-chart-orange px-1 py-2 text-xs font-medium text-accent-orange-foreground\" style={{height:`${value*factor}%`}}><span>{Math.round(value*factor)}%</span><span>{String.fromCharCode(65+index)}</span></div>)}</div><div className=\"flex flex-1\">{['averageRating','satisfaction','returnRate'].map((name,index)=><div key={name} className=\"flex min-w-0 flex-1 flex-col items-center justify-center border-r border-border last:border-0\"><p className=\"text-base font-medium text-muted-foreground\">{[number(4.7,1),'92%',`${number(4.2,1)}%`][index]}</p><p className=\"mt-1 text-xs text-muted-foreground\">{t(key(name))}</p></div>)}</div></MarketingFrame>\n}\nconst productData=[{image:0,name:'Apple Watch S5 GPS 40MM',count:500},{image:4,name:'AirPods Max Green',count:960},{image:6,name:'iMac M1 24-inch Purple',count:45},{image:8,name:'iPhone 15 Pro 512GB',count:150},{image:1,name:'MacBook Air',count:380},{image:3,name:'AirPods Pro',count:780}]\nfunction ProductRows({ query = '' }: { query?: string }) {\n const {t,number}=useMarketing()\n const matches=productData.filter(product=>product.name.toLocaleLowerCase().includes(query.toLocaleLowerCase()))\n return matches.length ? <div>{matches.map(product=><div key={product.name} className=\"flex min-h-17 items-center gap-3 border-b border-border px-5 py-3 last:border-0\"><img src={marketingProducts[product.image].src} alt=\"\" className=\"size-8 shrink-0 object-contain\"/><div className=\"min-w-0\"><p className=\"truncate text-sm font-medium\">{product.name}</p><p className=\"mt-1 text-xs text-muted-foreground\">{t(key('unitsSold'),undefined,{count:number(product.count)})}</p></div></div>)}</div> : <p role=\"status\" className=\"p-5 text-sm text-muted-foreground\">{t(key('noProducts'))}</p>\n}\nfunction MyProducts() {\n const {t,number}=useMarketing()\n const [query,setQuery]=React.useState('')\n return <MarketingFrame flush><Header title={t(key('myProducts'))} info value={number(468)} className=\"h-23 px-5 py-5\" action={<Details title={t(key('myProducts'))} label={t(key('seeAll'))}><ProductRows query={query}/></Details>}><Trend value={2.1} previous/></Header><div className=\"border-y border-border px-5\"><SearchInput aria-label={t(key('searchProducts'))} placeholder={t(key('searchProducts'))} value={query} onChange={setQuery} className=\"h-11 rounded-none border-0 px-0 shadow-none\"/></div><ScrollArea className=\"min-h-0 flex-1\" scrollbarSize=\"xs\"><ProductRows query={query}/></ScrollArea></MarketingFrame>\n}\nfunction ShippingTracking() {\n const {t,number,date}=useMarketing()\n const [period,setPeriod]=React.useState('weekly')\n const [status,setStatus]=React.useState('delivered')\n const [product,setProduct]=React.useState(0)\n const factor=(status==='delivered'?1:status==='inTransit'?0.55:0.2)*(period==='weekly'?1:period==='monthly'?3:12)\n const data=[0,10,44,59,0,29,0,0,45,29,29,10,29,29,0,59,29,29].map((value,index)=>({day:date(`2024-02-${String(19+Math.floor(index/3)).padStart(2,'0')}T00:00:00Z`,{weekday:'short'}),value:(value+product*3)*factor}))\n return <MarketingFrame flush><div className=\"flex h-35 shrink-0 flex-col gap-4 p-5\"><Header title={t(key('shippingTracking'))} value={number(Math.round(3844*factor))} info action={<Period compact value={period} onChange={setPeriod}/>}><Trend value={1.8} badge/></Header><Choices values={['delivered','inTransit','returned']} value={status} onChange={setStatus} label={t(key('shippingStatus'))}/></div><div className=\"flex h-12 shrink-0 items-center gap-2 border-y border-border px-5\"><img src={marketingProducts[product].src} alt=\"\" className=\"size-6\"/><span className=\"min-w-0 flex-1 truncate text-sm text-muted-foreground\">{marketingProducts[product].name}</span><div className=\"flex\"><CompactButton aria-label={t(key('previousProduct'))} onClick={()=>setProduct(current=>(current+marketingProducts.length-1)%marketingProducts.length)}><ChevronLeft aria-hidden=\"true\"/></CompactButton><CompactButton aria-label={t(key('nextProduct'))} onClick={()=>setProduct(current=>(current+1)%marketingProducts.length)}><ChevronRight aria-hidden=\"true\"/></CompactButton></div></div><ChartContainer config={{value:{label:t(key(status)),color:chartColor}}} className=\"min-h-0 flex-1\" aria-label={t(key('shippingTracking'))}><ResponsiveContainer width=\"100%\" height=\"100%\"><LineChart data={data} margin={{top:0,right:20,left:20,bottom:0}}><CartesianGrid stroke=\"var(--border)\"/><XAxis dataKey=\"day\" interval={2} tickLine={false} axisLine={false} tick={{fontSize:12,fill:'var(--muted-foreground)'}}/><Tooltip content={<ChartTooltipContent/>}/><Line type=\"stepAfter\" dataKey=\"value\" dot={false} stroke={chartColor} strokeWidth={1.5} isAnimationActive={false}/></LineChart></ResponsiveContainer></ChartContainer></MarketingFrame>\n}\nfunction ProductCategories() {\n const {t,number}=useMarketing()\n const [category,setCategory]=React.useState(0)\n const categories=['accessories','computers','audio']\n const percentage=[58,72,34][category]\n const details=<DataList rows={categories.map((name,index)=>({label:t(key(name)),value:t(key('productsCount'),undefined,{count:[45,32,18][index]})}))}/>\n return <MarketingFrame compact className=\"gap-4\"><Header title={t(key('productCategories'))} value={`${percentage}%`} info action={<Details title={t(key('productCategories'))}>{details}</Details>}><Trend value={2.1} previous/></Header><div className=\"flex h-7.5 shrink-0 gap-0.5\" role=\"meter\" aria-label={t(key('productCategories'))} aria-valuemin={0} aria-valuemax={100} aria-valuenow={percentage}>{Array.from({length:36},(_,index)=><span key={index} className={cn('min-w-0 flex-1',index<Math.round(percentage/100*36)?'bg-chart-orange':'bg-border')}/>)}</div><div className=\"flex min-h-5 items-center gap-2 text-sm text-muted-foreground\"><span>{t(key(categories[category]))}</span><div className=\"flex\"><CompactButton aria-label={t(key('previousCategory'))} onClick={()=>setCategory(current=>(current+2)%3)}><ChevronLeft aria-hidden=\"true\"/></CompactButton><CompactButton aria-label={t(key('nextCategory'))} onClick={()=>setCategory(current=>(current+1)%3)}><ChevronRight aria-hidden=\"true\"/></CompactButton></div><span className=\"ml-auto whitespace-nowrap\">{t(key('productsCount'),undefined,{count:number([45,32,18][category])})}</span><Trend value={3.2}/></div></MarketingFrame>\n}\nfunction CustomerSegments() {\n const {t,money}=useMarketing()\n const data=[{label:'premium',value:32,amount:9450},{label:'regular',value:46,amount:8320},{label:'new',value:20,amount:3280}]\n return <MarketingFrame compact><div className=\"flex h-5 shrink-0 items-center justify-between gap-2\"><h3 className=\"flex items-center gap-1 text-sm font-medium text-muted-foreground\">{t(key('customerSegments'))}<Info aria-hidden=\"true\" className=\"size-3 text-text-disabled\"/></h3><Trend value={5.8} previous/></div><div className=\"flex h-25 items-center gap-5\"><ChartContainer config={Object.fromEntries(data.map((item,index)=>[item.label,{label:t(key(item.label)),color:palette[index]}]))} className=\"size-25 shrink-0\" aria-label={t(key('customerSegments'))}><PieChart width={100} height={100}><Pie data={data} dataKey=\"value\" nameKey=\"label\" cx={45} cy={45} innerRadius={34} outerRadius={49} paddingAngle={3} stroke=\"none\" startAngle={90} endAngle={450} isAnimationActive={false}>{data.map((item,index)=><Cell key={item.label} fill={palette[index]}/>)}</Pie><Tooltip content={<ChartTooltipContent/>}/></PieChart></ChartContainer><div className=\"flex min-w-0 flex-1 flex-col gap-3\">{data.map((item,index)=><div key={item.label} className=\"flex items-center gap-2 text-sm text-muted-foreground\"><span className=\"size-2 shrink-0 rounded-full\" style={{background:palette[index]}}/><span className=\"min-w-0 flex-1 truncate\">{t(key(item.label))}</span><span>{money(item.amount)}</span><span className=\"text-xs\">{item.value}%</span></div>)}</div></div></MarketingFrame>\n}\nfunction SalesChannels() {\n const {t,number,date}=useMarketing()\n const [period,setPeriod]=React.useState('weekly')\n const multiplier=period==='weekly'?1:period==='monthly'?4:52\n return <MarketingFrame compact className=\"gap-4\"><Header title={t(key('salesChannels'))} value={number(246*multiplier)} info action={<Period compact value={period} onChange={setPeriod}/>}><Trend value={2.1} previous/></Header><div className=\"space-y-2\"><div className=\"flex justify-between text-xs text-muted-foreground\"><span>{date('2024-02-21T00:00:00Z')}</span><span>{date(period==='weekly'?'2024-02-28T00:00:00Z':period==='monthly'?'2024-03-21T00:00:00Z':'2025-02-21T00:00:00Z')}</span></div><div className=\"flex h-10.5 gap-1\" aria-label={t(key('salesChannels'))}>{[26,21,16,13,10,5,2].map((width,index)=><span key={index} className=\"rounded-sm\" style={{flex:width,background:['var(--chart-indigo)','var(--chart-cyan)','var(--chart-violet)','var(--chart-orange)','var(--chart-amber)','var(--chart-emerald)','var(--chart-teal)'][index]}}/>)}</div></div></MarketingFrame>\n}\nfunction CampaignData() {\n const {t,money}=useMarketing()\n return <MarketingFrame compact flush><Header title={t(key('campaignData'))} value={money(1750)} info className=\"h-23.5 px-5 py-5\" action={<Details title={t(key('campaignData'))}><DataList rows={[{label:t(key('campaignBudget')),value:money(1750)},{label:t(key('usedBudget')),value:money(32900)},{label:t(key('campaignProgress')),value:'45%'}]}/></Details>}><Badge size={20} appearance=\"lighter\" tone=\"success\">{t(key('lastDays'),undefined,{count:15})}</Badge></Header><div className=\"flex min-h-0 flex-1 border-t border-border\"><ChartContainer config={{value:{label:t(key('campaignData')),color:chartColor}}} className=\"w-44 min-w-0 shrink-0 border-r border-accent-orange-border\" aria-label={t(key('campaignData'))}><ResponsiveContainer width=\"100%\" height=\"100%\"><AreaChart data={salesSeries.slice(0,18).map((value,index)=>({index,value:value+index*2}))} margin={{top:0,left:0,right:0,bottom:0}}><Tooltip content={<ChartTooltipContent/>}/><Area dataKey=\"value\" type=\"linear\" stroke={chartColor} strokeWidth={1.5} fill={chartColor} fillOpacity={0.18} isAnimationActive={false}/></AreaChart></ResponsiveContainer></ChartContainer><div className=\"flex min-w-0 flex-1 flex-col justify-end p-4\"><p className=\"text-base font-medium\">45%</p><p className=\"text-xs text-muted-foreground\">{t(key('budgetUsed'),undefined,{amount:money(32900)})}</p></div></div></MarketingFrame>\n}\ntype TicketStatus = 'solved' | 'inProgress' | 'pending'\nconst ticketSamples: {name:string;status:TicketStatus;minutes:number;priority:string;id:number}[]=[{name:'James Brown',status:'solved',minutes:12,priority:'high',id:98745},{name:'Sophia Williams',status:'inProgress',minutes:48,priority:'medium',id:98744},{name:'Matthew Johnson',status:'pending',minutes:19,priority:'low',id:98743}]\nfunction SupportAnalytics() {\n const {t,number,date}=useMarketing()\n const [period,setPeriod]=React.useState('weekly')\n const [category,setCategory]=React.useState('technical')\n const [tab,setTab]=React.useState('allTickets')\n const [tickets,setTickets]=React.useState(ticketSamples)\n const [selected,setSelected]=React.useState<number|null>(null)\n const factor=(['technical','billing','account','product'].indexOf(category)+4)/4*(period==='weekly'?1:period==='monthly'?4:52)\n const current=tickets.find(ticket=>ticket.id===selected)\n const visible=tickets.filter(ticket=>tab==='allTickets'||(tab==='openTickets'?ticket.status!=='solved':ticket.status==='solved'))\n const metrics=[{label:'firstResponse',detail:'belowSla',value:t(key('minutes'),undefined,{count:Math.round(15*factor)}),delta:-22},{label:'resolutionTime',detail:'meetingSla',value:t(key('minutes'),undefined,{count:Math.round(48*factor)}),delta:-18},{label:'csat',detail:'aboveTarget',value:'4.8/5',delta:-0.3}]\n return <><MarketingFrame tall flush><div className=\"flex h-34 shrink-0 flex-col gap-3 p-5\"><Header title={t(key('supportAnalytics'))} value={number(Math.round(2450*factor))} info action={<Period compact value={period} onChange={setPeriod}/>}><Trend value={5.4}/><span className=\"text-sm text-muted-foreground\">{t(key('totalTickets'))}</span></Header><Choices values={['technical','billing','account','product']} value={category} onChange={setCategory} label={t(key('ticketCategory'))}/></div><ChartContainer config={{value:{label:t(key('responseTime')),color:chartColor}}} className=\"h-44 shrink-0 border-y border-border px-3 pt-4\" aria-label={t(key('responseTime'))}><ResponsiveContainer width=\"100%\" height=\"100%\"><BarChart data={[10,34,44,58,44,34,34].map((value,index)=>({day:date(`2024-02-${19+index}T00:00:00Z`,{weekday:'short'}),value:Math.round(value*factor)}))} margin={{top:0,right:0,left:0,bottom:0}}><XAxis dataKey=\"day\" axisLine={false} tickLine={false} tick={{fontSize:12,fill:'var(--muted-foreground)'}}/><Tooltip content={<ChartTooltipContent/>}/><ReferenceLine y={30*factor} stroke=\"var(--muted-foreground)\" strokeDasharray=\"4 4\" label={{value:t(key('targetMinutes'),undefined,{count:30}),fill:'var(--foreground)',fontSize:12,position:'insideTopLeft'}}/><Bar dataKey=\"value\" radius={[6,6,6,6]} isAnimationActive={false}>{Array.from({length:7},(_,index)=><Cell key={index} fill={index===4?chartColor:'var(--border)'}/>)}</Bar></BarChart></ResponsiveContainer></ChartContainer><Tabs value={tab} onValueChange={setTab} variant=\"underline\" className=\"min-h-0 flex-1 gap-0\"><TabsList className=\"h-12 w-full shrink-0 justify-start gap-4 px-5\"><TabsTrigger value=\"allTickets\" className=\"min-w-0 flex-auto justify-center px-0 text-sm font-normal data-[state=active]:border-accent-orange-border hover:text-accent-orange-text\">{t(key('allTickets'))}</TabsTrigger><TabsTrigger value=\"openTickets\" className=\"min-w-0 flex-auto justify-center px-0 text-sm font-normal data-[state=active]:border-accent-orange-border hover:text-accent-orange-text\">{t(key('openTickets'))}</TabsTrigger><TabsTrigger value=\"solvedTickets\" className=\"min-w-0 flex-auto justify-center px-0 text-sm font-normal data-[state=active]:border-accent-orange-border hover:text-accent-orange-text\">{t(key('solvedTickets'))}</TabsTrigger></TabsList>{['allTickets','openTickets','solvedTickets'].map(value=><TabsContent key={value} value={value} className=\"m-0 flex min-h-0 flex-1 flex-col\"><div className=\"flex shrink-0 flex-col gap-4 px-5 pb-5 pt-4\"><div className=\"flex text-xs text-muted-foreground\"><span className=\"flex-1\">{t(key('channels'))}</span><span className=\"w-18\">{t(key('actual'))}</span><span className=\"w-10\">{t(key('average'))}</span></div>{metrics.map((metric,index)=><div key={metric.label} className=\"flex items-center text-sm\"><div className=\"min-w-0 flex-1\"><p>{t(key(metric.label))}</p><p className=\"mt-1 text-xs text-muted-foreground\">{t(key(metric.detail))}</p></div><span className=\"w-18 text-muted-foreground\">{metric.value}</span><span className={cn('w-10',index===2?'text-status-error-icon':'text-status-success-icon')}>{number(metric.delta,index===2?1:0)}%</span></div>)}</div><div className=\"min-h-0 flex-1 border-t border-border px-5 pt-4\"><p className=\"mb-3 text-xs text-muted-foreground\">{t(key('recentTickets'))}</p><ScrollArea className=\"h-40\" scrollbarSize=\"xs\"><div className=\"flex flex-col gap-4\">{visible.map((ticket,index)=><Button key={ticket.id} variant=\"ghost\" onClick={()=>setSelected(ticket.id)} className=\"h-auto min-h-10 w-full justify-start gap-3 p-0 text-left font-normal hover:bg-muted\"><Avatar label={ticket.name} size={32} className={index===1?'bg-chart-amber/20 text-status-warning-foreground':index===2?'bg-chart-violet/20 text-accent-indigo':'bg-muted'}/><div className=\"min-w-0 flex-1\"><p className=\"truncate text-sm font-medium\">{ticket.name}</p><p className=\"mt-1 flex items-center gap-1 text-xs text-muted-foreground\">{t(key(ticket.priority))} · <span className={ticket.status==='solved'?'text-status-success-icon':'text-status-warning-icon'}>{t(key(ticket.status))}</span></p></div><div className=\"text-right\"><p className=\"text-sm\">{t(key('minutes'),undefined,{count:ticket.minutes})}</p><p className=\"mt-1 text-xs text-muted-foreground\">#TKT-{ticket.id}</p></div></Button>)}</div></ScrollArea></div></TabsContent>)}</Tabs></MarketingFrame><Dialog open={selected!==null} onOpenChange={open=>{if(!open)setSelected(null)}}><DialogContent onKeyDownCapture={event=>{if(event.key==='Enter'&&(event.ctrlKey||event.metaKey)){event.preventDefault();setSelected(null)}}}><DialogHeader><DialogTitle>{current?.name}</DialogTitle><DialogDescription>{current?`#TKT-${current.id}`:t(key('recentTickets'))}</DialogDescription></DialogHeader>{current?<DataList rows={[{label:t(key('status')),value:t(key(current.status))},{label:t(key('priority')),value:t(key(current.priority))}]}/>:null}<DialogFooter><Button variant=\"outline\" onClick={()=>setSelected(null)}>{t(key('close'))}</Button><Button disabled={current?.status==='solved'} onClick={()=>{setTickets(items=>items.map(ticket=>ticket.id===selected?{...ticket,status:'solved'}:ticket));setSelected(null)}}>{t(key('resolveTicket'))}</Button></DialogFooter></DialogContent></Dialog></>\n}\nconst activityKinds=[{key:'inventory',icon:'stack-line',color:'text-chart-violet',detail:'inventoryDetail'},{key:'price',icon:'price-tag-line',color:'text-status-success-icon',detail:'priceDetail'},{key:'newProduct',icon:'add-circle-line',color:'text-chart-violet',detail:'newProductDetail'},{key:'images',icon:'image-line',color:'text-status-warning-icon',detail:'imagesDetail'},{key:'descriptionUpdated',icon:'pencil-line',color:'text-muted-foreground',detail:'descriptionDetail'}] satisfies {key:string;icon:string;color:string;detail:string}[]\nfunction RecentActivities() {\n const {t,date}=useMarketing()\n const [period,setPeriod]=React.useState('today')\n const [query,setQuery]=React.useState('')\n const records=Array.from({length:period==='today'?7:period==='yesterday'?2:9},(_,index)=>({id:index,...activityKinds[index%5]})).filter(item=>[t(key(item.key)),t(key(item.detail)),t(key('activityProduct'))].join(' ').toLocaleLowerCase().includes(query.toLocaleLowerCase()))\n const list=<div className=\"flex flex-col gap-5\">{records.map((item,index)=><div key={item.id} className=\"relative flex gap-4\"><div className=\"relative flex w-7 shrink-0 justify-center\"><span className=\"z-10 flex size-7 items-center justify-center rounded-full border border-border bg-background shadow-xs\"><span aria-hidden=\"true\" className={cn('size-4 bg-current',item.color)} style={{maskImage:`url(\"${marketingActivityIcons[item.key as keyof typeof marketingActivityIcons]}\")`,WebkitMaskImage:`url(\"${marketingActivityIcons[item.key as keyof typeof marketingActivityIcons]}\")`,maskSize:'contain',WebkitMaskSize:'contain'}}/></span>{index<records.length-1?<span aria-hidden=\"true\" className=\"absolute bottom-0 top-8 w-px bg-border\"/>:null}</div><div className=\"min-w-0 flex-1\"><div className=\"flex items-center gap-2\"><p className=\"min-w-0 flex-1 text-sm font-medium\">{t(key(item.key))}</p><time className=\"text-overline text-muted-foreground\">{date('2024-02-21T11:30:00Z',{hour:'numeric',minute:'2-digit'})}</time></div><p className=\"mt-1 text-xs text-muted-foreground\">{item.key==='price'?t(key('seasonalDiscount')):t(key('activityProduct'))}</p><p className=\"mt-1 text-xs text-muted-foreground\">{t(key(item.detail))}</p></div></div>)}{!records.length?<p role=\"status\" className=\"text-sm text-muted-foreground\">{t(key('noActivities'))}</p>:null}</div>\n return <MarketingFrame tall flush><div className=\"flex h-33 shrink-0 flex-col gap-4 p-5\"><div className=\"flex items-start\"><div className=\"flex-1\"><h3 className=\"text-sm font-medium\">{t(key('recentActivities'))}</h3><p className=\"mt-1 text-sm text-muted-foreground\">{t(key('newActivities'),undefined,{count:5})}</p></div><Details title={t(key('recentActivities'))}>{list}</Details></div><Choices values={['today','yesterday','thisWeek']} value={period} onChange={setPeriod} label={t(key('activityPeriod'))}/></div><div className=\"border-y border-border px-5\"><SearchInput aria-label={t(key('searchActivities'))} placeholder={t(key('searchActivities'))} value={query} onChange={setQuery} className=\"h-12 rounded-none border-0 px-0 shadow-none\"/></div><ScrollArea className=\"min-h-0 flex-1 px-5 py-5\" scrollbarSize=\"xs\">{list}</ScrollArea></MarketingFrame>\n}\nconst widgets: Record<MarketingWidgetKind, React.ComponentType> = {\n 'total-sales':TotalSales,'total-visitors':TotalVisitors,'conversion-rate':ConversionRate,'user-retention':Retention,'weekly-visitors':WeeklyVisitors,'visitor-channels':Channels,geography:Geography,'realtime-visitors':RealtimeVisitors,'marketing-channels':()=> <Channels marketing/>,'product-performance':ProductPerformance,'shipping-tracking':ShippingTracking,'my-products':MyProducts,'product-categories':ProductCategories,'customer-segments':CustomerSegments,'sales-channels':SalesChannels,'campaign-data':CampaignData,'support-analytics':SupportAnalytics,'recent-activities':RecentActivities,\n}\nexport function MarketingWidgetDemo({kind}:{kind:MarketingWidgetKind}) { const Widget=widgets[kind]; return <Widget/> }\n"
|
|
3
|
+
export const marketingWidgetExampleCode = (expression: string) => source + "\n" + expression + "\n"
|