@cloud-ru/ds-theme 1.0.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.
Files changed (247) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/LICENSE +201 -0
  3. package/README.md +365 -0
  4. package/dist/cjs/components/ChildThemeProvider/ChildThemeProvider.d.ts +24 -0
  5. package/dist/cjs/components/ChildThemeProvider/ChildThemeProvider.js +25 -0
  6. package/dist/cjs/components/ChildThemeProvider/index.d.ts +1 -0
  7. package/dist/cjs/components/ChildThemeProvider/index.js +17 -0
  8. package/dist/cjs/components/RootThemeProvider/RootThemeProvider.d.ts +49 -0
  9. package/dist/cjs/components/RootThemeProvider/RootThemeProvider.js +30 -0
  10. package/dist/cjs/components/RootThemeProvider/index.d.ts +1 -0
  11. package/dist/cjs/components/RootThemeProvider/index.js +17 -0
  12. package/dist/cjs/components/ThemeProvider/ThemeProvider.d.ts +28 -0
  13. package/dist/cjs/components/ThemeProvider/ThemeProvider.js +16 -0
  14. package/dist/cjs/components/ThemeProvider/index.d.ts +1 -0
  15. package/dist/cjs/components/ThemeProvider/index.js +17 -0
  16. package/dist/cjs/components/ThemeScope/ThemeScope.d.ts +32 -0
  17. package/dist/cjs/components/ThemeScope/ThemeScope.js +65 -0
  18. package/dist/cjs/components/ThemeScope/index.d.ts +1 -0
  19. package/dist/cjs/components/ThemeScope/index.js +17 -0
  20. package/dist/cjs/components/index.d.ts +3 -0
  21. package/dist/cjs/components/index.js +19 -0
  22. package/dist/cjs/constants/appearance.d.ts +22 -0
  23. package/dist/cjs/constants/appearance.js +27 -0
  24. package/dist/cjs/constants/colorScheme.d.ts +17 -0
  25. package/dist/cjs/constants/colorScheme.js +23 -0
  26. package/dist/cjs/constants/index.d.ts +2 -0
  27. package/dist/cjs/constants/index.js +18 -0
  28. package/dist/cjs/context/appearanceContext.d.ts +20 -0
  29. package/dist/cjs/context/appearanceContext.js +22 -0
  30. package/dist/cjs/context/index.d.ts +4 -0
  31. package/dist/cjs/context/index.js +7 -0
  32. package/dist/cjs/context/themeContext.d.ts +12 -0
  33. package/dist/cjs/context/themeContext.js +15 -0
  34. package/dist/cjs/hooks/index.d.ts +4 -0
  35. package/dist/cjs/hooks/index.js +20 -0
  36. package/dist/cjs/hooks/useApplyCustomTheme.d.ts +27 -0
  37. package/dist/cjs/hooks/useApplyCustomTheme.js +39 -0
  38. package/dist/cjs/hooks/useColorScheme.d.ts +37 -0
  39. package/dist/cjs/hooks/useColorScheme.js +80 -0
  40. package/dist/cjs/hooks/useIsomorphicLayoutEffect.d.ts +7 -0
  41. package/dist/cjs/hooks/useIsomorphicLayoutEffect.js +16 -0
  42. package/dist/cjs/hooks/useThemeClassnames.d.ts +11 -0
  43. package/dist/cjs/hooks/useThemeClassnames.js +18 -0
  44. package/dist/cjs/hooks/useThemeConfig.d.ts +24 -0
  45. package/dist/cjs/hooks/useThemeConfig.js +15 -0
  46. package/dist/cjs/index.d.ts +7 -0
  47. package/dist/cjs/index.js +23 -0
  48. package/dist/cjs/keys.d.ts +4 -0
  49. package/dist/cjs/keys.js +19 -0
  50. package/dist/cjs/package.json +3 -0
  51. package/dist/cjs/ssr.d.ts +15 -0
  52. package/dist/cjs/ssr.js +38 -0
  53. package/dist/cjs/store/globalStore.d.ts +29 -0
  54. package/dist/cjs/store/globalStore.js +72 -0
  55. package/dist/cjs/store/index.d.ts +1 -0
  56. package/dist/cjs/store/index.js +17 -0
  57. package/dist/cjs/types/appearance.d.ts +23 -0
  58. package/dist/cjs/types/appearance.js +3 -0
  59. package/dist/cjs/types/colorScheme.d.ts +5 -0
  60. package/dist/cjs/types/colorScheme.js +2 -0
  61. package/dist/cjs/types/index.d.ts +2 -0
  62. package/dist/cjs/types/index.js +18 -0
  63. package/dist/cjs/utils/colorSchemeFromHeaders.d.ts +22 -0
  64. package/dist/cjs/utils/colorSchemeFromHeaders.js +44 -0
  65. package/dist/cjs/utils/colorSchemeStorage.d.ts +35 -0
  66. package/dist/cjs/utils/colorSchemeStorage.js +85 -0
  67. package/dist/cjs/utils/customTheme/buildBrandPaletteCss.d.ts +11 -0
  68. package/dist/cjs/utils/customTheme/buildBrandPaletteCss.js +35 -0
  69. package/dist/cjs/utils/customTheme/buildBrandPaletteVars.d.ts +11 -0
  70. package/dist/cjs/utils/customTheme/buildBrandPaletteVars.js +42 -0
  71. package/dist/cjs/utils/customTheme/color/apca.d.ts +4 -0
  72. package/dist/cjs/utils/customTheme/color/apca.js +62 -0
  73. package/dist/cjs/utils/customTheme/color/colorSpace.d.ts +5 -0
  74. package/dist/cjs/utils/customTheme/color/colorSpace.js +72 -0
  75. package/dist/cjs/utils/customTheme/color/conversions.d.ts +17 -0
  76. package/dist/cjs/utils/customTheme/color/conversions.js +101 -0
  77. package/dist/cjs/utils/customTheme/color/hex.d.ts +10 -0
  78. package/dist/cjs/utils/customTheme/color/hex.js +34 -0
  79. package/dist/cjs/utils/customTheme/color/index.d.ts +4 -0
  80. package/dist/cjs/utils/customTheme/color/index.js +20 -0
  81. package/dist/cjs/utils/customTheme/color/oklch.d.ts +3 -0
  82. package/dist/cjs/utils/customTheme/color/oklch.js +21 -0
  83. package/dist/cjs/utils/customTheme/color/types.d.ts +40 -0
  84. package/dist/cjs/utils/customTheme/color/types.js +2 -0
  85. package/dist/cjs/utils/customTheme/constants.d.ts +31 -0
  86. package/dist/cjs/utils/customTheme/constants.js +49 -0
  87. package/dist/cjs/utils/customTheme/generateBrandPalette.d.ts +10 -0
  88. package/dist/cjs/utils/customTheme/generateBrandPalette.js +25 -0
  89. package/dist/cjs/utils/customTheme/index.d.ts +4 -0
  90. package/dist/cjs/utils/customTheme/index.js +20 -0
  91. package/dist/cjs/utils/getThemeClassnames.d.ts +17 -0
  92. package/dist/cjs/utils/getThemeClassnames.js +38 -0
  93. package/dist/cjs/utils/index.d.ts +7 -0
  94. package/dist/cjs/utils/index.js +23 -0
  95. package/dist/cjs/utils/mergeAppearance.d.ts +7 -0
  96. package/dist/cjs/utils/mergeAppearance.js +17 -0
  97. package/dist/cjs/utils/resolveColorScheme.d.ts +7 -0
  98. package/dist/cjs/utils/resolveColorScheme.js +15 -0
  99. package/dist/cjs/utils/themeBootstrap.d.ts +12 -0
  100. package/dist/cjs/utils/themeBootstrap.js +19 -0
  101. package/dist/esm/components/ChildThemeProvider/ChildThemeProvider.d.ts +24 -0
  102. package/dist/esm/components/ChildThemeProvider/ChildThemeProvider.js +22 -0
  103. package/dist/esm/components/ChildThemeProvider/index.d.ts +1 -0
  104. package/dist/esm/components/ChildThemeProvider/index.js +1 -0
  105. package/dist/esm/components/RootThemeProvider/RootThemeProvider.d.ts +49 -0
  106. package/dist/esm/components/RootThemeProvider/RootThemeProvider.js +27 -0
  107. package/dist/esm/components/RootThemeProvider/index.d.ts +1 -0
  108. package/dist/esm/components/RootThemeProvider/index.js +1 -0
  109. package/dist/esm/components/ThemeProvider/ThemeProvider.d.ts +28 -0
  110. package/dist/esm/components/ThemeProvider/ThemeProvider.js +13 -0
  111. package/dist/esm/components/ThemeProvider/index.d.ts +1 -0
  112. package/dist/esm/components/ThemeProvider/index.js +1 -0
  113. package/dist/esm/components/ThemeScope/ThemeScope.d.ts +32 -0
  114. package/dist/esm/components/ThemeScope/ThemeScope.js +59 -0
  115. package/dist/esm/components/ThemeScope/index.d.ts +1 -0
  116. package/dist/esm/components/ThemeScope/index.js +1 -0
  117. package/dist/esm/components/index.d.ts +3 -0
  118. package/dist/esm/components/index.js +3 -0
  119. package/dist/esm/constants/appearance.d.ts +22 -0
  120. package/dist/esm/constants/appearance.js +24 -0
  121. package/dist/esm/constants/colorScheme.d.ts +17 -0
  122. package/dist/esm/constants/colorScheme.js +20 -0
  123. package/dist/esm/constants/index.d.ts +2 -0
  124. package/dist/esm/constants/index.js +2 -0
  125. package/dist/esm/context/appearanceContext.d.ts +20 -0
  126. package/dist/esm/context/appearanceContext.js +18 -0
  127. package/dist/esm/context/index.d.ts +4 -0
  128. package/dist/esm/context/index.js +2 -0
  129. package/dist/esm/context/themeContext.d.ts +12 -0
  130. package/dist/esm/context/themeContext.js +11 -0
  131. package/dist/esm/hooks/index.d.ts +4 -0
  132. package/dist/esm/hooks/index.js +4 -0
  133. package/dist/esm/hooks/useApplyCustomTheme.d.ts +27 -0
  134. package/dist/esm/hooks/useApplyCustomTheme.js +36 -0
  135. package/dist/esm/hooks/useColorScheme.d.ts +37 -0
  136. package/dist/esm/hooks/useColorScheme.js +76 -0
  137. package/dist/esm/hooks/useIsomorphicLayoutEffect.d.ts +7 -0
  138. package/dist/esm/hooks/useIsomorphicLayoutEffect.js +13 -0
  139. package/dist/esm/hooks/useThemeClassnames.d.ts +11 -0
  140. package/dist/esm/hooks/useThemeClassnames.js +15 -0
  141. package/dist/esm/hooks/useThemeConfig.d.ts +24 -0
  142. package/dist/esm/hooks/useThemeConfig.js +12 -0
  143. package/dist/esm/index.d.ts +7 -0
  144. package/dist/esm/index.js +7 -0
  145. package/dist/esm/keys.d.ts +4 -0
  146. package/dist/esm/keys.js +16 -0
  147. package/dist/esm/ssr.d.ts +15 -0
  148. package/dist/esm/ssr.js +15 -0
  149. package/dist/esm/store/globalStore.d.ts +29 -0
  150. package/dist/esm/store/globalStore.js +69 -0
  151. package/dist/esm/store/index.d.ts +1 -0
  152. package/dist/esm/store/index.js +1 -0
  153. package/dist/esm/types/appearance.d.ts +23 -0
  154. package/dist/esm/types/appearance.js +2 -0
  155. package/dist/esm/types/colorScheme.d.ts +5 -0
  156. package/dist/esm/types/colorScheme.js +1 -0
  157. package/dist/esm/types/index.d.ts +2 -0
  158. package/dist/esm/types/index.js +2 -0
  159. package/dist/esm/utils/colorSchemeFromHeaders.d.ts +22 -0
  160. package/dist/esm/utils/colorSchemeFromHeaders.js +40 -0
  161. package/dist/esm/utils/colorSchemeStorage.d.ts +35 -0
  162. package/dist/esm/utils/colorSchemeStorage.js +81 -0
  163. package/dist/esm/utils/customTheme/buildBrandPaletteCss.d.ts +11 -0
  164. package/dist/esm/utils/customTheme/buildBrandPaletteCss.js +32 -0
  165. package/dist/esm/utils/customTheme/buildBrandPaletteVars.d.ts +11 -0
  166. package/dist/esm/utils/customTheme/buildBrandPaletteVars.js +39 -0
  167. package/dist/esm/utils/customTheme/color/apca.d.ts +4 -0
  168. package/dist/esm/utils/customTheme/color/apca.js +59 -0
  169. package/dist/esm/utils/customTheme/color/colorSpace.d.ts +5 -0
  170. package/dist/esm/utils/customTheme/color/colorSpace.js +68 -0
  171. package/dist/esm/utils/customTheme/color/conversions.d.ts +17 -0
  172. package/dist/esm/utils/customTheme/color/conversions.js +91 -0
  173. package/dist/esm/utils/customTheme/color/hex.d.ts +10 -0
  174. package/dist/esm/utils/customTheme/color/hex.js +29 -0
  175. package/dist/esm/utils/customTheme/color/index.d.ts +4 -0
  176. package/dist/esm/utils/customTheme/color/index.js +4 -0
  177. package/dist/esm/utils/customTheme/color/oklch.d.ts +3 -0
  178. package/dist/esm/utils/customTheme/color/oklch.js +18 -0
  179. package/dist/esm/utils/customTheme/color/types.d.ts +40 -0
  180. package/dist/esm/utils/customTheme/color/types.js +1 -0
  181. package/dist/esm/utils/customTheme/constants.d.ts +31 -0
  182. package/dist/esm/utils/customTheme/constants.js +46 -0
  183. package/dist/esm/utils/customTheme/generateBrandPalette.d.ts +10 -0
  184. package/dist/esm/utils/customTheme/generateBrandPalette.js +22 -0
  185. package/dist/esm/utils/customTheme/index.d.ts +4 -0
  186. package/dist/esm/utils/customTheme/index.js +4 -0
  187. package/dist/esm/utils/getThemeClassnames.d.ts +17 -0
  188. package/dist/esm/utils/getThemeClassnames.js +34 -0
  189. package/dist/esm/utils/index.d.ts +7 -0
  190. package/dist/esm/utils/index.js +7 -0
  191. package/dist/esm/utils/mergeAppearance.d.ts +7 -0
  192. package/dist/esm/utils/mergeAppearance.js +14 -0
  193. package/dist/esm/utils/resolveColorScheme.d.ts +7 -0
  194. package/dist/esm/utils/resolveColorScheme.js +12 -0
  195. package/dist/esm/utils/themeBootstrap.d.ts +12 -0
  196. package/dist/esm/utils/themeBootstrap.js +16 -0
  197. package/dist/tsconfig.cjs.tsbuildinfo +1 -0
  198. package/dist/tsconfig.esm.tsbuildinfo +1 -0
  199. package/package.json +45 -0
  200. package/src/components/ChildThemeProvider/ChildThemeProvider.tsx +54 -0
  201. package/src/components/ChildThemeProvider/index.ts +1 -0
  202. package/src/components/RootThemeProvider/RootThemeProvider.tsx +87 -0
  203. package/src/components/RootThemeProvider/index.ts +1 -0
  204. package/src/components/ThemeProvider/ThemeProvider.tsx +41 -0
  205. package/src/components/ThemeProvider/index.ts +1 -0
  206. package/src/components/ThemeScope/ThemeScope.tsx +106 -0
  207. package/src/components/ThemeScope/index.ts +1 -0
  208. package/src/components/index.ts +3 -0
  209. package/src/constants/appearance.ts +27 -0
  210. package/src/constants/colorScheme.ts +23 -0
  211. package/src/constants/index.ts +2 -0
  212. package/src/context/appearanceContext.tsx +39 -0
  213. package/src/context/index.ts +4 -0
  214. package/src/context/themeContext.ts +18 -0
  215. package/src/hooks/index.ts +4 -0
  216. package/src/hooks/useApplyCustomTheme.ts +56 -0
  217. package/src/hooks/useColorScheme.ts +131 -0
  218. package/src/hooks/useIsomorphicLayoutEffect.ts +17 -0
  219. package/src/hooks/useThemeClassnames.ts +18 -0
  220. package/src/hooks/useThemeConfig.tsx +44 -0
  221. package/src/index.ts +7 -0
  222. package/src/keys.ts +18 -0
  223. package/src/ssr.ts +25 -0
  224. package/src/store/globalStore.ts +93 -0
  225. package/src/store/index.ts +1 -0
  226. package/src/types/appearance.ts +29 -0
  227. package/src/types/colorScheme.ts +7 -0
  228. package/src/types/index.ts +2 -0
  229. package/src/utils/colorSchemeFromHeaders.ts +57 -0
  230. package/src/utils/colorSchemeStorage.ts +117 -0
  231. package/src/utils/customTheme/buildBrandPaletteCss.ts +37 -0
  232. package/src/utils/customTheme/buildBrandPaletteVars.ts +56 -0
  233. package/src/utils/customTheme/color/apca.ts +74 -0
  234. package/src/utils/customTheme/color/colorSpace.ts +82 -0
  235. package/src/utils/customTheme/color/conversions.ts +113 -0
  236. package/src/utils/customTheme/color/hex.ts +39 -0
  237. package/src/utils/customTheme/color/index.ts +4 -0
  238. package/src/utils/customTheme/color/oklch.ts +22 -0
  239. package/src/utils/customTheme/color/types.ts +32 -0
  240. package/src/utils/customTheme/constants.ts +57 -0
  241. package/src/utils/customTheme/generateBrandPalette.ts +30 -0
  242. package/src/utils/customTheme/index.ts +4 -0
  243. package/src/utils/getThemeClassnames.ts +40 -0
  244. package/src/utils/index.ts +7 -0
  245. package/src/utils/mergeAppearance.ts +16 -0
  246. package/src/utils/resolveColorScheme.ts +15 -0
  247. package/src/utils/themeBootstrap.ts +18 -0
