M16 Logo Carousel
Read the following context files first:
- docs/PRD/07_Modules_and_Templates.md
- docs/dev-frontend-guides/03_MODULE_DEVELOPMENT_LIFECYCLE.md
- docs/dev-frontend-guides/05_BEM_SASS_THEMING.md
- docs/dev-frontend-guides/01_FIGMA_TO_CODE_WORKFLOW.md
- docs/dev-frontend-guides/04_STORYBOOK_FIRST_DEVELOPMENT.md
- docs/dev-frontend-guides/06_RESPONSIVE_IMAGES_PATTERN.md
- packages/modules/src/m04-page-hero/ (all files in this directory)
- packages/modules/src/registry.ts
- packages/ui/src/ (scan for available UI components)
Now create a new module with the following details:
Module ID: M{MODULE_NUMBER} Module Name: {MODULE_NAME} Figma Desktop: {FIGMA_DESKTOP_URL} Figma Mobile: {FIGMA_MOBILE_URL} Component Type: {STATIC or INTERACTIVE}
Description: {BRIEF_DESCRIPTION_OF_MODULE_PURPOSE}
Props: {LIST_EACH_PROP_WITH_TYPE_AND_DESCRIPTION}
UI Components to Use: {LIST_UI_COMPONENTS_FROM_PACKAGES_UI — e.g., Heading, BodyText, ResponsiveImage, LinkButton}
Layout: {DESCRIBE_LAYOUT — e.g., 2-column on desktop, stacked on mobile, max-width container}
--- INTERACTIVE ONLY (remove this section if Static) ---
Interaction Specification:
- Triggers: {WHAT_TRIGGERS_INTERACTION — e.g., click, swipe, scroll, hover, autoplay timer}
- State Changes: {WHAT_CHANGES — e.g., active slide index, open/closed panel, selected tab}
- Animations: {TRANSITIONS_AND_ANIMATIONS — e.g., slide left 300ms ease, fade 200ms, height auto}
- Keyboard Navigation: {KEY_BINDINGS — e.g., ArrowLeft/ArrowRight for slides, Enter/Space to toggle, Escape to close}
- Touch Gestures: {MOBILE_GESTURES — e.g., swipe left/right, pinch to zoom}
- Autoplay: {IF_APPLICABLE — interval, pause on hover, pause on focus, pause control button}
--- END INTERACTIVE ONLY ---
Development Process
Section titled “Development Process”Follow a frontend-first approach, in this exact order:
Phase A — Storybook
Section titled “Phase A — Storybook”- Define types in
.types.tsfirst, aligned with expected content structure - Write Storybook stories with realistic mock data BEFORE implementing the component
- Create these story variants: Default, MinimalContent, EmptyState, LongContent, AllOptions
- Stories must include full EN autodocs documentation with Figma links and feature bullet list
- Validate visual design against Figma in all 8 themes at breakpoints 375px, 768px, 1024px, 1440px
Phase B — React/Next.js
Section titled “Phase B — React/Next.js”- Implement the component —
.tsx+.scss(BEM + CSS custom properties) → Static: Server Component (no ‘use client’) → Interactive: Server wrapper.tsx+.client.tsx(‘use client’ ONLY in .client.tsx) - Write mapper —
.mapper.tstransforming Umbraco JSON → Omit<Props, ‘siteKey’ | ‘locale’> - Write tests —
.test.tsxcovering mapper logic and component rendering - Register in
packages/modules/src/registry.tswith{ component, mapper, moduleId } - Export via
index.tswith named exports
Phase B.5 — Pixel Perfect Visual Testing (MANDATORY)
Section titled “Phase B.5 — Pixel Perfect Visual Testing (MANDATORY)”- Fetch Figma baselines — Use Figma MCP
get_screenshotto capture desktop (1440x900) + mobile (375x812) PNGs to__figma_baselines__/{storyId}/ - Register Figma mapping — Add story ID → Figma node in
.storybook/visual-testing/figma-mapping.ts - Run visual tests —
pnpm --filter storybook visual:test(Storybook must be running) - Iterate — View diff in Pixel Perfect panel, fix CSS until diff < 0.5%
- Promote baselines — Click “Baseline” in panel or
pnpm --filter storybook visual:update - Commit baselines —
__figma_baselines__/and__visual_snapshots__/are git-tracked
Phase C — Umbraco (when ready for CMS integration)
Section titled “Phase C — Umbraco (when ready for CMS integration)”- Define Element Type schema (use prompt template 16)
- Create Element Type in Umbraco backoffice
- Add as allowed block on target page Document Types
- Create test content and verify API output
- Validate mapper against real API response
Files to Create
Section titled “Files to Create”For STATIC modules:
- packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/index.ts
- packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.tsx
- packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.scss
- packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.types.ts
- packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.mapper.ts
- packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.stories.tsx
- packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.test.tsx
For INTERACTIVE modules (add .client.tsx):
- packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/index.ts (Server Component wrapper)
- packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.client.tsx (‘use client’)
- packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.scss
- packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.types.ts
- packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.mapper.ts
- packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.stories.tsx
- packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.test.tsx
Then register the module in packages/modules/src/registry.ts.
Conventions
Section titled “Conventions”- BEM class names with SASS (no CSS Modules, no Tailwind)
- All colors, fonts, spacing via CSS custom properties (var(—token-name))
- SCSS uses BEM nesting with
&__element,&--modifier(max 3 levels) - Props interface includes siteKey: SiteKey and locale: string
- Mapper signature: (data: UmbracoElement) => Omit<Props, ‘siteKey’ | ‘locale’>
- Mapper handles missing/null fields gracefully with optional chaining and defaults
- Root element:
<section data-module="{camelCaseAlias}" data-module-id="M{XX}"> - Props interface includes
moduleId?: string(injected by page renderer) - Semantic HTML (section, article, nav, h2-h4)
- Every image uses imageDesktop + imageMobile with ResponsiveImage from @savoy/ui
- Responsive: mobile-first with min-width breakpoints (sm=640, md=768, lg=1024, xl=1280, 2xl=1440)
- Fluid layouts — no fixed pixel widths, no horizontal scroll
- Storybook stories use realistic hotel context data (room names, restaurants, prices — never lorem ipsum)
- Stories include tags: [‘autodocs’], Figma link in parameters.design, and full EN documentation
- Must render correctly for all 8 themes
- No TypeScript errors (pnpm tsc —noEmit passes)
Interactive Module Conventions (skip if Static)
Section titled “Interactive Module Conventions (skip if Static)”- ‘use client’ ONLY in .client.tsx — never in index.ts
- index.ts is a Server Component wrapper — pass only serializable data (no functions, Date, Map/Set)
- No useState/useEffect in index.ts — only in .client.tsx
- Keyboard navigation fully implemented (Arrow keys, Enter, Space, Escape as appropriate)
- ARIA attributes set and updated on state change (aria-expanded, aria-selected, aria-hidden, aria-live, role)
- Focus management — logical focus order, focus trap for modals/overlays, visible focus ring via :focus-visible
- Autoplay (if applicable) pauses on hover and keyboard focus, with visible pause/play button
- Touch gestures (if applicable) work on iOS Safari and Android Chrome
- Animations respect prefers-reduced-motion media query
- Storybook stories include interaction state variants (open/closed, active slide, etc.)
Common Pitfalls to Avoid
Section titled “Common Pitfalls to Avoid”- Hardcoding colors, fonts, or spacing — use var(—token-name) from packages/themes/src/
- Forgetting imageMobile — every image needs both desktop and mobile variants
- Mapper crash on null — Umbraco sends null for optional fields, always use ?. and fallbacks
- Wrong BEM nesting — use &__element inside the block, not standalone .block__element
- Missing registry entry — module won’t render on any page without it
- Desktop-first media queries — use min-width (mobile-first), never max-width
- Importing from wrong package — UI components from @savoy/ui, not relative paths
- Lorem ipsum in stories — use realistic hotel context
- (Interactive) Putting ‘use client’ in index.ts — only .client.tsx
- (Interactive) Non-serializable props crossing Server-Client boundary
- (Interactive) Missing keyboard navigation or ARIA updates on state change
- (Interactive) No prefers-reduced-motion handling on animations
- (Interactive) Forgetting focus trap cleanup on unmount
- Skipping Pixel Perfect visual tests — No module is complete without passing visual tests
- Not fetching Figma baselines — New modules must have Figma baselines for both desktop and mobile
- Not committing baselines —
__figma_baselines__/and__visual_snapshots__/must be in git