[BpkThemeProvider] Introduce typed theme system with BpkLegacyThemeProvider - #4911
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Introduces a new typed BpkThemeProvider (TS) alongside a renamed BpkLegacyThemeProvider, plus a token-sync generator that emits a strongly typed theme model and a themeToVars() mapper.
Changes:
- Add a token-sync script to generate
BpkThemetypes and a runtimethemeToVars()mapper from the DTCG token tree. - Introduce a new TS
BpkThemeProviderwith context/hook support and mode-specific theming via a scoped<style>tag. - Rename the existing JS provider to
BpkLegacyThemeProvider, update stories/tests, and adjust coverage ignores for generated files.
Reviewed changes
Copilot reviewed 19 out of 21 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| token-sync/src/typescript-theme-formatter.ts | New generator script to emit typed theme interfaces + themeToVars() from JSON tokens. |
| packages/backpack-web/src/bpk-theming/src/generated/BpkTheme.ts | Generated theme typings and themeToVars() mapping for CSS custom properties. |
| packages/backpack-web/src/bpk-theming/src/snapshots/BpkThemeProvider-test.tsx.snap | Snapshot updates for the new TS BpkThemeProvider. |
| packages/backpack-web/src/bpk-theming/src/snapshots/BpkThemeProvider-test.js.snap | Snapshot renames to reflect BpkLegacyThemeProvider. |
| packages/backpack-web/src/bpk-theming/src/BpkThemeProvider.tsx | New typed theme provider with context/hook and dark/light mode style injection. |
| packages/backpack-web/src/bpk-theming/src/BpkThemeProvider.js | Renames exported legacy provider implementation to BpkLegacyThemeProvider (component name/export). |
| packages/backpack-web/src/bpk-theming/src/BpkThemeProvider-test.tsx | New tests for typed provider behavior (partial themes, mode values, hook). |
| packages/backpack-web/src/bpk-theming/src/BpkThemeProvider-test.js | Updates legacy provider tests to import/use BpkLegacyThemeProvider. |
| packages/backpack-web/src/bpk-theming/index.js | New entrypoint exports for hook/types/default provider and legacy provider. |
| packages/backpack-web/src/bpk-theming/index.d.ts | Updates handwritten typings to refer to the legacy provider. |
| packages/backpack-web/src/bpk-component-price/src/BpkPrice.stories.tsx | Story updates to use BpkLegacyThemeProvider. |
| packages/backpack-web/src/bpk-component-chip/src/BpkChip.stories.tsx | Story updates to use BpkLegacyThemeProvider. |
| packages/backpack-web/src/bpk-component-checkbox/src/BpkCheckboxV2/BpkCheckboxV2.stories.tsx | Story updates to use BpkLegacyThemeProvider. |
| packages/backpack-web/src/bpk-component-checkbox-card/src/BpkCheckboxCard.stories.tsx | Story updates to use BpkLegacyThemeProvider. |
| packages/backpack-web/src/bpk-component-chatbot-input/src/BpkChatbotInput.stories.tsx | Story updates to use BpkLegacyThemeProvider and token imports. |
| packages/backpack-web/src/bpk-component-button/src/BpkButton.stories.tsx | Story updates to use BpkLegacyThemeProvider and refactors theme constant placement. |
| packages/backpack-web/src/bpk-component-badge/src/BpkBadge.stories.tsx | Story updates to use BpkLegacyThemeProvider. |
| packages/backpack-web/package.json | Updates export map entry for ./bpk-theming. |
| packages/backpack-web/jest.config.js | Ignores generated files from coverage. |
| package.json | Adds build:theme-types script and ignores generated files from coverage. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "./bpk-theming": "./src/bpk-theming/index.ts", | ||
| "./*": "./src/*/index.ts" |
| // @ts-expect-error Untyped import. See `decisions/imports-ts-suppressions.md`. | ||
|
|
||
| import { borderRadiusLg, corePrimaryDay } from '@skyscanner/bpk-foundations-web/tokens/base.es6'; |
| // @ts-ignore | ||
|
|
||
|
|
||
| import NewWindowIcon from '../../bpk-component-icon/sm/new-window'; |
| {styleTagContent && ( | ||
| // eslint-disable-next-line react/no-danger | ||
| <style dangerouslySetInnerHTML={{ __html: styleTagContent }} /> | ||
| )} |
| Object.entries(t).forEach(([key, value]) => { | ||
| if (isModeValue(value)) { | ||
| // Flat semantic key (e.g. corePrimary, textPrimary) | ||
| const probe = themeToVars({ [key]: sentinel } as BpkTheme); | ||
| const cssVar = Object.keys(probe)[0]; | ||
| if (cssVar) { | ||
| entries.push([cssVar, value.light, value.dark]); | ||
| } | ||
| } else if (value && typeof value === 'object' && !Array.isArray(value)) { | ||
| // Namespaced component key (e.g. button.bgPrimary) | ||
| const nsObj = value as Record<string, BpkThemedValue>; | ||
| Object.entries(nsObj).forEach(([nsKey, nsValue]) => { | ||
| if (!isModeValue(nsValue)) return; | ||
| const probe = themeToVars({ [key]: { [nsKey]: sentinel } } as BpkTheme); | ||
| const cssVar = Object.keys(probe)[0]; | ||
| if (cssVar) { |
| /** Additional inline styles applied to the wrapper element. */ | ||
| style?: Record<string, string>; |
| function toCamelFragment(segment: string): string { | ||
| return segment | ||
| .replace(/[^a-zA-Z0-9]+(.)/g, (_, c: string) => c.toUpperCase()) | ||
| .replace(/^(.)/, (c: string) => c.toLowerCase()) | ||
| .replace(/[^a-zA-Z0-9]/g, ''); | ||
| } |
|
Visit https://backpack.github.io/storybook-prs/4911 to see this build running in a browser. |
6 similar comments
|
Visit https://backpack.github.io/storybook-prs/4911 to see this build running in a browser. |
|
Visit https://backpack.github.io/storybook-prs/4911 to see this build running in a browser. |
|
Visit https://backpack.github.io/storybook-prs/4911 to see this build running in a browser. |
|
Visit https://backpack.github.io/storybook-prs/4911 to see this build running in a browser. |
|
Visit https://backpack.github.io/storybook-prs/4911 to see this build running in a browser. |
|
Visit https://backpack.github.io/storybook-prs/4911 to see this build running in a browser. |
…ovider
- Add new BpkThemeProvider.tsx: typed BpkTheme, partial-safe, React Context
+ useBpkTheme hook, { light, dark } mode-specific values via scoped <style>
tag, `as` prop
- Add token-sync/src/typescript-theme-formatter.ts: generates BpkTheme.ts
from DTCG backpack.light.json with flat semantic tokens + namespaced
component tokens and themeToVars() runtime mapper
- Rename old BpkThemeProvider.js → BpkLegacyThemeProvider.js; export as
{ BpkLegacyThemeProvider } for existing consumers
- Update bpk-theming entry point to index.ts; remove hand-written index.d.ts
- Migrate stories to use { BpkLegacyThemeProvider } named import
- Add coveragePathIgnorePatterns for generated/ directory
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
… Figma Add token-sync/ghost-tokens.json: keyed by DTCG component name, each entry declares a CSS var the component SCSS uses that has no Figma counterpart. Wire into typescript-theme-formatter.ts so ghost entries are merged into the generated BpkButtonTheme (and any future component interfaces) alongside Figma-sourced tokens. Initial Button ghosts: colourTextPrimary, colourTextPrimaryOnDark, colourTextPrimaryOnLight, colourTextSecondary, colourTextSecondaryOnDark, colourTextDestructivePressed, colourTextDisabled, colourTextLink, dimensionMinHeightLarge. To add ghosts for another component: append an entry to ghost-tokens.json and run `pnpm -w run build:theme-types`. Also fix stray @ts-expect-error placement in BpkChatbotInput.stories.tsx. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Replace BpkLegacyThemeProvider + flat camelCase keys with the new typed BpkThemeProvider and BpkTheme shape (button namespace). Removes the allButtonThemeAttributes import — no themeAttributes prop needed. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
THEMED_BUTTON_THEME now uses BpkThemedValue { light, dark } objects for all
colour tokens so the story responds to the Storybook colour-scheme toolbar.
Structural tokens (radius, min-height, padding) stay as plain strings.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…story
Replace placeholder colour values with exact token values from Figma:
Light: node-id=16727-13420 — standard nav radius (8px), navy primary
Dark: node-id=16727-13421 — pill radius (999px), blue primary (#024daf),
cyan featured (#d1f7ff/#84e9ff), dark canvas backgrounds
dimensionRadius now uses { light: '8px', dark: '999px' } to match the
shape change between the two Figma frames.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…c values
BpkThemedValue now accepts { light?, dark? } — either key is optional.
A value of { light: '#foo' } overrides only the light selector; { dark: '#foo' }
overrides only the dark selector. Both keys present behaves as before.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…eme="dark"])
{ light: '...' } without a dark value was bleeding into dark mode because the
unscoped #id selector applies in all modes. Now single-mode overrides use a
tight selector — light-only uses :root:not([data-theme="dark"]) #id, dark-only
uses :root[data-theme="dark"] #id. Both-modes behaviour is unchanged.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Reduce THEMED_BUTTON_THEME to the tokens that actually differ from the Backpack defaults — radius (pill) and secondary colour overrides. Removes the full colour spec; default CSS vars cover the rest. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: OpenCode <noreply@opencode.ai>
…typed provider to BpkThemeProviderV2 Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Stories using BpkThemeProvider (legacy) need no import changes — the default export still works. Reverts unintended formatting noise from the ESLint auto-fix. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
950f752 to
c1ac1a5
Compare
|
Visit https://backpack.github.io/storybook-prs/4911 to see this build running in a browser. |
Introduces a new typed theme provider alongside the existing one (now renamed to
BpkLegacyThemeProvider).New
BpkThemeProvider(TypeScript, default export):BpkTheme— flat semantic tokens (corePrimary,textPrimary, …) + namespaced component tokens (button.colourBgPrimary, …) generated from the DTCG token treeuseBpkTheme()hook for portal/Shadow DOM consumers{ light, dark }values inject a scoped<style>tagasprop for wrapper element overrideBpkLegacyThemeProvider(renamed fromBpkThemeProvider.js): unchanged behaviour, now exported as a named export{ BpkLegacyThemeProvider }. All affected stories migrated to use this.token-sync/src/typescript-theme-formatter.ts: generatessrc/generated/BpkTheme.tsfrombackpack.light.json— run withpnpm -w run build:theme-types.Remember to include the following changes:
README.md(If you have created a new component)README.md