@@ -0,0 +1,113 @@
1
+ // Матрицы и формулы преобразований цветовых пространств — из CSS Color 4
2
+ // (https://drafts.csswg.org/css-color-4/conversions.js) и работы Björn Ottosson по OKLab
3
+ // (https://bottosson.github.io/posts/oklab/). Константы менять нельзя; тип-контракт 3×3-матрицы
4
+ // фиксирован, чтобы обойтись без `any`/`@ts-ignore` в обобщённом умножении.
5
+
6
+ import { LAB, LCH, Mat3, RGB, Vec3, XYZ } from './types';
7
+
8
+ /** Умножение матрицы 3×3 на вектор из трёх компонентов. */
9
+ function mulV(m: Mat3, v: Vec3): Vec3 {
10
+ return [
11
+ m[0][0] * v[0] + m[0][1] * v[1] + m[0][2] * v[2],
12
+ m[1][0] * v[0] + m[1][1] * v[1] + m[1][2] * v[2],
13
+ m[2][0] * v[0] + m[2][1] * v[1] + m[2][2] * v[2],
14
+ ];
15
+ }
16
+
17
+ const XYZ_TO_LMS: Mat3 = [
18
+ [0.8190224432164319, 0.3619062562801221, -0.12887378261216414],
19
+ [0.0329836671980271, 0.9292868468965546, 0.03614466816999844],
20
+ [0.048177199566046255, 0.26423952494422764, 0.6335478258136937],
21
+ ];
22
+
23
+ const LMS_TO_OKLAB: Mat3 = [
24
+ [0.2104542553, 0.793617785, -0.0040720468],
25
+ [1.9779984951, -2.428592205, 0.4505937099],
26
+ [0.0259040371, 0.7827717662, -0.808675766],
27
+ ];
28
+
29
+ const LMS_TO_XYZ: Mat3 = [
30
+ [1.2268798733741557, -0.5578149965554813, 0.28139105017721583],
31
+ [-0.04057576262431372, 1.1122868293970594, -0.07171106666151701],
32
+ [-0.07637294974672142, -0.4214933239627914, 1.5869240244272418],
33
+ ];
34
+
35
+ // prettier-ignore
36
+ const OKLAB_TO_LMS: Mat3 = [
37
+ // eslint-disable-next-line no-loss-of-precision
38
+ [0.99999999845051981432, 0.39633779217376785678, 0.21580375806075880339],
39
+ // eslint-disable-next-line no-loss-of-precision
40
+ [1.0000000088817607767, -0.1055613423236563494, -0.063854174771705903402],
41
+ // eslint-disable-next-line no-loss-of-precision
42
+ [1.0000000546724109177, -0.089484182094965759684, -1.2914855378640917399],
43
+ ];
44
+
45
+ const LIN_SRGB_TO_XYZ: Mat3 = [
46
+ [0.41239079926595934, 0.357584339383878, 0.1804807884018343],
47
+ [0.21263900587151027, 0.715168678767756, 0.07219231536073371],
48
+ [0.01933081871559182, 0.11919477979462598, 0.9505321522496607],
49
+ ];
50
+
51
+ const XYZ_TO_LIN_SRGB: Mat3 = [
52
+ [3.2409699419045226, -1.537383177570094, -0.4986107602930034],
53
+ [-0.9692436362808796, 1.8759675015077202, 0.04155505740717559],
54
+ [0.05563007969699366, -0.20397695888897652, 1.0569715142428786],
55
+ ];
56
+
57
+ /** XYZ (относительно D65) → OKLab. `L` в диапазоне `[0,1]`. */
58
+ export function xyzToOklab(xyz: XYZ): LAB {
59
+ const lms = mulV(XYZ_TO_LMS, xyz);
60
+ const lmsCbrt: Vec3 = [Math.cbrt(lms[0]), Math.cbrt(lms[1]), Math.cbrt(lms[2])];
61
+
62
+ return mulV(LMS_TO_OKLAB, lmsCbrt);
63
+ }
64
+
65
+ /** OKLab → XYZ (относительно D65). */
66
+ export function oklabToXyz(lab: LAB): XYZ {
67
+ const lms = mulV(OKLAB_TO_LMS, lab);
68
+ const lmsCubed: Vec3 = [lms[0] ** 3, lms[1] ** 3, lms[2] ** 3];
69
+
70
+ return mulV(LMS_TO_XYZ, lmsCubed);
71
+ }
72
+
73
+ /** OKLab → OKLCH. Hue возвращается в градусах `[0,360)`. */
74
+ export function oklabToOklch(lab: LAB): LCH {
75
+ const hue = (Math.atan2(lab[2], lab[1]) * 180) / Math.PI;
76
+
77
+ return [lab[0], Math.sqrt(lab[1] ** 2 + lab[2] ** 2), hue >= 0 ? hue : hue + 360];
78
+ }
79
+
80
+ /** OKLCH → OKLab. */
81
+ export function oklchToOklab(lch: LCH): LAB {
82
+ return [lch[0], lch[1] * Math.cos((lch[2] * Math.PI) / 180), lch[1] * Math.sin((lch[2] * Math.PI) / 180)];
83
+ }
84
+
85
+ /** sRGB → linear-light sRGB (обратная gamma-коррекция), расширенная функция для отрицательных значений. */
86
+ export function linSRGB(rgb: RGB): RGB {
87
+ return rgb.map(val => {
88
+ const sign = val < 0 ? -1 : 1;
89
+ const abs = Math.abs(val);
90
+
91
+ return abs < 0.04045 ? val / 12.92 : sign * Math.pow((abs + 0.055) / 1.055, 2.4);
92
+ }) as RGB;
93
+ }
94
+
95
+ /** linear-light sRGB → sRGB (gamma-коррекция), расширенная функция для отрицательных значений. */
96
+ export function gamSRGB(rgb: RGB): RGB {
97
+ return rgb.map(val => {
98
+ const sign = val < 0 ? -1 : 1;
99
+ const abs = Math.abs(val);
100
+
101
+ return abs > 0.0031308 ? sign * (1.055 * Math.pow(abs, 1 / 2.4) - 0.055) : 12.92 * val;
102
+ }) as RGB;
103
+ }
104
+
105
+ /** linear-light sRGB → CIE XYZ (белая точка D65). */
106
+ export function linSRGBToXYZ(rgb: RGB): XYZ {
107
+ return mulV(LIN_SRGB_TO_XYZ, rgb);
108
+ }
109
+
110
+ /** CIE XYZ → linear-light sRGB. */
111
+ export function xyzToLinSRGB(xyz: XYZ): RGB {
112
+ return mulV(XYZ_TO_LIN_SRGB, xyz);
113
+ }
@@ -0,0 +1,39 @@
1
+ import { RGB } from './types';
2
+
3
+ const SHORT_HEX = /^#?([\da-f])([\da-f])([\da-f])([\da-f])?$/i;
4
+ const LONG_HEX = /^#?([\da-f]{2})([\da-f]{2})([\da-f]{2})([\da-f]{2})?$/i;
5
+
6
+ /**
7
+ * Разбирает hex-строку в sRGB-каналы `[r, g, b]` в диапазоне 0–255. Поддерживает `#rgb`, `#rgba`,
8
+ * `#rrggbb`, `#rrggbbaa` (с ведущим `#` и без). Alpha-канал игнорируется. Невалидная строка → `null`.
9
+ */
10
+ export function parseHex(hex: string): RGB | null {
11
+ const trimmed = hex.trim();
12
+
13
+ const short = SHORT_HEX.exec(trimmed);
14
+ if (short) {
15
+ return [parseInt(short[1] + short[1], 16), parseInt(short[2] + short[2], 16), parseInt(short[3] + short[3], 16)];
16
+ }
17
+
18
+ const long = LONG_HEX.exec(trimmed);
19
+ if (long) {
20
+ return [parseInt(long[1], 16), parseInt(long[2], 16), parseInt(long[3], 16)];
21
+ }
22
+
23
+ return null;
24
+ }
25
+
26
+ /** Проверяет, что строка — валидный hex-цвет (`#rgb` / `#rgba` / `#rrggbb` / `#rrggbbaa`). */
27
+ export function isValidHex(hex: string): boolean {
28
+ return parseHex(hex) !== null;
29
+ }
30
+
31
+ /** sRGB-каналы 0–1 → hex-строка `#rrggbb`. Каналы клампятся в диапазон. */
32
+ export function srgbToHex([r, g, b]: RGB): string {
33
+ const toHex = (channel: number): string =>
34
+ Math.round(Math.min(255, Math.max(0, channel * 255)))
35
+ .toString(16)
36
+ .padStart(2, '0');
37
+
38
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
39
+ }
@@ -0,0 +1,4 @@
1
+ export * from './types';
2
+ export * from './hex';
3
+ export * from './colorSpace';
4
+ export * from './apca';
@@ -0,0 +1,22 @@
1
+ import { oklabToOklch, oklabToXyz, oklchToOklab, xyzToOklab } from './conversions';
2
+ import { LCH, TLchModel, XYZ } from './types';
3
+
4
+ // L компонента OKLCH хранится наружу в процентах [0–100], внутри модели — в [0–1].
5
+ function toDisplayOKLCH([l, c, h]: LCH): LCH {
6
+ return [l * 100, c, h];
7
+ }
8
+
9
+ function fromDisplayOKLCH([l, c, h]: LCH): LCH {
10
+ return [l / 100, c, h];
11
+ }
12
+
13
+ /** Модель OKLCH: перевод LCH↔XYZ через OKLab. `L` наружу — в процентах. */
14
+ export const oklch: TLchModel = {
15
+ lch2xyz: (lch: LCH): XYZ => oklabToXyz(oklchToOklab(fromDisplayOKLCH(lch))),
16
+ xyz2lch: (xyz: XYZ): LCH => toDisplayOKLCH(oklabToOklch(xyzToOklab(xyz))),
17
+ ranges: {
18
+ l: { min: 0, max: 100, step: 0.5 },
19
+ c: { min: 0, max: 0.33, step: 0.005 },
20
+ h: { min: 0, max: 360, step: 0.5 },
21
+ },
22
+ };
@@ -0,0 +1,32 @@
1
+ /** Вектор из трёх чисел — канал цвета в любом из пространств (RGB/XYZ/LAB/LCH). */
2
+ export type Vec3 = [number, number, number];
3
+
4
+ /** Матрица 3×3 для линейных преобразований цветовых пространств. */
5
+ export type Mat3 = [Vec3, Vec3, Vec3];
6
+
7
+ export type XYZ = Vec3;
8
+ export type LAB = Vec3;
9
+ export type LCH = Vec3;
10
+ export type RGB = Vec3;
11
+
12
+ /** Разобранный цвет: OKLCH-компоненты (`l`,`c`,`h`), sRGB-каналы 0–255 (`r`,`g`,`b`) и hex. */
13
+ export type TColor = {
14
+ l: number;
15
+ c: number;
16
+ h: number;
17
+ r: number;
18
+ g: number;
19
+ b: number;
20
+ hex: string;
21
+ };
22
+
23
+ /** Модель цветового пространства LCH: конвертеры в/из XYZ и диапазоны компонентов. */
24
+ export type TLchModel = {
25
+ ranges: {
26
+ l: { min: number; max: number; step: number };
27
+ c: { min: number; max: number; step: number };
28
+ h: { min: number; max: number; step: number };
29
+ };
30
+ xyz2lch(xyz: XYZ): LCH;
31
+ lch2xyz(lch: LCH): XYZ;
32
+ };
@@ -0,0 +1,57 @@
1
+ // Опорная палитра и тон-константы для генерации кастомного бренд-цвета. Тоновый набор и дефолтные
2
+ // hex совпадают с `--sn-brand-color-primary-<tone>` бренда `brandA` из `@cloud-ru/figma-variables`:
3
+ // генератор держит `L`/`C` этих тонов и поворачивает hue к seed-цвету потребителя.
4
+
5
+ /** Тон акцента (`--sn-brand-color-primary-55`) — им становится сам seed-цвет. */
6
+ export const PRIMARY_ACCENT_TONE = '55';
7
+
8
+ /** Near-white тон для текста на акценте (совпадает с дефолтом `onAccentLight` бренда `brandA`). */
9
+ export const NEAR_WHITE_TONE = '99';
10
+
11
+ /** Near-dark тон для текста на акценте (совпадает с дефолтом `onAccentDark` бренда `brandA`). */
12
+ export const NEAR_DARK_TONE = '5';
13
+
14
+ /** Все числовые тоны палитры `--sn-brand-color-primary-<tone>`. */
15
+ export const BRAND_PRIMARY_TONES = ['5', '10', '15', '25', '45', '55', '60', '65', '80', '90', '95', '99'] as const;
16
+
17
+ export type BrandPrimaryTone = (typeof BRAND_PRIMARY_TONES)[number];
18
+
19
+ /** Опорные hex тонов бренда `brandA` — источник `L`/`C` для генерации по любому seed-цвету. */
20
+ export const BASE_BRAND_PALETTE: Record<BrandPrimaryTone, string> = {
21
+ '5': '#21372f',
22
+ '10': '#243e35',
23
+ '15': '#2b483c',
24
+ '25': '#2c5e49',
25
+ '45': '#22775b',
26
+ '55': '#389f74',
27
+ '60': '#5ebb91',
28
+ '65': '#85ceaa',
29
+ '80': '#caeadb',
30
+ '90': '#edf7f1',
31
+ '95': '#f5fdf8',
32
+ '99': '#fbfffc',
33
+ };
34
+
35
+ /** Префикс CSS-переменных бренд-палитры. */
36
+ export const BRAND_PRIMARY_VAR_PREFIX = '--sn-brand-color-primary-';
37
+
38
+ /** Alpha-суффикс для `--sn-brand-color-primary-transparent` (совпадает с дефолтом `#389f7424` бренда `brandA`). */
39
+ export const TRANSPARENT_ALPHA_SUFFIX = '24';
40
+
41
+ /**
42
+ * Тинты акцента для activated-состояний (`--sn-brand-color-state-activated-*-background`) — это
43
+ * акцентный тон с alpha. Суффиксы совпадают с дефолтами brandA (`#389f7426/59/73`). Без них
44
+ * activated-заливки (выбранная строка таблицы, active-состояния) не следуют за кастомным бренд-цветом.
45
+ */
46
+ export const ACTIVATED_ALPHA_SUFFIX = {
47
+ default: '26',
48
+ hovered: '59',
49
+ pressed: '73',
50
+ } as const;
51
+
52
+ /** Имена CSS-переменных activated-заливок. */
53
+ export const BRAND_ACTIVATED_VAR = {
54
+ default: '--sn-brand-color-state-activated-default-background',
55
+ hovered: '--sn-brand-color-state-activated-hovered-background',
56
+ pressed: '--sn-brand-color-state-activated-pressed-background',
57
+ } as const;
@@ -0,0 +1,30 @@
1
+ import { hex2color, lch2color } from './color';
2
+ import { BASE_BRAND_PALETTE, BRAND_PRIMARY_TONES, BrandPrimaryTone, PRIMARY_ACCENT_TONE } from './constants';
3
+
4
+ /** Сгенерированная бренд-палитра: тон → hex. */
5
+ export type BrandPalette = Record<BrandPrimaryTone, string>;
6
+
7
+ /**
8
+ * Строит бренд-палитру из одного seed-цвета: берёт `L`/`C` каждого опорного тона `BASE_BRAND_PALETTE`
9
+ * и поворачивает hue к hue seed-цвета (OKLCH). Тон акцента (`55`) становится самим seed-цветом.
10
+ * Так один цвет потребителя перекрашивает всю шкалу `--sn-brand-color-primary-<tone>`, сохраняя
11
+ * светлоту/насыщенность DS-палитры. Невалидный hex → `null`.
12
+ */
13
+ export function generateBrandPalette(seedColor: string): BrandPalette | null {
14
+ const seed = hex2color(seedColor);
15
+ if (!seed) {
16
+ return null;
17
+ }
18
+
19
+ const { h: hue } = seed;
20
+ const palette = {} as BrandPalette;
21
+
22
+ for (const tone of BRAND_PRIMARY_TONES) {
23
+ const baseHex = tone === PRIMARY_ACCENT_TONE ? seedColor : BASE_BRAND_PALETTE[tone];
24
+ const base = hex2color(baseHex);
25
+
26
+ palette[tone] = base ? lch2color([base.l, base.c, hue]).hex : baseHex;
27
+ }
28
+
29
+ return palette;
30
+ }
@@ -0,0 +1,4 @@
1
+ export * from './constants';
2
+ export * from './generateBrandPalette';
3
+ export * from './buildBrandPaletteVars';
4
+ export * from './buildBrandPaletteCss';
@@ -0,0 +1,40 @@
1
+ import { ThemeAppearance } from '../types/appearance';
2
+
3
+ /**
4
+ * Чистая функция: собирает **полный** набор `sn-*` классов из осей оформления списком токенов.
5
+ * Полный набор обязателен на каждой DOM-границе — токены `@cloud-ru/figma-variables` не
6
+ * переопределяются по одной оси через CSS-каскад (см. providers-standard.md). Базовые слои
7
+ * (`sn-base-styles`, `sn-figmaStyles`, `sn-components`) и материал (`sn-yes`/`sn-no`)
8
+ * — всегда; `density`/`colorScheme`/`brand`/`brandRole` — если заданы.
9
+ *
10
+ * Списочная форма — источник истины для `getThemeClassnames` (строка через `join`) и для
11
+ * `element.classList.add(...)`, которому нужны отдельные токены без промежуточного `split`.
12
+ */
13
+ export function getThemeClassnameList(appearance: ThemeAppearance = {}): string[] {
14
+ const { density, colorScheme, brand, brandRole, acrylic } = appearance;
15
+
16
+ const classes = ['sn-base-styles', 'sn-figmaStyles', 'sn-components', acrylic ? 'sn-yes' : 'sn-no'];
17
+
18
+ if (density) {
19
+ classes.push(`sn-${density}`);
20
+ }
21
+ if (colorScheme) {
22
+ classes.push(`sn-${colorScheme}`);
23
+ }
24
+ if (brand) {
25
+ classes.push(`sn-${brand}`);
26
+ }
27
+ if (brandRole) {
28
+ classes.push(`sn-${brandRole}`);
29
+ }
30
+
31
+ return classes;
32
+ }
33
+
34
+ /**
35
+ * Строковая форма набора `sn-*` (через `join`). Используется на SSR (строка класса на `<html>`) и
36
+ * как основа хука `useThemeClassnames`. Для `classList`-операций бери `getThemeClassnameList`.
37
+ */
38
+ export function getThemeClassnames(appearance: ThemeAppearance = {}): string {
39
+ return getThemeClassnameList(appearance).join(' ');
40
+ }
@@ -0,0 +1,7 @@
1
+ export * from './mergeAppearance';
2
+ export * from './getThemeClassnames';
3
+ export * from './resolveColorScheme';
4
+ export * from './colorSchemeStorage';
5
+ export * from './colorSchemeFromHeaders';
6
+ export * from './themeBootstrap';
7
+ export * from './customTheme';
@@ -0,0 +1,16 @@
1
+ import { ThemeAppearance } from '../types/appearance';
2
+
3
+ /**
4
+ * Сливает оформление родителя с переопределениями: незаданные (`undefined`) оси `overrides` НЕ
5
+ * затирают унаследованные (`??` сохраняет, в т.ч. `acrylic: false`). Используется `ChildThemeProvider`
6
+ * для Figma-цепочки модификаторов.
7
+ */
8
+ export function mergeAppearance(parent: ThemeAppearance, overrides: Partial<ThemeAppearance>): ThemeAppearance {
9
+ return {
10
+ colorScheme: overrides.colorScheme ?? parent.colorScheme,
11
+ brand: overrides.brand ?? parent.brand,
12
+ brandRole: overrides.brandRole ?? parent.brandRole,
13
+ density: overrides.density ?? parent.density,
14
+ acrylic: overrides.acrylic ?? parent.acrylic,
15
+ };
16
+ }
@@ -0,0 +1,15 @@
1
+ import { COLOR_SCHEME, THEME_OVERRIDE } from '../constants/colorScheme';
2
+ import { ColorScheme, ThemeOverride } from '../types/colorScheme';
3
+
4
+ /**
5
+ * Резолв итоговой схемы: явный override (`light`/`dark`) побеждает; `system`/отсутствие — по
6
+ * системному `prefers-color-scheme` (`systemPrefersDark`). Поэтому «всегда светлая» остаётся
7
+ * светлой даже на тёмном устройстве.
8
+ */
9
+ export function resolveColorScheme(override: ThemeOverride | undefined, systemPrefersDark: boolean): ColorScheme {
10
+ if (override === THEME_OVERRIDE.Light || override === THEME_OVERRIDE.Dark) {
11
+ return override;
12
+ }
13
+
14
+ return systemPrefersDark ? COLOR_SCHEME.Dark : COLOR_SCHEME.Light;
15
+ }
@@ -0,0 +1,18 @@
1
+ import { THEME_OVERRIDE_STORAGE_KEY } from '../constants/colorScheme';
2
+
3
+ /**
4
+ * Возвращает строку синхронного inline-скрипта для `<head>` (до `<body>`): читает override из
5
+ * cookie + `prefers-color-scheme` и ставит `sn-light`/`sn-dark` на корень ДО первой отрисовки —
6
+ * устраняет «моргание» темы при перезагрузке. Вставляется через `<script dangerouslySetInnerHTML>`
7
+ * (Next) или статическим `<script>` в `<head>` (single-spa). Читает ту же cookie, что и SSR.
8
+ *
9
+ * `target` — CSS-селектор корня; по умолчанию `<html>` (`document.documentElement`).
10
+ */
11
+ export function getThemeBootstrapScript(options?: { storageKey?: string; target?: string }): string {
12
+ const storageKey = JSON.stringify(options?.storageKey ?? THEME_OVERRIDE_STORAGE_KEY);
13
+ const target = options?.target
14
+ ? `document.querySelector(${JSON.stringify(options.target)})`
15
+ : 'document.documentElement';
16
+
17
+ return `(function(){try{var k=${storageKey},m=document.cookie.match(new RegExp('(?:^|; )'+k+'=([^;]*)')),o=m?decodeURIComponent(m[1]):null,d=(o==='dark')||((o==='system'||!o)&&matchMedia('(prefers-color-scheme: dark)').matches),el=${target};if(el){el.classList.toggle('sn-dark',d);el.classList.toggle('sn-light',!d);}}catch(e){}})();`;
18
+ }