All files / packages/design-system-2/.storybook/decorators preview-decorator.js

0% Statements 0/48
0% Branches 0/38
0% Functions 0/9
0% Lines 0/47

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163                                                                                                                                                                                                                                                                                                                                     
import { html } from 'lit-html';
import MyThemes from '../themes.js';
import processThemeState from '../addons/theme-builder/src/utilities/theme-processor.js';
import { loadFont } from '../addons/theme-builder/src/utilities/font-options.js';
import { applyColorScheme } from './apply-color-scheme.js';
import { applyMainStylesheet } from './apply-main-stylesheet.js';
import { applyThemeStylesheets } from './apply-theme-stylesheets.js';
import { useGlobals, useEffect } from 'storybook/preview-api';
 
export const defaultThemeData = {
  colors: {
    brand: '#066AFE',
    success: '#056764',
    error: '#b60554',
    warning: '#8c4b02',
    info: '#0b5cab',
  },
  typography: {
    fontSize: '13px',
    fontFamily: 'default',
    lineHeight: 1.375,
    fontScale: 1,
  },
  spacing: {
    scale: 1,
  },
  borderRadius: {
    scale: 1,
  },
  shadows: {
    show: true,
  },
};
 
const getActiveThemeConfig = (activeTheme) => {
  const savedThemes = JSON.parse(localStorage.getItem('sds-theme-builder-saved-themes'));
  if (savedThemes) {
    const data = savedThemes.find((theme) => theme.name === activeTheme)?.themeData;
    return data;
  }
  return null;
};
 
/**
 * Storybook loader: awaits all stylesheet loads before the story renders.
 * This prevents Chromatic from snapshotting before CSS is ready.
 */
export async function stylesheetLoader(context) {
  const requestedTheme = context.globals.themeName || 'cosmos';
  // Stale localStorage may hold a themeName that no longer exists (e.g. the
  // old 'slds' key). Fall back to 'cosmos' so the story still renders.
  const theme = MyThemes[requestedTheme] || MyThemes.cosmos;
  const useThemeLayer = context.globals.themeLayer === true;
  const themeImports = useThemeLayer ? theme.themeLayer : theme.import;
  const storyStylesheets = context.parameters.stylesheets || [];
  const subthemeImports = theme.subtheme || [];
 
  await Promise.all([
    applyMainStylesheet(useThemeLayer),
    applyThemeStylesheets([...themeImports, ...storyStylesheets, ...subthemeImports]),
  ]);
}
 
/**
 * Storybook preview decorator: applies theme, color scheme, and Theme Builder state.
 */
export function previewDecorator(story, context) {
  // @feature: env-switch — SLDS 1 has no Cosmos theme.
  // Read the live toolbar globals so we can react when either toggle changes.
  const [globals, updateGlobals] = useGlobals();
  useEffect(() => {
    // On SLDS 1 + Cosmos → snap the theme back to Lightning Blue.
    // Updating the global also moves the Theme toolbar's shown value.
    if (globals.sldsVersion === 'slds1' && globals.themeName === 'cosmos') {
      updateGlobals({ themeName: 'lightning-blue' });
    }
  }, [globals.sldsVersion, globals.themeName]);
 
  const selectedTheme = context.globals.themeName || 'cosmos';
  const selectedScheme = context.globals.theme || 'light';
  let themeBuilderTheme =
    context.globals.brand === 'default' ? defaultThemeData : getActiveThemeConfig(context.globals.brand);
  if (context.globals.brand === 'temp') {
    themeBuilderTheme = context.globals.themeState;
  }
 
  // Dispatch event when any of the three properties change
  globalThis.dispatchEvent(
    new CustomEvent('storybook-globals-changed', {
      detail: {
        theme: selectedTheme,
        scheme: selectedScheme,
        themeState: themeBuilderTheme,
      },
      bubbles: true,
      composed: true,
    }),
  );
 
  // Apply light/dark color scheme.
  applyColorScheme(selectedScheme);
 
  // @feature: env-switch
  // Only stories that opt in (tags: ['env-switch']) respond to the SLDS Version
  // toggle. We set/clear --_slds-is-v2-enabled on <head> — the exact element the
  // JS reader queries (getComputedStyle(document.head)), matching sds-recipes.
  // Non-opted stories never override it and keep the SLDS2 default that the base
  // stylesheet sets on :where(html).
  if (context.tags?.includes('env-switch')) {
    const isV2 = (context.globals.sldsVersion ?? 'slds2') === 'slds2';
    // 'initial' resets the custom property to its guaranteed-invalid value, so
    // getPropertyValue() reads back as '' (falsy) even though the base CSS sets
    // it true on :where(html). Inline styles win over the :where() rule.
    document.head.style.setProperty('--_slds-is-v2-enabled', isV2 ? 'true' : 'initial');
  } else {
    document.head.style.removeProperty('--_slds-is-v2-enabled');
  }
 
  // Transparent background: photoshop-like checkerboard.
  // Toggled via the Background toolbar global.
  if (context.globals.background === 'transparent') {
    document.documentElement.dataset.transparent = '';
  } else {
    delete document.documentElement.dataset.transparent;
  }
 
  // ✅ Apply Theme Builder theme if available
  if (themeBuilderTheme) {
    console.log('🎨 Applying Theme Builder theme:', themeBuilderTheme);
  }
 
  // Creates a new arg `themeName` for every Story and sets value from global toolbar.
  const { args } = context;
  args.themeName = selectedTheme;
  args.sldsVersion = context.globals.sldsVersion;
 
  return html`
    <style id="theme-builder-styles">
      ${(() => {
        if (JSON.stringify(themeBuilderTheme) !== JSON.stringify(defaultThemeData)) {
          return `:root {
          ${(() => {
            const allHookMappings = processThemeState(themeBuilderTheme);
            console.log('allHookMappings', allHookMappings);
            return allHookMappings
              .map((mapping) => `${mapping.name}: ${mapping.value};`)
              .join('\n            ');
          })()}
          }`;
        }
      })()}
    </style>
    ${(() => {
      // Load font based on themeState.typography.fontFamily
      if (themeBuilderTheme?.typography?.fontFamily !== 'default') {
        loadFont(themeBuilderTheme.typography.fontFamily);
      }
      return '';
    })()}
    ${story()}
  `;
}