@unbox-plus/cli 0.20.4
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/LICENSE.md +46 -0
- package/README.md +175 -0
- package/bin/cli.js +439 -0
- package/package.json +43 -0
- package/src/colors.js +100 -0
- package/src/presets.js +241 -0
- package/src/theme.js +253 -0
- package/template/.claude/agents/assets-marca.md +40 -0
- package/template/.claude/agents/avaliador-visual.md +48 -0
- package/template/.claude/agents/branding-briefing.md +401 -0
- package/template/.claude/agents/conteudo-secao.md +46 -0
- package/template/.claude/agents/paginas-legais.md +44 -0
- package/template/.claude/settings.json +3 -0
- package/template/.claude/skills/frontend-design/ATTRIBUTION.md +3 -0
- package/template/.claude/skills/frontend-design/LICENSE.txt +177 -0
- package/template/.claude/skills/frontend-design/SKILL.md +55 -0
- package/template/.claude/skills/web-design-guidelines/ATTRIBUTION.md +3 -0
- package/template/.claude/skills/web-design-guidelines/SKILL.md +50 -0
- package/template/.claude/skills/web-design-guidelines/guidelines-snapshot.md +180 -0
- package/template/.env.example +158 -0
- package/template/.mcp.json.example +14 -0
- package/template/.nvmrc +1 -0
- package/template/CLAUDE.md +333 -0
- package/template/COVERAGE.md +80 -0
- package/template/DEPLOY.md +179 -0
- package/template/QA.md +108 -0
- package/template/README.md +152 -0
- package/template/agents/CONSTRUCAO.md +207 -0
- package/template/agents/MANAGER.md +192 -0
- package/template/agents/PADROES.md +255 -0
- package/template/agents/definitions/00-scaffold.md +56 -0
- package/template/agents/definitions/01-layout.md +47 -0
- package/template/agents/definitions/02-homepage.md +70 -0
- package/template/agents/definitions/03-catalog.md +86 -0
- package/template/agents/definitions/04-pdp.md +97 -0
- package/template/agents/definitions/05-cart.md +116 -0
- package/template/agents/definitions/06-checkout.md +146 -0
- package/template/agents/definitions/07-auth.md +130 -0
- package/template/agents/definitions/08-customer.md +55 -0
- package/template/agents/definitions/09-promotions.md +62 -0
- package/template/agents/definitions/10-feedback.md +128 -0
- package/template/agents/definitions/11-seo-infra.md +159 -0
- package/template/agents/definitions/12-deploy.md +173 -0
- package/template/agents/definitions/13-cro.md +275 -0
- package/template/agents/definitions/14-seo.md +388 -0
- package/template/agents/definitions/15-branding.md +26 -0
- package/template/agents/definitions/16-qa-visual.md +138 -0
- package/template/agents/definitions/17-aeo.md +257 -0
- package/template/app/(loja)/busca/page.tsx +72 -0
- package/template/app/(loja)/carrinho/oferta/page.tsx +57 -0
- package/template/app/(loja)/carrinho/page.tsx +160 -0
- package/template/app/(loja)/categoria/[tagSlug]/layout.tsx +31 -0
- package/template/app/(loja)/categoria/[tagSlug]/loading.tsx +17 -0
- package/template/app/(loja)/categoria/[tagSlug]/page.tsx +49 -0
- package/template/app/(loja)/checkout/page.tsx +80 -0
- package/template/app/(loja)/checkout/pix/[ref]/page.tsx +133 -0
- package/template/app/(loja)/conta/assinaturas/[referenceId]/page.tsx +112 -0
- package/template/app/(loja)/conta/assinaturas/page.tsx +51 -0
- package/template/app/(loja)/conta/enderecos/page.tsx +20 -0
- package/template/app/(loja)/conta/entrar/page.tsx +119 -0
- package/template/app/(loja)/conta/page.tsx +45 -0
- package/template/app/(loja)/conta/pedidos/[referenceId]/page.tsx +49 -0
- package/template/app/(loja)/conta/pedidos/page.tsx +104 -0
- package/template/app/(loja)/conta/preferencias/page.tsx +24 -0
- package/template/app/(loja)/devolucoes/page.tsx +36 -0
- package/template/app/(loja)/layout.tsx +22 -0
- package/template/app/(loja)/oferta/page.tsx +57 -0
- package/template/app/(loja)/page.tsx +86 -0
- package/template/app/(loja)/pedido/[referenceId]/page.tsx +78 -0
- package/template/app/(loja)/privacidade/page.tsx +113 -0
- package/template/app/(loja)/produto/[productSlug]/layout.tsx +23 -0
- package/template/app/(loja)/produto/[productSlug]/loading.tsx +20 -0
- package/template/app/(loja)/produto/[productSlug]/page.tsx +372 -0
- package/template/app/(loja)/produtos/loading.tsx +16 -0
- package/template/app/(loja)/produtos/page.tsx +33 -0
- package/template/app/(loja)/termos/page.tsx +104 -0
- package/template/app/acesso/page.tsx +129 -0
- package/template/app/api/account/addresses/route.ts +39 -0
- package/template/app/api/account/exists/route.ts +21 -0
- package/template/app/api/account/me/route.ts +25 -0
- package/template/app/api/account/otp/route.ts +26 -0
- package/template/app/api/account/preferences/route.ts +19 -0
- package/template/app/api/account/signin/route.ts +27 -0
- package/template/app/api/account/signout/route.ts +9 -0
- package/template/app/api/acesso/route.ts +171 -0
- package/template/app/api/capi/route.ts +34 -0
- package/template/app/api/cart/coupon/route.ts +49 -0
- package/template/app/api/cart/items/route.ts +45 -0
- package/template/app/api/cart/link/route.ts +69 -0
- package/template/app/api/cart/route.ts +113 -0
- package/template/app/api/cart/share/route.ts +21 -0
- package/template/app/api/cep/[code]/route.ts +18 -0
- package/template/app/api/checkout/address/route.ts +24 -0
- package/template/app/api/checkout/email/route.ts +41 -0
- package/template/app/api/checkout/installments/route.ts +16 -0
- package/template/app/api/checkout/route.ts +110 -0
- package/template/app/api/checkout/shipping/route.ts +65 -0
- package/template/app/api/checkout-destination/route.ts +31 -0
- package/template/app/api/order/[ref]/route.ts +17 -0
- package/template/app/api/payment-link/route.ts +28 -0
- package/template/app/api/revalidate/route.ts +52 -0
- package/template/app/api/shipping/quote/route.ts +29 -0
- package/template/app/api/subscriptions/[id]/route.ts +52 -0
- package/template/app/api/track/route.ts +28 -0
- package/template/app/api/unbox/catalogo/route.ts +136 -0
- package/template/app/api/unbox/paginas/route.ts +167 -0
- package/template/app/api/unbox/vitrine/route.ts +84 -0
- package/template/app/api/webhooks/unbox/route.ts +97 -0
- package/template/app/apple-icon.svg +5 -0
- package/template/app/error.tsx +15 -0
- package/template/app/globals.css +457 -0
- package/template/app/icon.svg +4 -0
- package/template/app/layout.tsx +90 -0
- package/template/app/llms.txt/route.ts +43 -0
- package/template/app/manifest.ts +21 -0
- package/template/app/not-found.tsx +18 -0
- package/template/app/opengraph-image.tsx +35 -0
- package/template/app/robots.ts +31 -0
- package/template/app/sitemap.ts +42 -0
- package/template/bootstrap.sh +87 -0
- package/template/components/account/account-shell.tsx +47 -0
- package/template/components/account/address-book.tsx +147 -0
- package/template/components/account/preferences-form.tsx +77 -0
- package/template/components/account/reorder-button.tsx +47 -0
- package/template/components/account/signout-button.tsx +20 -0
- package/template/components/account/subscription-actions.tsx +204 -0
- package/template/components/account-nav.tsx +41 -0
- package/template/components/address-fields.tsx +102 -0
- package/template/components/analytics/data-layer-ready.tsx +16 -0
- package/template/components/analytics/purchase-tracker.tsx +32 -0
- package/template/components/brand-search.tsx +34 -0
- package/template/components/cart/cart-provider.tsx +340 -0
- package/template/components/cart/mini-cart.tsx +228 -0
- package/template/components/cart-button.tsx +35 -0
- package/template/components/catalog/catalog-client.tsx +523 -0
- package/template/components/catalog/grid-skeleton.tsx +19 -0
- package/template/components/catalog/pager.tsx +66 -0
- package/template/components/catalog/product-grid-card.tsx +52 -0
- package/template/components/catalog/product-grid.tsx +27 -0
- package/template/components/checkout/checkout-client.tsx +1333 -0
- package/template/components/chrome/announce-bar.tsx +57 -0
- package/template/components/chrome/chrome-recipe.ts +13 -0
- package/template/components/chrome/footers/captura-botao.tsx +28 -0
- package/template/components/chrome/footers/colunas.tsx +110 -0
- package/template/components/chrome/footers/conversao.tsx +90 -0
- package/template/components/chrome/footers/editorial.tsx +98 -0
- package/template/components/chrome/footers/minimal.tsx +55 -0
- package/template/components/chrome/header-bar-mobile.tsx +64 -0
- package/template/components/chrome/headers/centralizado.tsx +71 -0
- package/template/components/chrome/headers/classico.tsx +64 -0
- package/template/components/chrome/headers/compacto.tsx +66 -0
- package/template/components/chrome/headers/equilibrado.tsx +73 -0
- package/template/components/chrome/headers/imersivo.tsx +62 -0
- package/template/components/chrome/registry.ts +70 -0
- package/template/components/chrome/solid-on-scroll.tsx +39 -0
- package/template/components/empty-state.tsx +22 -0
- package/template/components/home/combo-card-compact.tsx +67 -0
- package/template/components/home/combos-home.tsx +64 -0
- package/template/components/home/combos-section.tsx +177 -0
- package/template/components/home/home-recipe.ts +41 -0
- package/template/components/home/sections/attributes-marquee.tsx +178 -0
- package/template/components/home/sections/benefits.tsx +80 -0
- package/template/components/home/sections/bloco-html.tsx +47 -0
- package/template/components/home/sections/catalogo.ts +165 -0
- package/template/components/home/sections/category-pills.tsx +32 -0
- package/template/components/home/sections/combos-carousel.tsx +141 -0
- package/template/components/home/sections/comparison.tsx +73 -0
- package/template/components/home/sections/founder-story.tsx +40 -0
- package/template/components/home/sections/hero.tsx +187 -0
- package/template/components/home/sections/kits.tsx +11 -0
- package/template/components/home/sections/media-cards.tsx +70 -0
- package/template/components/home/sections/newsletter.tsx +62 -0
- package/template/components/home/sections/product-showcase.tsx +175 -0
- package/template/components/home/sections/purchase-hero.tsx +187 -0
- package/template/components/home/sections/quote-banner.tsx +50 -0
- package/template/components/home/sections/registry.ts +186 -0
- package/template/components/home/sections/reviews-carousel.tsx +66 -0
- package/template/components/home/sections/reviews.tsx +68 -0
- package/template/components/home/sections/ritual.tsx +96 -0
- package/template/components/home/sections/savings.tsx +47 -0
- package/template/components/home/sections/social-row.tsx +65 -0
- package/template/components/home/sections/spec-table.tsx +117 -0
- package/template/components/home/sections/stars.tsx +12 -0
- package/template/components/home/sections/stats-grid.tsx +54 -0
- package/template/components/home/sections/trust-bar.tsx +56 -0
- package/template/components/home/sections/trust-strip.tsx +40 -0
- package/template/components/home/sections/video-wall.tsx +59 -0
- package/template/components/landing/landing-recipe.ts +20 -0
- package/template/components/landing/oferta-sections.tsx +40 -0
- package/template/components/landing/product-picker.tsx +257 -0
- package/template/components/mobile-nav.tsx +71 -0
- package/template/components/order-status.tsx +163 -0
- package/template/components/powered-by-unbox.tsx +27 -0
- package/template/components/product/pdp/buy-box.tsx +604 -0
- package/template/components/product/pdp/catalog-grid.tsx +113 -0
- package/template/components/product/pdp/faq-modelo.ts +86 -0
- package/template/components/product/pdp/gallery.tsx +87 -0
- package/template/components/product/pdp/interactive.tsx +200 -0
- package/template/components/product/pdp/newsletter.tsx +64 -0
- package/template/components/product/pdp/payment-chips.tsx +22 -0
- package/template/components/product/pdp/pdp-view.tsx +227 -0
- package/template/components/product/pdp/recommendations.tsx +144 -0
- package/template/components/product/pdp/sections.tsx +173 -0
- package/template/components/quantity-stepper.tsx +36 -0
- package/template/components/search-box.tsx +37 -0
- package/template/components/site-footer.tsx +59 -0
- package/template/components/site-header.tsx +64 -0
- package/template/components/ui/accordion.tsx +72 -0
- package/template/components/ui/alert-dialog.tsx +187 -0
- package/template/components/ui/alert.tsx +76 -0
- package/template/components/ui/aspect-ratio.tsx +22 -0
- package/template/components/ui/avatar.tsx +109 -0
- package/template/components/ui/badge.tsx +52 -0
- package/template/components/ui/breadcrumb.tsx +125 -0
- package/template/components/ui/button.tsx +58 -0
- package/template/components/ui/card.tsx +103 -0
- package/template/components/ui/carousel.tsx +242 -0
- package/template/components/ui/command.tsx +196 -0
- package/template/components/ui/dialog.tsx +160 -0
- package/template/components/ui/dropdown-menu.tsx +268 -0
- package/template/components/ui/foto.tsx +54 -0
- package/template/components/ui/input-group.tsx +158 -0
- package/template/components/ui/input-otp.tsx +87 -0
- package/template/components/ui/input.tsx +20 -0
- package/template/components/ui/label.tsx +20 -0
- package/template/components/ui/navigation-menu.tsx +168 -0
- package/template/components/ui/pagination.tsx +132 -0
- package/template/components/ui/popover.tsx +90 -0
- package/template/components/ui/progress.tsx +83 -0
- package/template/components/ui/radio-group.tsx +38 -0
- package/template/components/ui/scroll-area.tsx +55 -0
- package/template/components/ui/select.tsx +201 -0
- package/template/components/ui/separator.tsx +25 -0
- package/template/components/ui/sheet.tsx +138 -0
- package/template/components/ui/skeleton.tsx +13 -0
- package/template/components/ui/sonner.tsx +44 -0
- package/template/components/ui/switch.tsx +32 -0
- package/template/components/ui/table.tsx +116 -0
- package/template/components/ui/tabs.tsx +82 -0
- package/template/components/ui/textarea.tsx +18 -0
- package/template/components/ui/toggle-group.tsx +89 -0
- package/template/components/ui/toggle.tsx +45 -0
- package/template/components/ui/tooltip.tsx +66 -0
- package/template/components.json +25 -0
- package/template/eslint.config.mjs +36 -0
- package/template/gitignore +21 -0
- package/template/lib/analytics.ts +369 -0
- package/template/lib/api.ts +60 -0
- package/template/lib/capi.ts +87 -0
- package/template/lib/cart-link.ts +51 -0
- package/template/lib/cart-normalize.ts +142 -0
- package/template/lib/cart-recovery.ts +23 -0
- package/template/lib/cart-response.ts +48 -0
- package/template/lib/catalog-map.ts +54 -0
- package/template/lib/catalog.ts +3 -0
- package/template/lib/checkout-lock.ts +21 -0
- package/template/lib/checkout-nav.ts +22 -0
- package/template/lib/config.ts +51 -0
- package/template/lib/crm.ts +40 -0
- package/template/lib/customer-session.ts +21 -0
- package/template/lib/dataloader.ts +23 -0
- package/template/lib/editable/config.ts +11 -0
- package/template/lib/editable/document.ts +1762 -0
- package/template/lib/editable/index.ts +3 -0
- package/template/lib/editable/primitives.tsx +821 -0
- package/template/lib/editable/provider.tsx +861 -0
- package/template/lib/editable/rastreio-navegacao.tsx +65 -0
- package/template/lib/editable/rastreio.tsx +168 -0
- package/template/lib/editable/server.ts +189 -0
- package/template/lib/editable/tokens.ts +22 -0
- package/template/lib/editable/verify.ts +24 -0
- package/template/lib/enrichment/combos.ts +184 -0
- package/template/lib/enrichment/index.ts +188 -0
- package/template/lib/enrichment/products.json +1 -0
- package/template/lib/env-check.ts +47 -0
- package/template/lib/format.ts +130 -0
- package/template/lib/icons.ts +67 -0
- package/template/lib/json-ld.ts +9 -0
- package/template/lib/llms-txt.ts +75 -0
- package/template/lib/mockup.ts +24 -0
- package/template/lib/newsletter.ts +12 -0
- package/template/lib/orders.ts +160 -0
- package/template/lib/queries.ts +66 -0
- package/template/lib/ratelimit.ts +47 -0
- package/template/lib/rotas-editaveis.ts +192 -0
- package/template/lib/sanitize.ts +36 -0
- package/template/lib/schemas.ts +78 -0
- package/template/lib/session.ts +98 -0
- package/template/lib/store-config.ts +67 -0
- package/template/lib/unbox/client.ts +829 -0
- package/template/lib/unbox/customer.ts +224 -0
- package/template/lib/unbox/errors.ts +97 -0
- package/template/lib/unbox/index.ts +10 -0
- package/template/lib/unbox/store.ts +113 -0
- package/template/lib/unbox/types.ts +195 -0
- package/template/lib/unbox/webhooks.ts +66 -0
- package/template/lib/utils.ts +6 -0
- package/template/lib/vitrine.ts +242 -0
- package/template/lib/webhook-store.ts +22 -0
- package/template/middleware.ts +178 -0
- package/template/next.config.ts +63 -0
- package/template/package.json +62 -0
- package/template/postcss.config.mjs +5 -0
- package/template/public/brand/coll/default.png +0 -0
- package/template/public/brand/hero-desktop.svg +12 -0
- package/template/public/brand/hero-mobile.svg +12 -0
- package/template/public/brand/heros/boutique-desktop.svg +11 -0
- package/template/public/brand/heros/boutique-mobile.svg +11 -0
- package/template/public/brand/heros/editorial-desktop.svg +11 -0
- package/template/public/brand/heros/editorial-mobile.svg +11 -0
- package/template/public/brand/heros/essencial-desktop.svg +12 -0
- package/template/public/brand/heros/essencial-mobile.svg +12 -0
- package/template/public/brand/heros/promocional-desktop.svg +12 -0
- package/template/public/brand/heros/promocional-mobile.svg +12 -0
- package/template/public/brand/logo-chrome.svg +6 -0
- package/template/public/brand/logo-white.svg +6 -0
- package/template/public/brand/logo.svg +7 -0
- package/template/public/brand/pay-amex.webp +0 -0
- package/template/public/brand/pay-elo.webp +0 -0
- package/template/public/brand/pay-mastercard.webp +0 -0
- package/template/public/brand/pay-pix.webp +0 -0
- package/template/public/brand/pay-visa.webp +0 -0
- package/template/public/brand/ph/avatar-a.svg +5 -0
- package/template/public/brand/ph/avatar-b.svg +5 -0
- package/template/public/brand/ph/avatar-c.svg +5 -0
- package/template/public/brand/ph/avatar-d.svg +5 -0
- package/template/public/brand/ph/photo-a.svg +7 -0
- package/template/public/brand/ph/photo-b.svg +6 -0
- package/template/public/brand/ph/photo-c.svg +6 -0
- package/template/public/brand/ph/poster-a.svg +6 -0
- package/template/public/brand/ph/poster-b.svg +6 -0
- package/template/public/brand/ph/poster-c.svg +6 -0
- package/template/public/unbox/powered-by-fundo-preto.png +0 -0
- package/template/public/unbox/powered-by-transparente.png +0 -0
- package/template/public/unbox/powered-by.png +0 -0
- package/template/scripts/abandoned-cart.ts +82 -0
- package/template/scripts/check-editable.mjs +467 -0
- package/template/scripts/check-honestidade.mjs +111 -0
- package/template/scripts/check-placeholder.mjs +200 -0
- package/template/scripts/check-recipe.mjs +76 -0
- package/template/scripts/check-unbox-brand.mjs +298 -0
- package/template/scripts/contraste.js +177 -0
- package/template/scripts/dump-catalog.ts +95 -0
- package/template/scripts/load-env.ts +13 -0
- package/template/scripts/medir-sistema.mjs +56 -0
- package/template/scripts/place-order-pix.ts +61 -0
- package/template/scripts/qa-screenshots.mjs +150 -0
- package/template/scripts/sistema.py +231 -0
- package/template/scripts/subscribe-webhook.ts +36 -0
- package/template/scripts/test-live.ts +186 -0
- package/template/scripts/vocabulario.py +264 -0
- package/template/tsconfig.json +27 -0
- package/tools/LEIA-ME.md +39 -0
- package/tools/check-template-neutro.mjs +111 -0
- package/tools/notion-doc.mjs +191 -0
- package/tools/tokenize-neutrals.mjs +0 -0
- package/tools/tokenize-radius.mjs +52 -0
- package/tools/workflow-storefront.legado.js +1207 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// CASCA do rodapé. Não desenha layout: busca os dados, escolhe a variante da receita
|
|
2
|
+
// (components/chrome/chrome-recipe.ts) e é dona do elemento <footer>.
|
|
3
|
+
//
|
|
4
|
+
// Duas coisas ficam AQUI de propósito, fora do alcance das variantes:
|
|
5
|
+
// 1. o elemento <footer class="store-layout site-chrome"> — `.site-chrome` esconde o chrome
|
|
6
|
+
// no checkout e a regra `footer.site-chrome` zera a margem em páginas full-bleed;
|
|
7
|
+
// 2. a barra inferior com <PoweredByUnbox /> — contrato da plataforma, verificado pelo
|
|
8
|
+
// prebuild (scripts/check-unbox-brand.mjs procura essa tag NESTE arquivo). Mantendo o
|
|
9
|
+
// selo na casca, nenhuma variante nova consegue derrubá-lo.
|
|
10
|
+
//
|
|
11
|
+
// EDITOR: a casca é dona da SEÇÃO do editor. O rodapé vive no container "chrome" e é `fixed`, como
|
|
12
|
+
// o cabeçalho e a faixa: o lojista edita o conteúdo, mas o rodapé não sai do lugar nem se oculta.
|
|
13
|
+
// Toda variante herda o escopo `chrome.footer` daqui. A linha de direitos é texto de marca (o
|
|
14
|
+
// lojista muda o ano e a razão social); o "Powered by Unbox" é contratual: fora dos primitivos por
|
|
15
|
+
// regra (README do editor, §8) e fora do gate por `data-editor-ignore`.
|
|
16
|
+
//
|
|
17
|
+
// Pra mudar o rodapé desta loja: troque `footer` em components/chrome/chrome-recipe.ts.
|
|
18
|
+
import { getShopData } from "@/lib/queries";
|
|
19
|
+
import { mockupOr } from "@/lib/mockup";
|
|
20
|
+
import { PoweredByUnbox } from "@/components/powered-by-unbox";
|
|
21
|
+
import { FOOTERS, type ChromeData } from "@/components/chrome/registry";
|
|
22
|
+
import { chromeRecipe } from "@/components/chrome/chrome-recipe";
|
|
23
|
+
// Exports NOMEADOS: este arquivo é server component e não consegue usar `Editable.*`
|
|
24
|
+
// (o objeto vem de um módulo "use client" e chega como undefined).
|
|
25
|
+
import { EditableSection, EditableText } from "@/lib/editable";
|
|
26
|
+
|
|
27
|
+
export async function SiteFooter() {
|
|
28
|
+
const shop = await mockupOr(getShopData(), null, "chrome/getShopData");
|
|
29
|
+
const shopName = shop?.name ?? "Minha Loja";
|
|
30
|
+
const data: ChromeData = { shopName, categories: [] };
|
|
31
|
+
|
|
32
|
+
const Footer = FOOTERS[chromeRecipe.footer];
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<EditableSection id="footer" kind="rodape" container="chrome" fixed label="Rodapé">
|
|
36
|
+
<footer className="store-layout site-chrome mt-12 bg-[var(--store-chrome-bg,#18181B)] text-[var(--store-chrome-text,#ffffff)]">
|
|
37
|
+
<Footer data={data} />
|
|
38
|
+
|
|
39
|
+
{/* barra inferior — fixa em todas as variantes (contrato Unbox) */}
|
|
40
|
+
<div className="border-t border-[var(--store-chrome-line,rgba(255,255,255,.1))]">
|
|
41
|
+
<div className="mx-auto max-w-[var(--container-max,1240px)] px-6 py-[18px] text-xs text-[var(--store-chrome-muted)]">
|
|
42
|
+
<div className="flex flex-wrap items-center justify-between gap-2">
|
|
43
|
+
<EditableText
|
|
44
|
+
path="direitos"
|
|
45
|
+
fallback={`© ${new Date().getFullYear()} ${shopName}. Todos os direitos reservados.`}
|
|
46
|
+
label="Linha de direitos autorais"
|
|
47
|
+
/>
|
|
48
|
+
{/* TODO: adicionar CNPJ e endereço da empresa */}
|
|
49
|
+
{/* obrigatório (contrato Unbox) — o build falha sem isso, não remova */}
|
|
50
|
+
<span data-editor-ignore="" style={{ display: "contents" }}>
|
|
51
|
+
<PoweredByUnbox className="text-[var(--store-chrome-muted)]" />
|
|
52
|
+
</span>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</footer>
|
|
57
|
+
</EditableSection>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// CASCA do header. Não desenha layout: busca os dados, escolhe a variante da receita
|
|
2
|
+
// (components/chrome/chrome-recipe.ts) e é dona do elemento <header>.
|
|
3
|
+
//
|
|
4
|
+
// Por que a casca é dona do <header>: as classes `store-layout site-chrome` são contrato —
|
|
5
|
+
// `.site-chrome` é o que esconde o chrome no checkout (app/globals.css). Se cada variante
|
|
6
|
+
// renderizasse o próprio wrapper, uma variante nova que esquecesse a classe faria o header
|
|
7
|
+
// da loja aparecer no checkout, sem erro de build. Aqui isso é impossível.
|
|
8
|
+
//
|
|
9
|
+
// EDITOR: a casca também é dona da SEÇÃO do editor. O cabeçalho vive no container "chrome" e é
|
|
10
|
+
// `fixed`: o lojista edita o conteúdo (logo), mas não tira o cabeçalho do lugar nem o oculta. Toda
|
|
11
|
+
// variante herda o escopo `chrome.header` daqui, então trocar de variante na receita não muda o
|
|
12
|
+
// caminho do logo (`chrome.header.logo`) que o lojista já editou.
|
|
13
|
+
//
|
|
14
|
+
// Pra mudar o header desta loja: troque `header` em components/chrome/chrome-recipe.ts.
|
|
15
|
+
// Variante nova: arquivo em components/chrome/headers/ + registry + agents/PADROES.md.
|
|
16
|
+
import { getShopData, getTopTags } from "@/lib/queries";
|
|
17
|
+
import { MiniCart } from "@/components/cart/mini-cart";
|
|
18
|
+
import type { NavCategory } from "@/components/mobile-nav";
|
|
19
|
+
import { AnnounceBar } from "@/components/chrome/announce-bar";
|
|
20
|
+
import { HEADERS, HEADER_SHELL, type ChromeData } from "@/components/chrome/registry";
|
|
21
|
+
import { chromeRecipe } from "@/components/chrome/chrome-recipe";
|
|
22
|
+
import { mockupOr } from "@/lib/mockup";
|
|
23
|
+
// Exports NOMEADOS: este arquivo é server component e não consegue usar `Editable.*`
|
|
24
|
+
// (o objeto vem de um módulo "use client" e chega como undefined).
|
|
25
|
+
import { EditableSection } from "@/lib/editable";
|
|
26
|
+
|
|
27
|
+
export async function SiteHeader() {
|
|
28
|
+
const [shop, tags] = await Promise.all([mockupOr(getShopData(), null, "chrome/getShopData"), mockupOr(getTopTags(), [], "chrome/getTopTags")]);
|
|
29
|
+
const categories: NavCategory[] = (tags ?? [])
|
|
30
|
+
.filter((t: any) => t.isVisible !== false)
|
|
31
|
+
.sort((a: any, b: any) => (a.position ?? 0) - (b.position ?? 0))
|
|
32
|
+
.map((t: any) => ({ id: t._id, name: t.displayTitle || t.name, slug: t.slug }));
|
|
33
|
+
const shopName = shop?.name ?? "Minha Loja";
|
|
34
|
+
const data: ChromeData = { shopName, categories };
|
|
35
|
+
|
|
36
|
+
const Header = HEADERS[chromeRecipe.header];
|
|
37
|
+
const modo = HEADER_SHELL[chromeRecipe.header];
|
|
38
|
+
|
|
39
|
+
// No modo sobreposto a PINTURA é da variante (.chrome-bar) + das regras de globals.css:
|
|
40
|
+
// a casca só posiciona. No sólido, a casca pinta como sempre.
|
|
41
|
+
const base = "store-layout site-chrome sticky top-0 z-50";
|
|
42
|
+
const skin =
|
|
43
|
+
modo === "solido"
|
|
44
|
+
? " border-b border-[var(--store-line)] bg-[var(--store-surface,#ffffff)] shadow-[0_1px_0_rgba(0,0,0,.02)]"
|
|
45
|
+
: "";
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<>
|
|
49
|
+
{/* No modo sobreposto o header flutua sobre o hero — uma barra sólida acima quebraria o efeito. */}
|
|
50
|
+
{modo === "solido" && <AnnounceBar />}
|
|
51
|
+
|
|
52
|
+
{/* header fixo — é filho direto do body, então gruda na página inteira.
|
|
53
|
+
data-chrome: as regras do modo sobreposto em globals.css exigem que a variante
|
|
54
|
+
escolhida CONCORDE (senão um header claro ficaria transparente sobre a foto). */}
|
|
55
|
+
<EditableSection id="header" kind="cabecalho" container="chrome" fixed label="Cabeçalho">
|
|
56
|
+
<header data-chrome={chromeRecipe.header} className={base + skin}>
|
|
57
|
+
<Header data={data} />
|
|
58
|
+
</header>
|
|
59
|
+
</EditableSection>
|
|
60
|
+
|
|
61
|
+
<MiniCart />
|
|
62
|
+
</>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion"
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
import { ChevronDownIcon, ChevronUpIcon } from "@/lib/icons"
|
|
5
|
+
|
|
6
|
+
function Accordion({ className, ...props }: AccordionPrimitive.Root.Props) {
|
|
7
|
+
return (
|
|
8
|
+
<AccordionPrimitive.Root
|
|
9
|
+
data-slot="accordion"
|
|
10
|
+
className={cn("flex w-full flex-col", className)}
|
|
11
|
+
{...props}
|
|
12
|
+
/>
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function AccordionItem({ className, ...props }: AccordionPrimitive.Item.Props) {
|
|
17
|
+
return (
|
|
18
|
+
<AccordionPrimitive.Item
|
|
19
|
+
data-slot="accordion-item"
|
|
20
|
+
className={cn("not-last:border-b", className)}
|
|
21
|
+
{...props}
|
|
22
|
+
/>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function AccordionTrigger({
|
|
27
|
+
className,
|
|
28
|
+
children,
|
|
29
|
+
...props
|
|
30
|
+
}: AccordionPrimitive.Trigger.Props) {
|
|
31
|
+
return (
|
|
32
|
+
<AccordionPrimitive.Header className="flex">
|
|
33
|
+
<AccordionPrimitive.Trigger
|
|
34
|
+
data-slot="accordion-trigger"
|
|
35
|
+
className={cn(
|
|
36
|
+
"group/accordion-trigger relative flex flex-1 items-start justify-between rounded-lg border border-transparent py-2.5 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:after:border-ring aria-disabled:pointer-events-none aria-disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-muted-foreground",
|
|
37
|
+
className
|
|
38
|
+
)}
|
|
39
|
+
{...props}
|
|
40
|
+
>
|
|
41
|
+
{children}
|
|
42
|
+
<ChevronDownIcon data-slot="accordion-trigger-icon" className="pointer-events-none shrink-0 group-aria-expanded/accordion-trigger:hidden" />
|
|
43
|
+
<ChevronUpIcon data-slot="accordion-trigger-icon" className="pointer-events-none hidden shrink-0 group-aria-expanded/accordion-trigger:inline" />
|
|
44
|
+
</AccordionPrimitive.Trigger>
|
|
45
|
+
</AccordionPrimitive.Header>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function AccordionContent({
|
|
50
|
+
className,
|
|
51
|
+
children,
|
|
52
|
+
...props
|
|
53
|
+
}: AccordionPrimitive.Panel.Props) {
|
|
54
|
+
return (
|
|
55
|
+
<AccordionPrimitive.Panel
|
|
56
|
+
data-slot="accordion-content"
|
|
57
|
+
className="overflow-hidden text-sm data-open:animate-accordion-down data-closed:animate-accordion-up"
|
|
58
|
+
{...props}
|
|
59
|
+
>
|
|
60
|
+
<div
|
|
61
|
+
className={cn(
|
|
62
|
+
"h-(--accordion-panel-height) pt-0 pb-2.5 data-ending-style:h-0 data-starting-style:h-0 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4",
|
|
63
|
+
className
|
|
64
|
+
)}
|
|
65
|
+
>
|
|
66
|
+
{children}
|
|
67
|
+
</div>
|
|
68
|
+
</AccordionPrimitive.Panel>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
import { Button } from "@/components/ui/button"
|
|
8
|
+
|
|
9
|
+
function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) {
|
|
10
|
+
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) {
|
|
14
|
+
return (
|
|
15
|
+
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) {
|
|
20
|
+
return (
|
|
21
|
+
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function AlertDialogOverlay({
|
|
26
|
+
className,
|
|
27
|
+
...props
|
|
28
|
+
}: AlertDialogPrimitive.Backdrop.Props) {
|
|
29
|
+
return (
|
|
30
|
+
<AlertDialogPrimitive.Backdrop
|
|
31
|
+
data-slot="alert-dialog-overlay"
|
|
32
|
+
className={cn(
|
|
33
|
+
"fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
|
|
34
|
+
className
|
|
35
|
+
)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function AlertDialogContent({
|
|
42
|
+
className,
|
|
43
|
+
size = "default",
|
|
44
|
+
...props
|
|
45
|
+
}: AlertDialogPrimitive.Popup.Props & {
|
|
46
|
+
size?: "default" | "sm"
|
|
47
|
+
}) {
|
|
48
|
+
return (
|
|
49
|
+
<AlertDialogPortal>
|
|
50
|
+
<AlertDialogOverlay />
|
|
51
|
+
<AlertDialogPrimitive.Popup
|
|
52
|
+
data-slot="alert-dialog-content"
|
|
53
|
+
data-size={size}
|
|
54
|
+
className={cn(
|
|
55
|
+
"group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
56
|
+
className
|
|
57
|
+
)}
|
|
58
|
+
{...props}
|
|
59
|
+
/>
|
|
60
|
+
</AlertDialogPortal>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function AlertDialogHeader({
|
|
65
|
+
className,
|
|
66
|
+
...props
|
|
67
|
+
}: React.ComponentProps<"div">) {
|
|
68
|
+
return (
|
|
69
|
+
<div
|
|
70
|
+
data-slot="alert-dialog-header"
|
|
71
|
+
className={cn(
|
|
72
|
+
"grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
|
|
73
|
+
className
|
|
74
|
+
)}
|
|
75
|
+
{...props}
|
|
76
|
+
/>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function AlertDialogFooter({
|
|
81
|
+
className,
|
|
82
|
+
...props
|
|
83
|
+
}: React.ComponentProps<"div">) {
|
|
84
|
+
return (
|
|
85
|
+
<div
|
|
86
|
+
data-slot="alert-dialog-footer"
|
|
87
|
+
className={cn(
|
|
88
|
+
"-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
|
|
89
|
+
className
|
|
90
|
+
)}
|
|
91
|
+
{...props}
|
|
92
|
+
/>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function AlertDialogMedia({
|
|
97
|
+
className,
|
|
98
|
+
...props
|
|
99
|
+
}: React.ComponentProps<"div">) {
|
|
100
|
+
return (
|
|
101
|
+
<div
|
|
102
|
+
data-slot="alert-dialog-media"
|
|
103
|
+
className={cn(
|
|
104
|
+
"mb-2 inline-flex size-10 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6",
|
|
105
|
+
className
|
|
106
|
+
)}
|
|
107
|
+
{...props}
|
|
108
|
+
/>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function AlertDialogTitle({
|
|
113
|
+
className,
|
|
114
|
+
...props
|
|
115
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
|
|
116
|
+
return (
|
|
117
|
+
<AlertDialogPrimitive.Title
|
|
118
|
+
data-slot="alert-dialog-title"
|
|
119
|
+
className={cn(
|
|
120
|
+
"font-heading text-base font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
|
|
121
|
+
className
|
|
122
|
+
)}
|
|
123
|
+
{...props}
|
|
124
|
+
/>
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function AlertDialogDescription({
|
|
129
|
+
className,
|
|
130
|
+
...props
|
|
131
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
|
|
132
|
+
return (
|
|
133
|
+
<AlertDialogPrimitive.Description
|
|
134
|
+
data-slot="alert-dialog-description"
|
|
135
|
+
className={cn(
|
|
136
|
+
"text-sm text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
|
|
137
|
+
className
|
|
138
|
+
)}
|
|
139
|
+
{...props}
|
|
140
|
+
/>
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function AlertDialogAction({
|
|
145
|
+
className,
|
|
146
|
+
...props
|
|
147
|
+
}: React.ComponentProps<typeof Button>) {
|
|
148
|
+
return (
|
|
149
|
+
<Button
|
|
150
|
+
data-slot="alert-dialog-action"
|
|
151
|
+
className={cn(className)}
|
|
152
|
+
{...props}
|
|
153
|
+
/>
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function AlertDialogCancel({
|
|
158
|
+
className,
|
|
159
|
+
variant = "outline",
|
|
160
|
+
size = "default",
|
|
161
|
+
...props
|
|
162
|
+
}: AlertDialogPrimitive.Close.Props &
|
|
163
|
+
Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
|
|
164
|
+
return (
|
|
165
|
+
<AlertDialogPrimitive.Close
|
|
166
|
+
data-slot="alert-dialog-cancel"
|
|
167
|
+
className={cn(className)}
|
|
168
|
+
render={<Button variant={variant} size={size} />}
|
|
169
|
+
{...props}
|
|
170
|
+
/>
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export {
|
|
175
|
+
AlertDialog,
|
|
176
|
+
AlertDialogAction,
|
|
177
|
+
AlertDialogCancel,
|
|
178
|
+
AlertDialogContent,
|
|
179
|
+
AlertDialogDescription,
|
|
180
|
+
AlertDialogFooter,
|
|
181
|
+
AlertDialogHeader,
|
|
182
|
+
AlertDialogMedia,
|
|
183
|
+
AlertDialogOverlay,
|
|
184
|
+
AlertDialogPortal,
|
|
185
|
+
AlertDialogTitle,
|
|
186
|
+
AlertDialogTrigger,
|
|
187
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/lib/utils"
|
|
5
|
+
|
|
6
|
+
const alertVariants = cva(
|
|
7
|
+
"group/alert relative grid w-full gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: "bg-card text-card-foreground",
|
|
12
|
+
destructive:
|
|
13
|
+
"bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current",
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
defaultVariants: {
|
|
17
|
+
variant: "default",
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
function Alert({
|
|
23
|
+
className,
|
|
24
|
+
variant,
|
|
25
|
+
...props
|
|
26
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
|
|
27
|
+
return (
|
|
28
|
+
<div
|
|
29
|
+
data-slot="alert"
|
|
30
|
+
role="alert"
|
|
31
|
+
className={cn(alertVariants({ variant }), className)}
|
|
32
|
+
{...props}
|
|
33
|
+
/>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
38
|
+
return (
|
|
39
|
+
<div
|
|
40
|
+
data-slot="alert-title"
|
|
41
|
+
className={cn(
|
|
42
|
+
"font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground",
|
|
43
|
+
className
|
|
44
|
+
)}
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function AlertDescription({
|
|
51
|
+
className,
|
|
52
|
+
...props
|
|
53
|
+
}: React.ComponentProps<"div">) {
|
|
54
|
+
return (
|
|
55
|
+
<div
|
|
56
|
+
data-slot="alert-description"
|
|
57
|
+
className={cn(
|
|
58
|
+
"text-sm text-balance text-muted-foreground md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4",
|
|
59
|
+
className
|
|
60
|
+
)}
|
|
61
|
+
{...props}
|
|
62
|
+
/>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
|
|
67
|
+
return (
|
|
68
|
+
<div
|
|
69
|
+
data-slot="alert-action"
|
|
70
|
+
className={cn("absolute top-2 right-2", className)}
|
|
71
|
+
{...props}
|
|
72
|
+
/>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export { Alert, AlertTitle, AlertDescription, AlertAction }
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils"
|
|
2
|
+
|
|
3
|
+
function AspectRatio({
|
|
4
|
+
ratio,
|
|
5
|
+
className,
|
|
6
|
+
...props
|
|
7
|
+
}: React.ComponentProps<"div"> & { ratio: number }) {
|
|
8
|
+
return (
|
|
9
|
+
<div
|
|
10
|
+
data-slot="aspect-ratio"
|
|
11
|
+
style={
|
|
12
|
+
{
|
|
13
|
+
"--ratio": ratio,
|
|
14
|
+
} as React.CSSProperties
|
|
15
|
+
}
|
|
16
|
+
className={cn("relative aspect-(--ratio)", className)}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { AspectRatio }
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar"
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils"
|
|
7
|
+
|
|
8
|
+
function Avatar({
|
|
9
|
+
className,
|
|
10
|
+
size = "default",
|
|
11
|
+
...props
|
|
12
|
+
}: AvatarPrimitive.Root.Props & {
|
|
13
|
+
size?: "default" | "sm" | "lg"
|
|
14
|
+
}) {
|
|
15
|
+
return (
|
|
16
|
+
<AvatarPrimitive.Root
|
|
17
|
+
data-slot="avatar"
|
|
18
|
+
data-size={size}
|
|
19
|
+
className={cn(
|
|
20
|
+
"group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten",
|
|
21
|
+
className
|
|
22
|
+
)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {
|
|
29
|
+
return (
|
|
30
|
+
<AvatarPrimitive.Image
|
|
31
|
+
data-slot="avatar-image"
|
|
32
|
+
className={cn(
|
|
33
|
+
"aspect-square size-full rounded-full object-cover",
|
|
34
|
+
className
|
|
35
|
+
)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function AvatarFallback({
|
|
42
|
+
className,
|
|
43
|
+
...props
|
|
44
|
+
}: AvatarPrimitive.Fallback.Props) {
|
|
45
|
+
return (
|
|
46
|
+
<AvatarPrimitive.Fallback
|
|
47
|
+
data-slot="avatar-fallback"
|
|
48
|
+
className={cn(
|
|
49
|
+
"flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs",
|
|
50
|
+
className
|
|
51
|
+
)}
|
|
52
|
+
{...props}
|
|
53
|
+
/>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
|
|
58
|
+
return (
|
|
59
|
+
<span
|
|
60
|
+
data-slot="avatar-badge"
|
|
61
|
+
className={cn(
|
|
62
|
+
"absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background select-none",
|
|
63
|
+
"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
|
|
64
|
+
"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
|
|
65
|
+
"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
|
|
66
|
+
className
|
|
67
|
+
)}
|
|
68
|
+
{...props}
|
|
69
|
+
/>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
74
|
+
return (
|
|
75
|
+
<div
|
|
76
|
+
data-slot="avatar-group"
|
|
77
|
+
className={cn(
|
|
78
|
+
"group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background",
|
|
79
|
+
className
|
|
80
|
+
)}
|
|
81
|
+
{...props}
|
|
82
|
+
/>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function AvatarGroupCount({
|
|
87
|
+
className,
|
|
88
|
+
...props
|
|
89
|
+
}: React.ComponentProps<"div">) {
|
|
90
|
+
return (
|
|
91
|
+
<div
|
|
92
|
+
data-slot="avatar-group-count"
|
|
93
|
+
className={cn(
|
|
94
|
+
"relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
|
|
95
|
+
className
|
|
96
|
+
)}
|
|
97
|
+
{...props}
|
|
98
|
+
/>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export {
|
|
103
|
+
Avatar,
|
|
104
|
+
AvatarImage,
|
|
105
|
+
AvatarFallback,
|
|
106
|
+
AvatarGroup,
|
|
107
|
+
AvatarGroupCount,
|
|
108
|
+
AvatarBadge,
|
|
109
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { mergeProps } from "@base-ui/react/merge-props"
|
|
2
|
+
import { useRender } from "@base-ui/react/use-render"
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
4
|
+
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
|
|
7
|
+
const badgeVariants = cva(
|
|
8
|
+
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
|
|
13
|
+
secondary:
|
|
14
|
+
"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
|
|
15
|
+
destructive:
|
|
16
|
+
"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
|
|
17
|
+
outline:
|
|
18
|
+
"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
|
|
19
|
+
ghost:
|
|
20
|
+
"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
|
|
21
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
defaultVariants: {
|
|
25
|
+
variant: "default",
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
function Badge({
|
|
31
|
+
className,
|
|
32
|
+
variant = "default",
|
|
33
|
+
render,
|
|
34
|
+
...props
|
|
35
|
+
}: useRender.ComponentProps<"span"> & VariantProps<typeof badgeVariants>) {
|
|
36
|
+
return useRender({
|
|
37
|
+
defaultTagName: "span",
|
|
38
|
+
props: mergeProps<"span">(
|
|
39
|
+
{
|
|
40
|
+
className: cn(badgeVariants({ variant }), className),
|
|
41
|
+
},
|
|
42
|
+
props
|
|
43
|
+
),
|
|
44
|
+
render,
|
|
45
|
+
state: {
|
|
46
|
+
slot: "badge",
|
|
47
|
+
variant,
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { Badge, badgeVariants }
|