@primer/styled-react 1.0.4-rc.f372aa7ad → 1.0.5-rc.f3b0d9cc2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"ThemeProvider.d.ts","sourceRoot":"","sources":["../../src/components/ThemeProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAMzB,eAAO,MAAM,gBAAgB,QAAQ,CAAA;AAKrC,MAAM,MAAM,KAAK,GAAG;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAC,CAAA;AACxC,KAAK,SAAS,GAAG,KAAK,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAA;AACnD,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,MAAM,CAAA;AAElD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,CAAC,EAAE,KAAK,CAAA;IACb,SAAS,CAAC,EAAE,iBAAiB,CAAA;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B,CAAA;AA8BD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAkF/E,CAAA;AAED,wBAAgB,QAAQ;YA/Gd,KAAK;kBACC,MAAM;gBACR,iBAAiB;wBACT,SAAS;0BACP,MAAM;gBAChB,MAAM;kBACJ,MAAM;kBACN,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,iBAAiB,CAAC,CAAC;kBACvD,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;oBAC1C,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;EAwG7D;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,UAG1F;AAqGD,eAAe,aAAa,CAAA"}
1
+ {"version":3,"file":"ThemeProvider.d.ts","sourceRoot":"","sources":["../../src/components/ThemeProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAKzB,eAAO,MAAM,gBAAgB,QAAQ,CAAA;AAKrC,MAAM,MAAM,KAAK,GAAG;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAC,CAAA;AACxC,KAAK,SAAS,GAAG,KAAK,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAA;AACnD,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,MAAM,CAAA;AAElD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,CAAC,EAAE,KAAK,CAAA;IACb,SAAS,CAAC,EAAE,iBAAiB,CAAA;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kBAAkB,CAAC,EAAE,OAAO,CAAA;CAC7B,CAAA;AA8CD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,iBAAiB,CAAC,kBAAkB,CAAC,CAyE/E,CAAA;AAED,wBAAgB,QAAQ;YAtHd,KAAK;kBACC,MAAM;gBACR,iBAAiB;wBACT,SAAS;0BACP,MAAM;gBAChB,MAAM;kBACJ,MAAM;kBACN,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,iBAAiB,CAAC,CAAC;kBACvD,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;oBAC1C,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;EA+G7D;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,UAG1F;AAqED,eAAe,aAAa,CAAA"}
@@ -1,5 +1,4 @@
1
1
  import React from 'react';
2
- import ReactDOM from 'react-dom';
3
2
  import { ThemeProvider as ThemeProvider$1 } from 'styled-components';
4
3
  import { theme, useId, useSyncedState } from '@primer/react';
5
4
  import deepmerge from 'deepmerge';
@@ -18,15 +17,27 @@ const ThemeContext = /*#__PURE__*/React.createContext({
18
17
  });
19
18
 
20
19
  // inspired from __NEXT_DATA__, we use application/json to avoid CSRF policy with inline scripts
20
+ const serverHandoffCache = new Map();
21
+ const emptyHandoff = {};
21
22
  const getServerHandoff = id => {
23
+ if (typeof document === 'undefined') return emptyHandoff;
24
+ const cached = serverHandoffCache.get(id);
25
+ if (cached !== undefined) return cached;
22
26
  try {
23
27
  const serverData = document.getElementById(`__PRIMER_DATA_${id}__`)?.textContent;
24
- if (serverData) return JSON.parse(serverData);
28
+ if (serverData) {
29
+ const parsed = JSON.parse(serverData);
30
+ serverHandoffCache.set(id, parsed);
31
+ return parsed;
32
+ }
25
33
  } catch (_error) {
26
34
  // if document/element does not exist or JSON is invalid, supress error
27
35
  }
28
- return {};
36
+ const empty = {};
37
+ serverHandoffCache.set(id, empty);
38
+ return empty;
29
39
  };
40
+ const emptySubscribe = () => () => {};
30
41
  const ThemeProvider = ({
31
42
  children,
32
43
  ...props
@@ -42,56 +53,33 @@ const ThemeProvider = ({
42
53
  // Initialize state
43
54
  const theme$1 = props.theme ?? fallbackTheme ?? theme;
44
55
  const uniqueDataId = useId();
45
- const {
46
- resolvedServerColorMode
47
- } = getServerHandoff(uniqueDataId);
48
- const resolvedColorModePassthrough = React.useRef(resolvedServerColorMode);
49
56
  const [colorMode, setColorMode] = useSyncedState(props.colorMode ?? fallbackColorMode ?? defaultColorMode);
50
57
  const [dayScheme, setDayScheme] = useSyncedState(props.dayScheme ?? fallbackDayScheme ?? defaultDayScheme);
51
58
  const [nightScheme, setNightScheme] = useSyncedState(props.nightScheme ?? fallbackNightScheme ?? defaultNightScheme);
52
59
  const systemColorMode = useSystemColorMode();
53
- // eslint-disable-next-line react-hooks/refs
54
- const resolvedColorMode = resolvedColorModePassthrough.current || resolveColorMode(colorMode, systemColorMode);
60
+ const clientColorMode = resolveColorMode(colorMode, systemColorMode);
61
+ // During SSR/hydration, use the server-rendered color mode from the handoff script tag
62
+ // to avoid mismatches. After hydration, resolve from client state.
63
+ const resolvedColorMode = React.useSyncExternalStore(emptySubscribe, () => clientColorMode, () => getServerHandoff(uniqueDataId).resolvedServerColorMode ?? clientColorMode);
55
64
  const colorScheme = chooseColorScheme(resolvedColorMode, dayScheme, nightScheme);
56
65
  const {
57
66
  resolvedTheme,
58
67
  resolvedColorScheme
59
68
  } = React.useMemo(() => applyColorScheme(theme$1, colorScheme), [theme$1, colorScheme]);
60
-
61
- // this effect will only run on client
62
- React.useEffect(function updateColorModeAfterServerPassthrough() {
63
- const resolvedColorModeOnClient = resolveColorMode(colorMode, systemColorMode);
64
- if (resolvedColorModePassthrough.current) {
65
- // if the resolved color mode passed on from the server is not the resolved color mode on client, change it!
66
- if (resolvedColorModePassthrough.current !== resolvedColorModeOnClient) {
67
- window.setTimeout(() => {
68
- // use ReactDOM.flushSync to prevent automatic batching of state updates since React 18
69
- // ref: https://github.com/reactwg/react-18/discussions/21
70
- ReactDOM.flushSync(() => {
71
- // override colorMode to whatever is resolved on the client to get a re-render
72
- setColorMode(resolvedColorModeOnClient);
73
- });
74
-
75
- // immediately after that, set the colorMode to what the user passed to respond to system color mode changes
76
- setColorMode(colorMode);
77
- });
78
- }
79
- resolvedColorModePassthrough.current = null;
80
- }
81
- }, [colorMode, systemColorMode, setColorMode]);
69
+ const contextValue = React.useMemo(() => ({
70
+ theme: resolvedTheme,
71
+ colorScheme,
72
+ colorMode,
73
+ resolvedColorMode,
74
+ resolvedColorScheme,
75
+ dayScheme,
76
+ nightScheme,
77
+ setColorMode,
78
+ setDayScheme,
79
+ setNightScheme
80
+ }), [resolvedTheme, colorScheme, colorMode, resolvedColorMode, resolvedColorScheme, dayScheme, nightScheme, setColorMode, setDayScheme, setNightScheme]);
82
81
  return /*#__PURE__*/jsx(ThemeContext.Provider, {
83
- value: {
84
- theme: resolvedTheme,
85
- colorScheme,
86
- colorMode,
87
- resolvedColorMode,
88
- resolvedColorScheme,
89
- dayScheme,
90
- nightScheme,
91
- setColorMode,
92
- setDayScheme,
93
- setNightScheme
94
- },
82
+ value: contextValue,
95
83
  children: /*#__PURE__*/jsxs(ThemeProvider$1, {
96
84
  theme: resolvedTheme,
97
85
  children: [children, props.preventSSRMismatch ? /*#__PURE__*/jsx("script", {
@@ -115,48 +103,20 @@ function useColorSchemeVar(values, fallback) {
115
103
  } = useTheme();
116
104
  return values[colorScheme] ?? fallback;
117
105
  }
106
+ function subscribeToSystemColorMode(callback) {
107
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
108
+ const media = window?.matchMedia?.('(prefers-color-scheme: dark)');
109
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
110
+ media?.addEventListener('change', callback);
111
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
112
+ return () => media?.removeEventListener('change', callback);
113
+ }
118
114
  function useSystemColorMode() {
119
- const [systemColorMode, setSystemColorMode] = React.useState(getSystemColorMode);
120
- React.useEffect(() => {
121
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
122
- const media = window?.matchMedia?.('(prefers-color-scheme: dark)');
123
- function matchesMediaToColorMode(matches) {
124
- return matches ? 'night' : 'day';
125
- }
126
- function handleChange(event) {
127
- const isNight = event.matches;
128
- setSystemColorMode(matchesMediaToColorMode(isNight));
129
- }
130
-
131
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
132
- if (media) {
133
- // just in case the preference changed before the event listener was attached
134
- const isNight = media.matches;
135
- setSystemColorMode(matchesMediaToColorMode(isNight));
136
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
137
- if (media.addEventListener !== undefined) {
138
- media.addEventListener('change', handleChange);
139
- return function cleanup() {
140
- media.removeEventListener('change', handleChange);
141
- };
142
- }
143
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
144
- else if (media.addListener !== undefined) {
145
- media.addListener(handleChange);
146
- return function cleanup() {
147
- media.removeListener(handleChange);
148
- };
149
- }
150
- }
151
- }, []);
152
- return systemColorMode;
115
+ return React.useSyncExternalStore(subscribeToSystemColorMode, getSystemColorMode, () => 'day');
153
116
  }
154
117
  function getSystemColorMode() {
155
118
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
156
- if (typeof window !== 'undefined' && window.matchMedia?.('(prefers-color-scheme: dark)')?.matches) {
157
- return 'night';
158
- }
159
- return 'day';
119
+ return window?.matchMedia?.('(prefers-color-scheme: dark)')?.matches ? 'night' : 'day';
160
120
  }
161
121
  function resolveColorMode(colorMode, systemColorMode) {
162
122
  switch (colorMode) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primer/styled-react",
3
- "version": "1.0.4-rc.f372aa7ad",
3
+ "version": "1.0.5-rc.f3b0d9cc2",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -40,15 +40,18 @@
40
40
  "@types/styled-system": "^5.1.23",
41
41
  "@types/styled-system__css": "^5.0.16",
42
42
  "@types/styled-system__theme-get": "^5.0.1",
43
+ "clsx": "^2.1.1",
44
+ "deepmerge": "^4.3.1",
45
+ "focus-visible": "^5.2.1",
43
46
  "styled-system": "^5.1.5"
44
47
  },
45
48
  "devDependencies": {
46
49
  "@babel/preset-react": "^7.28.5",
47
50
  "@babel/preset-typescript": "^7.28.5",
48
51
  "@primer/primitives": "10.x || 11.x",
49
- "@primer/react": "^38.18.0",
52
+ "@primer/react": "^38.19.0",
50
53
  "@rollup/plugin-babel": "^6.1.0",
51
- "@storybook/react-vite": "^10.1.11",
54
+ "@storybook/react-vite": "^10.3.3",
52
55
  "@types/react": "18.3.11",
53
56
  "@types/react-dom": "18.3.1",
54
57
  "@types/styled-components": "^5.1.26",