From 3dcf03d46b6834c3d0fd6f43a9208d234c730405 Mon Sep 17 00:00:00 2001
From: Hellen <el-makarova@yandex-team.ru>
Date: Mon, 25 Dec 2023 19:04:36 +0300
Subject: [PATCH] feat: calculate body class names seperately (#1197)

---
 README.md                                   |  9 +++++++
 src/components/theme/ThemeProvider.tsx      | 22 +++++++++--------
 src/components/theme/constants.ts           |  2 --
 src/components/theme/getBodyClassName.ts    | 26 +++++++++++++++++++++
 src/components/theme/index.ts               |  1 +
 src/components/theme/updateBodyClassName.ts | 22 ++++++++---------
 6 files changed, 58 insertions(+), 24 deletions(-)
 create mode 100644 src/components/theme/getBodyClassName.ts

diff --git a/README.md b/README.md
index 8fb5ba6ba1..d2edd26615 100644
--- a/README.md
+++ b/README.md
@@ -56,6 +56,15 @@ or add specific CSS-classes to the root node:
 </html>
 ```
 
+it is possible to generate initial CSS-classes during SSR:
+
+```js
+import {getInitialRootClassName} from '@gravity-ui/uikit';
+
+const theme = 'light';
+const rootClassName = getInitialRootClassName({theme});
+```
+
 ```js
 // index.js
 import {createRoot} from 'react-dom/client';
diff --git a/src/components/theme/ThemeProvider.tsx b/src/components/theme/ThemeProvider.tsx
index 6714b7ac53..506c4cde01 100644
--- a/src/components/theme/ThemeProvider.tsx
+++ b/src/components/theme/ThemeProvider.tsx
@@ -1,17 +1,13 @@
 import React from 'react';
 
-import {block, blockNew} from '../utils/cn';
-
 import {ThemeContext} from './ThemeContext';
 import {ThemeSettingsContext} from './ThemeSettingsContext';
-import {DEFAULT_DARK_THEME, DEFAULT_LIGHT_THEME, DEFAULT_THEME, ROOT_CLASS_NAME} from './constants';
+import {DEFAULT_DARK_THEME, DEFAULT_LIGHT_THEME, DEFAULT_THEME} from './constants';
+import {getDeprecatedRootClassName, getRootClassName} from './getBodyClassName';
 import type {RealTheme, Theme} from './types';
 import {updateBodyClassName} from './updateBodyClassName';
 import {useSystemTheme} from './useSystemTheme';
 
-const b = block(ROOT_CLASS_NAME);
-const bNew = blockNew(ROOT_CLASS_NAME);
-
 interface ThemeProviderExternalProps {}
 
 interface ThemeProviderDefaultProps {
@@ -66,10 +62,16 @@ export function ThemeProvider({
             <ThemeSettingsContext.Provider value={themeSettingsContext}>
                 {scoped ? (
                     <div
-                        className={bNew({theme: themeValue, 'native-scrollbar': nativeScrollbar}, [
-                            b({theme: themeValue, 'native-scrollbar': nativeScrollbar}),
-                            rootClassName,
-                        ])}
+                        className={getRootClassName(
+                            {theme: themeValue, 'native-scrollbar': nativeScrollbar},
+                            [
+                                getDeprecatedRootClassName({
+                                    theme: themeValue,
+                                    'native-scrollbar': nativeScrollbar,
+                                }),
+                                rootClassName,
+                            ],
+                        )}
                     >
                         {children}
                     </div>
diff --git a/src/components/theme/constants.ts b/src/components/theme/constants.ts
index 89db77e2a5..bfff80768d 100644
--- a/src/components/theme/constants.ts
+++ b/src/components/theme/constants.ts
@@ -5,5 +5,3 @@ export const DEFAULT_DARK_THEME = 'dark';
 export const LIGHT_THEMES = ['light', 'light-hc'];
 export const DARK_THEMES = ['dark', 'dark-hc'];
 export const THEMES = [...LIGHT_THEMES, ...DARK_THEMES];
-
-export const ROOT_CLASS_NAME = 'root';
diff --git a/src/components/theme/getBodyClassName.ts b/src/components/theme/getBodyClassName.ts
new file mode 100644
index 0000000000..5c658a49f7
--- /dev/null
+++ b/src/components/theme/getBodyClassName.ts
@@ -0,0 +1,26 @@
+import {block, blockNew} from '../utils/cn';
+
+import type {RealTheme} from './types';
+import type {BodyClassNameModifiers} from './updateBodyClassName';
+
+const ROOT_CLASS_NAME = 'root';
+
+const bNew = blockNew(ROOT_CLASS_NAME);
+const b = block(ROOT_CLASS_NAME);
+
+export function getDeprecatedRootClassName(
+    modifier?: Partial<BodyClassNameModifiers & {theme: RealTheme | boolean}>,
+) {
+    return b(modifier);
+}
+export function getRootClassName(
+    modifier?: Partial<BodyClassNameModifiers & {theme: RealTheme | boolean}>,
+    addition?: string[],
+) {
+    return bNew(modifier, addition);
+}
+
+export function getInitialRootClassName(props: {theme?: RealTheme} = {}) {
+    const {theme} = props;
+    return getRootClassName({theme});
+}
diff --git a/src/components/theme/index.ts b/src/components/theme/index.ts
index 63ad45175b..baa894abfd 100644
--- a/src/components/theme/index.ts
+++ b/src/components/theme/index.ts
@@ -8,4 +8,5 @@ export * from './useThemeType';
 export * from './withTheme';
 export * from './withThemeValue';
 export * from './getThemeType';
+export {getInitialRootClassName} from './getBodyClassName';
 export type {Theme, RealTheme, ThemeType} from './types';
diff --git a/src/components/theme/updateBodyClassName.ts b/src/components/theme/updateBodyClassName.ts
index 46a1597a98..02d3ffbf3f 100644
--- a/src/components/theme/updateBodyClassName.ts
+++ b/src/components/theme/updateBodyClassName.ts
@@ -1,12 +1,10 @@
-import {block, blockNew, modsClassName} from '../utils/cn';
+import {modsClassName} from '../utils/cn';
 
-import {ROOT_CLASS_NAME} from './constants';
+import {getDeprecatedRootClassName, getRootClassName} from './getBodyClassName';
 import type {RealTheme} from './types';
 
-const b = block(ROOT_CLASS_NAME);
-const bNew = blockNew(ROOT_CLASS_NAME);
-const rootClassName = b();
-const rootNewClassName = bNew();
+const rootClassName = getDeprecatedRootClassName();
+const rootNewClassName = getRootClassName();
 
 export type BodyClassNameModifiers = {
     'native-scrollbar': boolean;
@@ -41,19 +39,19 @@ export function updateBodyClassName(
     }
 
     [...bodyEl.classList].forEach((cls) => {
-        if (cls.startsWith(modsClassName(b({theme: true})))) {
+        if (cls.startsWith(modsClassName(getDeprecatedRootClassName({theme: true})))) {
             bodyEl.classList.remove(cls);
         }
 
-        if (cls.startsWith(modsClassName(bNew({theme: true})))) {
+        if (cls.startsWith(modsClassName(getRootClassName({theme: true})))) {
             bodyEl.classList.remove(cls);
         }
     });
-    bodyEl.classList.add(modsClassName(b({theme: newTheme})));
-    bodyEl.classList.add(modsClassName(bNew({theme: newTheme})));
+    bodyEl.classList.add(modsClassName(getDeprecatedRootClassName({theme: newTheme})));
+    bodyEl.classList.add(modsClassName(getRootClassName({theme: newTheme})));
 
     for (const [key, value] of Object.entries({...defaultModifiers, ...modifiers})) {
-        bodyEl.classList.toggle(modsClassName(b({[key]: true})), value);
-        bodyEl.classList.toggle(modsClassName(bNew({[key]: true})), value);
+        bodyEl.classList.toggle(modsClassName(getDeprecatedRootClassName({[key]: true})), value);
+        bodyEl.classList.toggle(modsClassName(getRootClassName({[key]: true})), value);
     }
 }