-
e388d8e: Section
slotsprop now retains structural typing of block props at consumer call sites.Previously,
props.slots.<slotName>[i].propswas wrapped inSimplifyDeep, which recursively walked every nested block-schema decorator (headingStyle,sublineStyle, …). Past a certain schema depth the cumulative type instantiation exceeded TypeScript's 50-level limit and surfaced asTS2589: Type instantiation is excessively deep and possibly infinitein any section that mapped over its slot blocks.The wrapper now uses bounded
Simplify(one level) at the right boundaries — the outer slot map, each block item, and the block'spropsshape. IDE hover output is unchanged; structural typing is unchanged. Sections that previously needed anas unknown as ReadonlyArray<{ props: Record<string, any> }>cast onprops.slots.*to silence TS2589 can drop the cast and accessblock.props.<field>with full type inference.No runtime change. No API change.
-
0861f3e: Add
laioutr:beforeModuleRegisterNuxt hook. Fires before the ui-kit module registers each upstream Nuxt module, with{ name, key, options }. Consumers can mutateoptionsto override defaults applied byregisterModule.// nuxt.config.ts export default defineNuxtConfig({ hooks: { 'laioutr:beforeModuleRegister': ({ name, options }) => { if (name === '@nuxt/icon') { options.foobar = { ...options.foobar, customProvider: { /* … */ }, }; } }, }, });
-
705ad15: Expand the
Mediatype family with audio support and richer video metadata.Additions
MediaAudio(type: 'audio') — new variant ofMediafor playable audio assets, withsources: MediaSourceAudio[], optionalcover?: MediaImage(album art / podcast cover), and optionaltracks?: MediaTextTrack[](chapters, transcripts).MediaSourceAudio— single audio source withprovider,src, optionalformat(MIME),length, andstreaming.MediaTextTrack— timed text track (subtitles, captions, descriptions, chapters, metadata) forMediaVideoandMediaAudio, mapping to the HTML<track>element.MediaSourceVideo.streaming?: 'progressive' | 'hls' | 'dash'— delivery-format discriminator.progressiveis assumed when omitted.MediaVideo.tracks?: MediaTextTrack[]— subtitle/caption/chapter tracks for the video.- Studio
mediafields can now useallowedTypes: ['audio'], which narrows the field value toMediaAudio.
Breaking:
MediaVideo.previewrenamed toMediaVideo.posterMatches the HTML
<video poster="…">attribute and the customer-facing terminology used in Studio.// Before const video: MediaVideo = { type: 'video', sources: [...], preview: posterImage, }; // After const video: MediaVideo = { type: 'video', sources: [...], poster: posterImage, };
Breaking:
Mediaunion widened to includeMediaAudioCode that exhaustively switches on
media.typewithout a default branch will need to handle'audio':switch (media.type) { case 'image': return renderImage(media); case 'video': return renderVideo(media); case 'audio': return renderAudio(media); // new — handle or fall through to default }
-
b5ed67d:
Footeraccepts two new optional props:logoPosition?: 'top' | 'bottom'(default'top', current behavior) andlogoAlignment?: 'left' | 'center' | 'right'(default'left', only meaningful whenlogoPosition === 'bottom').When
logoPosition === 'bottom':- The top logo-column is hidden entirely (logo image + contact-info slot). Menus span the full top row.
- The logo image renders inside the bottom bar between the icons cluster (lower email + social icons, left) and the copyright (right). At
sm+the bottom bar becomes a 3-cell grid1fr auto 1fr;logoAlignmentcontrols the logo'sjustify-selfwithin the auto cell. - On mobile (column flex) the logo sits in its own row above the copyright, with
justify-contentdriven bylogoAlignment.
Three new Storybook stories cover
LogoBottomCenter,LogoBottomLeft,LogoBottomRight. -
bfce22f: Editors can now pick a button size per CTA across every section and block.
buttonFields(fromshared-fields/button.ts) exposes a new Size selector alongside Label / Link / Variant, with optionsxs/s/m/l. Default is'm', so existing CTAs render unchanged unless an editor explicitly picks a different size.- New
buttonSizeOptionsexport fromshared-fields/buttonSize.tsfor sections or blocks that need an inline button-size select outside ofbuttonFields(mirrorsbuttonVariantOptions). - Every section and block that surfaces a CTA — including the banner family, product/category/content sliders, hero slider, header, page-not-found, plan-card slider, and the corresponding standalone blocks — now respects the editor-chosen size.
For ui consumers building their own CTA shapes, the following components accept an optional
sizeon each CTA entry (defaults preserve their previous hardcoded rendering):HeroSlide,PageNotFound,BannerShowcase,ContentGrid(ui)HeaderBasic.ctaButtons[],ContentSlider/CategoryCardSlider/ProductSlidercta types (ui)PlanCardexposes new optionalctaVariant/ctaSizepropsSwiperChrome.buttons[]acceptssize?: ButtonSize(ui-kit)
-
0506962: Forward the new
sectionBackground(andcustomSectionBackground) props through the ui-layer wrappers that route to Backdrop:ContaineracceptssectionBackground?: BackdropBackgroundandcustomSectionBackground?: string, resolves them, and forwards to<Backdrop :section-background="...">.MediaTextacceptssectionBackground?: BackdropBackgroundand forwards to<Backdrop :section-background="...">.
Both default to
'none', so existing consumers render unchanged. -
fc5c0f9: Fix
FilterOffCanvas/FilterModalfilter selections never reaching the parent'sselectedFilters.FilterPanelContentbound all four inner filter components via:checked/@update:checked, but onlyFilterOffCanvasSwatchListstill used a'checked'v-model channel — the other three (FilterOffCanvasCheckboxList,FilterOffCanvasRangeSlider,FilterOffCanvasSwitchItem) had moved to the canonicalmodelValuechannel, so:checkedfell through to their root elements as a raw HTML attribute and@update:checkedwas never emitted. Verified at runtime in Storybook: brand checkboxes did not toggle, the price-range slider receivedchecked="[object Object]"as a string attribute, and the boolean stock-filter switch toggled its own visual state but never propagated. The boolean branch additionally passed:title="filter.label"toFilterOffCanvasSwitchItem, whose prop islabel, so the switch's text label rendered empty.FilterOffCanvasSwatchList: v-model channel renamed from'checked'to the defaultmodelValue, with{ default: () => [] }. Drops the now-unnecessary null guard in the toggle handler. Consumers usingv-model:checked="…"must switch tov-model="…"(orv-model:model-value="…").FilterPanelContent: every inner-item binding rewritten to:model-value/@update:model-value. The boolean / non-boolean branches were split into distinct<FilterOffCanvasSwitchItem>and<FilterOffCanvasAccordionItem>elements instead of a<component :is>union, fixing the:title/:labelmismatch and removing dead:icon/:value/:active-valueprops thatSwitchItemdid not declare.
Matches the v-model contract from
DECISIONS.md(modelValue/update:modelValuefor form inputs). -
f195c25: Editor: every section AND block with a configurable heading or subline now exposes an HTML-element selector directly next to the field via the existing
as: 'style'decorator popup — no more separate "SEO" panel at the bottom.- Heading options:
H1–H6/DIV - Subline options:
P/H1–H6/DIV
Sections — newly available (13):
SectionBannerBasic,SectionBannerShowcase,SectionBannerIntegrated,SectionBrandHero,SectionCategoryCardSlider,SectionCategoryCardGrid,SectionContentGrid,SectionContentSlider,SectionEditorialGrid,SectionLogoGrid,SectionLogoSlider,SectionNewsletterRegistration,SectionPageHero,SectionPageNotFound. Section defaults: heading →h2, subline →div(was implicitp; sections render subline as a plain block now by default).Sections — migrated (2):
SectionProductSlider,SectionProductSliderShowcaselose their dedicated "SEO" fieldset;headingElementis now stored atheadingStyle.element(object decorator path) instead of top-levelheadingElement. Action required: anyone with storedheadingElementvalues on these two sections must re-pick the heading element in Studio once.Blocks — newly available (7):
BlockBannerBasic,BlockBannerShowcase,BlockBannerIntegrated,BlockCard,BlockHeroSliderSlide,BlockProductDetailVariantSelectionConfigurator,BlockText. Block defaults: heading →h3, subline →h4(one level below their hosting section's heading).Naming: ui-component props use the Reka convention
asinstead oftag. The underlying components now acceptheadingAs?/sublineAs?props (renamed fromheadingTag?/sublineTag?). Template attributes are:heading-as/:subline-as. The ui-kit types follow suit:TextGroupHeadingAs/TextGroupSublineAs(previously*Tag).TextGroupSublineAsnow includes'h1'so editors can override a subline to a page-level heading when SEO requires it.ui changes:
BannerBasic,BannerShowcase,BannerIntegrated,BrandHero,CategoryCardSlider,CategoryCardGrid,ContentGrid,ContentSlider,EditorialGrid,HeroSlide,LogoGrid,LogoSlider,NewsletterRegistration,PageHero,PageNotFound,ProductSlider,ProductSliderShowcase,VariantSelectorConfiguratoraccept new optionalheadingAs/sublineAsprops (additive). Where a component renders heading/subline manually instead of viaTextGroup, the props drive<component :is>to pick the element at render time.ui-kit changes:
TextGroup: prop renameheadingTag→headingAs,sublineTag→sublineAs; type renameTextGroupHeadingTag→TextGroupHeadingAs,TextGroupSublineTag→TextGroupSublineAs.TextGroupSublineAswidened to include'h1'.SwiperChrome: same prop rename plus added optionalsublineAsforwarding to its internalTextGroup.Card: new optionalheadingAs?/sublineAs?props forwarded toLTextGroup.AlertDialog: new optionaltitleAs/descriptionAsprops (forwarded fromDialogStore.DialogOptions); the inner<Text>underDialogTitle/DialogDescriptionusesas-childso the emitted tag followsText'sas.
Breaking (ui-kit): the prop and type rename on
TextGroupis a minor-version break in the public API for any external consumer that importsTextGroupHeadingTag/TextGroupSublineTagor binds:heading-tag/:subline-tagdirectly. Update to the new names.Internal:
shared-fields/headingElement.tsexportsheadingElementOptionsandsublineElementOptions; the top-levelheadingElementFieldis removed (only consumed by ProductSlider/Showcase, both migrated to the nested decorator). - Heading options:
-
6b16c11:
HeroSlideaccepts two new optional props:headingSize?: 's' | 'm' | 'l'andsublineSize?: 's' | 'm' | 'l', forwarded to the underlying<Text>elements. Defaults preserve previous rendering (headingSize: 'l',sublineSize: 'm'). -
8e8168b: Adds opening-hours primitives and the location-card domain components.
@laioutr-core/ui-kit— opening-hours primitives:useOpeningStatus(openingHours, now)composable — reactiveisOpen+ next state-change event (closes-at / opens-today / opens-tomorrow / opens-on-weekday / opens-on-date) across a 30-day horizon; respects IANA timezone and one-off date exceptions.OpeningStatusIndicator(atom) — open/closed pill driven by the composable.OpeningStatusDetail(atom) — localized one-liner for the next state change.OpeningStatus(molecule) — Indicator + Detail combined.OpeningHoursWeeklyTable(molecule) — weekly schedule rendered as aDescriptionList, grouping consecutive same-hours days ("Monday – Wednesday" etc.).- New i18n keys:
openingStatus.*,openingHoursWeeklyTable.*, andlocationCard.*(the last consumed cross-package by@laioutr-core/ui).
@laioutr-core/ui— location domain components:LocationCard— list / map-popup variants, composes the ui-kit opening-hours primitives.PaymentMethodList— payment-method chips for location detail views.
The opening-hours primitives live in ui-kit because
OpeningHoursis a Schema.org-style general concept (offices, doctors, restaurants — not commerce-specific). The location-card components live in ui because they encode commerce/storefront placement decisions. -
328ee9d: -
HeaderBasicMenu→MenuBasic. The component was a generic basic navigation menu (used by bothSectionHeaderBasicandSectionHeaderShop), not a sub-component ofHeaderBasic. Moved out of theHeaderBasic/folder into its ownMenuBasic/folder per the parent-prefix-naming rule.- Exported type
HeaderProps→MenuBasicProps. - Exported type
MenuItem→MenuBasicItem. - Root CSS class
.header-basic-menu→.menu-basic(parent-scoped rule.s-header-shop__desktop-nav .menu-basicupdated accordingly). BlockMenuHeaderBasic→BlockMenuBasic. The wrapping block follows the new component name per theBlock<Name>convention.defineBlock({ component: 'BlockMenuHeaderBasic' })string →'BlockMenuBasic'— stored project configurations that reference this block by name need a one-time migration.- Studio label
'Basic Header Menu'→'Basic Menu'. Tags extended with'Navigation'.
Consumer imports updated:
SectionHeaderBasic,SectionHeaderShop:BlockMenuHeaderBasicDefinition→BlockMenuBasicDefinition, restrictTo updated.BlockMenuBasicitself:HeaderBasicMenuimport →MenuBasic.
No template / runtime behavior change beyond the rename. Lint + typecheck clean.
- Exported type
-
8464f9e:
HeroSlideaccepts two new optional props:headingTextShadow?: 'none' | 'soft' | 'strong'andsublineTextShadow?: 'none' | 'soft' | 'strong'. When set, BEM modifier classes (slide-card__heading--text-shadow-{value},slide-card__subline--text-shadow-{value}) apply a layered text-shadow for legibility against busy hero backgrounds. Defaults to'none', so existing slides render unchanged. The shadow values resolve from the global--text-shadow-soft/--text-shadow-strongcustom properties shipped by ui-kit. MarkedTODO FIGMAuntil dedicated tokens land in ui-tokens. The newtextShadowprop onCaptionFlagis also forwarded for slide captions rendered as flag chips. -
176d022:
NewsletterRegistrationaccepts two new optional props:caption?: string(rendered as a<Text type="caption" size="s">above the heading inside the existing headings group) andbody?: string(rendered as a<RichContent>block between the headings group and the form). Mirrors the caption + body fields exposed byBlockText. Both props are unset by default, so existing consumers render identically.Breaking: the
image?: MediaImageprop is removed in favor of a newmediaslot. Right-side / top-on-mobile media area is now consumer-provided via<template #media>...</template>instead of a single image — Sections that wrap this component decide what goes there (media, text, card, button, …). The legacytheme.image('newsletterRegistrationTeaser')fallback is gone; if no media is provided, the content side takes the full width. New Storybook storyWithMediaSlotshows the pattern.