@nanisoft/prism-ui 0.5.0 → 0.6.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/LICENSE +21 -0
- package/README.md +81 -0
- package/THIRD-PARTY-NOTICES.md +48 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/blocks/app-shell-01/app-shell.d.ts +38 -0
- package/dist/blocks/app-shell-01/app-shell.d.ts.map +1 -0
- package/dist/blocks/app-shell-01/app-shell.js +27 -0
- package/dist/blocks/app-shell-01/index.d.ts +3 -0
- package/dist/blocks/app-shell-01/index.d.ts.map +1 -0
- package/dist/blocks/app-shell-01/index.js +1 -0
- package/dist/blocks/auth-form-01/auth-form.d.ts +52 -0
- package/dist/blocks/auth-form-01/auth-form.d.ts.map +1 -0
- package/dist/blocks/auth-form-01/auth-form.js +23 -0
- package/dist/blocks/auth-form-01/index.d.ts +3 -0
- package/dist/blocks/auth-form-01/index.d.ts.map +1 -0
- package/dist/blocks/auth-form-01/index.js +1 -0
- package/dist/blocks/cta-01/cta.d.ts +83 -0
- package/dist/blocks/cta-01/cta.d.ts.map +1 -0
- package/dist/blocks/cta-01/cta.js +29 -0
- package/dist/blocks/cta-01/index.d.ts +3 -0
- package/dist/blocks/cta-01/index.d.ts.map +1 -0
- package/dist/blocks/cta-01/index.js +1 -0
- package/dist/blocks/data-table-01/data-table.d.ts +102 -0
- package/dist/blocks/data-table-01/data-table.d.ts.map +1 -0
- package/dist/blocks/data-table-01/data-table.js +116 -0
- package/dist/blocks/data-table-01/index.d.ts +3 -0
- package/dist/blocks/data-table-01/index.d.ts.map +1 -0
- package/dist/blocks/data-table-01/index.js +1 -0
- package/dist/blocks/feature-grid-01/feature-grid.d.ts +78 -0
- package/dist/blocks/feature-grid-01/feature-grid.d.ts.map +1 -0
- package/dist/blocks/feature-grid-01/feature-grid.js +24 -0
- package/dist/blocks/feature-grid-01/index.d.ts +3 -0
- package/dist/blocks/feature-grid-01/index.d.ts.map +1 -0
- package/dist/blocks/feature-grid-01/index.js +1 -0
- package/dist/blocks/hero-01/hero.d.ts +59 -0
- package/dist/blocks/hero-01/hero.d.ts.map +1 -0
- package/dist/blocks/hero-01/hero.js +24 -0
- package/dist/blocks/hero-01/index.d.ts +2 -0
- package/dist/blocks/hero-01/index.d.ts.map +1 -0
- package/dist/blocks/hero-01/index.js +1 -0
- package/dist/blocks/index.d.ts +28 -0
- package/dist/blocks/index.d.ts.map +1 -0
- package/dist/blocks/index.js +26 -0
- package/dist/blocks/instrument-panel-01/index.d.ts +3 -0
- package/dist/blocks/instrument-panel-01/index.d.ts.map +1 -0
- package/dist/blocks/instrument-panel-01/index.js +1 -0
- package/dist/blocks/instrument-panel-01/instrument-panel.d.ts +114 -0
- package/dist/blocks/instrument-panel-01/instrument-panel.d.ts.map +1 -0
- package/dist/blocks/instrument-panel-01/instrument-panel.js +64 -0
- package/dist/blocks/logo-strip-01/index.d.ts +3 -0
- package/dist/blocks/logo-strip-01/index.d.ts.map +1 -0
- package/dist/blocks/logo-strip-01/index.js +1 -0
- package/dist/blocks/logo-strip-01/logo-strip.d.ts +84 -0
- package/dist/blocks/logo-strip-01/logo-strip.d.ts.map +1 -0
- package/dist/blocks/logo-strip-01/logo-strip.js +40 -0
- package/dist/blocks/note-grid-01/index.d.ts +3 -0
- package/dist/blocks/note-grid-01/index.d.ts.map +1 -0
- package/dist/blocks/note-grid-01/index.js +1 -0
- package/dist/blocks/note-grid-01/note-grid.d.ts +79 -0
- package/dist/blocks/note-grid-01/note-grid.d.ts.map +1 -0
- package/dist/blocks/note-grid-01/note-grid.js +37 -0
- package/dist/blocks/page-header-01/index.d.ts +3 -0
- package/dist/blocks/page-header-01/index.d.ts.map +1 -0
- package/dist/blocks/page-header-01/index.js +1 -0
- package/dist/blocks/page-header-01/page-header.d.ts +48 -0
- package/dist/blocks/page-header-01/page-header.d.ts.map +1 -0
- package/dist/blocks/page-header-01/page-header.js +27 -0
- package/dist/blocks/pricing-01/index.d.ts +2 -0
- package/dist/blocks/pricing-01/index.d.ts.map +1 -0
- package/dist/blocks/pricing-01/index.js +1 -0
- package/dist/blocks/pricing-01/pricing.d.ts +35 -0
- package/dist/blocks/pricing-01/pricing.d.ts.map +1 -0
- package/dist/blocks/pricing-01/pricing.js +20 -0
- package/dist/blocks/process-rail-01/index.d.ts +3 -0
- package/dist/blocks/process-rail-01/index.d.ts.map +1 -0
- package/dist/blocks/process-rail-01/index.js +1 -0
- package/dist/blocks/process-rail-01/process-rail.d.ts +99 -0
- package/dist/blocks/process-rail-01/process-rail.d.ts.map +1 -0
- package/dist/blocks/process-rail-01/process-rail.js +58 -0
- package/dist/blocks/product-grid-01/index.d.ts +3 -0
- package/dist/blocks/product-grid-01/index.d.ts.map +1 -0
- package/dist/blocks/product-grid-01/index.js +1 -0
- package/dist/blocks/product-grid-01/product-grid.d.ts +114 -0
- package/dist/blocks/product-grid-01/product-grid.d.ts.map +1 -0
- package/dist/blocks/product-grid-01/product-grid.js +51 -0
- package/dist/blocks/settings-panel-01/index.d.ts +3 -0
- package/dist/blocks/settings-panel-01/index.d.ts.map +1 -0
- package/dist/blocks/settings-panel-01/index.js +1 -0
- package/dist/blocks/settings-panel-01/settings-panel.d.ts +71 -0
- package/dist/blocks/settings-panel-01/settings-panel.d.ts.map +1 -0
- package/dist/blocks/settings-panel-01/settings-panel.js +38 -0
- package/dist/blocks/site-footer/index.d.ts +3 -0
- package/dist/blocks/site-footer/index.d.ts.map +1 -0
- package/dist/blocks/site-footer/index.js +1 -0
- package/dist/blocks/site-footer/site-footer.d.ts +135 -0
- package/dist/blocks/site-footer/site-footer.d.ts.map +1 -0
- package/dist/blocks/site-footer/site-footer.js +41 -0
- package/dist/blocks/site-header/index.d.ts +3 -0
- package/dist/blocks/site-header/index.d.ts.map +1 -0
- package/dist/blocks/site-header/index.js +1 -0
- package/dist/blocks/site-header/site-header.d.ts +142 -0
- package/dist/blocks/site-header/site-header.d.ts.map +1 -0
- package/dist/blocks/site-header/site-header.js +46 -0
- package/dist/blocks/stack-grid-01/index.d.ts +3 -0
- package/dist/blocks/stack-grid-01/index.d.ts.map +1 -0
- package/dist/blocks/stack-grid-01/index.js +1 -0
- package/dist/blocks/stack-grid-01/stack-grid.d.ts +118 -0
- package/dist/blocks/stack-grid-01/stack-grid.d.ts.map +1 -0
- package/dist/blocks/stack-grid-01/stack-grid.js +49 -0
- package/dist/blocks/stats-01/index.d.ts +2 -0
- package/dist/blocks/stats-01/index.d.ts.map +1 -0
- package/dist/blocks/stats-01/index.js +1 -0
- package/dist/blocks/stats-01/stats.d.ts +28 -0
- package/dist/blocks/stats-01/stats.d.ts.map +1 -0
- package/dist/blocks/stats-01/stats.js +27 -0
- package/dist/blocks/status-ledger-01/index.d.ts +3 -0
- package/dist/blocks/status-ledger-01/index.d.ts.map +1 -0
- package/dist/blocks/status-ledger-01/index.js +1 -0
- package/dist/blocks/status-ledger-01/status-ledger.d.ts +131 -0
- package/dist/blocks/status-ledger-01/status-ledger.d.ts.map +1 -0
- package/dist/blocks/status-ledger-01/status-ledger.js +80 -0
- package/dist/catalog.d.ts +28 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +672 -0
- package/dist/components/index.d.ts +64 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +43 -0
- package/dist/components/ui/accordion.d.ts +65 -0
- package/dist/components/ui/accordion.d.ts.map +1 -0
- package/dist/components/ui/accordion.js +30 -0
- package/dist/components/ui/alert.d.ts +19 -0
- package/dist/components/ui/alert.d.ts.map +1 -0
- package/dist/components/ui/alert.js +31 -0
- package/dist/components/ui/avatar.d.ts +47 -0
- package/dist/components/ui/avatar.d.ts.map +1 -0
- package/dist/components/ui/avatar.js +30 -0
- package/dist/components/ui/badge.d.ts +14 -0
- package/dist/components/ui/badge.d.ts.map +1 -0
- package/dist/components/ui/badge.js +26 -0
- package/dist/components/ui/breadcrumb.d.ts +35 -0
- package/dist/components/ui/breadcrumb.d.ts.map +1 -0
- package/dist/components/ui/breadcrumb.js +48 -0
- package/dist/components/ui/button.d.ts +16 -0
- package/dist/components/ui/button.d.ts.map +1 -0
- package/dist/components/ui/button.js +63 -0
- package/dist/components/ui/card.d.ts +41 -0
- package/dist/components/ui/card.d.ts.map +1 -0
- package/dist/components/ui/card.js +53 -0
- package/dist/components/ui/checkbox.d.ts +43 -0
- package/dist/components/ui/checkbox.d.ts.map +1 -0
- package/dist/components/ui/checkbox.js +18 -0
- package/dist/components/ui/cta-link.d.ts +45 -0
- package/dist/components/ui/cta-link.d.ts.map +1 -0
- package/dist/components/ui/cta-link.js +81 -0
- package/dist/components/ui/diagram.d.ts +154 -0
- package/dist/components/ui/diagram.d.ts.map +1 -0
- package/dist/components/ui/diagram.js +150 -0
- package/dist/components/ui/dialog.d.ts +81 -0
- package/dist/components/ui/dialog.d.ts.map +1 -0
- package/dist/components/ui/dialog.js +60 -0
- package/dist/components/ui/dropdown-menu.d.ts +189 -0
- package/dist/components/ui/dropdown-menu.d.ts.map +1 -0
- package/dist/components/ui/dropdown-menu.js +67 -0
- package/dist/components/ui/fact-list.d.ts +90 -0
- package/dist/components/ui/fact-list.d.ts.map +1 -0
- package/dist/components/ui/fact-list.js +38 -0
- package/dist/components/ui/field.d.ts +36 -0
- package/dist/components/ui/field.d.ts.map +1 -0
- package/dist/components/ui/field.js +46 -0
- package/dist/components/ui/input.d.ts +12 -0
- package/dist/components/ui/input.d.ts.map +1 -0
- package/dist/components/ui/input.js +14 -0
- package/dist/components/ui/kbd.d.ts +10 -0
- package/dist/components/ui/kbd.d.ts.map +1 -0
- package/dist/components/ui/kbd.js +12 -0
- package/dist/components/ui/pagination.d.ts +40 -0
- package/dist/components/ui/pagination.d.ts.map +1 -0
- package/dist/components/ui/pagination.js +49 -0
- package/dist/components/ui/popover.d.ts +56 -0
- package/dist/components/ui/popover.d.ts.map +1 -0
- package/dist/components/ui/popover.js +25 -0
- package/dist/components/ui/product-mark.d.ts +115 -0
- package/dist/components/ui/product-mark.d.ts.map +1 -0
- package/dist/components/ui/product-mark.js +82 -0
- package/dist/components/ui/product-switcher.d.ts +108 -0
- package/dist/components/ui/product-switcher.d.ts.map +1 -0
- package/dist/components/ui/product-switcher.js +47 -0
- package/dist/components/ui/progress.d.ts +38 -0
- package/dist/components/ui/progress.d.ts.map +1 -0
- package/dist/components/ui/progress.js +17 -0
- package/dist/components/ui/prose.d.ts +88 -0
- package/dist/components/ui/prose.d.ts.map +1 -0
- package/dist/components/ui/prose.js +53 -0
- package/dist/components/ui/radio-group.d.ts +66 -0
- package/dist/components/ui/radio-group.d.ts.map +1 -0
- package/dist/components/ui/radio-group.js +30 -0
- package/dist/components/ui/section.d.ts +60 -0
- package/dist/components/ui/section.d.ts.map +1 -0
- package/dist/components/ui/section.js +21 -0
- package/dist/components/ui/select.d.ts +124 -0
- package/dist/components/ui/select.d.ts.map +1 -0
- package/dist/components/ui/select.js +47 -0
- package/dist/components/ui/separator.d.ts +16 -0
- package/dist/components/ui/separator.d.ts.map +1 -0
- package/dist/components/ui/separator.js +15 -0
- package/dist/components/ui/skeleton.d.ts +12 -0
- package/dist/components/ui/skeleton.d.ts.map +1 -0
- package/dist/components/ui/skeleton.js +14 -0
- package/dist/components/ui/slider.d.ts +43 -0
- package/dist/components/ui/slider.d.ts.map +1 -0
- package/dist/components/ui/slider.js +23 -0
- package/dist/components/ui/switch.d.ts +42 -0
- package/dist/components/ui/switch.d.ts.map +1 -0
- package/dist/components/ui/switch.js +16 -0
- package/dist/components/ui/table.d.ts +27 -0
- package/dist/components/ui/table.d.ts.map +1 -0
- package/dist/components/ui/table.js +43 -0
- package/dist/components/ui/tabs.d.ts +66 -0
- package/dist/components/ui/tabs.d.ts.map +1 -0
- package/dist/components/ui/tabs.js +29 -0
- package/dist/components/ui/textarea.d.ts +11 -0
- package/dist/components/ui/textarea.d.ts.map +1 -0
- package/dist/components/ui/textarea.js +13 -0
- package/dist/components/ui/tooltip.d.ts +63 -0
- package/dist/components/ui/tooltip.d.ts.map +1 -0
- package/dist/components/ui/tooltip.js +25 -0
- package/dist/components/ui/typography.d.ts +32 -0
- package/dist/components/ui/typography.d.ts.map +1 -0
- package/dist/components/ui/typography.js +51 -0
- package/dist/index.d.ts +79 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +68 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +5 -0
- package/dist/pages/auth-page/index.d.ts +36 -0
- package/dist/pages/auth-page/index.d.ts.map +1 -0
- package/dist/pages/auth-page/index.js +23 -0
- package/dist/pages/blog-post-page/index.d.ts +174 -0
- package/dist/pages/blog-post-page/index.d.ts.map +1 -0
- package/dist/pages/blog-post-page/index.js +49 -0
- package/dist/pages/dashboard-page/index.d.ts +36 -0
- package/dist/pages/dashboard-page/index.d.ts.map +1 -0
- package/dist/pages/dashboard-page/index.js +24 -0
- package/dist/pages/index.d.ts +21 -0
- package/dist/pages/index.d.ts.map +1 -0
- package/dist/pages/index.js +14 -0
- package/dist/pages/marketing-page/index.d.ts +43 -0
- package/dist/pages/marketing-page/index.d.ts.map +1 -0
- package/dist/pages/marketing-page/index.js +25 -0
- package/dist/pages/not-found-page/index.d.ts +112 -0
- package/dist/pages/not-found-page/index.d.ts.map +1 -0
- package/dist/pages/not-found-page/index.js +46 -0
- package/dist/pages/settings-page/index.d.ts +47 -0
- package/dist/pages/settings-page/index.d.ts.map +1 -0
- package/dist/pages/settings-page/index.js +24 -0
- package/dist/provider/index.d.ts +12 -0
- package/dist/provider/index.d.ts.map +1 -0
- package/dist/provider/index.js +9 -0
- package/dist/provider/provider.d.ts +42 -0
- package/dist/provider/provider.d.ts.map +1 -0
- package/dist/provider/provider.js +178 -0
- package/dist/provider/theme-script.d.ts +55 -0
- package/dist/provider/theme-script.d.ts.map +1 -0
- package/dist/provider/theme-script.js +79 -0
- package/dist/styles.css +2894 -0
- package/dist/theming/index.d.ts +173 -0
- package/dist/theming/index.d.ts.map +1 -0
- package/dist/theming/index.js +186 -0
- package/package.json +89 -18
- package/components.json +0 -21
- package/public/r/badge.json +0 -26
- package/public/r/button.json +0 -26
- package/public/r/card.json +0 -25
- package/public/r/cta-01.json +0 -44
- package/public/r/feature-grid-01.json +0 -44
- package/public/r/hero-01.json +0 -44
- package/public/r/pricing-01.json +0 -46
- package/public/r/registry.json +0 -294
- package/public/r/section.json +0 -25
- package/public/r/stats-01.json +0 -44
- package/registry.json +0 -294
- package/src/components/ds/blocks/cta-01/block.json +0 -8
- package/src/components/ds/blocks/cta-01/cta.tsx +0 -62
- package/src/components/ds/blocks/cta-01/index.tsx +0 -1
- package/src/components/ds/blocks/feature-grid-01/block.json +0 -8
- package/src/components/ds/blocks/feature-grid-01/feature-grid.tsx +0 -69
- package/src/components/ds/blocks/feature-grid-01/index.tsx +0 -1
- package/src/components/ds/blocks/hero-01/block.json +0 -8
- package/src/components/ds/blocks/hero-01/hero.tsx +0 -85
- package/src/components/ds/blocks/hero-01/index.tsx +0 -1
- package/src/components/ds/blocks/pricing-01/block.json +0 -8
- package/src/components/ds/blocks/pricing-01/index.tsx +0 -1
- package/src/components/ds/blocks/pricing-01/pricing.tsx +0 -122
- package/src/components/ds/blocks/section.meta.json +0 -6
- package/src/components/ds/blocks/section.tsx +0 -86
- package/src/components/ds/blocks/stats-01/block.json +0 -8
- package/src/components/ds/blocks/stats-01/index.tsx +0 -1
- package/src/components/ds/blocks/stats-01/stats.tsx +0 -92
- package/src/components/ui/badge.tsx +0 -37
- package/src/components/ui/button.tsx +0 -77
- package/src/components/ui/card.tsx +0 -65
- package/src/lib/utils.ts +0 -6
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ProductMark } from '../../components/ui/product-mark';
|
|
3
|
+
import { Section, SectionHeading } from '../../components/ui/section';
|
|
4
|
+
/**
|
|
5
|
+
* A set of products as a stack of hairline rows: a two-part mark, a name, one
|
|
6
|
+
* tagline and a destination per row.
|
|
7
|
+
*
|
|
8
|
+
* All four NaniSoft sites publish this section, and each one drew it by hand with
|
|
9
|
+
* the same three parts in the same order: a colour mark for the product, the
|
|
10
|
+
* product's name, and one line about what it is, on a hairline row with the whole
|
|
11
|
+
* row a link. Each site also resolved the mark's ink in JavaScript from the live
|
|
12
|
+
* mode, which is the part that is a second source of truth: four copies of the
|
|
13
|
+
* rule that a pastel is a fill and the name is an ink, and four copies of the
|
|
14
|
+
* arithmetic that a filled dot fails contrast on a light card.
|
|
15
|
+
*
|
|
16
|
+
* **The mark is two parts, and it is `ProductMark`.** The ring is `brand-ink` and
|
|
17
|
+
* the core is the product's own `primary`, with the name in the brand ink beside
|
|
18
|
+
* it. That split is the contested shape this Block ships, and it is settled by a
|
|
19
|
+
* measurement rather than by taste: a pastel brand value is a fill and never text,
|
|
20
|
+
* so the core may be `primary` and the name may not, and a *filled* mark on a
|
|
21
|
+
* light card measures under the threshold a mark has to clear, so the mark is a
|
|
22
|
+
* hairline ring around the fill rather than a disc of it. A product row that drew
|
|
23
|
+
* a single filled dot with the name in the same value would be wrong in both
|
|
24
|
+
* directions at once, and nothing in the rendered result would say which.
|
|
25
|
+
*
|
|
26
|
+
* The pack is carried through `ProductMark` rather than set here, so the mark's
|
|
27
|
+
* own `data-pack` boundary lands on the mark and the product's hue follows from
|
|
28
|
+
* the pack the caller passed rather than from whichever pack the page is wearing.
|
|
29
|
+
* That is the half of the pack-boundary law a hand-written mark gets wrong: it
|
|
30
|
+
* resolves a colour once, at mount, from one element's computed style, and it
|
|
31
|
+
* keeps showing the old pack's values when a boundary lands above it.
|
|
32
|
+
*
|
|
33
|
+
* The row is a link, not a click handler. The whole row is an `a` with the
|
|
34
|
+
* product's `href`, so a status bar shows the destination, a context menu copies
|
|
35
|
+
* it, and middle-click opens it in a new tab. The mark is `aria-hidden` inside
|
|
36
|
+
* it, so the link is announced once, by name, rather than as a shape and a word.
|
|
37
|
+
*
|
|
38
|
+
* A row carries one line of tagline. A second line is a second paragraph and
|
|
39
|
+
* belongs above the grid in `description`, where it applies to the set rather
|
|
40
|
+
* than to one member of it.
|
|
41
|
+
*
|
|
42
|
+
* It is a server Component: no hook, no state, no client code and no mode. The
|
|
43
|
+
* mark resolves its colour through the cascade, so a page that renders this in a
|
|
44
|
+
* server file needs nothing from the provider mounted.
|
|
45
|
+
*/
|
|
46
|
+
export function ProductGrid01({ eyebrow, title, description, products, caption, headingLevel = 'h2', }) {
|
|
47
|
+
if (products.length === 0)
|
|
48
|
+
return null;
|
|
49
|
+
return (_jsxs(Section, { children: [title ? (_jsx(SectionHeading, { as: headingLevel, align: "left", eyebrow: eyebrow, title: title, description: description, className: "mb-12" })) : null, _jsx("ul", { "data-slot": "product-grid", className: "flex flex-col", children: products.map((product) => (_jsx("li", { "data-slot": "product-grid-row", className: "border-b first:border-t", children: _jsxs("a", { href: product.href, rel: product.newTab ? 'noopener noreferrer' : undefined, target: product.newTab ? '_blank' : undefined, className: "hover:bg-accent/50 focus-visible:ring-ring flex flex-col gap-1 px-2 py-4 transition-colors duration-fast ease-out sm:flex-row sm:items-baseline sm:gap-6 focus-visible:ring-[3px] focus-visible:outline-none", children: [_jsx("span", { className: "flex shrink-0 items-center sm:w-64", children: _jsx(ProductMark, { id: product.id, name: product.name, pack: product.pack, size: "md" }) }), _jsx("span", { className: "text-muted-foreground text-pretty text-sm", children: product.tagline })] }) }, product.id))) }), caption ? _jsx("p", { className: "text-muted-foreground mt-6 text-pretty text-sm", children: caption }) : null] }));
|
|
50
|
+
}
|
|
51
|
+
export default ProductGrid01;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/blocks/settings-panel-01/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAC3D,YAAY,EACV,oBAAoB,EACpB,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,kBAAkB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SettingsPanel01, default } from './settings-panel';
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { type FormEvent, type ReactNode } from 'react';
|
|
2
|
+
export type SettingsPanelOption = {
|
|
3
|
+
label: string;
|
|
4
|
+
value: string;
|
|
5
|
+
};
|
|
6
|
+
type SettingsPanelFieldBase = {
|
|
7
|
+
id: string;
|
|
8
|
+
label: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
};
|
|
12
|
+
export type SettingsPanelField = (SettingsPanelFieldBase & {
|
|
13
|
+
kind: 'text';
|
|
14
|
+
type?: 'text' | 'email' | 'url';
|
|
15
|
+
placeholder?: string;
|
|
16
|
+
value?: string;
|
|
17
|
+
defaultValue?: string;
|
|
18
|
+
onChange?: (value: string) => void;
|
|
19
|
+
}) | (SettingsPanelFieldBase & {
|
|
20
|
+
kind: 'textarea';
|
|
21
|
+
placeholder?: string;
|
|
22
|
+
rows?: number;
|
|
23
|
+
value?: string;
|
|
24
|
+
defaultValue?: string;
|
|
25
|
+
onChange?: (value: string) => void;
|
|
26
|
+
}) | (SettingsPanelFieldBase & {
|
|
27
|
+
kind: 'switch';
|
|
28
|
+
checked?: boolean;
|
|
29
|
+
defaultChecked?: boolean;
|
|
30
|
+
onCheckedChange?: (checked: boolean) => void;
|
|
31
|
+
}) | (SettingsPanelFieldBase & {
|
|
32
|
+
kind: 'select';
|
|
33
|
+
placeholder?: string;
|
|
34
|
+
options: SettingsPanelOption[];
|
|
35
|
+
value?: string;
|
|
36
|
+
defaultValue?: string;
|
|
37
|
+
onValueChange?: (value: string) => void;
|
|
38
|
+
});
|
|
39
|
+
export type SettingsPanelSection = {
|
|
40
|
+
id: string;
|
|
41
|
+
title: string;
|
|
42
|
+
description?: string;
|
|
43
|
+
fields: SettingsPanelField[];
|
|
44
|
+
};
|
|
45
|
+
export type SettingsPanel01Props = {
|
|
46
|
+
title: string;
|
|
47
|
+
description?: string;
|
|
48
|
+
/** The settings groups, each rendered with a heading and its own fields. */
|
|
49
|
+
sections: SettingsPanelSection[];
|
|
50
|
+
/** The submit button label. Omit to render no footer. */
|
|
51
|
+
submitLabel?: string;
|
|
52
|
+
onSubmit?: (event: FormEvent<HTMLFormElement>) => void;
|
|
53
|
+
/** A secondary control, such as Cancel. */
|
|
54
|
+
secondaryAction?: {
|
|
55
|
+
label: string;
|
|
56
|
+
onClick?: () => void;
|
|
57
|
+
};
|
|
58
|
+
/** Extra content in the footer, e.g. a status note. */
|
|
59
|
+
footer?: ReactNode;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* A settings form grouped into sections.
|
|
63
|
+
*
|
|
64
|
+
* A field is a small object describing one control: a text input, a textarea,
|
|
65
|
+
* a select or a switch, with its label, description and value handlers. The
|
|
66
|
+
* block renders the card, the groups and the footer; it owns no values and no
|
|
67
|
+
* copy, so a consumer wires it to whatever form state or mutation they use.
|
|
68
|
+
*/
|
|
69
|
+
export declare function SettingsPanel01({ title, description, sections, submitLabel, onSubmit, secondaryAction, footer, }: SettingsPanel01Props): import("react").JSX.Element;
|
|
70
|
+
export default SettingsPanel01;
|
|
71
|
+
//# sourceMappingURL=settings-panel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings-panel.d.ts","sourceRoot":"","sources":["../../../src/blocks/settings-panel-01/settings-panel.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAY,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AA6BhE,MAAM,MAAM,mBAAmB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAElE,KAAK,sBAAsB,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,kBAAkB,GAC1B,CAAC,sBAAsB,GAAG;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,KAAK,CAAA;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CACnC,CAAC,GACF,CAAC,sBAAsB,GAAG;IACxB,IAAI,EAAE,UAAU,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CACnC,CAAC,GACF,CAAC,sBAAsB,GAAG;IACxB,IAAI,EAAE,QAAQ,CAAA;IACd,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;CAC7C,CAAC,GACF,CAAC,sBAAsB,GAAG;IACxB,IAAI,EAAE,QAAQ,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,mBAAmB,EAAE,CAAA;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CACxC,CAAC,CAAA;AAEN,MAAM,MAAM,oBAAoB,GAAG;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,kBAAkB,EAAE,CAAA;CAC7B,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,4EAA4E;IAC5E,QAAQ,EAAE,oBAAoB,EAAE,CAAA;IAChC,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,eAAe,CAAC,KAAK,IAAI,CAAA;IACtD,2CAA2C;IAC3C,eAAe,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;KAAE,CAAA;IACzD,uDAAuD;IACvD,MAAM,CAAC,EAAE,SAAS,CAAA;CACnB,CAAA;AAyFD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,WAAW,EACX,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,eAAe,EACf,MAAM,GACP,EAAE,oBAAoB,+BAmDtB;AAED,eAAe,eAAe,CAAA"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Fragment } from 'react';
|
|
4
|
+
import { Button } from '../../components/ui/button';
|
|
5
|
+
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from '../../components/ui/card';
|
|
6
|
+
import { Field, FieldDescription, FieldGroup, FieldLabel, } from '../../components/ui/field';
|
|
7
|
+
import { Input } from '../../components/ui/input';
|
|
8
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '../../components/ui/select';
|
|
9
|
+
import { Separator } from '../../components/ui/separator';
|
|
10
|
+
import { Switch } from '../../components/ui/switch';
|
|
11
|
+
import { Textarea } from '../../components/ui/textarea';
|
|
12
|
+
function SettingsField({ field }) {
|
|
13
|
+
if (field.kind === 'switch') {
|
|
14
|
+
return (_jsxs(Field, { className: "flex-row items-center justify-between gap-4", children: [_jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx(FieldLabel, { htmlFor: field.id, children: field.label }), field.description ? (_jsx(FieldDescription, { children: field.description })) : null] }), _jsx(Switch, { id: field.id, checked: field.checked, defaultChecked: field.defaultChecked, onCheckedChange: field.onCheckedChange, disabled: field.disabled })] }));
|
|
15
|
+
}
|
|
16
|
+
if (field.kind === 'select') {
|
|
17
|
+
return (_jsxs(Field, { children: [_jsx(FieldLabel, { htmlFor: field.id, children: field.label }), _jsxs(Select, { value: field.value, defaultValue: field.defaultValue, onValueChange: field.onValueChange
|
|
18
|
+
? (value) => field.onValueChange?.(value ?? '')
|
|
19
|
+
: undefined, disabled: field.disabled, items: Object.fromEntries(field.options.map((option) => [option.value, option.label])), children: [_jsx(SelectTrigger, { id: field.id, className: "w-full", children: _jsx(SelectValue, { placeholder: field.placeholder }) }), _jsx(SelectContent, { children: field.options.map((option) => (_jsx(SelectItem, { value: option.value, children: option.label }, option.value))) })] }), field.description ? _jsx(FieldDescription, { children: field.description }) : null] }));
|
|
20
|
+
}
|
|
21
|
+
if (field.kind === 'textarea') {
|
|
22
|
+
return (_jsxs(Field, { children: [_jsx(FieldLabel, { htmlFor: field.id, children: field.label }), _jsx(Textarea, { id: field.id, rows: field.rows, placeholder: field.placeholder, value: field.value, defaultValue: field.defaultValue, onChange: field.onChange ? (event) => field.onChange?.(event.target.value) : undefined, disabled: field.disabled }), field.description ? _jsx(FieldDescription, { children: field.description }) : null] }));
|
|
23
|
+
}
|
|
24
|
+
return (_jsxs(Field, { children: [_jsx(FieldLabel, { htmlFor: field.id, children: field.label }), _jsx(Input, { id: field.id, type: field.type ?? 'text', placeholder: field.placeholder, value: field.value, defaultValue: field.defaultValue, onChange: field.onChange ? (event) => field.onChange?.(event.target.value) : undefined, disabled: field.disabled }), field.description ? _jsx(FieldDescription, { children: field.description }) : null] }));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A settings form grouped into sections.
|
|
28
|
+
*
|
|
29
|
+
* A field is a small object describing one control: a text input, a textarea,
|
|
30
|
+
* a select or a switch, with its label, description and value handlers. The
|
|
31
|
+
* block renders the card, the groups and the footer; it owns no values and no
|
|
32
|
+
* copy, so a consumer wires it to whatever form state or mutation they use.
|
|
33
|
+
*/
|
|
34
|
+
export function SettingsPanel01({ title, description, sections, submitLabel, onSubmit, secondaryAction, footer, }) {
|
|
35
|
+
const hasFooter = Boolean(submitLabel || secondaryAction || footer);
|
|
36
|
+
return (_jsx("form", { onSubmit: onSubmit, className: "w-full", children: _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: title }), description ? _jsx(CardDescription, { children: description }) : null] }), _jsx(CardContent, { className: "flex flex-col", children: sections.map((section, index) => (_jsxs(Fragment, { children: [index > 0 ? _jsx(Separator, { className: "my-6" }) : null, _jsxs("section", { className: "flex flex-col gap-4", "aria-labelledby": `${section.id}-heading`, children: [_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("h3", { id: `${section.id}-heading`, className: "text-lg font-semibold tracking-tight", children: section.title }), section.description ? (_jsx("p", { className: "text-muted-foreground text-sm", children: section.description })) : null] }), _jsx(FieldGroup, { children: section.fields.map((field) => (_jsx(SettingsField, { field: field }, field.id))) })] })] }, section.id))) }), hasFooter ? (_jsxs(CardFooter, { className: "justify-end gap-2", children: [footer, secondaryAction ? (_jsx(Button, { type: "button", variant: "outline", onClick: secondaryAction.onClick, children: secondaryAction.label })) : null, submitLabel ? _jsx(Button, { type: "submit", children: submitLabel }) : null] })) : null] }) }));
|
|
37
|
+
}
|
|
38
|
+
export default SettingsPanel01;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/blocks/site-footer/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACnD,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SiteFooter, default } from './site-footer';
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { type ProductMarkProps } from '../../components/ui/product-mark';
|
|
3
|
+
/**
|
|
4
|
+
* One destination in a footer column.
|
|
5
|
+
*
|
|
6
|
+
* `href` is required. A footer link with no destination is a label, and a column
|
|
7
|
+
* of labels in a footer reads as a broken page rather than as an omission.
|
|
8
|
+
*/
|
|
9
|
+
export type SiteFooterLink = {
|
|
10
|
+
/** The text of the link. */
|
|
11
|
+
label: string;
|
|
12
|
+
/** Where the link goes. */
|
|
13
|
+
href: string;
|
|
14
|
+
/**
|
|
15
|
+
* Opens the destination in a new browsing context, which defaults the link
|
|
16
|
+
* relationship to `noopener noreferrer`. Prism does not decide what counts as
|
|
17
|
+
* external, so the caller declares it.
|
|
18
|
+
*/
|
|
19
|
+
newTab?: boolean;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* One column of a footer's destinations: a title and the links under it.
|
|
23
|
+
*
|
|
24
|
+
* The title is required, because a column of links with no heading is a list
|
|
25
|
+
* that gives a reader no clue what the group is, and a footer's whole job is to
|
|
26
|
+
* be scannable rather than read.
|
|
27
|
+
*/
|
|
28
|
+
export type SiteFooterColumn = {
|
|
29
|
+
/** The heading of the column, which names the group. */
|
|
30
|
+
title: string;
|
|
31
|
+
/** The destinations in the group, in the order a reader should meet them. */
|
|
32
|
+
links: readonly SiteFooterLink[];
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* One link out of the site: a destination with an icon beside its label.
|
|
36
|
+
*
|
|
37
|
+
* The icon is a `ReactNode` rather than a component name, because the icon is
|
|
38
|
+
* the consumer's: it is their brand's mark for a destination, not one of a set
|
|
39
|
+
* Prism owns. The label is still required, because an icon alone is not a
|
|
40
|
+
* destination a screen reader can announce.
|
|
41
|
+
*/
|
|
42
|
+
export type SiteFooterSocial = {
|
|
43
|
+
/** The accessible name of the link, and the text beside the icon. */
|
|
44
|
+
label: string;
|
|
45
|
+
/** Where the link goes. */
|
|
46
|
+
href: string;
|
|
47
|
+
/**
|
|
48
|
+
* The mark drawn beside the label. `aria-hidden` is applied to it here, so an
|
|
49
|
+
* icon is a shape beside a word and never a second reading of the word.
|
|
50
|
+
*/
|
|
51
|
+
icon?: ReactNode;
|
|
52
|
+
/**
|
|
53
|
+
* Opens the destination in a new browsing context, which defaults the link
|
|
54
|
+
* relationship to `noopener noreferrer`. A destination off the site is
|
|
55
|
+
* declared by the caller, because Prism does not parse URLs.
|
|
56
|
+
*/
|
|
57
|
+
newTab?: boolean;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* The props a SiteFooter takes.
|
|
61
|
+
*
|
|
62
|
+
* The product is required and nothing else is. A footer with no columns, no
|
|
63
|
+
* social links and no legal line is a valid footer: it is a brand lockup and
|
|
64
|
+
* nothing else, and one of the four NaniSoft sites is exactly that.
|
|
65
|
+
*/
|
|
66
|
+
export type SiteFooterProps = {
|
|
67
|
+
/**
|
|
68
|
+
* The product this footer belongs to: the id, name and pack its `ProductMark`
|
|
69
|
+
* is drawn from. The footer renders the mark rather than taking a slot,
|
|
70
|
+
* because the brand lockup is the one part of a footer that is never
|
|
71
|
+
* product-specific and every consumer would otherwise reimplement it.
|
|
72
|
+
*/
|
|
73
|
+
product: Pick<ProductMarkProps, 'id' | 'name' | 'pack'>;
|
|
74
|
+
/**
|
|
75
|
+
* The groups of destinations: the sections of this site, its documentation, and
|
|
76
|
+
* anywhere else the site sends a reader. Omit it for a footer that carries only
|
|
77
|
+
* the brand lockup.
|
|
78
|
+
*/
|
|
79
|
+
columns?: readonly SiteFooterColumn[];
|
|
80
|
+
/**
|
|
81
|
+
* The links that leave the site. A `ReactNode` icon is drawn beside each label
|
|
82
|
+
* and hidden from assistive technology, so a screen reader announces the label
|
|
83
|
+
* once.
|
|
84
|
+
*/
|
|
85
|
+
social?: readonly SiteFooterSocial[];
|
|
86
|
+
/**
|
|
87
|
+
* A slot for the line at the foot of the footer: a copyright, a licence, a
|
|
88
|
+
* version, a legal link. A slot rather than a prop, because every one of those
|
|
89
|
+
* is a fact about a specific product and about the year it was published, and a
|
|
90
|
+
* Block that held a year would be a Block that went stale.
|
|
91
|
+
*/
|
|
92
|
+
legal?: ReactNode;
|
|
93
|
+
/**
|
|
94
|
+
* Layout only, exactly as on every Component. Changing a Prism-owned visual
|
|
95
|
+
* property from here is prohibited.
|
|
96
|
+
*/
|
|
97
|
+
className?: string;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* The footer of a product site: a brand lockup, grouped destinations, the links
|
|
101
|
+
* that leave the site, and an optional slot for the legal line.
|
|
102
|
+
*
|
|
103
|
+
* It is a Block and not a Component because it is a region of a page: it
|
|
104
|
+
* composes a `ProductMark` and two sets of links and owns the block they sit in.
|
|
105
|
+
* All four NaniSoft sites compose this exact footer in their root layout, and
|
|
106
|
+
* each one wrote its own column model, its own mark and its own current-page
|
|
107
|
+
* treatment.
|
|
108
|
+
*
|
|
109
|
+
* Every string is a prop and the Block ships none. There is no site name, no
|
|
110
|
+
* copyright, no year, no "built with" line and no default column: a footer that
|
|
111
|
+
* hardcoded any of them would hand every consumer a claim about a product that
|
|
112
|
+
* is not theirs and a year that was not this one.
|
|
113
|
+
*
|
|
114
|
+
* The columns are a grid that collapses to one column, and the brand lockup
|
|
115
|
+
* stays above them at every width. A footer read as three columns on a phone is
|
|
116
|
+
* three unreadable columns; a footer read as one column is a list a reader can
|
|
117
|
+
* scan, which is the only job a footer has on a narrow viewport.
|
|
118
|
+
*
|
|
119
|
+
* Each column is a `nav` with its own accessible name, taken from the column's
|
|
120
|
+
* title. A reader who navigates by landmark can therefore reach "Documentation"
|
|
121
|
+
* rather than arriving at one anonymous list of links.
|
|
122
|
+
*
|
|
123
|
+
* The social links open in a new browsing context by default, because a link that
|
|
124
|
+
* leaves the site and comes back is a reader who has lost the page they were on.
|
|
125
|
+
* That default is a decision this Block makes, and `newTab={false}` on one link
|
|
126
|
+
* overrides it: Prism does not decide which destinations are external, so the
|
|
127
|
+
* per-link prop wins over the default rather than the other way round.
|
|
128
|
+
*
|
|
129
|
+
* It is a server Component. It fetches nothing, it holds no state, and it
|
|
130
|
+
* imports no router: a consumer renders it from a server file and the links are
|
|
131
|
+
* plain anchors the consumer's own router can intercept.
|
|
132
|
+
*/
|
|
133
|
+
export declare function SiteFooter({ product, columns, social, legal, className, }: SiteFooterProps): import("react").JSX.Element;
|
|
134
|
+
export default SiteFooter;
|
|
135
|
+
//# sourceMappingURL=site-footer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"site-footer.d.ts","sourceRoot":"","sources":["../../../src/blocks/site-footer/site-footer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,EAAe,KAAK,gBAAgB,EAAE,MAAM,kCAAkC,CAAA;AAGrF;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED;;;;;;GAMG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAA;IACb,6EAA6E;IAC7E,KAAK,EAAE,SAAS,cAAc,EAAE,CAAA;CACjC,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAA;IACb,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ;;;OAGG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B;;;;;OAKG;IACH,OAAO,EAAE,IAAI,CAAC,gBAAgB,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC,CAAA;IACvD;;;;OAIG;IACH,OAAO,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAA;IACrC;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAA;IACpC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,SAAS,CAAA;IACjB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,OAAO,EACP,MAAM,EACN,KAAK,EACL,SAAS,GACV,EAAE,eAAe,+BA8DjB;AAED,eAAe,UAAU,CAAA"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ProductMark } from '../../components/ui/product-mark';
|
|
3
|
+
import { cn } from '../../lib/utils';
|
|
4
|
+
/**
|
|
5
|
+
* The footer of a product site: a brand lockup, grouped destinations, the links
|
|
6
|
+
* that leave the site, and an optional slot for the legal line.
|
|
7
|
+
*
|
|
8
|
+
* It is a Block and not a Component because it is a region of a page: it
|
|
9
|
+
* composes a `ProductMark` and two sets of links and owns the block they sit in.
|
|
10
|
+
* All four NaniSoft sites compose this exact footer in their root layout, and
|
|
11
|
+
* each one wrote its own column model, its own mark and its own current-page
|
|
12
|
+
* treatment.
|
|
13
|
+
*
|
|
14
|
+
* Every string is a prop and the Block ships none. There is no site name, no
|
|
15
|
+
* copyright, no year, no "built with" line and no default column: a footer that
|
|
16
|
+
* hardcoded any of them would hand every consumer a claim about a product that
|
|
17
|
+
* is not theirs and a year that was not this one.
|
|
18
|
+
*
|
|
19
|
+
* The columns are a grid that collapses to one column, and the brand lockup
|
|
20
|
+
* stays above them at every width. A footer read as three columns on a phone is
|
|
21
|
+
* three unreadable columns; a footer read as one column is a list a reader can
|
|
22
|
+
* scan, which is the only job a footer has on a narrow viewport.
|
|
23
|
+
*
|
|
24
|
+
* Each column is a `nav` with its own accessible name, taken from the column's
|
|
25
|
+
* title. A reader who navigates by landmark can therefore reach "Documentation"
|
|
26
|
+
* rather than arriving at one anonymous list of links.
|
|
27
|
+
*
|
|
28
|
+
* The social links open in a new browsing context by default, because a link that
|
|
29
|
+
* leaves the site and comes back is a reader who has lost the page they were on.
|
|
30
|
+
* That default is a decision this Block makes, and `newTab={false}` on one link
|
|
31
|
+
* overrides it: Prism does not decide which destinations are external, so the
|
|
32
|
+
* per-link prop wins over the default rather than the other way round.
|
|
33
|
+
*
|
|
34
|
+
* It is a server Component. It fetches nothing, it holds no state, and it
|
|
35
|
+
* imports no router: a consumer renders it from a server file and the links are
|
|
36
|
+
* plain anchors the consumer's own router can intercept.
|
|
37
|
+
*/
|
|
38
|
+
export function SiteFooter({ product, columns, social, legal, className, }) {
|
|
39
|
+
return (_jsx("footer", { "data-slot": "site-footer", className: cn('border-border bg-background w-full border-t', className), children: _jsxs("div", { className: "mx-auto flex w-full max-w-6xl flex-col gap-10 px-6 py-12 lg:px-8", children: [_jsxs("div", { className: "flex flex-col gap-10 md:flex-row md:justify-between", children: [_jsxs("div", { className: "flex flex-col gap-4", children: [_jsx(ProductMark, { id: product.id, name: product.name, pack: product.pack, size: "lg" }), social && social.length > 0 ? (_jsx("ul", { className: "flex flex-wrap items-center gap-4", children: social.map((link) => (_jsx("li", { children: _jsxs("a", { href: link.href, rel: link.newTab === false ? undefined : 'noopener noreferrer', target: link.newTab === false ? undefined : '_blank', className: "text-muted-foreground hover:text-foreground inline-flex items-center gap-2 rounded-sm text-sm font-medium transition-colors duration-fast ease-out", children: [link.icon ? (_jsx("span", { "aria-hidden": true, className: "inline-flex items-center", children: link.icon })) : null, link.label] }) }, link.href))) })) : null] }), columns && columns.length > 0 ? (_jsx("div", { className: "grid gap-8 sm:grid-cols-2 lg:grid-cols-3", children: columns.map((column) => (_jsxs("nav", { "aria-label": column.title, className: "flex flex-col gap-3", children: [_jsx("h2", { className: "text-sm font-semibold", children: column.title }), _jsx("ul", { className: "flex flex-col gap-2", children: column.links.map((link) => (_jsx("li", { children: _jsx("a", { href: link.href, rel: link.newTab ? 'noopener noreferrer' : undefined, target: link.newTab ? '_blank' : undefined, className: "text-muted-foreground hover:text-foreground rounded-sm text-sm transition-colors duration-fast ease-out", children: link.label }) }, link.href))) })] }, column.title))) })) : null] }), legal ? _jsx("div", { className: "text-muted-foreground text-sm", children: legal }) : null] }) }));
|
|
40
|
+
}
|
|
41
|
+
export default SiteFooter;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/blocks/site-header/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACnD,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SiteHeader, default } from './site-header';
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { type SwitcherProduct } from '../../components/ui/product-switcher';
|
|
3
|
+
import { type ProductMarkProps } from '../../components/ui/product-mark';
|
|
4
|
+
/**
|
|
5
|
+
* One destination in the header's own navigation.
|
|
6
|
+
*
|
|
7
|
+
* `href` is required and `current` is opt-in. The header marks the current page
|
|
8
|
+
* with `aria-current="page"` rather than with a colour, so a link's state is
|
|
9
|
+
* carried by the attribute every assistive technology already reads and a reader
|
|
10
|
+
* who cannot see the colour still knows where they are.
|
|
11
|
+
*/
|
|
12
|
+
export type SiteHeaderLink = {
|
|
13
|
+
/** The text of the link. */
|
|
14
|
+
label: string;
|
|
15
|
+
/** Where the link goes. */
|
|
16
|
+
href: string;
|
|
17
|
+
/**
|
|
18
|
+
* Marks this link as the page the reader is on. Set it on the one link whose
|
|
19
|
+
* destination is the current route; leave it off every other link.
|
|
20
|
+
*/
|
|
21
|
+
current?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Opens the destination in a new browsing context, which defaults the link
|
|
24
|
+
* relationship to `noopener noreferrer`. Prism does not decide what counts as
|
|
25
|
+
* external: a cross-origin destination is not by itself a reason to open a tab.
|
|
26
|
+
*/
|
|
27
|
+
newTab?: boolean;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* The props a SiteHeader takes.
|
|
31
|
+
*
|
|
32
|
+
* There is no product directory in this package, so the product the header is on
|
|
33
|
+
* is passed as the three facts its mark is drawn from rather than as an id this
|
|
34
|
+
* package would have to resolve. That is the same answer `ProductMark` gives,
|
|
35
|
+
* and for the same reason: a consumer that composes a set of its own products
|
|
36
|
+
* composes the header too.
|
|
37
|
+
*
|
|
38
|
+
* The product is the only required field but one. A header with no navigation, no
|
|
39
|
+
* switcher and no actions is a valid header: it is a brand lockup and nothing
|
|
40
|
+
* else, and one of the four NaniSoft sites is exactly that.
|
|
41
|
+
*/
|
|
42
|
+
export type SiteHeaderProps = {
|
|
43
|
+
/**
|
|
44
|
+
* The product this header belongs to: the id, name and pack its `ProductMark`
|
|
45
|
+
* is drawn from. The header renders the mark itself rather than a slot, because
|
|
46
|
+
* a header whose brand lockup is a slot is a header every consumer has to
|
|
47
|
+
* reimplement, and that is the one part of a site header that is never
|
|
48
|
+
* product-specific.
|
|
49
|
+
*/
|
|
50
|
+
product: Pick<ProductMarkProps, 'id' | 'name' | 'pack'>;
|
|
51
|
+
/**
|
|
52
|
+
* The set of products to move between, rendered as a `ProductSwitcher` between
|
|
53
|
+
* the mark and the navigation. Omit it on a site that is the only product, and
|
|
54
|
+
* pass it on a site that is one of several. A set of one renders one mark, so
|
|
55
|
+
* pass nothing rather than a switcher with a single member.
|
|
56
|
+
*/
|
|
57
|
+
products?: readonly SwitcherProduct[];
|
|
58
|
+
/**
|
|
59
|
+
* The header's own destinations: the sections of this site. Omit it for a
|
|
60
|
+
* header that carries only the brand lockup.
|
|
61
|
+
*/
|
|
62
|
+
nav?: readonly SiteHeaderLink[];
|
|
63
|
+
/**
|
|
64
|
+
* The accessible name of the site navigation, which is also the visible word a
|
|
65
|
+
* screen reader announces before the list.
|
|
66
|
+
*
|
|
67
|
+
* It is required rather than defaulted because it is a word a reader hears, and
|
|
68
|
+
* a Block that ships no copy ships no reader-facing copy either. A header that
|
|
69
|
+
* defaulted this would put its own name on every consumer's site navigation, and
|
|
70
|
+
* "Site" is right for a company site and wrong for a product that has a docs
|
|
71
|
+
* section it wants called something else.
|
|
72
|
+
*/
|
|
73
|
+
navLabel: string;
|
|
74
|
+
/**
|
|
75
|
+
* The accessible name of the product switcher. Required whenever `products` is
|
|
76
|
+
* passed, for the same reason `navLabel` is: it is a word a reader hears, and a
|
|
77
|
+
* Block that ships no copy ships none of those either. Omit it and the switcher
|
|
78
|
+
* takes the site navigation's name, which is right when the two are the same set
|
|
79
|
+
* of places and wrong when they are not.
|
|
80
|
+
*/
|
|
81
|
+
productsLabel?: string;
|
|
82
|
+
/**
|
|
83
|
+
* A slot for the controls a product owns on the right: an account menu, a sign
|
|
84
|
+
* in link, a mode toggle, a theme picker. A slot rather than a prop, because
|
|
85
|
+
* every one of those is application state and none of them is a fact a header
|
|
86
|
+
* can be told.
|
|
87
|
+
*/
|
|
88
|
+
actions?: ReactNode;
|
|
89
|
+
/**
|
|
90
|
+
* Keeps the header at the top of the viewport as the page scrolls. Off by
|
|
91
|
+
* default, because a sticky header is a decision about a page rather than about
|
|
92
|
+
* a header, and a header that is sticky on a page with no scroll costs the
|
|
93
|
+
* reader a strip of their viewport.
|
|
94
|
+
*/
|
|
95
|
+
sticky?: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Layout only, exactly as on every Component. Changing a Prism-owned visual
|
|
98
|
+
* property from here is prohibited.
|
|
99
|
+
*/
|
|
100
|
+
className?: string;
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* The header of a product site: a brand lockup, an optional product switcher, the
|
|
104
|
+
* site's own navigation and an optional slot for application controls.
|
|
105
|
+
*
|
|
106
|
+
* It is a Block and not a Component because it is a region of a page rather than
|
|
107
|
+
* a control: it composes a `ProductMark`, a `ProductSwitcher` and a set of
|
|
108
|
+
* links, and it owns the bar they sit in. All four NaniSoft sites compose this
|
|
109
|
+
* exact bar in their root layout, and each one wrote its own: the mark in one
|
|
110
|
+
* place, the switcher in a popover in another, the navigation in a third, and
|
|
111
|
+
* each wrote the focus and current-page wiring separately.
|
|
112
|
+
*
|
|
113
|
+
* Every string is a prop and the Block ships none. There is no site name, no
|
|
114
|
+
* default navigation, no "sign in" and no product list: a header that hardcoded
|
|
115
|
+
* any of them would hand every consumer a claim about a product that is not
|
|
116
|
+
* theirs, which is the defect this Block exists to remove.
|
|
117
|
+
*
|
|
118
|
+
* The bar is one flex row that wraps. The brand lockup and the actions hold
|
|
119
|
+
* their ends, the switcher and the navigation sit in the middle and wrap onto a
|
|
120
|
+
* second line rather than overflowing, so a narrow viewport gets a taller header
|
|
121
|
+
* and never a clipped one. The brand lockup is the one element that never
|
|
122
|
+
* wraps: a wordmark broken across two lines is not a wordmark.
|
|
123
|
+
*
|
|
124
|
+
* The navigation is a `nav` with an accessible name and the switcher is a second
|
|
125
|
+
* `nav`, because a reader who navigates by landmark needs two regions rather than
|
|
126
|
+
* one region with two lists in it. The actions slot is not a region at all: it is
|
|
127
|
+
* whatever the consumer composed, and wrapping it in a landmark would be a claim
|
|
128
|
+
* about content this Block does not own.
|
|
129
|
+
*
|
|
130
|
+
* The bottom hairline is `border-border`, so the bar separates from the page by
|
|
131
|
+
* a line rather than by a fill change, which is how every surface in this system
|
|
132
|
+
* is separated: in the base pack `background` and `card` resolve to the same
|
|
133
|
+
* value, so a fill is not available as a separator.
|
|
134
|
+
*
|
|
135
|
+
* It is a server Component. It fetches nothing, it holds no state, and a page
|
|
136
|
+
* that renders it in a server file needs nothing from the provider mounted: the
|
|
137
|
+
* pack it wears is the pack of whatever boundary is above it, which is the
|
|
138
|
+
* mechanism the whole system uses.
|
|
139
|
+
*/
|
|
140
|
+
export declare function SiteHeader({ product, products, nav, navLabel, productsLabel, actions, sticky, className, }: SiteHeaderProps): import("react").JSX.Element;
|
|
141
|
+
export default SiteHeader;
|
|
142
|
+
//# sourceMappingURL=site-header.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"site-header.d.ts","sourceRoot":"","sources":["../../../src/blocks/site-header/site-header.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EAAe,KAAK,gBAAgB,EAAE,MAAM,kCAAkC,CAAA;AAGrF;;;;;;;GAOG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B;;;;;;OAMG;IACH,OAAO,EAAE,IAAI,CAAC,gBAAgB,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC,CAAA;IACvD;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,SAAS,eAAe,EAAE,CAAA;IACrC;;;OAGG;IACH,GAAG,CAAC,EAAE,SAAS,cAAc,EAAE,CAAA;IAC/B;;;;;;;;;OASG;IACH,QAAQ,EAAE,MAAM,CAAA;IAChB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,SAAS,CAAA;IACnB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,QAAQ,EACR,GAAG,EACH,QAAQ,EACR,aAAa,EACb,OAAO,EACP,MAAc,EACd,SAAS,GACV,EAAE,eAAe,+BA+CjB;AAED,eAAe,UAAU,CAAA"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ProductSwitcher, } from '../../components/ui/product-switcher';
|
|
3
|
+
import { ProductMark } from '../../components/ui/product-mark';
|
|
4
|
+
import { cn } from '../../lib/utils';
|
|
5
|
+
/**
|
|
6
|
+
* The header of a product site: a brand lockup, an optional product switcher, the
|
|
7
|
+
* site's own navigation and an optional slot for application controls.
|
|
8
|
+
*
|
|
9
|
+
* It is a Block and not a Component because it is a region of a page rather than
|
|
10
|
+
* a control: it composes a `ProductMark`, a `ProductSwitcher` and a set of
|
|
11
|
+
* links, and it owns the bar they sit in. All four NaniSoft sites compose this
|
|
12
|
+
* exact bar in their root layout, and each one wrote its own: the mark in one
|
|
13
|
+
* place, the switcher in a popover in another, the navigation in a third, and
|
|
14
|
+
* each wrote the focus and current-page wiring separately.
|
|
15
|
+
*
|
|
16
|
+
* Every string is a prop and the Block ships none. There is no site name, no
|
|
17
|
+
* default navigation, no "sign in" and no product list: a header that hardcoded
|
|
18
|
+
* any of them would hand every consumer a claim about a product that is not
|
|
19
|
+
* theirs, which is the defect this Block exists to remove.
|
|
20
|
+
*
|
|
21
|
+
* The bar is one flex row that wraps. The brand lockup and the actions hold
|
|
22
|
+
* their ends, the switcher and the navigation sit in the middle and wrap onto a
|
|
23
|
+
* second line rather than overflowing, so a narrow viewport gets a taller header
|
|
24
|
+
* and never a clipped one. The brand lockup is the one element that never
|
|
25
|
+
* wraps: a wordmark broken across two lines is not a wordmark.
|
|
26
|
+
*
|
|
27
|
+
* The navigation is a `nav` with an accessible name and the switcher is a second
|
|
28
|
+
* `nav`, because a reader who navigates by landmark needs two regions rather than
|
|
29
|
+
* one region with two lists in it. The actions slot is not a region at all: it is
|
|
30
|
+
* whatever the consumer composed, and wrapping it in a landmark would be a claim
|
|
31
|
+
* about content this Block does not own.
|
|
32
|
+
*
|
|
33
|
+
* The bottom hairline is `border-border`, so the bar separates from the page by
|
|
34
|
+
* a line rather than by a fill change, which is how every surface in this system
|
|
35
|
+
* is separated: in the base pack `background` and `card` resolve to the same
|
|
36
|
+
* value, so a fill is not available as a separator.
|
|
37
|
+
*
|
|
38
|
+
* It is a server Component. It fetches nothing, it holds no state, and a page
|
|
39
|
+
* that renders it in a server file needs nothing from the provider mounted: the
|
|
40
|
+
* pack it wears is the pack of whatever boundary is above it, which is the
|
|
41
|
+
* mechanism the whole system uses.
|
|
42
|
+
*/
|
|
43
|
+
export function SiteHeader({ product, products, nav, navLabel, productsLabel, actions, sticky = false, className, }) {
|
|
44
|
+
return (_jsx("header", { "data-slot": "site-header", className: cn('border-border bg-background w-full border-b', sticky && 'sticky top-0 z-30', className), children: _jsxs("div", { className: "mx-auto flex w-full max-w-6xl flex-wrap items-center gap-x-6 gap-y-3 px-6 py-3 lg:px-8", children: [_jsx("a", { href: "/", className: "flex shrink-0 items-center rounded-sm", children: _jsx(ProductMark, { id: product.id, name: product.name, pack: product.pack, size: "md" }) }), products && products.length > 0 ? (_jsx(ProductSwitcher, { products: products, currentId: product.id, size: "sm", label: productsLabel ?? navLabel })) : null, nav && nav.length > 0 ? (_jsx("nav", { "aria-label": navLabel, className: "flex flex-wrap items-center gap-x-5 gap-y-2", children: nav.map((link) => (_jsx("a", { href: link.href, "aria-current": link.current ? 'page' : undefined, rel: link.newTab ? 'noopener noreferrer' : undefined, target: link.newTab ? '_blank' : undefined, className: "text-muted-foreground hover:text-foreground rounded-sm text-sm font-medium transition-colors duration-fast ease-out", children: link.label }, link.href))) })) : null, actions ? (_jsx("div", { className: "ms-auto flex items-center gap-3", children: actions })) : null] }) }));
|
|
45
|
+
}
|
|
46
|
+
export default SiteHeader;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/blocks/stack-grid-01/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACnD,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { StackGrid01, default } from './stack-grid';
|