Skip to content

M10 Highlights

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: M10 - Highlight Module Name: M10 -Highlight Figma Desktop: https://www.figma.com/design/VFdCObxYyZjSRFdK5pU1df/01_Savoy_library?node-id=931-19084&m=dev Figma Mobile: https://www.figma.com/design/VFdCObxYyZjSRFdK5pU1df/01_Savoy_library?node-id=1176-5297&m=dev Component Type: STATIC

Follow a frontend-first approach, in this exact order:

  1. Define types in .types.ts first, aligned with expected content structure
  2. Write Storybook stories with realistic mock data BEFORE implementing the component
  3. Create these story variants: Default, MinimalContent, EmptyState, LongContent, AllOptions
  4. Stories must include full EN autodocs documentation with Figma links and feature bullet list
  5. Validate visual design against Figma in all 8 themes at breakpoints 375px, 768px, 1024px, 1440px
  1. 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)
  2. Write mapper — .mapper.ts transforming Umbraco JSON → Omit<Props, ‘siteKey’ | ‘locale’>
  3. Write tests — .test.tsx covering mapper logic and component rendering
  4. Register in packages/modules/src/registry.ts with { component, mapper, moduleId }
  5. Export via index.ts with named exports

Phase C — Umbraco (when ready for CMS integration)

Section titled “Phase C — Umbraco (when ready for CMS integration)”
  1. Define Element Type schema (use prompt template 16)
  2. Create Element Type in Umbraco backoffice
  3. Add as allowed block on target page Document Types
  4. Create test content and verify API output
  5. Validate mapper against real API response

For STATIC modules:

  1. packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/index.ts
  2. packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.tsx
  3. packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.scss
  4. packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.types.ts
  5. packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.mapper.ts
  6. packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.stories.tsx
  7. packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.test.tsx

For INTERACTIVE modules (add .client.tsx):

  1. packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/index.ts (Server Component wrapper)
  2. packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.client.tsx (‘use client’)
  3. packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.scss
  4. packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.types.ts
  5. packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.mapper.ts
  6. packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.stories.tsx
  7. packages/modules/src/m{MODULE_NUMBER}-{KEBAB_NAME}/{PascalName}.test.tsx

Then register the module in packages/modules/src/registry.ts.

  • 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.)
  1. Hardcoding colors, fonts, or spacing — use var(—token-name) from packages/themes/src/
  2. Forgetting imageMobile — every image needs both desktop and mobile variants
  3. Mapper crash on null — Umbraco sends null for optional fields, always use ?. and fallbacks
  4. Wrong BEM nesting — use &__element inside the block, not standalone .block__element
  5. Missing registry entry — module won’t render on any page without it
  6. Desktop-first media queries — use min-width (mobile-first), never max-width
  7. Importing from wrong package — UI components from @savoy/ui, not relative paths
  8. Lorem ipsum in stories — use realistic hotel context
  9. (Interactive) Putting ‘use client’ in index.ts — only .client.tsx
  10. (Interactive) Non-serializable props crossing Server-Client boundary
  11. (Interactive) Missing keyboard navigation or ARIA updates on state change
  12. (Interactive) No prefers-reduced-motion handling on animations
  13. (Interactive) Forgetting focus trap cleanup on unmount