@vamos-ui/react 0.3.0 → 0.4.1
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/README.md +4 -0
- package/dist/blocks/block-heading.js +1 -1
- package/dist/blocks/block-image.js +1 -1
- package/dist/blocks/block-link.js +2 -2
- package/dist/blocks/index.d.ts +5 -5
- package/dist/blocks/index.js +5 -5
- package/dist/blocks/labels.d.ts +8 -0
- package/dist/blocks/labels.js +60 -0
- package/dist/blocks/section-header.js +1 -1
- package/dist/blocks/section.js +2 -2
- package/dist/blocks/site/_kit-1.js +1 -1
- package/dist/blocks/site/announcement-bar.js +1 -1
- package/dist/blocks/site/article-layout.d.ts +1 -0
- package/dist/blocks/site/article-layout.js +2 -1
- package/dist/blocks/site/blog-list.d.ts +1 -0
- package/dist/blocks/site/blog-list.js +3 -2
- package/dist/blocks/site/contact-dock.js +3 -1
- package/dist/blocks/site/contact-form.js +1 -1
- package/dist/blocks/site/cta.d.ts +2 -2
- package/dist/blocks/site/cta.js +6 -4
- package/dist/blocks/site/feature-grid.d.ts +1 -0
- package/dist/blocks/site/feature-grid.js +5 -4
- package/dist/blocks/site/feature-tabs.js +1 -1
- package/dist/blocks/site/footer.d.ts +11 -2
- package/dist/blocks/site/footer.js +36 -11
- package/dist/blocks/site/gallery-section.d.ts +6 -1
- package/dist/blocks/site/gallery-section.js +5 -4
- package/dist/blocks/site/hero.d.ts +3 -2
- package/dist/blocks/site/hero.js +7 -5
- package/dist/blocks/site/logo-strip.d.ts +1 -0
- package/dist/blocks/site/logo-strip.js +1 -1
- package/dist/blocks/site/map-hours.d.ts +2 -2
- package/dist/blocks/site/map-hours.js +4 -2
- package/dist/blocks/site/media-text.d.ts +4 -1
- package/dist/blocks/site/media-text.js +4 -3
- package/dist/blocks/site/menu-list.d.ts +7 -3
- package/dist/blocks/site/menu-list.js +34 -7
- package/dist/blocks/site/navbar.d.ts +1 -0
- package/dist/blocks/site/navbar.js +6 -5
- package/dist/blocks/site/not-found.js +2 -1
- package/dist/blocks/site/page-intro.d.ts +1 -0
- package/dist/blocks/site/page-intro.js +2 -1
- package/dist/blocks/site/process.d.ts +1 -0
- package/dist/blocks/site/process.js +2 -1
- package/dist/blocks/site/registry-1.d.ts +1 -1
- package/dist/blocks/site/registry-2.d.ts +5 -0
- package/dist/blocks/site/registry-2.js +5 -3
- package/dist/blocks/site/rich-text-section.js +2 -1
- package/dist/blocks/site/shared/heading-lines.d.ts +9 -0
- package/dist/blocks/site/shared/heading-lines.js +32 -0
- package/dist/blocks/site/shared/image-sizes.d.ts +25 -0
- package/dist/blocks/site/shared/image-sizes.js +37 -0
- package/dist/blocks/site/shared/island-keys.d.ts +1 -1
- package/dist/blocks/site/shared/island-keys.js +1 -1
- package/dist/blocks/site/shared/labels-2.d.ts +17 -1
- package/dist/blocks/site/shared/labels-2.js +832 -6
- package/dist/blocks/site/shared/open-now.d.ts +1 -1
- package/dist/blocks/site/shared/open-now.js +1 -1
- package/dist/blocks/site/shared/opening-hours.d.ts +18 -2
- package/dist/blocks/site/shared/opening-hours.js +36 -7
- package/dist/blocks/site/shared/tap.d.ts +9 -0
- package/dist/blocks/site/shared/tap.js +16 -0
- package/dist/blocks/site/site-shell.js +3 -3
- package/dist/blocks/site/team.d.ts +1 -1
- package/dist/blocks/site/team.js +4 -3
- package/dist/blocks/site/testimonials.d.ts +2 -2
- package/dist/blocks/site/testimonials.js +3 -3
- package/dist/business/approval-flow.js +2 -2
- package/dist/business/import-wizard.js +1 -1
- package/dist/business/line-items.js +1 -1
- package/dist/business/multi-step-form.js +2 -2
- package/dist/business/permission-matrix.js +1 -1
- package/dist/business/setup-checklist.js +2 -2
- package/dist/business/signer-status.js +1 -1
- package/dist/charts/bar-chart.js +1 -1
- package/dist/charts/bar-list.js +1 -1
- package/dist/charts/chart-shell.d.ts +9 -2
- package/dist/charts/chart-shell.js +11 -5
- package/dist/charts/chart.js +1 -1
- package/dist/charts/combo-chart.js +5 -7
- package/dist/charts/donut-chart.js +1 -1
- package/dist/charts/funnel.js +1 -1
- package/dist/charts/gauge.js +2 -2
- package/dist/charts/utils.d.ts +10 -0
- package/dist/charts/utils.js +46 -0
- package/dist/components/accordion.d.ts +1 -1
- package/dist/components/accordion.js +8 -8
- package/dist/components/activity-timeline.js +11 -11
- package/dist/components/address-field.js +3 -3
- package/dist/components/alert-dialog.d.ts +1 -1
- package/dist/components/alert-dialog.js +8 -8
- package/dist/components/alert.js +2 -2
- package/dist/components/app-shell.js +3 -3
- package/dist/components/aspect-ratio.d.ts +26 -0
- package/dist/components/aspect-ratio.js +42 -0
- package/dist/components/attachment-list.js +2 -2
- package/dist/components/audit-log.js +1 -1
- package/dist/components/avatar.d.ts +1 -1
- package/dist/components/avatar.js +8 -8
- package/dist/components/backup-codes.js +1 -1
- package/dist/components/badge.js +3 -3
- package/dist/components/breadcrumb.js +10 -9
- package/dist/components/button-group.js +4 -4
- package/dist/components/button.js +4 -4
- package/dist/components/calendar.js +5 -5
- package/dist/components/card.js +8 -8
- package/dist/components/carousel.d.ts +12 -3
- package/dist/components/carousel.js +17 -14
- package/dist/components/checkbox.d.ts +1 -1
- package/dist/components/checkbox.js +6 -6
- package/dist/components/collapsible.d.ts +36 -0
- package/dist/components/collapsible.js +37 -0
- package/dist/components/color-picker.js +3 -3
- package/dist/components/combobox.js +1 -1
- package/dist/components/command.js +9 -9
- package/dist/components/comment-thread.js +3 -3
- package/dist/components/context-menu.d.ts +1 -1
- package/dist/components/context-menu.js +9 -9
- package/dist/components/currency-input.js +1 -1
- package/dist/components/date-picker.js +2 -2
- package/dist/components/date-range-picker.js +1 -1
- package/dist/components/description-list.js +2 -2
- package/dist/components/dialog.d.ts +1 -1
- package/dist/components/dialog.js +9 -9
- package/dist/components/dropdown-menu.d.ts +1 -1
- package/dist/components/dropdown-menu.js +9 -9
- package/dist/components/editor-layout.js +9 -9
- package/dist/components/empty-state.js +2 -2
- package/dist/components/entity-card.js +1 -1
- package/dist/components/file-upload.js +5 -5
- package/dist/components/formatted-input.js +3 -3
- package/dist/components/hierarchy-chart.js +1 -1
- package/dist/components/hover-card.d.ts +1 -1
- package/dist/components/hover-card.js +3 -3
- package/dist/components/iban-input.js +1 -1
- package/dist/components/id-input.js +3 -3
- package/dist/components/image.d.ts +18 -2
- package/dist/components/image.js +16 -9
- package/dist/components/input-otp.js +6 -6
- package/dist/components/input.d.ts +1 -1
- package/dist/components/input.js +6 -6
- package/dist/components/item.js +4 -4
- package/dist/components/kbd.js +3 -3
- package/dist/components/list-layout.js +9 -9
- package/dist/components/menubar.d.ts +96 -0
- package/dist/components/menubar.js +123 -0
- package/dist/components/native-select.js +1 -1
- package/dist/components/navigation-menu.d.ts +1 -1
- package/dist/components/navigation-menu.js +12 -11
- package/dist/components/notification-center.js +4 -4
- package/dist/components/page-header.js +3 -3
- package/dist/components/pagination.js +9 -9
- package/dist/components/phone-input.js +1 -1
- package/dist/components/pin-map.js +1 -1
- package/dist/components/placeholder-art.d.ts +51 -0
- package/dist/components/placeholder-art.js +82 -0
- package/dist/components/popover.d.ts +1 -1
- package/dist/components/popover.js +7 -7
- package/dist/components/progress.js +3 -3
- package/dist/components/qr-code.js +1 -1
- package/dist/components/radio-group.d.ts +1 -1
- package/dist/components/radio-group.js +4 -4
- package/dist/components/record-detail-layout.js +10 -10
- package/dist/components/record-picker.js +2 -2
- package/dist/components/resizable.js +2 -2
- package/dist/components/scroll-area.d.ts +1 -1
- package/dist/components/scroll-area.js +4 -4
- package/dist/components/select.d.ts +1 -1
- package/dist/components/select.js +10 -10
- package/dist/components/separator.d.ts +1 -1
- package/dist/components/separator.js +3 -3
- package/dist/components/settings-layout.js +6 -6
- package/dist/components/sheet.js +3 -3
- package/dist/components/sidebar.js +12 -11
- package/dist/components/signature-pad.js +1 -1
- package/dist/components/skeleton.js +5 -5
- package/dist/components/sla-timer.js +2 -2
- package/dist/components/slider.d.ts +1 -1
- package/dist/components/slider.js +2 -2
- package/dist/components/slug-input.js +1 -1
- package/dist/components/sparkline.js +1 -1
- package/dist/components/spinner.js +2 -2
- package/dist/components/stat.js +5 -5
- package/dist/components/status-picker.js +2 -2
- package/dist/components/stepper.js +1 -1
- package/dist/components/switch.d.ts +1 -1
- package/dist/components/switch.js +3 -3
- package/dist/components/table.js +10 -10
- package/dist/components/tabs.d.ts +1 -1
- package/dist/components/tabs.js +11 -11
- package/dist/components/tag-input.js +1 -1
- package/dist/components/task-list.js +1 -1
- package/dist/components/time-picker.js +3 -3
- package/dist/components/toggle.d.ts +2 -1
- package/dist/components/toggle.js +7 -6
- package/dist/components/tooltip.d.ts +1 -1
- package/dist/components/tooltip.js +5 -4
- package/dist/components/top-bar.js +4 -4
- package/dist/components/tour.js +1 -1
- package/dist/components/tree-select.js +2 -2
- package/dist/components/tree-view.js +3 -3
- package/dist/components/typography.js +11 -11
- package/dist/components/user-picker.js +2 -2
- package/dist/components/version-history.js +3 -3
- package/dist/data-table/data-table.js +1 -1
- package/dist/data-table/filter-bar.js +2 -2
- package/dist/data-table/index.d.ts +15 -8
- package/dist/data-table/index.js +9 -8
- package/dist/data-table/saved-views.js +1 -1
- package/dist/editor/block-editor.js +1 -1
- package/dist/editor/context.js +1 -1
- package/dist/editor/menus.js +1 -1
- package/dist/fonts/font-picker.js +3 -3
- package/dist/fonts/pairing-picker.js +1 -1
- package/dist/fonts/provider.js +1 -1
- package/dist/index.d.ts +188 -90
- package/dist/index.js +94 -90
- package/dist/lib/data-islands.js +1 -1
- package/dist/lib/field-context.js +1 -1
- package/dist/lib/floating-arrow.js +1 -1
- package/dist/lib/islands.js +1 -1
- package/dist/lib/layer.js +3 -3
- package/dist/lib/locale.js +2 -2
- package/dist/lib/menu.js +2 -2
- package/dist/lib/provider.js +4 -4
- package/dist/lib/theme-scope.js +4 -3
- package/dist/lib/value-format.js +1 -1
- package/dist/lib/variants.js +2 -2
- package/dist/locales/ar.d.ts +2087 -0
- package/dist/locales/ar.js +2088 -0
- package/dist/locales/de.d.ts +2087 -0
- package/dist/locales/de.js +2088 -0
- package/dist/locales/en.d.ts +1 -0
- package/dist/locales/en.js +1 -0
- package/dist/locales/es.d.ts +2087 -0
- package/dist/locales/es.js +2087 -0
- package/dist/locales/fr.d.ts +2087 -0
- package/dist/locales/fr.js +2088 -0
- package/dist/locales/tr.d.ts +1 -0
- package/dist/locales/tr.js +1 -0
- package/dist/media/focal-point.js +1 -1
- package/dist/media/gallery.js +1 -1
- package/dist/media/image-cropper.js +1 -1
- package/dist/media/image-field.js +2 -2
- package/dist/media/lightbox.js +4 -3
- package/dist/media/link-preview.js +3 -3
- package/dist/media/media-library.js +4 -4
- package/dist/media/media-picker.js +1 -1
- package/dist/media/media-provider.js +1 -1
- package/dist/media/video-player.js +4 -4
- package/dist/motion/count-up.js +1 -1
- package/dist/motion/motion-scope.js +2 -2
- package/dist/motion/page-transition.js +1 -1
- package/dist/motion/parallax.js +2 -2
- package/dist/motion/pointer.js +3 -3
- package/dist/motion/reveal.js +3 -3
- package/dist/motion/skeleton-swap.js +1 -1
- package/dist/motion/text-reveal.d.ts +3 -1
- package/dist/motion/text-reveal.js +6 -4
- package/dist/motion/ticker.js +1 -1
- package/dist/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/dist/render/block-renderer.js +1 -1
- package/dist/scheduler/gantt.js +2 -2
- package/dist/scheduler/resource-calendar.js +2 -2
- package/dist/scheduler/scheduler.js +3 -3
- package/dist/scheduler/time-tracker.js +4 -4
- package/dist/sortable/kanban.js +2 -2
- package/dist/sortable/sortable-list.js +2 -2
- package/dist/sortable/sortable.js +4 -4
- package/dist/styles/blocks.css +18 -9
- package/dist/styles/editors.css +2 -1
- package/dist/styles/media.css +31 -5
- package/dist/styles.css +39 -9
- package/package.json +1 -1
|
@@ -8,6 +8,7 @@ import { BlockLink } from "../block-link.js";
|
|
|
8
8
|
import { SkipLink } from "../section.js";
|
|
9
9
|
import { NavbarSubmenu } from "./navbar-submenu.js";
|
|
10
10
|
import { CtaList, useBlockIds, siteText } from "./_kit-1.js";
|
|
11
|
+
import { TAP } from "./shared/tap.js";
|
|
11
12
|
/** National format in the number's own locale, international elsewhere ("0216 000 00 00" for tr). */
|
|
12
13
|
export function formatSitePhone(e164, locale = "en-US") {
|
|
13
14
|
const p = parsePhoneNumberFromString(e164);
|
|
@@ -34,16 +35,16 @@ export function Navbar({ variant = "bar", logo, logoDark, wordmark, homeHref = "
|
|
|
34
35
|
const lang = baseLanguage(locale);
|
|
35
36
|
const phoneText = phone ? formatSitePhone(phone, locale) : undefined;
|
|
36
37
|
const linkProps = { components, labels, locale };
|
|
37
|
-
const brand = (_jsxs(BlockLink, { href: homeHref, ...linkProps, className: "vb-nav-brand flex shrink-0 items-center gap-3 rounded-[2px] focus-ring", children: [logo ? (_jsxs(_Fragment, { children: [_jsx(BlockImage, { ...logo, decorative: !!wordmark || logo.decorative, alt: wordmark ? undefined : logo.alt, priority: true, className: cn("vb-nav-logo", logoDark && "vb-logo-light"), components: components }), logoDark ? _jsx(BlockImage, { ...logoDark, decorative: true, priority: true, className: "vb-nav-logo vb-logo-dark", components: components }) : null] })) : null, wordmark || !logo ? (_jsx("span", { className: cn("vb-nav-wordmark font-display text-fg", variant === "split" ? "text-[1.625rem]" : "text-[1.375rem]"), "data-field": editMode ? "props.wordmark" : undefined, children: wordmark })) : null] }));
|
|
38
|
+
const brand = (_jsxs(BlockLink, { href: homeHref, ...linkProps, className: "vb-nav-brand flex min-h-6 shrink-0 items-center gap-3 pointer-coarse:min-h-11 rounded-[2px] focus-ring", children: [logo ? (_jsxs(_Fragment, { children: [_jsx(BlockImage, { ...logo, decorative: !!wordmark || logo.decorative, alt: wordmark ? undefined : logo.alt, priority: true, sizes: "12rem", className: cn("vb-nav-logo", logoDark && "vb-logo-light"), components: components }), logoDark ? _jsx(BlockImage, { ...logoDark, decorative: true, priority: true, sizes: "12rem", className: "vb-nav-logo vb-logo-dark", components: components }) : null] })) : null, wordmark || !logo ? (_jsx("span", { className: cn("vb-nav-wordmark font-display text-fg", variant === "split" ? "text-[1.625rem]" : "text-[1.375rem]"), "data-field": editMode ? "props.wordmark" : undefined, children: wordmark })) : null] }));
|
|
38
39
|
const renderLink = (item, key, className) => (_jsx(BlockLink, { href: item.href, newTab: item.newTab, ...linkProps, "aria-current": isCurrent(item.href, currentPath) ? "page" : undefined, className: cn("vb-nav-link", className), children: item.label }, key));
|
|
39
40
|
const desktopItem = (item, i) => item.children?.length ? (_jsx("li", { children: _jsx(NavbarSubmenu, { label: item.label, active: inGroup(item, currentPath), wide: item.children.some((c) => c.description), children: _jsx("ul", { className: cn("grid gap-0.5", item.children.some((c) => c.description) && "grid-cols-2"), children: item.children.map((c, j) => (_jsx("li", { children: _jsxs(BlockLink, { href: c.href, newTab: c.newTab, ...linkProps, "aria-current": isCurrent(c.href, currentPath) ? "page" : undefined, className: "vb-nav-sublink flex flex-col gap-0.5 rounded-ui-sm px-3 py-2.5 text-sm text-fg transition-colors hover:bg-hover focus-ring", children: [_jsx("span", { className: "font-medium", children: c.label }), c.description ? _jsx("span", { className: "text-small leading-snug text-fg-muted", children: c.description }) : null] }) }, j))) }) }) }, i)) : (_jsx("li", { children: renderLink(item, i) }, i));
|
|
40
41
|
const linkList = (list, offset = 0, className) => (_jsx("ul", { className: cn("vb-nav-links hidden items-center @4xl:flex", variant === "rail" ? "gap-1" : "gap-7", className), children: list.map((it, i) => desktopItem(it, i + offset)) }));
|
|
41
|
-
const phoneLink = phone ? (_jsxs("a", { href: `tel:${phone}`, "aria-label": t("callUs", { phone: phoneText }), "data-track": "call", className: "vb-nav-phone hidden items-center gap-2 rounded-[2px] font-mono text-small tabular text-fg-muted transition-colors hover:text-fg focus-ring @6xl:inline-flex", children: [_jsx(Phone, { "aria-hidden": true, strokeWidth: 1.5, className: "size-3.5" }), phoneText] })) : null;
|
|
42
|
+
const phoneLink = phone ? (_jsxs("a", { href: `tel:${phone}`, "aria-label": t("callUs", { phone: phoneText }), "data-track": "call", className: "vb-nav-phone hidden min-h-6 items-center gap-2 rounded-[2px] font-mono text-small tabular text-fg-muted transition-colors hover:text-fg focus-ring @6xl:inline-flex", children: [_jsx(Phone, { "aria-hidden": true, strokeWidth: 1.5, className: "size-3.5" }), phoneText] })) : null;
|
|
42
43
|
const localeSwitch = locales?.length ? (_jsx("ul", { "aria-label": t("languages"), className: "hidden items-center gap-1 font-mono text-eyebrow uppercase tracking-[0.08em] @4xl:flex", children: locales.map((l, i) => {
|
|
43
44
|
const current = l.current ?? baseLanguage(l.code) === lang;
|
|
44
|
-
return (_jsxs("li", { className: "flex items-center gap-1", children: [i > 0 ? _jsx("span", { "aria-hidden": true, className: "text-fg-subtle", children: "/" }) : null, _jsx("a", { href: l.href, hrefLang: l.code, lang: l.code, "aria-current": current ? "true" : undefined, className: cn("rounded-[2px] px-0.5 transition-colors focus-ring", current ? "text-fg" : "text-fg-muted hover:text-fg"), children: l.code })] }, l.code));
|
|
45
|
+
return (_jsxs("li", { className: "flex items-center gap-1", children: [i > 0 ? _jsx("span", { "aria-hidden": true, className: "text-fg-subtle", children: "/" }) : null, _jsx("a", { href: l.href, hrefLang: l.code, lang: l.code, "aria-current": current ? "true" : undefined, className: cn("inline-flex min-h-6 min-w-6 items-center justify-center rounded-[2px] px-0.5 transition-colors focus-ring", current ? "text-fg" : "text-fg-muted hover:text-fg"), children: l.code })] }, l.code));
|
|
45
46
|
}) })) : null;
|
|
46
|
-
const trigger = (always) => (_jsx("button", { type: "button", popoverTarget: panelId, "aria-haspopup": "dialog", "aria-controls": panelId, className: cn("vb-nav-trigger inline-flex h-10 items-center gap-2 rounded-ui px-2 text-sm font-medium text-fg transition-colors hover:bg-hover focus-ring", !always && "@4xl:hidden"), children: always ? (_jsx("span", { className: "font-mono text-eyebrow uppercase tracking-[0.08em]", children: t("openMenu") })) : (_jsxs(_Fragment, { children: [_jsx(Menu, { "aria-hidden": true, strokeWidth: 1.5, className: "size-5" }), _jsx("span", { className: "sr-only", children: t("openMenu") })] })) }));
|
|
47
|
+
const trigger = (always) => (_jsx("button", { type: "button", popoverTarget: panelId, "aria-haspopup": "dialog", "aria-controls": panelId, className: cn("vb-nav-trigger inline-flex h-10 min-w-10 items-center justify-center gap-2 rounded-ui px-2 text-sm font-medium text-fg transition-colors hover:bg-hover focus-ring pointer-coarse:h-11 pointer-coarse:min-w-11", !always && "@4xl:hidden"), children: always ? (_jsx("span", { className: "font-mono text-eyebrow uppercase tracking-[0.08em]", children: t("openMenu") })) : (_jsxs(_Fragment, { children: [_jsx(Menu, { "aria-hidden": true, strokeWidth: 1.5, className: "size-5" }), _jsx("span", { className: "sr-only", children: t("openMenu") })] })) }));
|
|
47
48
|
const primaryAction = actions?.length ? _jsx(CtaList, { actions: actions, ...linkProps, size: "md", max: variant === "bar" || variant === "minimal" ? 1 : 2, className: "hidden flex-nowrap gap-x-2 @2xl:flex" }) : null;
|
|
48
49
|
let row;
|
|
49
50
|
if (variant === "split") {
|
|
@@ -60,5 +61,5 @@ export function Navbar({ variant = "bar", logo, logoDark, wordmark, homeHref = "
|
|
|
60
61
|
row = (_jsxs(_Fragment, { children: [brand, linkList(items, 0, "ms-4"), _jsxs("div", { className: "ms-auto flex items-center gap-5", children: [phoneLink, localeSwitch, primaryAction, trigger(false)] })] }));
|
|
61
62
|
}
|
|
62
63
|
const panelLinkSize = variant === "minimal" ? "font-display text-display-l" : "font-display text-display-m";
|
|
63
|
-
return (_jsxs("header", { id: anchor, "data-variant": variant, "data-sticky": sticky ? "" : undefined, "data-overlay": overlay ? "" : undefined, "data-block-id": editMode ? id : undefined, className: cn("vb-navbar", overlay && "dark", className), children: [skipLink ? _jsx(SkipLink, { labels: labels, locale: locale }) : null, _jsx("nav", { "aria-label": t("mainNavigation"), className: cn("vb-container vb-navbar-row items-center", variant === "split" ? "grid grid-cols-[1fr_auto_1fr] gap-6" : "flex gap-6"), children: row }), _jsx("div", { id: panelId, popover: "auto", role: "dialog", "aria-label": t("menuDialog"), className: "vb-nav-panel", children: _jsxs("div", { className: "vb-container flex min-h-full flex-col", children: [_jsxs("div", { className: "vb-navbar-row flex items-center justify-between gap-6", children: [brand, _jsxs("button", { type: "button", popoverTarget: panelId, popoverTargetAction: "hide", className: "inline-flex h-10 items-center gap-2 rounded-ui px-2 text-fg transition-colors hover:bg-hover focus-ring", children: [_jsx("span", { className: variant === "minimal" ? "font-mono text-eyebrow uppercase tracking-[0.08em]" : "sr-only", children: t("closeMenu") }), _jsx(X, { "aria-hidden": true, strokeWidth: 1.5, className: "size-5" })] })] }), _jsx("ul", { className: "flex flex-col py-[clamp(1.5rem,6vh,4rem)]", children: items.map((it, i) => (_jsxs("li", { className: "border-b border-line", children: [renderLink(it, i, cn("vb-nav-panel-link block py-3 text-fg", panelLinkSize)), it.children?.length ? (_jsx("ul", { className: "flex flex-wrap gap-x-6 gap-y-2 pb-4", children: it.children.map((c, j) => (_jsx("li", { children: renderLink(c, j, "text-small text-fg-muted hover:text-fg") }, j))) })) : null] }, i))) }), _jsxs("div", { className: "mt-auto flex flex-col gap-8 pb-[max(2rem,env(safe-area-inset-bottom))]", children: [actions?.length ? _jsx(CtaList, { actions: actions, ...linkProps, size: "lg" }) : null, phone || panelMeta?.length || locales?.length ? (_jsxs("dl", { className: "grid gap-x-8 gap-y-4 border-t border-line pt-6 text-small sm:grid-cols-3", children: [phone ? (_jsxs("div", { children: [_jsx("dt", { className: "font-mono text-eyebrow uppercase tracking-[0.08em] text-fg-muted", children: t("phoneLabel") }), _jsx("dd", { className: "mt-1", children: _jsx("a", { href: `tel:${phone}`, "data-track": "call", className: "tabular text-fg underline decoration-line underline-offset-4 focus-ring", children: phoneText }) })] })) : null, panelMeta?.map((m, i) => (_jsxs("div", { children: [_jsx("dt", { className: "font-mono text-eyebrow uppercase tracking-[0.08em] text-fg-muted", children: m.label }), _jsx("dd", { className: "mt-1 text-fg", children: m.value })] }, i))), locales?.length ? (_jsxs("div", { children: [_jsx("dt", { className: "font-mono text-eyebrow uppercase tracking-[0.08em] text-fg-muted", children: t("languages") }), _jsx("dd", { className: "mt-1 flex gap-3", children: locales.map((l) => (_jsx("a", { href: l.href, hrefLang: l.code, lang: l.code, "aria-current": (l.current ?? baseLanguage(l.code) === lang) ? "true" : undefined, className: "font-mono uppercase text-fg-muted aria-[current]:text-fg focus-ring", children: l.code }, l.code))) })] })) : null] })) : null] })] }) })] }));
|
|
64
|
+
return (_jsxs("header", { id: anchor, "data-variant": variant, "data-sticky": sticky ? "" : undefined, "data-overlay": overlay ? "" : undefined, "data-block-id": editMode ? id : undefined, className: cn("vb-navbar", overlay && "dark", className), children: [skipLink ? _jsx(SkipLink, { labels: labels, locale: locale }) : null, _jsx("nav", { "aria-label": t("mainNavigation"), className: cn("vb-container vb-navbar-row items-center", variant === "split" ? "grid grid-cols-[1fr_auto_1fr] gap-6" : "flex gap-6"), children: row }), _jsx("div", { id: panelId, popover: "auto", role: "dialog", "aria-label": t("menuDialog"), className: "vb-nav-panel", children: _jsxs("div", { className: "vb-container flex min-h-full flex-col", children: [_jsxs("div", { className: "vb-navbar-row flex items-center justify-between gap-6", children: [brand, _jsxs("button", { type: "button", popoverTarget: panelId, popoverTargetAction: "hide", className: "inline-flex h-10 min-w-10 items-center justify-center gap-2 rounded-ui px-2 text-fg transition-colors hover:bg-hover focus-ring pointer-coarse:h-11 pointer-coarse:min-w-11", children: [_jsx("span", { className: variant === "minimal" ? "font-mono text-eyebrow uppercase tracking-[0.08em]" : "sr-only", children: t("closeMenu") }), _jsx(X, { "aria-hidden": true, strokeWidth: 1.5, className: "size-5" })] })] }), _jsx("ul", { className: "flex flex-col py-[clamp(1.5rem,6vh,4rem)]", children: items.map((it, i) => (_jsxs("li", { className: "border-b border-line", children: [renderLink(it, i, cn("vb-nav-panel-link block py-3 text-fg", panelLinkSize)), it.children?.length ? (_jsx("ul", { className: "flex flex-wrap gap-x-6 gap-y-2 pb-4 pointer-coarse:gap-y-0", children: it.children.map((c, j) => (_jsx("li", { children: renderLink(c, j, cn(TAP, "text-small text-fg-muted hover:text-fg")) }, j))) })) : null] }, i))) }), _jsxs("div", { className: "mt-auto flex flex-col gap-8 pb-[max(2rem,env(safe-area-inset-bottom))]", children: [actions?.length ? _jsx(CtaList, { actions: actions, ...linkProps, size: "lg" }) : null, phone || panelMeta?.length || locales?.length ? (_jsxs("dl", { className: "grid gap-x-8 gap-y-4 border-t border-line pt-6 text-small sm:grid-cols-3", children: [phone ? (_jsxs("div", { children: [_jsx("dt", { className: "font-mono text-eyebrow uppercase tracking-[0.08em] text-fg-muted", children: t("phoneLabel") }), _jsx("dd", { className: "mt-1", children: _jsx("a", { href: `tel:${phone}`, "data-track": "call", className: cn(TAP, "tabular text-fg underline decoration-line underline-offset-4 focus-ring"), children: phoneText }) })] })) : null, panelMeta?.map((m, i) => (_jsxs("div", { children: [_jsx("dt", { className: "font-mono text-eyebrow uppercase tracking-[0.08em] text-fg-muted", children: m.label }), _jsx("dd", { className: "mt-1 text-fg", children: m.value })] }, i))), locales?.length ? (_jsxs("div", { children: [_jsx("dt", { className: "font-mono text-eyebrow uppercase tracking-[0.08em] text-fg-muted", children: t("languages") }), _jsx("dd", { className: "mt-1 flex gap-3 pointer-coarse:gap-1", children: locales.map((l) => (_jsx("a", { href: l.href, hrefLang: l.code, lang: l.code, "aria-current": (l.current ?? baseLanguage(l.code) === lang) ? "true" : undefined, className: cn(TAP, "min-w-6 justify-center font-mono uppercase text-fg-muted aria-[current]:text-fg focus-ring pointer-coarse:min-w-11"), children: l.code }, l.code))) })] })) : null] })) : null] })] }) })] }));
|
|
64
65
|
}
|
|
@@ -7,6 +7,7 @@ import { BlockHeading } from "../block-heading.js";
|
|
|
7
7
|
import { BlockLink } from "../block-link.js";
|
|
8
8
|
import { site2Text } from "./shared/labels-2.js";
|
|
9
9
|
import { fieldAttr, prettyPhone, telHref, useBlockIds2 } from "./shared/kit-2.js";
|
|
10
|
+
import { headingLines } from "./shared/heading-lines.js";
|
|
10
11
|
const KIND = {
|
|
11
12
|
"404": { code: "notFoundCode", title: "notFoundTitle", text: "notFoundText" },
|
|
12
13
|
"500": { code: "serverErrorCode", title: "serverErrorTitle", text: "serverErrorText" },
|
|
@@ -27,7 +28,7 @@ export function NotFound({ variant = "plain", kind = "404", title, text, links =
|
|
|
27
28
|
const { base, headingId } = useBlockIds2(id, "not-found");
|
|
28
29
|
const k = KIND[kind];
|
|
29
30
|
const showLinks = variant === "links";
|
|
30
|
-
return (_jsx(Section, { theme: theme, anchor: anchor, headingId: headingId, blockId: id, blockType: "not-found", editMode: editMode, className: cn("vb-not-found", className), "data-kind": kind, "data-variant": variant, children: _jsxs(Container, { className: "grid gap-x-(--gutter) gap-y-14 @4xl:grid-cols-12", children: [_jsxs("div", { className: "flex flex-col gap-6 @4xl:col-span-8", children: [_jsxs("p", { className: "flex items-center gap-3 font-mono text-eyebrow uppercase tracking-[0.08em] text-fg-muted", children: [_jsx("span", { className: "tabular text-fg", children: t(k.code) }), _jsx("span", { "aria-hidden": true, className: "h-px w-8 bg-current opacity-40" })] }), _jsx(BlockHeading, { id: headingId, level: headingLevel, size: "display-l", text: title ?? t(k.title), editMode: editMode, field: "props.title", labels: labels, locale: locale, className: "max-w-[20ch]" }), _jsx("p", { className: "max-w-[52ch] text-lead text-fg-muted text-pretty", ...fieldAttr(editMode, "text"), children: text ?? t(k.text) }), _jsxs("div", { className: "flex flex-wrap items-center gap-x-6 gap-y-3 pt-2", children: [reset ? (_jsxs("button", { type: "button", onClick: reset, className: cn(buttonVariants({ variant: "primary", size: "lg" }), "vb-action-primary"), children: [_jsx(RotateCcw, { "aria-hidden": true, strokeWidth: 1.75 }), t("tryAgain")] })) : null, _jsxs(BlockLink, { href: homeHref, appearance: reset ? "link" : kind === "maintenance" && phone ? "secondary" : "primary", components: components, labels: labels, locale: locale, children: [t("backHome"), reset ? _jsx(ArrowRight, { "aria-hidden": true, strokeWidth: 1.5, className: "size-[1em] rtl:-scale-x-100" }) : null] }), kind === "maintenance" && phone ? (_jsx("a", { href: telHref(phone), className: cn(buttonVariants({ variant: "primary", size: "lg" }), "vb-action-primary tabular"), "data-track": "call", children: prettyPhone(phone) })) : null] })] }), showLinks ? (_jsxs("div", { className: "flex flex-col gap-10 border-t border-line pt-8 @4xl:col-span-12", children: [search ? (_jsxs("form", { role: "search", action: search.action, method: "get", className: "flex max-w-xl flex-col gap-2", children: [_jsx("label", { htmlFor: `${base}-q`, className: "font-mono text-eyebrow uppercase tracking-[0.08em] text-fg-muted", children: t("searchSite") }), _jsxs("div", { className: "flex gap-2", children: [_jsxs("span", { className: "relative flex-1", children: [_jsx(Search, { "aria-hidden": true, strokeWidth: 1.5, className: "pointer-events-none absolute start-3 top-1/2 size-4 -translate-y-1/2 text-fg-muted" }), _jsx("input", { id: `${base}-q`, type: "search", name: search.param ?? "q", placeholder: t("searchPlaceholder"), className: "h-12 w-full rounded-ui border border-field-border bg-field ps-9 pe-3 text-body text-fg placeholder:text-fg-subtle transition-colors hover:border-field-border-hover focus-ring" })] }), _jsx("button", { type: "submit", className: buttonVariants({ variant: "secondary", size: "lg" }), children: t("search") })] })] })) : null, links.length ? (_jsxs("nav", { "aria-labelledby": `${base}-pop`, className: "flex flex-col gap-4", children: [_jsx("p", { id: `${base}-pop`, className: "font-mono text-eyebrow uppercase tracking-[0.08em] text-fg-muted", children: t("popularPages") }), _jsx("ul", { className: "grid gap-x-(--gutter) @2xl:grid-cols-2", children: links.slice(0, 6).map((l, i) => (_jsx("li", { className: "border-t border-line", children: _jsxs(BlockLink, { href: l.href, newTab: l.newTab, components: components, labels: labels, locale: locale, className: "group/nf flex items-center justify-between gap-4 py-4 text-body text-fg transition-colors hover:text-accent-ink focus-ring rounded-[2px]", children: [_jsx("span", { children: l.label }), _jsx(ArrowRight, { "aria-hidden": true, strokeWidth: 1.5, className: "size-4 text-fg-subtle transition-colors group-hover/nf:text-current rtl:-scale-x-100" })] }) }, i))) })] })) : null, phone && kind !== "maintenance" ? (_jsx("p", { className: "text-small text-fg-muted", children: t("stillStuck", { phone: "\u0000" })
|
|
31
|
+
return (_jsx(Section, { theme: theme, anchor: anchor, headingId: headingId, blockId: id, blockType: "not-found", editMode: editMode, className: cn("vb-not-found", className), "data-kind": kind, "data-variant": variant, children: _jsxs(Container, { className: "grid gap-x-(--gutter) gap-y-14 @4xl:grid-cols-12", children: [_jsxs("div", { className: "flex flex-col gap-6 @4xl:col-span-8", children: [_jsxs("p", { className: "flex items-center gap-3 font-mono text-eyebrow uppercase tracking-[0.08em] text-fg-muted", children: [_jsx("span", { className: "tabular text-fg", children: t(k.code) }), _jsx("span", { "aria-hidden": true, className: "h-px w-8 bg-current opacity-40" })] }), _jsx(BlockHeading, { id: headingId, level: headingLevel, size: "display-l", text: headingLines(title ?? t(k.title)), editMode: editMode, field: "props.title", labels: labels, locale: locale, className: "max-w-[20ch]" }), _jsx("p", { className: "max-w-[52ch] text-lead text-fg-muted text-pretty", ...fieldAttr(editMode, "text"), children: text ?? t(k.text) }), _jsxs("div", { className: "flex flex-wrap items-center gap-x-6 gap-y-3 pt-2", children: [reset ? (_jsxs("button", { type: "button", onClick: reset, className: cn(buttonVariants({ variant: "primary", size: "lg" }), "vb-action-primary"), children: [_jsx(RotateCcw, { "aria-hidden": true, strokeWidth: 1.75 }), t("tryAgain")] })) : null, _jsxs(BlockLink, { href: homeHref, appearance: reset ? "link" : kind === "maintenance" && phone ? "secondary" : "primary", components: components, labels: labels, locale: locale, children: [t("backHome"), reset ? _jsx(ArrowRight, { "aria-hidden": true, strokeWidth: 1.5, className: "size-[1em] rtl:-scale-x-100" }) : null] }), kind === "maintenance" && phone ? (_jsx("a", { href: telHref(phone), className: cn(buttonVariants({ variant: "primary", size: "lg" }), "vb-action-primary tabular"), "data-track": "call", children: prettyPhone(phone) })) : null] })] }), showLinks ? (_jsxs("div", { className: "flex flex-col gap-10 border-t border-line pt-8 @4xl:col-span-12", children: [search ? (_jsxs("form", { role: "search", action: search.action, method: "get", className: "flex max-w-xl flex-col gap-2", children: [_jsx("label", { htmlFor: `${base}-q`, className: "font-mono text-eyebrow uppercase tracking-[0.08em] text-fg-muted", children: t("searchSite") }), _jsxs("div", { className: "flex gap-2", children: [_jsxs("span", { className: "relative flex-1", children: [_jsx(Search, { "aria-hidden": true, strokeWidth: 1.5, className: "pointer-events-none absolute start-3 top-1/2 size-4 -translate-y-1/2 text-fg-muted" }), _jsx("input", { id: `${base}-q`, type: "search", name: search.param ?? "q", placeholder: t("searchPlaceholder"), className: "h-12 w-full rounded-ui border border-field-border bg-field ps-9 pe-3 text-body text-fg placeholder:text-fg-subtle transition-colors hover:border-field-border-hover focus-ring" })] }), _jsx("button", { type: "submit", className: buttonVariants({ variant: "secondary", size: "lg" }), children: t("search") })] })] })) : null, links.length ? (_jsxs("nav", { "aria-labelledby": `${base}-pop`, className: "flex flex-col gap-4", children: [_jsx("p", { id: `${base}-pop`, className: "font-mono text-eyebrow uppercase tracking-[0.08em] text-fg-muted", children: t("popularPages") }), _jsx("ul", { className: "grid gap-x-(--gutter) @2xl:grid-cols-2", children: links.slice(0, 6).map((l, i) => (_jsx("li", { className: "border-t border-line", children: _jsxs(BlockLink, { href: l.href, newTab: l.newTab, components: components, labels: labels, locale: locale, className: "group/nf flex items-center justify-between gap-4 py-4 text-body text-fg transition-colors hover:text-accent-ink focus-ring rounded-[2px]", children: [_jsx("span", { children: l.label }), _jsx(ArrowRight, { "aria-hidden": true, strokeWidth: 1.5, className: "size-4 text-fg-subtle transition-colors group-hover/nf:text-current rtl:-scale-x-100" })] }) }, i))) })] })) : null, phone && kind !== "maintenance" ? (_jsx("p", { className: "text-small text-fg-muted", children: t("stillStuck", { phone: "\u0000" })
|
|
31
32
|
.split("\u0000")
|
|
32
33
|
.flatMap((part, i) => i === 0
|
|
33
34
|
? [part]
|
|
@@ -20,6 +20,7 @@ export interface PageIntroProps extends Omit<BlockBaseProps, "variant" | "labels
|
|
|
20
20
|
/** The page `h1` (inner pages). One `*emphasis*`. */
|
|
21
21
|
title: React.ReactNode;
|
|
22
22
|
lead?: React.ReactNode;
|
|
23
|
+
/** 21:9 band under the intro. `sizes`: `min(100vw, 90rem)`. */
|
|
23
24
|
media?: SiteImage;
|
|
24
25
|
/** ruled: facts on the end side ("Service area" · "Asian side"), up to 3. */
|
|
25
26
|
meta?: SiteMeta[];
|
|
@@ -5,6 +5,7 @@ import { BlockHeading } from "../block-heading.js";
|
|
|
5
5
|
import { BlockImage } from "../block-image.js";
|
|
6
6
|
import { BlockLink } from "../block-link.js";
|
|
7
7
|
import { field, siteText, useBlockIds } from "./_kit-1.js";
|
|
8
|
+
import { headingLines } from "./shared/heading-lines.js";
|
|
8
9
|
/** `BreadcrumbList` JSON-LD for the crumbs (absolute URLs need `origin`). */
|
|
9
10
|
export function breadcrumbJsonLd(crumbs, origin = "") {
|
|
10
11
|
if (!crumbs.length)
|
|
@@ -31,7 +32,7 @@ export function PageIntro({ variant = "simple", breadcrumb, eyebrow, title, lead
|
|
|
31
32
|
const last = i === breadcrumb.length - 1;
|
|
32
33
|
return (_jsxs("li", { className: "flex items-center gap-2", children: [i > 0 ? (_jsx("span", { "aria-hidden": true, className: "text-fg-subtle/70 rtl:-scale-x-100", children: "/" })) : null, c.href && !last ? (_jsx(BlockLink, { href: c.href, ...linkProps, className: "rounded-[2px] transition-colors hover:text-fg focus-ring", children: c.label })) : (_jsx("span", { "aria-current": last ? "page" : undefined, className: last ? "text-fg-muted" : undefined, children: c.label }))] }, i));
|
|
33
34
|
}) }) })) : eyebrow ? (_jsx("p", { className: "font-mono text-eyebrow uppercase tracking-[0.08em] text-fg-muted", ...field(editMode, "eyebrow"), children: eyebrow })) : null;
|
|
34
|
-
const heading = _jsx(BlockHeading, { id: headingId, level: headingLevel, size: "display-l", text: title, editMode: editMode, field: "props.title", labels: labels, locale: locale, className: "max-w-[20ch]" });
|
|
35
|
+
const heading = _jsx(BlockHeading, { id: headingId, level: headingLevel, size: "display-l", text: headingLines(title), editMode: editMode, field: "props.title", labels: labels, locale: locale, className: "max-w-[20ch]" });
|
|
35
36
|
const leadEl = lead ? (_jsx("p", { className: "vb-lead max-w-[52ch] text-lead text-fg-muted", ...field(editMode, "lead"), children: lead })) : null;
|
|
36
37
|
let content;
|
|
37
38
|
if (variant === "ruled") {
|
|
@@ -8,6 +8,7 @@ export interface StepsItem {
|
|
|
8
8
|
text?: React.ReactNode;
|
|
9
9
|
/** "1–2 weeks", "45 min". */
|
|
10
10
|
duration?: React.ReactNode;
|
|
11
|
+
/** 3:2 step photo, max 28rem wide. `sizes`: `(min-width: 56rem) min(34vw, 26rem), min(100vw, 28rem)`. */
|
|
11
12
|
media?: SiteImage;
|
|
12
13
|
}
|
|
13
14
|
export interface StepsProps extends Omit<BlockBaseProps, "variant" | "labels"> {
|
|
@@ -5,6 +5,7 @@ import { SectionHeader } from "../section-header.js";
|
|
|
5
5
|
import { BlockImage } from "../block-image.js";
|
|
6
6
|
import { CtaList, indexLabel, useBlockIds } from "./_kit-1.js";
|
|
7
7
|
import { Reveal } from "../../motion/reveal.js";
|
|
8
|
+
import { columnSizes } from "./shared/image-sizes.js";
|
|
8
9
|
/**
|
|
9
10
|
* "How we work" — discovery → design → launch; a first spa visit; SaaS setup. An ordered list
|
|
10
11
|
* (the order is announced; the numbers are visual). Server component.
|
|
@@ -25,7 +26,7 @@ export function Steps({ variant = "vertical", eyebrow, title, lead, steps, actio
|
|
|
25
26
|
return (_jsx(Tag, { className: cn("text-body text-fg-muted text-pretty", Tag === "p" ? "max-w-[52ch]" : "flex min-w-0 flex-col gap-3"), ...f(i, "text"), children: s.text }));
|
|
26
27
|
};
|
|
27
28
|
const duration = (s, i) => s.duration ? (_jsx("span", { className: "inline-flex w-fit items-center rounded-full border border-line px-2.5 py-0.5 font-mono text-[0.6875rem] tabular tracking-[0.04em] text-fg-muted", ...f(i, "duration"), children: s.duration })) : null;
|
|
28
|
-
const media = (s) => (s.media ? _jsx(BlockImage, { ...s.media, aspect: "3:2", sizes:
|
|
29
|
+
const media = (s) => (s.media ? _jsx(BlockImage, { ...s.media, aspect: "3:2", sizes: columnSizes(4 / 12, { narrow: "min(100vw, 28rem)" }), className: "mt-2 max-w-md rounded-ui", components: components, labels: labels }) : null);
|
|
29
30
|
const header = title || eyebrow || lead ? _jsx(SectionHeader, { eyebrow: eyebrow, title: title, lead: lead, align: variant === "horizontal" && lead ? "split" : "start", headingLevel: headingLevel, headingId: headingId, editMode: editMode, labels: labels, locale: locale }) : null;
|
|
30
31
|
const actionsEl = actions?.length ? _jsx(CtaList, { actions: actions, components: components, labels: labels, locale: locale, editMode: editMode }) : null;
|
|
31
32
|
let content;
|
|
@@ -535,7 +535,7 @@ export declare const mediaTextBlock: import("../schema/index.js").BlockDefinitio
|
|
|
535
535
|
"4:5": "4:5";
|
|
536
536
|
"3:2": "3:2";
|
|
537
537
|
}>>;
|
|
538
|
-
}, z.core.$strip>, "side" | "
|
|
538
|
+
}, z.core.$strip>, "side" | "bleed" | "overlap">;
|
|
539
539
|
export declare const ctaBlock: import("../schema/index.js").BlockDefinition<z.ZodObject<{
|
|
540
540
|
eyebrow: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
|
|
541
541
|
title: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>;
|
|
@@ -589,9 +589,14 @@ export declare const menuBlock: import("../schema/index.js").BlockDefinition<z.Z
|
|
|
589
589
|
vegetarian: "vegetarian";
|
|
590
590
|
vegan: "vegan";
|
|
591
591
|
"gluten-free": "gluten-free";
|
|
592
|
+
"dairy-free": "dairy-free";
|
|
593
|
+
"nut-free": "nut-free";
|
|
594
|
+
halal: "halal";
|
|
592
595
|
spicy: "spicy";
|
|
593
596
|
new: "new";
|
|
594
597
|
"chef-pick": "chef-pick";
|
|
598
|
+
"to-share": "to-share";
|
|
599
|
+
seasonal: "seasonal";
|
|
595
600
|
}>>>;
|
|
596
601
|
allergens: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
597
602
|
sesame: "sesame";
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// team, blog list, article, contact form, map & hours, menu, gallery, consent, contact dock,
|
|
3
3
|
// footer, 404. Server / admin only — this file imports zod; components never import it.
|
|
4
4
|
import { z, f, defineBlock, createRegistry } from "../schema/index.js";
|
|
5
|
+
import * as React from "react";
|
|
5
6
|
import { PricingTable } from "./pricing-table.js";
|
|
6
7
|
import { ComparisonTable } from "./comparison-table.js";
|
|
7
8
|
import { Testimonials } from "./testimonials.js";
|
|
@@ -10,7 +11,7 @@ import { BlogList } from "./blog-list.js";
|
|
|
10
11
|
import { ArticleLayout } from "./article-layout.js";
|
|
11
12
|
import { ContactForm } from "./contact-form.js";
|
|
12
13
|
import { MapHours, localBusinessJsonLd } from "./map-hours.js";
|
|
13
|
-
import { MenuList, menuJsonLd, ALLERGENS } from "./menu-list.js";
|
|
14
|
+
import { MenuList, menuJsonLd, ALLERGENS, MENU_TAGS } from "./menu-list.js";
|
|
14
15
|
import { GallerySection } from "./gallery-section.js";
|
|
15
16
|
import { ConsentBanner } from "./consent-banner.js";
|
|
16
17
|
import { ContactDock } from "./contact-dock.js";
|
|
@@ -367,7 +368,7 @@ const MenuItemSchema = f.group({
|
|
|
367
368
|
description: f.text({ max: 160 }).optional(),
|
|
368
369
|
prices: f.list(f.group({ label: f.text({ max: 20 }).optional(), amount: f.money().optional() }), { min: 1, max: 4 }),
|
|
369
370
|
duration: f.text({ max: 12 }).optional(),
|
|
370
|
-
tags: z.array(z.enum(
|
|
371
|
+
tags: z.array(z.enum(MENU_TAGS)).optional(),
|
|
371
372
|
allergens: z.array(z.enum(ALLERGENS)).optional(),
|
|
372
373
|
image: f.image({ aspect: "1:1" }).optional(),
|
|
373
374
|
unavailable: f.toggle().optional(),
|
|
@@ -518,7 +519,8 @@ export const footerBlock = defineBlock({
|
|
|
518
519
|
},
|
|
519
520
|
jsonLd: (p) => ({ "@type": "Organization", name: p.company?.legalName ?? p.brand.name, telephone: p.contact?.phone, email: p.contact?.email, sameAs: p.social.map((s) => s.href) }),
|
|
520
521
|
lint: (p) => (!p.company ? [{ code: "missing-company-details", severity: "info", message: "Sites that sell online usually must show the legal name and tax / registry numbers in the footer.", path: "props.company" }] : []),
|
|
521
|
-
component
|
|
522
|
+
// The renderer emits the definition's jsonLd once per page; the component must not repeat it.
|
|
523
|
+
component: asBlock((p) => React.createElement(Footer, { ...p, jsonLd: false })),
|
|
522
524
|
});
|
|
523
525
|
/* ------------------------------------------------------------------ 404 */
|
|
524
526
|
export const notFoundBlock = defineBlock({
|
|
@@ -4,6 +4,7 @@ import { Container, Section } from "../section.js";
|
|
|
4
4
|
import { BlockHeading } from "../block-heading.js";
|
|
5
5
|
import { RichText } from "../rich-text.js";
|
|
6
6
|
import { field, useBlockIds } from "./_kit-1.js";
|
|
7
|
+
import { headingLines } from "./shared/heading-lines.js";
|
|
7
8
|
/**
|
|
8
9
|
* Long text — an about page, a privacy notice, a service description. Pure server rendering of
|
|
9
10
|
* the RichDoc format; no editor code reaches the site.
|
|
@@ -15,7 +16,7 @@ import { field, useBlockIds } from "./_kit-1.js";
|
|
|
15
16
|
export function RichTextSection({ variant = "column", eyebrow, title, body, dropCap, note, size = "md", id, theme, anchor, className, headingLevel = 2, labels, locale, components, editMode }) {
|
|
16
17
|
const { base, headingId } = useBlockIds(id, "text");
|
|
17
18
|
const offset = (title ? 1 : 0);
|
|
18
|
-
const heading = title ? _jsx(BlockHeading, { id: headingId, level: headingLevel, size: "display-m", text: title, editMode: editMode, field: "props.title", labels: labels, locale: locale }) : null;
|
|
19
|
+
const heading = title ? _jsx(BlockHeading, { id: headingId, level: headingLevel, size: "display-m", text: headingLines(title), editMode: editMode, field: "props.title", labels: labels, locale: locale }) : null;
|
|
19
20
|
const eyebrowEl = eyebrow ? (_jsx("p", { className: "font-mono text-eyebrow uppercase tracking-[0.08em] text-fg-muted", ...field(editMode, "eyebrow"), children: eyebrow })) : null;
|
|
20
21
|
const text = (cls) => (_jsx(RichText, { doc: body, size: size, headingOffset: offset, components: components, labels: labels, locale: locale, editMode: editMode, field: "props.body", className: cls }));
|
|
21
22
|
let content;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* A heading string with hand-set line breaks: `"Ateşte közde,\nmuhabbet sofrada."`. Each line is
|
|
4
|
+
* a block `<span>` and the lines are joined by a real space, so `textContent` (search engines,
|
|
5
|
+
* screen readers, reader mode, copy-paste) reads "közde, muhabbet", never "közde,muhabbet".
|
|
6
|
+
* `*emphasis*` works across and within lines. Strings without `\n` and ReactNodes pass through
|
|
7
|
+
* unchanged (BlockHeading renders their emphasis). Server-safe.
|
|
8
|
+
*/
|
|
9
|
+
export declare function headingLines(text: React.ReactNode): React.ReactNode;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { parseEmphasis } from "../../schema/emphasis.js";
|
|
4
|
+
/**
|
|
5
|
+
* A heading string with hand-set line breaks: `"Ateşte közde,\nmuhabbet sofrada."`. Each line is
|
|
6
|
+
* a block `<span>` and the lines are joined by a real space, so `textContent` (search engines,
|
|
7
|
+
* screen readers, reader mode, copy-paste) reads "közde, muhabbet", never "közde,muhabbet".
|
|
8
|
+
* `*emphasis*` works across and within lines. Strings without `\n` and ReactNodes pass through
|
|
9
|
+
* unchanged (BlockHeading renders their emphasis). Server-safe.
|
|
10
|
+
*/
|
|
11
|
+
export function headingLines(text) {
|
|
12
|
+
if (typeof text !== "string" || !text.includes("\n"))
|
|
13
|
+
return text;
|
|
14
|
+
const lines = [[]];
|
|
15
|
+
for (const part of parseEmphasis(text)) {
|
|
16
|
+
part.text.split("\n").forEach((chunk, i) => {
|
|
17
|
+
if (i > 0)
|
|
18
|
+
lines.push([]);
|
|
19
|
+
if (chunk)
|
|
20
|
+
lines[lines.length - 1].push({ text: chunk, em: part.em });
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
const filled = lines
|
|
24
|
+
.map((parts) => parts.map((p, i, all) => {
|
|
25
|
+
let t = i === 0 ? p.text.trimStart() : p.text;
|
|
26
|
+
if (i === all.length - 1)
|
|
27
|
+
t = t.trimEnd();
|
|
28
|
+
return { ...p, text: t };
|
|
29
|
+
}).filter((p) => p.text))
|
|
30
|
+
.filter((parts) => parts.length);
|
|
31
|
+
return filled.map((parts, i) => (_jsxs(React.Fragment, { children: [i > 0 ? " " : null, _jsx("span", { className: "vb-heading-line block", children: parts.map((p, j) => (p.em ? _jsx("em", { children: p.text }, j) : _jsx(React.Fragment, { children: p.text }, j))) })] }, i)));
|
|
32
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** `--container` (76rem) — the content column every Container caps at. */
|
|
2
|
+
export declare const CONTAINER_REM = 76;
|
|
3
|
+
/**
|
|
4
|
+
* A column that spans `fraction` of the container from `from` up, and `narrow` below.
|
|
5
|
+
* `columnSizes(5 / 12)` → `"(min-width: 56rem) min(42vw, 32rem), 100vw"`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function columnSizes(fraction: number, opts?: {
|
|
8
|
+
from?: "42rem" | "56rem";
|
|
9
|
+
narrow?: string;
|
|
10
|
+
bleed?: boolean;
|
|
11
|
+
}): string;
|
|
12
|
+
/** Common presets. */
|
|
13
|
+
export declare const SITE_SIZES: {
|
|
14
|
+
/** Edge-to-edge photo (full-bleed hero, editorial strip). */
|
|
15
|
+
readonly bleed: "100vw";
|
|
16
|
+
/** A photo as wide as the content container. */
|
|
17
|
+
readonly container: "min(100vw, 76rem)";
|
|
18
|
+
/** A photo only shown on narrow containers (< 56rem), full width there. */
|
|
19
|
+
readonly narrowOnly: "min(100vw, 56rem)";
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Filmstrip cell: fixed height `clamp(15rem, 38cqi, 30rem)`, width = height × ratio.
|
|
23
|
+
* `filmstripSizes(4 / 5)` → `"max(12rem, min(31vw, 24rem))"`.
|
|
24
|
+
*/
|
|
25
|
+
export declare function filmstripSizes(ratio: number): string;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Responsive `sizes` for site-block photos. Every photo in a site block passes one of these to
|
|
2
|
+
// BlockImage → the kit Image / `components.Image` (e.g. next/image), so a CDN loader picks a
|
|
3
|
+
// 400–800 px file on a phone instead of the 3840 px top step. Fractions follow the block's 12-col
|
|
4
|
+
// grid and are capped at the content container (`--container`, 76rem): a 2560 px monitor does
|
|
5
|
+
// not fetch a 1500 px file for a 33% column that is never wider than 25rem.
|
|
6
|
+
// Server-safe, no React.
|
|
7
|
+
/** `--container` (76rem) — the content column every Container caps at. */
|
|
8
|
+
export const CONTAINER_REM = 76;
|
|
9
|
+
/**
|
|
10
|
+
* A column that spans `fraction` of the container from `from` up, and `narrow` below.
|
|
11
|
+
* `columnSizes(5 / 12)` → `"(min-width: 56rem) min(42vw, 32rem), 100vw"`.
|
|
12
|
+
*/
|
|
13
|
+
export function columnSizes(fraction, opts = {}) {
|
|
14
|
+
const { from = "56rem", narrow = "100vw", bleed = false } = opts;
|
|
15
|
+
const vw = Math.ceil(fraction * 100);
|
|
16
|
+
// A column bleeding to the section edge grows past the container on very wide screens.
|
|
17
|
+
const wide = bleed ? `${vw}vw` : `min(${vw}vw, ${Math.ceil(fraction * CONTAINER_REM)}rem)`;
|
|
18
|
+
return `(min-width: ${from}) ${wide}, ${narrow}`;
|
|
19
|
+
}
|
|
20
|
+
/** Common presets. */
|
|
21
|
+
export const SITE_SIZES = {
|
|
22
|
+
/** Edge-to-edge photo (full-bleed hero, editorial strip). */
|
|
23
|
+
bleed: "100vw",
|
|
24
|
+
/** A photo as wide as the content container. */
|
|
25
|
+
container: `min(100vw, ${CONTAINER_REM}rem)`,
|
|
26
|
+
/** A photo only shown on narrow containers (< 56rem), full width there. */
|
|
27
|
+
narrowOnly: "min(100vw, 56rem)",
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Filmstrip cell: fixed height `clamp(15rem, 38cqi, 30rem)`, width = height × ratio.
|
|
31
|
+
* `filmstripSizes(4 / 5)` → `"max(12rem, min(31vw, 24rem))"`.
|
|
32
|
+
*/
|
|
33
|
+
export function filmstripSizes(ratio) {
|
|
34
|
+
const r = ratio > 0 ? ratio : 0.8;
|
|
35
|
+
const n = (x) => Math.round(x * 10) / 10;
|
|
36
|
+
return `max(${n(15 * r)}rem, min(${Math.ceil(38 * r)}vw, ${n(30 * r)}rem))`;
|
|
37
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare const carouselLabelKeys: readonly ["previous", "next", "slideCounter", "slideLabel", "carousel", "slide"];
|
|
2
2
|
export type CarouselLabels = Record<(typeof carouselLabelKeys)[number], string>;
|
|
3
|
-
export declare const openNowLabelKeys: readonly ["openNow", "
|
|
3
|
+
export declare const openNowLabelKeys: readonly ["openNow", "openUntil", "closingSoon", "closingSoonUntil", "openAllDay", "closed", "closedToday", "opensAt", "opensTodayAt", "opensTomorrowAt"];
|
|
4
4
|
export type OpenNowLabels = Record<(typeof openNowLabelKeys)[number], string>;
|
|
5
5
|
export declare const contactIslandLabelKeys: readonly ["send", "sending", "sent", "optional", "errorSummary", "errorServer", "errorServerCall", "privacyRead", "privacyLink", "privacyRequired", "marketingConsent", "honeypot", "choose", "errorRequired", "errorEmail", "errorPhone", "errorTooLong"];
|
|
6
6
|
export type ContactIslandLabels = Record<(typeof contactIslandLabelKeys)[number], string>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Label keys each client island needs. Kept out of the "use client" files: a server component
|
|
2
2
|
// importing a plain value from a client module would receive a client reference, not the array.
|
|
3
3
|
export const carouselLabelKeys = ["previous", "next", "slideCounter", "slideLabel", "carousel", "slide"];
|
|
4
|
-
export const openNowLabelKeys = ["openNow", "
|
|
4
|
+
export const openNowLabelKeys = ["openNow", "openUntil", "closingSoon", "closingSoonUntil", "openAllDay", "closed", "closedToday", "opensAt", "opensTodayAt", "opensTomorrowAt"];
|
|
5
5
|
export const contactIslandLabelKeys = [
|
|
6
6
|
"send",
|
|
7
7
|
"sending",
|
|
@@ -86,12 +86,15 @@ export declare const site2Labels: {
|
|
|
86
86
|
email: string;
|
|
87
87
|
hours: string;
|
|
88
88
|
openNow: string;
|
|
89
|
-
|
|
89
|
+
openUntil: string;
|
|
90
90
|
closingSoon: string;
|
|
91
|
+
closingSoonUntil: string;
|
|
92
|
+
openAllDay: string;
|
|
91
93
|
closed: string;
|
|
92
94
|
closedToday: string;
|
|
93
95
|
opensAt: string;
|
|
94
96
|
opensTodayAt: string;
|
|
97
|
+
opensTomorrowAt: string;
|
|
95
98
|
open24h: string;
|
|
96
99
|
openingHours: string;
|
|
97
100
|
openingHoursOf: string;
|
|
@@ -119,6 +122,11 @@ export declare const site2Labels: {
|
|
|
119
122
|
tagSpicy: string;
|
|
120
123
|
tagNew: string;
|
|
121
124
|
tagChefPick: string;
|
|
125
|
+
tagDairyFree: string;
|
|
126
|
+
tagNutFree: string;
|
|
127
|
+
tagHalal: string;
|
|
128
|
+
tagToShare: string;
|
|
129
|
+
tagSeasonal: string;
|
|
122
130
|
allergen_gluten: string;
|
|
123
131
|
allergen_crustaceans: string;
|
|
124
132
|
allergen_eggs: string;
|
|
@@ -196,6 +204,14 @@ export declare const site2Labels: {
|
|
|
196
204
|
export type Site2Labels = typeof site2Labels;
|
|
197
205
|
/** Turkish dictionary. Legal-flavoured texts are neutral templates — review with counsel. */
|
|
198
206
|
export declare const site2LabelsTr: Site2Labels;
|
|
207
|
+
/** German dictionary. Legal-flavoured texts are neutral templates — review with counsel. */
|
|
208
|
+
export declare const site2LabelsDe: Site2Labels;
|
|
209
|
+
/** French dictionary. Legal-flavoured texts are neutral templates — review with counsel. */
|
|
210
|
+
export declare const site2LabelsFr: Site2Labels;
|
|
211
|
+
/** Spanish dictionary. Legal-flavoured texts are neutral templates — review with counsel. */
|
|
212
|
+
export declare const site2LabelsEs: Site2Labels;
|
|
213
|
+
/** Arabic dictionary (Modern Standard Arabic). Legal-flavoured texts are neutral templates — review with counsel. */
|
|
214
|
+
export declare const site2LabelsAr: Site2Labels;
|
|
199
215
|
/** Labels accepted by every registry-2 block: infrastructure + these keys. */
|
|
200
216
|
export type Site2BlockLabels = Partial<BlockLabels & Site2Labels>;
|
|
201
217
|
export type Site2Key = keyof (BlockLabels & Site2Labels);
|