@thorprovider/blocks 0.1.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 +24 -0
- package/.turbo/turbo-test.log +10 -0
- package/.turbo/turbo-type-check.log +0 -0
- package/AGENTS.md +1 -0
- package/README.md +161 -0
- package/components.json +33 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/contracts.d.mts +563 -0
- package/dist/contracts.d.ts +563 -0
- package/dist/contracts.js +207 -0
- package/dist/contracts.js.map +1 -0
- package/dist/contracts.mjs +168 -0
- package/dist/contracts.mjs.map +1 -0
- package/dist/index.d.mts +2408 -0
- package/dist/index.d.ts +2408 -0
- package/dist/index.js +16422 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +16352 -0
- package/dist/index.mjs.map +1 -0
- package/docs/standards/data-contracts.md +70 -0
- package/docs/standards/theming.md +85 -0
- package/package.json +62 -0
- package/src/blocks/application/app-shells/app-shell-1-sidebar.tsx +198 -0
- package/src/blocks/application/app-shells/app-shell-1.tsx +196 -0
- package/src/blocks/application/login-forms/login-page-1.tsx +246 -0
- package/src/blocks/application/login-forms/login-page-2.tsx +207 -0
- package/src/blocks/application/password-recovery/forgot-password-page-1-brand-mark.tsx +38 -0
- package/src/blocks/application/password-recovery/forgot-password-page-1.tsx +320 -0
- package/src/blocks/application/signup-forms/signup-page-1.tsx +231 -0
- package/src/blocks/application/verification/verify-1.tsx +307 -0
- package/src/blocks/application/verification/verify-confetti.tsx +192 -0
- package/src/blocks/e-commerce/category-filters/category-filter-1.tsx +260 -0
- package/src/blocks/e-commerce/category-filters/category-filter-2-filter-panel.tsx +179 -0
- package/src/blocks/e-commerce/category-filters/category-filter-2.tsx +341 -0
- package/src/blocks/e-commerce/category-filters/category-filter-4-chip-rail.tsx +57 -0
- package/src/blocks/e-commerce/category-filters/category-filter-4-drawer.tsx +124 -0
- package/src/blocks/e-commerce/category-filters/category-filter-4-fields.tsx +170 -0
- package/src/blocks/e-commerce/category-filters/category-filter-4-sidebar.tsx +52 -0
- package/src/blocks/e-commerce/category-filters/category-filter-4-sort-sheet.tsx +57 -0
- package/src/blocks/e-commerce/category-filters/category-filter-4-state.ts +68 -0
- package/src/blocks/e-commerce/category-filters/category-filter-4.tsx +298 -0
- package/src/blocks/e-commerce/checkout-forms/card-formatting.test.ts +47 -0
- package/src/blocks/e-commerce/checkout-forms/card-formatting.ts +9 -0
- package/src/blocks/e-commerce/checkout-forms/checkbox-check.tsx +74 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-confirmation-fulfilment-progress.tsx +189 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-confirmation.tsx +283 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-field-feedback.tsx +29 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-contact-step.tsx +96 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-country-field.tsx +51 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-order-summary.tsx +139 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-payment-step.tsx +184 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-progress.tsx +36 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1-shipping-step.tsx +107 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-1.tsx +235 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-2-order-summary.tsx +130 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-2-payment-card.tsx +175 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-2-shipping-card.tsx +190 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-2.tsx +312 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-information-step.tsx +91 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-order-summary.tsx +110 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-payment-step.tsx +137 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-progress.tsx +68 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-review-step.tsx +79 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3-shipping-step.tsx +172 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-3.tsx +392 -0
- package/src/blocks/e-commerce/checkout-forms/checkout-form-types.ts +7 -0
- package/src/blocks/e-commerce/navigation/category-icon.test.tsx +54 -0
- package/src/blocks/e-commerce/navigation/category-icon.tsx +94 -0
- package/src/blocks/e-commerce/navigation/mega-menu.test.tsx +139 -0
- package/src/blocks/e-commerce/navigation/mega-menu.tsx +271 -0
- package/src/blocks/e-commerce/navigation/mobile-category-drawer.test.tsx +152 -0
- package/src/blocks/e-commerce/navigation/mobile-category-drawer.tsx +276 -0
- package/src/blocks/e-commerce/order-history/order-history-1.tsx +159 -0
- package/src/blocks/e-commerce/order-history/order-history-2.tsx +222 -0
- package/src/blocks/e-commerce/product-categories/avatar-group.tsx +86 -0
- package/src/blocks/e-commerce/product-categories/product-category-1.tsx +126 -0
- package/src/blocks/e-commerce/product-categories/product-category-2.tsx +100 -0
- package/src/blocks/e-commerce/product-categories/product-category-4.tsx +129 -0
- package/src/blocks/e-commerce/product-categories/product-category-5.tsx +218 -0
- package/src/blocks/e-commerce/product-list/product-card-1.tsx +131 -0
- package/src/blocks/e-commerce/product-list/product-card-2.tsx +71 -0
- package/src/blocks/e-commerce/product-list/product-card-3-reaction-toggle.tsx +166 -0
- package/src/blocks/e-commerce/product-list/product-card-3.tsx +215 -0
- package/src/blocks/e-commerce/product-list/product-card-5-checkbox-check.tsx +98 -0
- package/src/blocks/e-commerce/product-list/product-card-5-reaction-toggle.tsx +171 -0
- package/src/blocks/e-commerce/product-list/product-card-5.tsx +367 -0
- package/src/blocks/e-commerce/product-overview/product-overview-1.tsx +201 -0
- package/src/blocks/e-commerce/product-overview/product-overview-2.tsx +272 -0
- package/src/blocks/e-commerce/product-overview/product-overview-5-gallery.tsx +59 -0
- package/src/blocks/e-commerce/product-overview/product-overview-5-lightbox-tab.tsx +104 -0
- package/src/blocks/e-commerce/product-overview/product-overview-5-reviews-tab.tsx +82 -0
- package/src/blocks/e-commerce/product-overview/product-overview-5-tabs.tsx +69 -0
- package/src/blocks/e-commerce/product-overview/product-overview-5.tsx +274 -0
- package/src/blocks/e-commerce/product-overview/product-quick-view-card.tsx +135 -0
- package/src/blocks/e-commerce/product-overview/product-quick-view-dialog.tsx +393 -0
- package/src/blocks/e-commerce/product-overview/product-quick-view.tsx +136 -0
- package/src/blocks/e-commerce/reviews/review-rating-1-review-form.tsx +133 -0
- package/src/blocks/e-commerce/reviews/review-rating-1.tsx +178 -0
- package/src/blocks/e-commerce/reviews/review-rating-2.tsx +246 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-1-spinning-counter.tsx +142 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-1.tsx +291 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-item-row.tsx +110 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-recommendations.tsx +51 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-spinning-counter.tsx +146 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-2.tsx +295 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-3-spinning-counter.tsx +142 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-3.tsx +342 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-4-spinning-counter.tsx +146 -0
- package/src/blocks/e-commerce/shopping-carts/shopping-cart-4.tsx +391 -0
- package/src/blocks/e-commerce/store-containers/store-container-1.tsx +29 -0
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.tsx +244 -0
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-2.tsx +283 -0
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-5.tsx +318 -0
- package/src/blocks/e-commerce/storefront-hero/storefront-hero-6.tsx +205 -0
- package/src/blocks/marketing/banners/avatar-group.tsx +69 -0
- package/src/blocks/marketing/banners/banner-1.tsx +188 -0
- package/src/blocks/marketing/cta/cta-section-1.tsx +112 -0
- package/src/blocks/marketing/faq/faq-block-1.tsx +45 -0
- package/src/blocks/marketing/features/feature-1.tsx +97 -0
- package/src/blocks/marketing/features/feature-2.tsx +82 -0
- package/src/blocks/marketing/footers/footer-section-1.tsx +141 -0
- package/src/blocks/marketing/footers/footer-section-3.tsx +192 -0
- package/src/blocks/marketing/footers/footer-section-4.tsx +355 -0
- package/src/blocks/marketing/hero-sections/avatar-group.tsx +86 -0
- package/src/blocks/marketing/hero-sections/hero-section-1.tsx +135 -0
- package/src/blocks/marketing/logo-clouds/logo-cloud-1-logo-icon.tsx +52 -0
- package/src/blocks/marketing/logo-clouds/logo-cloud-1.tsx +56 -0
- package/src/blocks/marketing/navbars/navbar-1.tsx +267 -0
- package/src/blocks/marketing/navbars/navbar-2.tsx +351 -0
- package/src/blocks/marketing/navbars/navbar-3.tsx +303 -0
- package/src/blocks/marketing/navbars/navbar-4-account-menu.tsx +118 -0
- package/src/blocks/marketing/navbars/navbar-4-cart-sheet.tsx +248 -0
- package/src/blocks/marketing/navbars/navbar-4-like-button.tsx +177 -0
- package/src/blocks/marketing/navbars/navbar-4-mobile-nav.tsx +104 -0
- package/src/blocks/marketing/navbars/navbar-4-product-grid.tsx +219 -0
- package/src/blocks/marketing/navbars/navbar-4-search-bar.tsx +52 -0
- package/src/blocks/marketing/navbars/navbar-4.tsx +411 -0
- package/src/blocks/marketing/pricing/pricing-section-1.tsx +94 -0
- package/src/blocks/marketing/statistics/statistic-1.tsx +93 -0
- package/src/blocks/marketing/testimonials/testimonial-1.tsx +141 -0
- package/src/blocks/marketing/testimonials/testimonial-2.tsx +108 -0
- package/src/blocks/shared/reaction-toggle.tsx +145 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/index.ts +200 -0
- package/src/lib/contracts.test.ts +32 -0
- package/src/lib/contracts.ts +235 -0
- package/src/lib/types/menu.ts +43 -0
- package/src/lib/types.ts +4 -0
- package/src/lib/utils.ts +10 -0
- package/src/ui/accordion.tsx +65 -0
- package/src/ui/avatar.tsx +108 -0
- package/src/ui/badge.tsx +47 -0
- package/src/ui/breadcrumb.tsx +108 -0
- package/src/ui/button-group.tsx +83 -0
- package/src/ui/button.tsx +63 -0
- package/src/ui/card.tsx +91 -0
- package/src/ui/carousel.tsx +241 -0
- package/src/ui/dialog.tsx +158 -0
- package/src/ui/drawer.tsx +134 -0
- package/src/ui/dropdown-menu.tsx +256 -0
- package/src/ui/empty.tsx +103 -0
- package/src/ui/field.tsx +248 -0
- package/src/ui/input-group.tsx +170 -0
- package/src/ui/input-otp.tsx +76 -0
- package/src/ui/input.tsx +20 -0
- package/src/ui/label.tsx +23 -0
- package/src/ui/navigation-menu.tsx +167 -0
- package/src/ui/pagination.tsx +127 -0
- package/src/ui/progress.tsx +30 -0
- package/src/ui/radio-group.tsx +44 -0
- package/src/ui/select.tsx +189 -0
- package/src/ui/separator.tsx +27 -0
- package/src/ui/sheet.tsx +142 -0
- package/src/ui/sidebar.tsx +726 -0
- package/src/ui/skeleton.tsx +13 -0
- package/src/ui/table.tsx +115 -0
- package/src/ui/tabs.tsx +90 -0
- package/src/ui/textarea.tsx +17 -0
- package/src/ui/toggle-group.tsx +83 -0
- package/src/ui/toggle.tsx +46 -0
- package/src/ui/tooltip.tsx +56 -0
- package/tsconfig.json +20 -0
- package/tsup.config.ts +16 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Block data contracts"
|
|
3
|
+
purpose: "Warn block authors and hosts about the two boundary data-hygiene rules that prevent empty-src and duplicate-key console errors"
|
|
4
|
+
lastReviewed: "2026-09-27"
|
|
5
|
+
type: standards
|
|
6
|
+
tags: [blocks, contracts, data-boundary, images, react-keys]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Block data contracts
|
|
10
|
+
|
|
11
|
+
Blocks receive data via props and render it blindly. Two recurring defects at
|
|
12
|
+
the data boundary — `src=""` and duplicate React keys — are both caused by the
|
|
13
|
+
**host/boundary** emitting invalid values, not by the block. This document
|
|
14
|
+
states the two hygiene rules every boundary (mapper/transform/store spec) must
|
|
15
|
+
follow, and the defensive pattern every block should apply as a second layer.
|
|
16
|
+
|
|
17
|
+
## Rule 1: Optional fields are never `""` — use `undefined` or a fallback
|
|
18
|
+
|
|
19
|
+
A field that "may be absent" is a field that is **optional**. Serializing it as
|
|
20
|
+
an empty string is a lie: it looks like data but renders as a broken element
|
|
21
|
+
(`<img src="">` → browser warning, broken layout).
|
|
22
|
+
|
|
23
|
+
- **Genuinely optional fields** → emit `undefined`. React omits the attribute
|
|
24
|
+
entirely, so `<img src={undefined}>` never warns.
|
|
25
|
+
- **Fields whose contract requires a string** (e.g. `image: z.string()`) →
|
|
26
|
+
emit a non-empty **fallback**, never `""`. An `undefined` here would fail Zod
|
|
27
|
+
validation at the renderer boundary.
|
|
28
|
+
- **Never** reach for `?? ''` in a mapper, transform, or store spec.
|
|
29
|
+
|
|
30
|
+
Reference pattern — category images in electrostore
|
|
31
|
+
(`projects/electrostore/lib/electrostore/category-mappers.ts`):
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
export const CATEGORY_IMAGE_FALLBACK = 'data:image/svg+xml;utf8,...'
|
|
35
|
+
// image: category.image ?? category.metadata?.image ?? CATEGORY_IMAGE_FALLBACK
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The same pattern ships in the blocks library as
|
|
39
|
+
`PRODUCT_IMAGE_FALLBACK` (SVG data-URI) exported from
|
|
40
|
+
`@thorprovider/blocks/contracts`. Hosts may override with a store-specific
|
|
41
|
+
fallback; blocks render it as the last-resort value.
|
|
42
|
+
|
|
43
|
+
## Rule 2: React keys are unique by contract, not by convention
|
|
44
|
+
|
|
45
|
+
Never use a data field (href, id, image URL) as a React key unless uniqueness
|
|
46
|
+
is **guaranteed by contract**. Placeholder or repeated hrefs (e.g. two utility
|
|
47
|
+
links pointing at the same route) silently produce duplicate keys.
|
|
48
|
+
|
|
49
|
+
- **Dedupe at the boundary** when concatenating data from multiple sources
|
|
50
|
+
(categories + utility links + account link).
|
|
51
|
+
- **Blocks** that receive arrays use composite stable keys
|
|
52
|
+
(`key={`${item.id}:${item.label}`}`) so a duplicate id from any host cannot
|
|
53
|
+
collide.
|
|
54
|
+
- Duplicate `href` values in navigation are often a sign of placeholder data —
|
|
55
|
+
fix the data, don't just silence the warning.
|
|
56
|
+
|
|
57
|
+
## Second layer: blocks render defensively
|
|
58
|
+
|
|
59
|
+
Even with correct boundaries, blocks should render
|
|
60
|
+
`src={value || undefined}` for every image prop. This is a cheap invariant
|
|
61
|
+
that makes the whole catalog immune to one misbehaving host.
|
|
62
|
+
|
|
63
|
+
## Review checklist (boundary code)
|
|
64
|
+
|
|
65
|
+
- [ ] No `?? ''` or `|| ''` on visual fields (image, avatar, icon) in mappers/transforms.
|
|
66
|
+
- [ ] Required `z.string()` fields always receive a non-empty fallback.
|
|
67
|
+
- [ ] Optional fields emit `undefined`, never `""`.
|
|
68
|
+
- [ ] Arrays passed as React children have unique keys guaranteed by contract or deduped at the boundary.
|
|
69
|
+
- [ ] Concatenated arrays (categories + links) are deduped before render.
|
|
70
|
+
- [ ] Every `<img>`/`<AvatarImage>` in the block uses `src={expr || undefined}`.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Theme token contract"
|
|
3
|
+
purpose: "Define the theme tokens library blocks may reference and the rules that keep block styling theme-agnostic and AA-conformant"
|
|
4
|
+
lastReviewed: "2026-09-28"
|
|
5
|
+
type: standards
|
|
6
|
+
tags: [blocks, theming, tokens, contrast, a11y]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Theme token contract
|
|
10
|
+
|
|
11
|
+
`@thorprovider/blocks` ships **zero CSS**. Block styling resolves against the
|
|
12
|
+
consumer's own theme tokens at build time (Tailwind `@source` points at the
|
|
13
|
+
library source). This document is the contract between the library and every
|
|
14
|
+
theme that renders it: the tokens blocks may reference, the status rule, and
|
|
15
|
+
the documented exceptions.
|
|
16
|
+
|
|
17
|
+
## Contract tokens
|
|
18
|
+
|
|
19
|
+
A conformant consumer theme defines **each** token below with its foreground
|
|
20
|
+
pair, in every mode the theme declares (light and dark where dark mode
|
|
21
|
+
exists). Foreground pairs SHALL meet WCAG AA contrast (≥ 4.5:1 normal text,
|
|
22
|
+
≥ 3:1 UI components / large text).
|
|
23
|
+
|
|
24
|
+
| Token | Foreground | Role |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| `--background` | `--foreground` | Page surface / body text |
|
|
27
|
+
| `--card` | `--card-foreground` | Card surface |
|
|
28
|
+
| `--popover` | `--popover-foreground` | Popover / dialog surface |
|
|
29
|
+
| `--primary` | `--primary-foreground` | Brand / main CTAs |
|
|
30
|
+
| `--secondary` | `--secondary-foreground` | Secondary surfaces |
|
|
31
|
+
| `--muted` | `--muted-foreground` | De-emphasized surfaces / hints |
|
|
32
|
+
| `--accent` | `--accent-foreground` | Hover / selected surface (neutral) |
|
|
33
|
+
| `--destructive` | `--destructive-foreground` | Errors, removals, danger |
|
|
34
|
+
| `--success` | `--success-foreground` | Positive status (savings, in stock) |
|
|
35
|
+
| `--warning` | `--warning-foreground` | Caution status (low stock, notices) |
|
|
36
|
+
| `--border` | — | Hairlines, dividers |
|
|
37
|
+
| `--input` | — | Input borders / fills |
|
|
38
|
+
| `--ring` | — | Focus rings |
|
|
39
|
+
|
|
40
|
+
`accent` is a **surface** token, not a brand token. Brand color belongs in
|
|
41
|
+
`--primary`. `accent` SHALL be a neutral, AA-safe surface so hover/selected
|
|
42
|
+
states across nav, links, and menus stay readable.
|
|
43
|
+
|
|
44
|
+
## Rules
|
|
45
|
+
|
|
46
|
+
### Rule 1: Blocks reference only contract tokens
|
|
47
|
+
|
|
48
|
+
A block SHALL NOT hardcode raw Tailwind palette utilities (`bg-orange-500`,
|
|
49
|
+
`text-green-600`, `fill-amber-500`, `bg-green-50`), arbitrary CSS values
|
|
50
|
+
(`bg-[#0070ba]`), or app-only tokens (`bg-promo`). Every color class SHALL
|
|
51
|
+
resolve against a contract token.
|
|
52
|
+
|
|
53
|
+
### Rule 2: Status has one of two shapes
|
|
54
|
+
|
|
55
|
+
- **Status chip / badge** → use a `Badge` variant (`secondary`, `outline`,
|
|
56
|
+
`destructive`) and let the label carry the meaning.
|
|
57
|
+
- **Inline status text, icon, or dot** → use the semantic tokens
|
|
58
|
+
(`text-success`, `bg-success`, `text-warning`, `bg-warning`).
|
|
59
|
+
|
|
60
|
+
Rating stars use `fill-foreground` / `fill-current` — rating color carries no
|
|
61
|
+
status semantics.
|
|
62
|
+
|
|
63
|
+
### Rule 3: Primitives use contract foreground pairs
|
|
64
|
+
|
|
65
|
+
UI primitives (button, badge, ...) SHALL use `text-destructive-foreground`
|
|
66
|
+
(never `text-white`) on destructive surfaces so the pair resolves against the
|
|
67
|
+
consumer's tokens in light and dark mode.
|
|
68
|
+
|
|
69
|
+
### Rule 4: Theming.md is the review-time gate
|
|
70
|
+
|
|
71
|
+
Adding a color class not in this contract is a review-time violation. Theme
|
|
72
|
+
authors verify pairs with `scripts/contrast-check.mjs` in the Thor Commerce
|
|
73
|
+
repo, which asserts every pair in both themes meets AA.
|
|
74
|
+
|
|
75
|
+
## Documented exceptions
|
|
76
|
+
|
|
77
|
+
Two cases are legitimate and MUST NOT be converted to theme tokens:
|
|
78
|
+
|
|
79
|
+
- **Brand colors supplied as data.** Payment-brand chips (e.g. PayPal blue),
|
|
80
|
+
product swatches, and category icon colors arrive as data props or app-local
|
|
81
|
+
tokens (`--nav`, `--stock`, `--urgent`, `--promo` in electrostore) and are
|
|
82
|
+
styled by the app, never referenced by library blocks.
|
|
83
|
+
- **Overlays over imagery.** `bg-black/50 text-white`, `bg-white/90`,
|
|
84
|
+
photo-count pills, lightbox controls. Contrast is image-dependent and cannot
|
|
85
|
+
be guaranteed by theme tokens; these stay as raw overlay utilities.
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@thorprovider/blocks",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Reusable, framework-agnostic React component blocks for Thor Commerce storefronts.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"development": "./dist/index.mjs",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js",
|
|
13
|
+
"default": "./dist/index.mjs"
|
|
14
|
+
},
|
|
15
|
+
"./contracts": {
|
|
16
|
+
"types": "./dist/contracts.d.ts",
|
|
17
|
+
"development": "./dist/contracts.mjs",
|
|
18
|
+
"import": "./dist/contracts.mjs",
|
|
19
|
+
"require": "./dist/contracts.js",
|
|
20
|
+
"default": "./dist/contracts.mjs"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsup",
|
|
25
|
+
"dev": "tsup --watch",
|
|
26
|
+
"type-check": "tsc --noEmit",
|
|
27
|
+
"test": "vitest run --config vitest.config.ts",
|
|
28
|
+
"generate:agent-index": "node ../../scripts/generate-package-agents-index.cjs packages/blocks @thorprovider/blocks"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"react": "^19.0.0",
|
|
32
|
+
"react-dom": "^19.0.0"
|
|
33
|
+
},
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/thorprovider/thor-commerce",
|
|
37
|
+
"directory": "packages/blocks"
|
|
38
|
+
},
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@base-ui/react": "^1.8.0",
|
|
42
|
+
"@thorprovider/types": "^5.7.7",
|
|
43
|
+
"class-variance-authority": "^0.7.1",
|
|
44
|
+
"clsx": "^2.1.1",
|
|
45
|
+
"cn": "^0.4.0",
|
|
46
|
+
"embla-carousel-react": "^8.6.0",
|
|
47
|
+
"input-otp": "^1.5.0",
|
|
48
|
+
"lucide-react": "^1.47.0",
|
|
49
|
+
"motion": "^13.4.3",
|
|
50
|
+
"radix-ui": "^1.6.7",
|
|
51
|
+
"tailwind-merge": "^3.3.1",
|
|
52
|
+
"vaul": "^1.1.2",
|
|
53
|
+
"zod": "^3.25.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/react": "^19",
|
|
57
|
+
"@types/react-dom": "^19",
|
|
58
|
+
"tsup": "^8.0.0",
|
|
59
|
+
"typescript": "^5.0.0",
|
|
60
|
+
"vitest": "^3.2.4"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
BarChart3,
|
|
5
|
+
ChevronsUpDown,
|
|
6
|
+
FolderKanban,
|
|
7
|
+
Home,
|
|
8
|
+
LayoutDashboard,
|
|
9
|
+
Package,
|
|
10
|
+
Settings,
|
|
11
|
+
ShoppingCart,
|
|
12
|
+
User,
|
|
13
|
+
Users,
|
|
14
|
+
} from 'lucide-react'
|
|
15
|
+
import type { ComponentType } from 'react'
|
|
16
|
+
|
|
17
|
+
import { Avatar, AvatarFallback } from '../../../ui/avatar'
|
|
18
|
+
import {
|
|
19
|
+
DropdownMenu,
|
|
20
|
+
DropdownMenuContent,
|
|
21
|
+
DropdownMenuGroup,
|
|
22
|
+
DropdownMenuItem,
|
|
23
|
+
DropdownMenuLabel,
|
|
24
|
+
DropdownMenuTrigger,
|
|
25
|
+
} from '../../../ui/dropdown-menu'
|
|
26
|
+
import { Input } from '../../../ui/input'
|
|
27
|
+
import {
|
|
28
|
+
Sidebar,
|
|
29
|
+
SidebarContent,
|
|
30
|
+
SidebarFooter,
|
|
31
|
+
SidebarGroup,
|
|
32
|
+
SidebarGroupContent,
|
|
33
|
+
SidebarGroupLabel,
|
|
34
|
+
SidebarHeader,
|
|
35
|
+
SidebarMenu,
|
|
36
|
+
SidebarMenuButton,
|
|
37
|
+
SidebarMenuItem,
|
|
38
|
+
useSidebar,
|
|
39
|
+
} from '../../../ui/sidebar'
|
|
40
|
+
|
|
41
|
+
export type AppShell1NavIcon =
|
|
42
|
+
| 'layout-dashboard'
|
|
43
|
+
| 'package'
|
|
44
|
+
| 'user'
|
|
45
|
+
| 'settings'
|
|
46
|
+
| 'home'
|
|
47
|
+
| 'shopping-cart'
|
|
48
|
+
| 'bar-chart-3'
|
|
49
|
+
| 'users'
|
|
50
|
+
| 'folder-kanban'
|
|
51
|
+
|
|
52
|
+
export interface AppShell1NavItem {
|
|
53
|
+
label: string
|
|
54
|
+
href: string
|
|
55
|
+
icon: AppShell1NavIcon
|
|
56
|
+
active?: boolean
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface AppShell1User {
|
|
60
|
+
name: string
|
|
61
|
+
email: string
|
|
62
|
+
initials: string
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const appShell1NavIcons: Record<AppShell1NavIcon, ComponentType> = {
|
|
66
|
+
'layout-dashboard': LayoutDashboard,
|
|
67
|
+
package: Package,
|
|
68
|
+
user: User,
|
|
69
|
+
settings: Settings,
|
|
70
|
+
home: Home,
|
|
71
|
+
'shopping-cart': ShoppingCart,
|
|
72
|
+
'bar-chart-3': BarChart3,
|
|
73
|
+
users: Users,
|
|
74
|
+
'folder-kanban': FolderKanban,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// SidebarInset must remain a direct sibling of Sidebar. The sidebar primitive persists open state in the `sidebar_state` cookie.
|
|
78
|
+
function BrandMark() {
|
|
79
|
+
return (
|
|
80
|
+
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
81
|
+
<path
|
|
82
|
+
d="M26 24.75C26.4142 24.75 26.75 24.4142 26.75 24C26.75 23.5858 26.4142 23.25 26 23.25V24.75ZM26 23.25H11V24.75H26V23.25ZM8.75 21V15H7.25V21H8.75ZM11 23.25C9.75736 23.25 8.75 22.2426 8.75 21H7.25C7.25 23.0711 8.92893 24.75 11 24.75V23.25Z"
|
|
83
|
+
fill="currentColor"
|
|
84
|
+
/>
|
|
85
|
+
<path
|
|
86
|
+
d="M1.5 3.25C1.08579 3.25 0.75 3.58579 0.75 4C0.75 4.41421 1.08579 4.75 1.5 4.75V3.25ZM1.5 4.75H6V3.25H1.5V4.75ZM7.25 6V21H8.75V6H7.25ZM6 4.75C6.69036 4.75 7.25 5.30964 7.25 6H8.75C8.75 4.48122 7.51878 3.25 6 3.25V4.75Z"
|
|
87
|
+
fill="currentColor"
|
|
88
|
+
/>
|
|
89
|
+
<path
|
|
90
|
+
d="M22 21.75C22.4142 21.75 22.75 21.4142 22.75 21C22.75 20.5858 22.4142 20.25 22 20.25V21.75ZM22 20.25H11V21.75H22V20.25ZM8.75 18V12H7.25V18H8.75ZM11 20.25C9.75736 20.25 8.75 19.2426 8.75 18H7.25C7.25 20.0711 8.92893 21.75 11 21.75V20.25Z"
|
|
91
|
+
fill="currentColor"
|
|
92
|
+
/>
|
|
93
|
+
<path
|
|
94
|
+
d="M27.2057 19.754C27.0654 20.1438 26.6357 20.346 26.246 20.2057C25.8562 20.0654 25.654 19.6357 25.7943 19.246L27.2057 19.754ZM30.0361 9.67744L29.3305 9.4234L29.3305 9.4234L30.0361 9.67744ZM25.7943 19.246L29.3305 9.4234L30.7418 9.93148L27.2057 19.754L25.7943 19.246ZM28.1543 7.75L8 7.75V6.25L28.1543 6.25V7.75ZM29.3305 9.4234C29.6237 8.60882 29.0201 7.75 28.1543 7.75V6.25C30.059 6.25 31.3869 8.13941 30.7418 9.93148L29.3305 9.4234Z"
|
|
95
|
+
fill="currentColor"
|
|
96
|
+
/>
|
|
97
|
+
<path
|
|
98
|
+
d="M13.5 21.75C13.0858 21.75 12.75 21.4142 12.75 21C12.75 20.5858 13.0858 20.25 13.5 20.25V21.75ZM26.7111 19.009L27.4174 19.2613L27.4174 19.2613L26.7111 19.009ZM13.5 20.25H23.8858V21.75H13.5V20.25ZM26.0048 18.7568L27.7937 13.7477L29.2063 14.2523L27.4174 19.2613L26.0048 18.7568ZM23.8858 20.25C24.8367 20.25 25.6849 19.6522 26.0048 18.7568L27.4174 19.2613C26.8843 20.7537 25.4706 21.75 23.8858 21.75V20.25Z"
|
|
99
|
+
fill="currentColor"
|
|
100
|
+
/>
|
|
101
|
+
<path d="M21.1694 10.5806L14.5651 17.1849" stroke="currentColor" />
|
|
102
|
+
<path d="M22.1694 14.5806L18.5632 18.1868" stroke="currentColor" />
|
|
103
|
+
<circle cx="13.1" cy="26.1" r="1.7" stroke="currentColor" />
|
|
104
|
+
<circle cx="22.1" cy="26.1" r="1.7" stroke="currentColor" />
|
|
105
|
+
</svg>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface AppShell1SidebarProps {
|
|
110
|
+
brandName?: string
|
|
111
|
+
navGroupLabel?: string
|
|
112
|
+
navItems?: AppShell1NavItem[]
|
|
113
|
+
user?: AppShell1User
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function AppShell1Sidebar({
|
|
117
|
+
brandName,
|
|
118
|
+
navGroupLabel = 'Workspace',
|
|
119
|
+
navItems = [],
|
|
120
|
+
user,
|
|
121
|
+
}: AppShell1SidebarProps) {
|
|
122
|
+
const { setOpenMobile } = useSidebar()
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<Sidebar>
|
|
126
|
+
<SidebarHeader>
|
|
127
|
+
<a
|
|
128
|
+
href="#resumen"
|
|
129
|
+
className="flex min-h-11 items-center gap-2 rounded-lg px-2 font-semibold"
|
|
130
|
+
onClick={() => setOpenMobile(false)}
|
|
131
|
+
>
|
|
132
|
+
<BrandMark />
|
|
133
|
+
{brandName}
|
|
134
|
+
</a>
|
|
135
|
+
</SidebarHeader>
|
|
136
|
+
<SidebarContent>
|
|
137
|
+
<SidebarGroup>
|
|
138
|
+
<SidebarGroupLabel>{navGroupLabel}</SidebarGroupLabel>
|
|
139
|
+
<SidebarGroupContent>
|
|
140
|
+
<nav aria-label={navGroupLabel}>
|
|
141
|
+
<SidebarMenu>
|
|
142
|
+
{navItems.map(item => {
|
|
143
|
+
const Icon = appShell1NavIcons[item.icon]
|
|
144
|
+
return (
|
|
145
|
+
<SidebarMenuItem key={item.label}>
|
|
146
|
+
<SidebarMenuButton isActive={item.active} asChild>
|
|
147
|
+
<a
|
|
148
|
+
href={item.href}
|
|
149
|
+
aria-current={item.active ? 'page' : undefined}
|
|
150
|
+
aria-label={item.label}
|
|
151
|
+
onClick={() => setOpenMobile(false)}
|
|
152
|
+
>
|
|
153
|
+
<Icon />
|
|
154
|
+
<span>{item.label}</span>
|
|
155
|
+
</a>
|
|
156
|
+
</SidebarMenuButton>
|
|
157
|
+
</SidebarMenuItem>
|
|
158
|
+
)
|
|
159
|
+
})}
|
|
160
|
+
</SidebarMenu>
|
|
161
|
+
</nav>
|
|
162
|
+
</SidebarGroupContent>
|
|
163
|
+
</SidebarGroup>
|
|
164
|
+
<div className="px-2 md:hidden">
|
|
165
|
+
<Input aria-label="Buscar en el espacio de trabajo" placeholder="Buscar…" />
|
|
166
|
+
</div>
|
|
167
|
+
</SidebarContent>
|
|
168
|
+
{user ? (
|
|
169
|
+
<SidebarFooter>
|
|
170
|
+
<SidebarMenu>
|
|
171
|
+
<SidebarMenuItem>
|
|
172
|
+
<DropdownMenu>
|
|
173
|
+
<DropdownMenuTrigger asChild>
|
|
174
|
+
<SidebarMenuButton className="min-h-11">
|
|
175
|
+
<Avatar className="size-7">
|
|
176
|
+
<AvatarFallback>{user.initials}</AvatarFallback>
|
|
177
|
+
</Avatar>
|
|
178
|
+
<span className="truncate">{user.name}</span>
|
|
179
|
+
<ChevronsUpDown className="ml-auto" />
|
|
180
|
+
</SidebarMenuButton>
|
|
181
|
+
</DropdownMenuTrigger>
|
|
182
|
+
<DropdownMenuContent side="top">
|
|
183
|
+
<DropdownMenuGroup>
|
|
184
|
+
<DropdownMenuLabel>{user.email}</DropdownMenuLabel>
|
|
185
|
+
<DropdownMenuItem>Configuración de la cuenta</DropdownMenuItem>
|
|
186
|
+
<DropdownMenuItem>Cerrar sesión</DropdownMenuItem>
|
|
187
|
+
</DropdownMenuGroup>
|
|
188
|
+
</DropdownMenuContent>
|
|
189
|
+
</DropdownMenu>
|
|
190
|
+
</SidebarMenuItem>
|
|
191
|
+
</SidebarMenu>
|
|
192
|
+
</SidebarFooter>
|
|
193
|
+
) : null}
|
|
194
|
+
</Sidebar>
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export default AppShell1Sidebar
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Bell, Plus, Search } from 'lucide-react'
|
|
4
|
+
import type { ReactNode } from 'react'
|
|
5
|
+
|
|
6
|
+
import type { CTA } from '../../../lib/types'
|
|
7
|
+
import {
|
|
8
|
+
Breadcrumb,
|
|
9
|
+
BreadcrumbItem,
|
|
10
|
+
BreadcrumbLink,
|
|
11
|
+
BreadcrumbList,
|
|
12
|
+
BreadcrumbPage,
|
|
13
|
+
BreadcrumbSeparator,
|
|
14
|
+
} from '../../../ui/breadcrumb'
|
|
15
|
+
import { Button } from '../../../ui/button'
|
|
16
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../../../ui/card'
|
|
17
|
+
import { Input } from '../../../ui/input'
|
|
18
|
+
import { Separator } from '../../../ui/separator'
|
|
19
|
+
import { SidebarInset, SidebarProvider, SidebarTrigger } from '../../../ui/sidebar'
|
|
20
|
+
import AppShell1Sidebar from './app-shell-1-sidebar'
|
|
21
|
+
import type { AppShell1NavItem, AppShell1User } from './app-shell-1-sidebar'
|
|
22
|
+
|
|
23
|
+
export interface AppShell1BreadcrumbItem {
|
|
24
|
+
label: string
|
|
25
|
+
href?: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface AppShell1Stat {
|
|
29
|
+
label: string
|
|
30
|
+
value: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface AppShell1Props {
|
|
34
|
+
brandName?: string
|
|
35
|
+
navGroupLabel?: string
|
|
36
|
+
navItems?: AppShell1NavItem[]
|
|
37
|
+
user?: AppShell1User
|
|
38
|
+
breadcrumb?: AppShell1BreadcrumbItem[]
|
|
39
|
+
pageTitle?: string
|
|
40
|
+
pageDescription?: string
|
|
41
|
+
action?: CTA
|
|
42
|
+
stats?: AppShell1Stat[]
|
|
43
|
+
children?: ReactNode
|
|
44
|
+
className?: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function AppHeader({ breadcrumb }: { breadcrumb?: AppShell1BreadcrumbItem[] }) {
|
|
48
|
+
return (
|
|
49
|
+
<header className="flex h-14 shrink-0 items-center gap-3 border-b px-4 sm:px-6">
|
|
50
|
+
<SidebarTrigger aria-label="Toggle navigation" className="min-h-11 min-w-11" />
|
|
51
|
+
<Separator orientation="vertical" className="data-vertical:h-5 data-vertical:self-center" />
|
|
52
|
+
{breadcrumb && breadcrumb.length > 0 ? (
|
|
53
|
+
<Breadcrumb>
|
|
54
|
+
<BreadcrumbList>
|
|
55
|
+
{breadcrumb.map((item, index) => {
|
|
56
|
+
const isLast = index === breadcrumb.length - 1
|
|
57
|
+
return (
|
|
58
|
+
<div key={item.label} className="contents">
|
|
59
|
+
<BreadcrumbItem className={isLast ? undefined : 'hidden sm:flex'}>
|
|
60
|
+
{isLast ? (
|
|
61
|
+
<BreadcrumbPage>{item.label}</BreadcrumbPage>
|
|
62
|
+
) : item.href ? (
|
|
63
|
+
<BreadcrumbLink href={item.href}>{item.label}</BreadcrumbLink>
|
|
64
|
+
) : (
|
|
65
|
+
<span>{item.label}</span>
|
|
66
|
+
)}
|
|
67
|
+
</BreadcrumbItem>
|
|
68
|
+
{!isLast ? (
|
|
69
|
+
<BreadcrumbSeparator className="hidden sm:flex" />
|
|
70
|
+
) : null}
|
|
71
|
+
</div>
|
|
72
|
+
)
|
|
73
|
+
})}
|
|
74
|
+
</BreadcrumbList>
|
|
75
|
+
</Breadcrumb>
|
|
76
|
+
) : null}
|
|
77
|
+
<div className="ml-auto hidden w-56 md:block">
|
|
78
|
+
<div className="relative">
|
|
79
|
+
<Search className="pointer-events-none absolute left-2.5 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
|
|
80
|
+
<Input
|
|
81
|
+
aria-label="Buscar en el espacio de trabajo"
|
|
82
|
+
placeholder="Buscar…"
|
|
83
|
+
className="h-9 pl-9"
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
<Button variant="ghost" size="icon" aria-label="Notifications, 3 unread">
|
|
88
|
+
<Bell />
|
|
89
|
+
<span className="sr-only">3 unread</span>
|
|
90
|
+
</Button>
|
|
91
|
+
</header>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function AppContent({
|
|
96
|
+
pageTitle,
|
|
97
|
+
pageDescription,
|
|
98
|
+
action,
|
|
99
|
+
stats,
|
|
100
|
+
children,
|
|
101
|
+
}: {
|
|
102
|
+
pageTitle?: string
|
|
103
|
+
pageDescription?: string
|
|
104
|
+
action?: CTA
|
|
105
|
+
stats?: AppShell1Stat[]
|
|
106
|
+
children?: ReactNode
|
|
107
|
+
}) {
|
|
108
|
+
return (
|
|
109
|
+
<main className="flex flex-1 flex-col gap-6 p-4 sm:p-6">
|
|
110
|
+
{pageTitle || pageDescription || action ? (
|
|
111
|
+
<div className="flex items-center justify-between gap-4">
|
|
112
|
+
<div>
|
|
113
|
+
{pageTitle ? (
|
|
114
|
+
<h1 className="text-2xl font-semibold tracking-tight">{pageTitle}</h1>
|
|
115
|
+
) : null}
|
|
116
|
+
{pageDescription ? (
|
|
117
|
+
<p className="text-sm text-muted-foreground">{pageDescription}</p>
|
|
118
|
+
) : null}
|
|
119
|
+
</div>
|
|
120
|
+
{action ? (
|
|
121
|
+
<Button asChild>
|
|
122
|
+
<a href={action.href}>
|
|
123
|
+
<Plus data-icon="inline-start" />
|
|
124
|
+
{action.label}
|
|
125
|
+
</a>
|
|
126
|
+
</Button>
|
|
127
|
+
) : null}
|
|
128
|
+
</div>
|
|
129
|
+
) : null}
|
|
130
|
+
{stats && stats.length > 0 ? (
|
|
131
|
+
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
132
|
+
{stats.map(stat => (
|
|
133
|
+
<Card key={stat.label}>
|
|
134
|
+
<CardHeader>
|
|
135
|
+
<CardDescription>{stat.label}</CardDescription>
|
|
136
|
+
<CardTitle className="text-3xl">{stat.value}</CardTitle>
|
|
137
|
+
</CardHeader>
|
|
138
|
+
</Card>
|
|
139
|
+
))}
|
|
140
|
+
</div>
|
|
141
|
+
) : null}
|
|
142
|
+
<Card className="min-h-72">
|
|
143
|
+
<CardHeader>
|
|
144
|
+
<CardTitle>Project activity</CardTitle>
|
|
145
|
+
<CardDescription>Your product content goes inside this shell.</CardDescription>
|
|
146
|
+
</CardHeader>
|
|
147
|
+
<CardContent className="flex flex-1">
|
|
148
|
+
{children ?? (
|
|
149
|
+
<div className="flex flex-1 items-center justify-center rounded-lg border border-dashed text-sm text-muted-foreground">
|
|
150
|
+
<Search className="mr-2 size-4" />
|
|
151
|
+
Replace with your screen
|
|
152
|
+
</div>
|
|
153
|
+
)}
|
|
154
|
+
</CardContent>
|
|
155
|
+
</Card>
|
|
156
|
+
</main>
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function AppShell1({
|
|
161
|
+
brandName,
|
|
162
|
+
navGroupLabel,
|
|
163
|
+
navItems,
|
|
164
|
+
user,
|
|
165
|
+
breadcrumb,
|
|
166
|
+
pageTitle,
|
|
167
|
+
pageDescription,
|
|
168
|
+
action,
|
|
169
|
+
stats,
|
|
170
|
+
children,
|
|
171
|
+
className,
|
|
172
|
+
}: AppShell1Props) {
|
|
173
|
+
return (
|
|
174
|
+
<SidebarProvider className={className}>
|
|
175
|
+
<AppShell1Sidebar
|
|
176
|
+
brandName={brandName}
|
|
177
|
+
navGroupLabel={navGroupLabel}
|
|
178
|
+
navItems={navItems}
|
|
179
|
+
user={user}
|
|
180
|
+
/>
|
|
181
|
+
<SidebarInset>
|
|
182
|
+
<AppHeader breadcrumb={breadcrumb} />
|
|
183
|
+
<AppContent
|
|
184
|
+
pageTitle={pageTitle}
|
|
185
|
+
pageDescription={pageDescription}
|
|
186
|
+
action={action}
|
|
187
|
+
stats={stats}
|
|
188
|
+
>
|
|
189
|
+
{children}
|
|
190
|
+
</AppContent>
|
|
191
|
+
</SidebarInset>
|
|
192
|
+
</SidebarProvider>
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export default AppShell1
|