@tpitre/story-ui 4.5.2 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -881,101 +881,77 @@ function StoryUIPanel({ mcpPort }: StoryUIPanelProps) {
881
881
  return () => clearInterval(intervalId);
882
882
  }, []);
883
883
 
884
- // Detect Storybook theme
884
+ // Detect Storybook MANAGER theme (not preview background)
885
+ // This ensures Story UI follows Storybook's overall theme, not the story preview background toggle
885
886
  useEffect(() => {
886
- const detectTheme = () => {
887
- const body = document.body;
888
- const html = document.documentElement;
889
-
890
- // Check URL parameters for Storybook background setting
891
- const urlParams = new URLSearchParams(window.location.search);
892
- const globals = urlParams.get('globals') || '';
893
- const hasStorybookLightBg = globals.includes('backgrounds.value:light');
894
- const hasStorybookDarkBg = globals.includes('backgrounds.value:dark') ||
895
- globals.includes('backgrounds.value:%23') || // Hex colors starting with #
896
- globals.includes('backgrounds.value:!hex');
897
-
898
- // Check parent frame URL if we're in an iframe (Storybook 8+)
899
- let parentHasDarkBg = false;
900
- let parentHasLightBg = false;
901
- let parentHasDarkClass = false;
887
+ const detectManagerTheme = () => {
888
+ let managerIsDark = false;
889
+
890
+ // Strategy 1: Check parent frame for Storybook manager theme (Storybook 8+)
891
+ // The manager theme is set in .storybook/manager.tsx via addons.setConfig({ theme: themes.dark })
902
892
  try {
903
893
  if (window.parent !== window) {
904
- const parentUrl = new URL(window.parent.location.href);
905
- const parentGlobals = parentUrl.searchParams.get('globals') || '';
906
- parentHasLightBg = parentGlobals.includes('backgrounds.value:light');
907
- parentHasDarkBg = parentGlobals.includes('backgrounds.value:dark') ||
908
- parentGlobals.includes('backgrounds.value:%23');
909
- // Check parent document for Storybook dark theme classes
910
894
  const parentBody = window.parent.document.body;
911
895
  const parentHtml = window.parent.document.documentElement;
912
- parentHasDarkClass = parentBody.classList.contains('sb-dark') ||
913
- parentHtml.classList.contains('dark') ||
914
- parentHtml.getAttribute('data-theme') === 'dark' ||
915
- parentBody.getAttribute('data-theme') === 'dark';
916
- // Check Storybook 8+ manager theme
917
- const sbMainEl = window.parent.document.querySelector('.sb-main-padded, .sb-show-main');
918
- if (sbMainEl) {
919
- const sbBgColor = window.getComputedStyle(sbMainEl).backgroundColor;
920
- const sbRgb = sbBgColor.match(/\d+/g);
921
- if (sbRgb && sbRgb.length >= 3) {
922
- const sbLuminance = (0.299 * parseInt(sbRgb[0]) + 0.587 * parseInt(sbRgb[1]) + 0.114 * parseInt(sbRgb[2])) / 255;
923
- if (sbLuminance < 0.5) parentHasDarkClass = true;
896
+
897
+ // Check for Storybook's dark theme class (most reliable)
898
+ if (parentBody.classList.contains('sb-dark') ||
899
+ parentHtml.classList.contains('sb-dark') ||
900
+ parentHtml.getAttribute('data-theme') === 'dark' ||
901
+ parentBody.getAttribute('data-theme') === 'dark') {
902
+ managerIsDark = true;
903
+ }
904
+
905
+ // Check Storybook manager sidebar/header background color as fallback
906
+ // The manager UI elements use the theme colors, not the preview background
907
+ const managerEl = window.parent.document.querySelector('.sb-sidebar, [class*="sidebar"], .sb-bar');
908
+ if (managerEl && !managerIsDark) {
909
+ const bgColor = window.getComputedStyle(managerEl).backgroundColor;
910
+ const rgb = bgColor.match(/\d+/g);
911
+ if (rgb && rgb.length >= 3) {
912
+ const luminance = (0.299 * parseInt(rgb[0]) + 0.587 * parseInt(rgb[1]) + 0.114 * parseInt(rgb[2])) / 255;
913
+ managerIsDark = luminance < 0.5;
924
914
  }
925
915
  }
926
916
  }
927
917
  } catch {
928
- // Cross-origin access not allowed, ignore
918
+ // Cross-origin access not allowed, fall back to system preference
929
919
  }
930
920
 
931
- // Check the actual background color of the body
932
- const bgColor = window.getComputedStyle(body).backgroundColor;
933
- const rgb = bgColor.match(/\d+/g);
934
- let isBackgroundDark = false;
935
- if (rgb && rgb.length >= 3) {
936
- const luminance = (0.299 * parseInt(rgb[0]) + 0.587 * parseInt(rgb[1]) + 0.114 * parseInt(rgb[2])) / 255;
937
- isBackgroundDark = luminance < 0.5;
921
+ // Strategy 2: If not in iframe or can't detect, use system preference
922
+ if (!managerIsDark) {
923
+ const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
924
+ managerIsDark = systemPrefersDark;
938
925
  }
939
926
 
940
- // Check system preference as fallback
941
- const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
942
-
943
- // Explicit light mode takes precedence - if user selected "light" in Storybook, respect that
944
- const hasExplicitLightMode = hasStorybookLightBg || parentHasLightBg;
945
-
946
- // Explicit dark mode indicators
947
- const hasExplicitDarkMode =
948
- body.classList.contains('sb-dark') ||
949
- html.classList.contains('dark') ||
950
- html.getAttribute('data-theme') === 'dark' ||
951
- body.getAttribute('data-theme') === 'dark' ||
952
- hasStorybookDarkBg ||
953
- parentHasDarkBg;
954
-
955
- // Determine dark mode: explicit light mode forces light, otherwise check dark indicators
956
- const isDark = hasExplicitLightMode
957
- ? false
958
- : (hasExplicitDarkMode || parentHasDarkClass || isBackgroundDark || systemPrefersDark);
959
- dispatch({ type: 'SET_DARK_MODE', payload: isDark });
927
+ dispatch({ type: 'SET_DARK_MODE', payload: managerIsDark });
960
928
  };
961
- detectTheme();
962
929
 
963
- // Listen for URL changes (Storybook uses popstate for navigation)
964
- window.addEventListener('popstate', detectTheme);
930
+ detectManagerTheme();
965
931
 
966
- // Poll for changes since Storybook might change background without popstate
967
- const intervalId = setInterval(detectTheme, 500);
932
+ // Poll for changes (manager theme changes are rare but possible)
933
+ const intervalId = setInterval(detectManagerTheme, 1000);
968
934
 
969
- const observer = new MutationObserver(detectTheme);
970
- observer.observe(document.body, { attributes: true, attributeFilter: ['class', 'data-theme', 'style'] });
971
- observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class', 'data-theme', 'style'] });
935
+ // Listen for system preference changes
972
936
  const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
973
- mediaQuery.addEventListener('change', detectTheme);
937
+ mediaQuery.addEventListener('change', detectManagerTheme);
938
+
939
+ // Observe parent document for theme changes if accessible
940
+ let parentObserver: MutationObserver | null = null;
941
+ try {
942
+ if (window.parent !== window) {
943
+ parentObserver = new MutationObserver(detectManagerTheme);
944
+ parentObserver.observe(window.parent.document.body, { attributes: true, attributeFilter: ['class', 'data-theme'] });
945
+ parentObserver.observe(window.parent.document.documentElement, { attributes: true, attributeFilter: ['class', 'data-theme'] });
946
+ }
947
+ } catch {
948
+ // Cross-origin, ignore
949
+ }
950
+
974
951
  return () => {
975
- window.removeEventListener('popstate', detectTheme);
976
952
  clearInterval(intervalId);
977
- observer.disconnect();
978
- mediaQuery.removeEventListener('change', detectTheme);
953
+ mediaQuery.removeEventListener('change', detectManagerTheme);
954
+ parentObserver?.disconnect();
979
955
  };
980
956
  }, []);
981
957