forte-mb-ui
v1.5.0
Published
A standalone UI library of **CMS-decoupled** MB components, extracted from the main `igw-frontend-work` Next.js app together with their Storybook stories. This is the first extraction pass: only components that depend purely on the shared design foundatio
Downloads
839
Readme
mb-ui
A standalone UI library of CMS-decoupled MB components, extracted
from the main igw-frontend-work Next.js app together with their Storybook
stories. This is the first extraction pass: only components that depend purely on
the shared design foundation (no FirstSpirit/fsxa-api, no @/server/*, no
context providers) were moved.
Quick start
pnpm install
pnpm storybook # http://localhost:6006
pnpm typecheck # tsc --noEmitUse the Brand toolbar in Storybook to switch between messe, bazaar,
fruit, and igw themes.
Using it in another project
This package ships TypeScript source (no pre-built dist). It relies on
next/font/local, styled-components, and React server/client components, so it
must be compiled by the consuming Next.js (App Router) app rather than
pre-bundled. Consumers therefore add it to transpilePackages.
1. Install
pnpm add mb-ui
# peer dependencies (provided by your app, kept as singletons):
pnpm add next react react-dom styled-components
# needed to import the global stylesheet:
pnpm add -D sass2. Configure next.config
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
// Required: compile the package's TS/TSX + next/font in your app's pipeline.
transpilePackages: ["mb-ui"],
// Required for styled-components SSR (server components / streaming).
compiler: { styledComponents: true },
};
export default nextConfig;3. Load the global styles once (root layout)
import "mb-ui/styles.scss";4. Register a theme
The components read the active brand synchronously via getTheme(), so a theme
must be registered before they render — on the server and the client.
// app/layout.tsx (server component)
import { loadTheme, ThemeNames } from "mb-ui";
import "mb-ui/styles.scss";
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
await loadTheme(ThemeNames.messe); // register for server components
return (
<html lang="de">
<body>
<ThemeRegistry brand={ThemeNames.messe}>{children}</ThemeRegistry>
</body>
</html>
);
}// app/ThemeRegistry.tsx
"use client";
import { ThemeProvider } from "styled-components";
import { getEagerTheme, setActiveTheme, ThemeNames } from "mb-ui";
export default function ThemeRegistry({
brand,
children,
}: {
brand: ThemeNames;
children: React.ReactNode;
}) {
const theme = getEagerTheme(brand);
setActiveTheme(brand, theme); // register for client components
// make the brand visible to the synchronous currentTheme() reader
if (typeof window !== "undefined") window.__ENV = { currentTheme: brand };
theme.fonts.forEach((f) =>
document.documentElement.classList.add(f.variable),
);
return (
<ThemeProvider theme={theme}>
{children}
<theme.GlobalStyle />
</ThemeProvider>
);
}The default brand can also be driven by env vars:
MESSE_THEME(server) andNEXT_PUBLIC_MESSE_THEME(client). Seesrc/themes/currentTheme.ts.
5. Use components
import { CTAButton, Accordion, IconArrowRight } from "mb-ui";
export function Example() {
return (
<CTAButton href="/tickets" variant="primary">
Tickets
</CTAButton>
);
}What's inside
src/
themes/ # theme infra (registry, tokens, types) + base + messe/bazaar/fruit/igw
ui-utils/ # small helpers used by the components (Link, text, date, …)
assets/ # icons, SVG components, Storybook demo assets
ui/
index.tsx # re-exports @webfox-sc/core primitives
molecules/ Breadcrumb, SearchForm
ui-atoms/ FormField, TextInput
ui-molecules/ FullWidthContainer
ui-sections/ Spacer, Table, Quote, SourceCode, HorizontalLine,
TopLink, Countdown, AtoZList, TooltipContent, BlogLatestTextInput and FullWidthContainer are supporting components pulled in because
FormField's story and SourceCode depend on them.
Extracted components
| Component | Notes |
| -------------- | ------------------------------------------------ |
| Spacer | layout spacer |
| Table | themed table styles |
| Quote | + QuoteWrapper, bubble SVGs |
| SourceCode | + SourceCodeStyleWrapper, FullWidthContainer |
| HorizontalLine | |
| TopLink | |
| Countdown | + useCountdown |
| AtoZList | |
| TooltipContent | uses react-popper-tooltip |
| BlogLatest | + BlogLatestItem |
| Breadcrumb | + BreadcrumbStyles |
| SearchForm | |
| FormField | + TextInput |
CMS-free components (rebuilt for the chatbot landing-page builder)
These were rewritten from scratch with plain prop APIs — no CMS data, no
context providers, no next/navigation. They keep the original look (via
@/themes) but take everything they render as props, so a chatbot can compose
landing pages directly.
| Component | Path (src/ui/) | API highlights |
| ------------ | -------------------------- | ---------------------------------------------------------------------------------------- |
| CTAButton | atoms/CTAButton | href (renders <a>) or onClick (renders <button>); variant, iconVariant |
| LogoSlider | ui-sections/LogoSlider | slides[], slidesPerView, autoplay, navigation (uses swiper/react directly) |
| Accordion | ui-sections/Accordion | items[] with content; internal open state, allowMultiple, CSS grid-rows animation |
| ImageGallery | ui-sections/ImageGallery | images[]; self-contained lightbox (keyboard + prev/next), no dialog-provider |
| Numbers | ui-sections/Numbers | items[]; self-contained count-up via IntersectionObserver, locale-aware formatting |
| Milestones | ui-sections/Milestones | items[] (year/title); themeVariant |
All image rendering uses plain <img> (no next/image / cache-control / CMS
asset pipeline).
Deliberately NOT extracted yet (need decoupling first)
These still pull CMS/app coupling — candidates for a later pass:
- BlogTeaser — pulls
Image(→@/lib/logger,@/app/api/...) and theBlogListsection. - QuotationSlider — pulls
SwiperSlider(→dialog-provider).
Foundation notes / how to add a brand
- Styling is styled-components driven by
@/themes. Components callgetTheme().components.X; the active theme is registered in the Storybook preview viasetActiveTheme(...). theme.tsinlines a few types (CTAButton*,DecorationMapping) that originally lived in not-yet-extracted components, so the theme system stays self-contained. Keep them in sync if the originals change.- The theme system carries
messe,bazaar,fruit,igw. To add another brand:- copy
src/themes/<brand>/from the source app (drop its*.stories.*/*.mock.*files), - add it to the
ThemeNamesenum insrc/themes/themes.ts, - add the eager import in
src/themes/eagerThemes.ts, the dynamic case insrc/themes/themeRegistry.ts, the loader entry insrc/themes/themeTokensLoader.ts, and a toolbar item in.storybook/preview.tsx.
- copy
Tooling
Storybook 10 (@storybook/nextjs-vite) — the Next.js framework is used only
because the theme fonts load via next/font/local; none of the extracted
components require Next at runtime.
Deploying Storybook (Azure Static Web Apps)
Storybook is published to Azure Static Web Apps in the Messe Berlin
subscription (f0258078-7d50-4924-afe7-905ff3dce39b), resource group
messe-berlin-ai-workshop, app name messe-berlin-ui-storybook.
Live URL (default hostname): https://lemon-dune-0bf191603.7.azurestaticapps.net
CI/CD
GitHub Actions workflow .github/workflows/deploy-storybook.yml:
- push to
main→ production deployment - pull requests → preview environment (closed automatically when the PR closes)
- manual →
workflow_dispatchfrom the Actions tab
One-time GitHub secret
Add the Static Web Apps deployment token as repository secret
AZURE_STATIC_WEB_APPS_API_TOKEN:
az staticwebapp secrets list \
--name messe-berlin-ui-storybook \
--resource-group messe-berlin-ai-workshop \
--subscription f0258078-7d50-4924-afe7-905ff3dce39b \
--query "properties.apiKey" -o tsvThen in GitHub: Settings → Secrets and variables → Actions → New repository secret.
Infrastructure (Bicep)
Templates live in infra/. Deploy or update with:
az deployment sub create \
--name messe-berlin-ui-storybook \
--location westeurope \
--template-file infra/main.bicep \
--parameters infra/main.bicepparam \
--subscription f0258078-7d50-4924-afe7-905ff3dce39bLocal build artifact
pnpm build-storybook
cp staticwebapp.config.json storybook-static/The staticwebapp.config.json at the repo root is copied into the build output
so deep links to individual stories work on the Static Web App.
