From 761b58a308fce8932d070af57fd9166067f34a7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=B0=D0=B3=D0=BE=D0=B7=D0=B8=D0=BD=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=B8=D1=82=D0=B0=20=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0?= =?UTF-8?q?=D0=BD=D0=B4=D1=80=D0=BE=D0=B2=D0=B8=D1=87?= Date: Mon, 13 Apr 2026 09:29:49 +0500 Subject: [PATCH 1/6] =?UTF-8?q?perf:=20=D1=83=D1=81=D0=BA=D0=BE=D1=80?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20eslint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавил кеширование и игнорирование вспомогательных папок --- eslint.config.ts | 14 ++++++++++++++ package.json | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/eslint.config.ts b/eslint.config.ts index 509fd71..8b9966f 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -7,7 +7,21 @@ export default defineConfig([ { files: ['configs/eslint/**/*'], rules: { 'max-lines': 'off' } }, { ignores: [ + 'node_modules/**/*', + '.expo/**/*', + '.git/**/*', + '.idea/**/*', 'dist/**/*', + 'build/**/*', + 'coverage/**/*', + '**/*.min.js', + '.gradle/**/*', + 'android/**/*', + 'ios/**/*', + '.yarn/**/*', + '.vscode/**/*', + '.jest/**/*', + '.gemini/**/*', '.storybook/**/*', 'configs/cz-conventional-mobile/**/*', ], diff --git a/package.json b/package.json index 9f0494e..7227774 100644 --- a/package.json +++ b/package.json @@ -33,8 +33,8 @@ "storybook-generate": "sb-rn-get-stories --config-path .storybook && sed -i -e 's/export const view = global.view/export const view: ReturnType = global.view/' .storybook/storybook.requires.ts && prettier .storybook --write", "doctor": "expo-doctor", "check": "expo install --check", - "lint:check": "eslint .", - "lint:fix": "eslint --fix .", + "lint:check": "eslint --cache .", + "lint:fix": "eslint --fix --cache .", "prettier:check": "prettier . --check", "prettier:fix": "prettier . --write", "prettier:watch": "onchange . -- prettier --write --ignore-unknown \"{{changed}}\"", From 775f46a2220f6a3b1d7a4c7c329e471c650537ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=B0=D0=B3=D0=BE=D0=B7=D0=B8=D0=BD=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=B8=D1=82=D0=B0=20=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0?= =?UTF-8?q?=D0=BD=D0=B4=D1=80=D0=BE=D0=B2=D0=B8=D1=87?= Date: Mon, 13 Apr 2026 09:31:47 +0500 Subject: [PATCH 2/6] =?UTF-8?q?perf:=20=D1=83=D1=81=D0=BA=D0=BE=D1=80?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20jest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Поднял количество maxWorker до максимума --- jest.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jest.config.ts b/jest.config.ts index b5a706d..f070054 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -14,7 +14,7 @@ const config: Config.InitialOptions = { coverageReporters: ['text', 'text-summary'], moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], testRunner: 'jest-circus', - maxWorkers: 4, + maxWorkers: '100%', rootDir: '.', moduleNameMapper: { '\\.svg': '/__mocks__/svgMock.js' }, setupFiles: ['/jest.setup.ts'], From adf7e7a20f4dcd1a6daac19d2639a2d7d164538a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=B0=D0=B3=D0=BE=D0=B7=D0=B8=D0=BD=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=B8=D1=82=D0=B0=20=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0?= =?UTF-8?q?=D0=BD=D0=B4=D1=80=D0=BE=D0=B2=D0=B8=D1=87?= Date: Mon, 13 Apr 2026 14:46:18 +0500 Subject: [PATCH 3/6] =?UTF-8?q?test:=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D1=81=D0=BD=D0=B0=D0=BF=D1=88=D0=BE=D1=82=D1=8B?= =?UTF-8?q?,=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D1=8B=D0=B5=20=D0=BC=D0=BE=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jest.d.ts | 6 +++ jest.setup.ts | 47 +++++++++++++++++++ .../__snapshots__/Accordion.test.tsx.snap | 4 +- .../__snapshots__/DialogHeader.test.tsx.snap | 12 ++--- .../MenuItemTemplate.test.tsx.snap | 2 +- .../__snapshots__/Message.test.tsx.snap | 6 +-- .../__snapshots__/Rating.test.tsx.snap | 16 +++---- .../__snapshots__/RatingClear.test.tsx.snap | 16 +++---- .../__snapshots__/RatingItem.test.tsx.snap | 16 +++---- .../__snapshots__/Service.test.tsx.snap | 12 ++--- 10 files changed, 95 insertions(+), 42 deletions(-) diff --git a/jest.d.ts b/jest.d.ts index a86a404..d41f79d 100644 --- a/jest.d.ts +++ b/jest.d.ts @@ -3,3 +3,9 @@ type PropertyCombinations = { [K in keyof T]: Array } declare let generatePropsCombinations: ( properties: PropertyCombinations ) => T[] + +declare module '@react-native/normalize-colors' { + const normalizeColors: (color: string | number) => number | null + + export default normalizeColors +} diff --git a/jest.setup.ts b/jest.setup.ts index 5fbb78c..28a57c3 100644 --- a/jest.setup.ts +++ b/jest.setup.ts @@ -1,9 +1,56 @@ import 'jest-extended' import 'react-native-gesture-handler/jestSetup' import { setUpTests } from 'react-native-reanimated' +import 'react-native-unistyles/mocks' setUpTests() +type ThemeName = 'light' | 'dark' + +interface MockedUnistylesModule { + StyleSheet: { create: (...args: unknown[]) => unknown } + UnistylesRuntime: { + miniRuntime: unknown + setTheme: jest.Mock + themeName: ThemeName | undefined + } + useUnistyles: jest.Mock + withUnistyles: jest.Mock +} + +const { darkTheme, lightTheme } = require('./src/theme') + +const unistyles = jest.requireMock( + 'react-native-unistyles' +) as MockedUnistylesModule + +const getTheme = (themeName: ThemeName | undefined) => + themeName === 'dark' ? darkTheme : lightTheme + +const originalStyleSheetCreate = unistyles.StyleSheet.create +const runtime = unistyles.UnistylesRuntime + +runtime.themeName = 'light' +runtime.setTheme = jest.fn((themeName: ThemeName) => { + runtime.themeName = themeName +}) + +unistyles.useUnistyles = jest.fn(() => ({ + theme: getTheme(runtime.themeName), + rt: runtime, +})) + +unistyles.StyleSheet.create = jest.fn( + (stylesheet: Parameters[0]) => + typeof stylesheet === 'function' + ? originalStyleSheetCreate(() => + stylesheet(getTheme(runtime.themeName), runtime.miniRuntime) + ) + : originalStyleSheetCreate(stylesheet) +) + +unistyles.withUnistyles = jest.fn((Component: T) => Component) + generatePropsCombinations = (properties: PropertyCombinations): T[] => { const keys = Object.keys(properties) as Array diff --git a/src/components/Accordion/__tests__/__snapshots__/Accordion.test.tsx.snap b/src/components/Accordion/__tests__/__snapshots__/Accordion.test.tsx.snap index 163abe4..f17b7d8 100644 --- a/src/components/Accordion/__tests__/__snapshots__/Accordion.test.tsx.snap +++ b/src/components/Accordion/__tests__/__snapshots__/Accordion.test.tsx.snap @@ -344,7 +344,7 @@ exports[`Accordion Header elements maximal 1`] = ` strokeWidth={2} > Date: Mon, 13 Apr 2026 14:47:57 +0500 Subject: [PATCH 4/6] =?UTF-8?q?chore:=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D1=8B=20eslint/prettier=20=D0=BA=D0=BE=D0=BD?= =?UTF-8?q?=D1=84=D0=B8=D0=B3=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .prettierignore | 6 +++++- eslint.config.ts | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.prettierignore b/.prettierignore index 7207909..b109670 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,4 +1,8 @@ ios/Pods +ios +.github/workflows .yarn android/app/.cxx -CHANGELOG.md \ No newline at end of file +android/app/build +android/build +CHANGELOG.md diff --git a/eslint.config.ts b/eslint.config.ts index 8b9966f..063ecab 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -5,6 +5,11 @@ import { MobileConfig } from './configs/eslint' export default defineConfig([ ...MobileConfig, { files: ['configs/eslint/**/*'], rules: { 'max-lines': 'off' } }, + // Временное решение до миграции компонентов + { + files: ['src/components/**/*.{ts,tsx}'], + rules: { '@typescript-eslint/no-deprecated': 'off' }, + }, { ignores: [ 'node_modules/**/*', From ba3279ddd91abc09f303744521dee882cf174bd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=B0=D0=B3=D0=BE=D0=B7=D0=B8=D0=BD=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=B8=D1=82=D0=B0=20=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0?= =?UTF-8?q?=D0=BD=D0=B4=D1=80=D0=BE=D0=B2=D0=B8=D1=87?= Date: Mon, 13 Apr 2026 15:02:41 +0500 Subject: [PATCH 5/6] =?UTF-8?q?feat(unistyles):=20=D0=BC=D0=B8=D0=B3=D1=80?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F=20=D1=81=D1=82=D0=B8=D0=BB=D0=B5=D0=B9?= =?UTF-8?q?=20=D0=BD=D0=B0=20unistyles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MIGRATION.md | 79 +++++++++++++++++++++ package.json | 4 +- src/hooks/__tests__/useChangeTheme.test.tsx | 50 ++++++++----- src/hooks/__tests__/useTheme.test.tsx | 34 ++++++--- src/hooks/useChangeTheme.ts | 20 +++++- src/hooks/useFonts.ts | 17 +++-- src/hooks/useTheme.ts | 17 +++-- src/index.ts | 14 +++- src/theme/ThemeContext.tsx | 55 -------------- src/theme/darkTheme.ts | 1 + src/theme/index.ts | 21 +++++- src/theme/lightTheme.ts | 1 + src/theme/types.ts | 1 + src/utils/makeStyles.ts | 49 +++++-------- yarn.lock | 63 +++++++++++++--- 15 files changed, 283 insertions(+), 143 deletions(-) create mode 100644 MIGRATION.md delete mode 100644 src/theme/ThemeContext.tsx diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 0000000..bf31224 --- /dev/null +++ b/MIGRATION.md @@ -0,0 +1,79 @@ +# Миграция на Unistyles V3 + +Стили переведены на `react-native-unistyles`. Удален собственный `React Context` +и провайдер. + +## Изменения + +### `ThemeContextProvider` — удалён + +Темы регистрируются автоматически. Обёртка больше не нужна. + +### `useFonts` — deprecated + +Используйте `useUnistyles`: + +```tsx +import { useUnistyles } from '@cdek-it/react-native-ui-kit' + +const { theme } = useUnistyles() +theme.fonts +``` + +Или прямо в стилях через `StyleSheet.create(...)`. + +### `makeStyles` — deprecated + +Используйте `StyleSheet.create(...)`: + +```tsx +import { StyleSheet } from '@cdek-it/react-native-ui-kit' + +const styles = StyleSheet.create((theme) => ({ + container: { backgroundColor: theme.Button.Brand.buttonBg }, +})) +``` + +`makeStyles` использует `useUnistyles()`, что вызывает React-ререндеры при смене +темы. `StyleSheet.create(...)` — нативный путь, обновляет стили **без** +ререндеров. + +SDK реэкспортирует `StyleSheet`, `useUnistyles`, `UnistylesRuntime` и +`withUnistyles`, поэтому потребителям не нужно импортировать +`react-native-unistyles` напрямую. + +### `useTheme()` — deprecated + +```tsx +import { UnistylesRuntime, useUnistyles } from '@cdek-it/react-native-ui-kit' + +const themeName = UnistylesRuntime.themeName // 'light' | 'dark' +``` + +Для реактивного поведения используйте `useUnistyles()`: + +```tsx +const { rt } = useUnistyles() +rt.themeName +``` + +### `useChangeTheme()` — deprecated + +```tsx +import { UnistylesRuntime } from '@cdek-it/react-native-ui-kit' + +UnistylesRuntime.setTheme('dark') +``` + +## Babel конфигурация + +Для получения нативного обновления стилей без React-ререндеров: + +1. Используйте `StyleSheet.create(...)`. +2. Добавьте `autoProcessPaths` в Babel-конфиг вашего приложения. + +Документация: + +- [useUnistyles](https://www.unistyl.es/v3/references/use-unistyles/) +- [StyleSheet](https://www.unistyl.es/v3/references/stylesheet/) +- [Babel plugin](https://www.unistyl.es/v3/other/babel-plugin/) diff --git a/package.json b/package.json index 7227774..af6da0b 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,8 @@ "pod-install": "bundle exec pod install --project-directory=ios" }, "dependencies": { - "@tabler/icons-react-native": "^3.36.0" + "@tabler/icons-react-native": "3.36.1", + "react-native-unistyles": "3.2.3" }, "devDependencies": { "@babel/core": "7.28.5", @@ -106,6 +107,7 @@ "react-native": "0.81.5", "react-native-advanced-input-mask": "1.4.6", "react-native-gesture-handler": "2.29.1", + "react-native-nitro-modules": "0.35.2", "react-native-reanimated": "4.1.1", "react-native-safe-area-context": "5.6.2", "react-native-svg": "15.15.1", diff --git a/src/hooks/__tests__/useChangeTheme.test.tsx b/src/hooks/__tests__/useChangeTheme.test.tsx index f1e5c2b..8f24423 100644 --- a/src/hooks/__tests__/useChangeTheme.test.tsx +++ b/src/hooks/__tests__/useChangeTheme.test.tsx @@ -1,29 +1,41 @@ -import { renderHook } from '@testing-library/react-native' +/* eslint-disable @typescript-eslint/no-deprecated -- Проверяем deprecated API на обратную совместимость. */ +import { act, renderHook } from '@testing-library/react-native' +import { UnistylesRuntime } from 'react-native-unistyles' -import { ThemeContext, ThemeVariant } from '../../theme' +import { ThemeVariant } from '../../theme' import { useChangeTheme } from '../useChangeTheme' describe('useChangeTheme', () => { - test('returns correct function', () => { - const mockedChangeTheme = jest.fn() - const { result } = renderHook(() => useChangeTheme(), { - wrapper: ({ children }) => ( - - {children} - - ), + beforeEach(() => { + jest.clearAllMocks() + }) + + test('вызывает UnistylesRuntime.setTheme с "light" для ThemeVariant.Light', () => { + const { result } = renderHook(() => useChangeTheme()) + + act(() => { + result.current(ThemeVariant.Light) + }) + + expect(UnistylesRuntime.setTheme).toHaveBeenCalledWith('light') + }) + + test('вызывает UnistylesRuntime.setTheme с "dark" для ThemeVariant.Dark', () => { + const { result } = renderHook(() => useChangeTheme()) + + act(() => { + result.current(ThemeVariant.Dark) }) - expect(result.current).toStrictEqual(expect.any(Function)) + expect(UnistylesRuntime.setTheme).toHaveBeenCalledWith('dark') + }) + + test('возвращает стабильную ссылку на callback', () => { + const { result, rerender } = renderHook(() => useChangeTheme()) + const first = result.current - result.current?.(ThemeVariant.Dark) + rerender({}) - expect(mockedChangeTheme).toHaveBeenCalledWith(ThemeVariant.Dark) + expect(result.current).toBe(first) }) }) diff --git a/src/hooks/__tests__/useTheme.test.tsx b/src/hooks/__tests__/useTheme.test.tsx index bcb3fcf..c4cd598 100644 --- a/src/hooks/__tests__/useTheme.test.tsx +++ b/src/hooks/__tests__/useTheme.test.tsx @@ -1,16 +1,32 @@ +/* eslint-disable @typescript-eslint/no-deprecated -- Проверяем deprecated API на обратную совместимость. */ import { renderHook } from '@testing-library/react-native' +import { UnistylesRuntime } from 'react-native-unistyles' -import { ThemeContextProvider, ThemeVariant } from '../../theme' +import { ThemeVariant } from '../../theme' import { useTheme } from '../useTheme' +const setThemeName = (themeName: 'light' | 'dark') => { + Object.assign(UnistylesRuntime, { themeName }) +} + describe('useTheme', () => { - test('returns correct function', () => { - const { result } = renderHook(() => useTheme(), { - wrapper: ({ children }) => ( - {children} - ), - }) - - expect(Object.values(ThemeVariant)).toContain(result.current) + afterEach(() => { + setThemeName('light') + }) + + test('возвращает ThemeVariant.Light, когда активна светлая тема', () => { + setThemeName('light') + + const { result } = renderHook(() => useTheme()) + + expect(result.current).toBe(ThemeVariant.Light) + }) + + test('возвращает ThemeVariant.Dark, когда активна тёмная тема', () => { + setThemeName('dark') + + const { result } = renderHook(() => useTheme()) + + expect(result.current).toBe(ThemeVariant.Dark) }) }) diff --git a/src/hooks/useChangeTheme.ts b/src/hooks/useChangeTheme.ts index 896e21f..f0f889d 100644 --- a/src/hooks/useChangeTheme.ts +++ b/src/hooks/useChangeTheme.ts @@ -1,7 +1,21 @@ -import { useContext } from 'react' +import { useCallback } from 'react' +import { UnistylesRuntime } from 'react-native-unistyles' -import { ThemeContext } from '../theme' +import { ThemeVariant } from '../theme' +const THEME_MAP: Record = { + [ThemeVariant.Light]: 'light', + [ThemeVariant.Dark]: 'dark', +} + +/** + * Возвращает callback для переключения темы. + * + * @deprecated Используйте `UnistylesRuntime.setTheme` из SDK напрямую. + * Будет удалён в следующей minor версии. + */ export const useChangeTheme = () => { - return useContext(ThemeContext).changeTheme + return useCallback((theme: ThemeVariant) => { + UnistylesRuntime.setTheme(THEME_MAP[theme]) + }, []) } diff --git a/src/hooks/useFonts.ts b/src/hooks/useFonts.ts index 9bd519d..5c70ec8 100644 --- a/src/hooks/useFonts.ts +++ b/src/hooks/useFonts.ts @@ -1,7 +1,16 @@ -import { useContext } from 'react' +import { useUnistyles } from 'react-native-unistyles' -import { ThemeContext } from '../theme' +import type { FontsConfig } from '../theme' -export const useFonts = () => { - return useContext(ThemeContext).fonts +/** + * Возвращает конфигурацию шрифтов. + * + * @deprecated Используйте `useUnistyles().theme.fonts` или `StyleSheet.create(...)` + * из SDK. + * Будет удалён в следующей minor версии. + */ +export const useFonts = (): FontsConfig => { + const { theme } = useUnistyles() + + return theme.fonts } diff --git a/src/hooks/useTheme.ts b/src/hooks/useTheme.ts index 5f9057a..3e8b5ab 100644 --- a/src/hooks/useTheme.ts +++ b/src/hooks/useTheme.ts @@ -1,7 +1,14 @@ -import { useContext } from 'react' +import { UnistylesRuntime } from 'react-native-unistyles' -import { ThemeContext } from '../theme' +import { ThemeVariant } from '../theme' -export const useTheme = () => { - return useContext(ThemeContext).theme -} +/** + * Возвращает текущую тему. + * + * @deprecated Используйте `UnistylesRuntime.themeName` из SDK. + * + * Для реактивного поведения: `useUnistyles().rt.themeName`. + * Будет удалён в следующей minor версии. + */ +export const useTheme = (): ThemeVariant => + UnistylesRuntime.themeName === 'dark' ? ThemeVariant.Dark : ThemeVariant.Light diff --git a/src/index.ts b/src/index.ts index 4b9cc53..64e8feb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,22 @@ export * from './components' export { useChangeTheme } from './hooks/useChangeTheme' export { - ThemeContext, - ThemeContextProvider, ThemeVariant, darkTheme, lightTheme, + type ThemeType, + type FontsConfig, + type FontsConfigType, } from './theme' +export { + StyleSheet, + UnistylesRuntime, + useUnistyles, + withUnistyles, +} from 'react-native-unistyles' +/* eslint-disable @typescript-eslint/no-deprecated -- Deprecated API сохраняем в публичном интерфейсе для обратной совместимости. */ +export { useFonts } from './hooks/useFonts' export { makeStyles } from './utils/makeStyles' export { useTheme } from './hooks/useTheme' +/* eslint-enable @typescript-eslint/no-deprecated */ export { SvgUniversal, type SvgSource } from './utils/SvgUniversal' diff --git a/src/theme/ThemeContext.tsx b/src/theme/ThemeContext.tsx deleted file mode 100644 index e62e96c..0000000 --- a/src/theme/ThemeContext.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { - type ReactNode, - createContext, - useCallback, - useMemo, - useState, -} from 'react' - -import { SkeletonContextProvider } from '../utils/SkeletonContext' - -import { ThemeVariant, type FontsConfig } from './types' - -interface ThemeContextType { - theme: ThemeVariant - fonts: FontsConfig - changeTheme: (theme: ThemeVariant) => void -} - -export interface ThemeContextProviderProps { - readonly initialTheme?: ThemeVariant - readonly fonts?: FontsConfig - readonly children: ReactNode -} - -const defaultThemeContext: ThemeContextType = { - theme: ThemeVariant.Light, - fonts: { primary: 'TT Fellows', secondary: 'PT Sans' }, - changeTheme: () => { - /* do nothing */ - }, -} -export const ThemeContext = createContext(defaultThemeContext) - -export const ThemeContextProvider = ({ - children, - initialTheme = defaultThemeContext.theme, - fonts = defaultThemeContext.fonts, -}: ThemeContextProviderProps) => { - const [theme, setTheme] = useState(initialTheme) - - const changeTheme = useCallback((nextTheme: ThemeVariant) => { - setTheme(nextTheme) - }, []) - - const contextValue = useMemo( - () => ({ theme, fonts, changeTheme }), - [theme, fonts, changeTheme] - ) - - return ( - - {children} - - ) -} diff --git a/src/theme/darkTheme.ts b/src/theme/darkTheme.ts index bf0edf2..46a628d 100644 --- a/src/theme/darkTheme.ts +++ b/src/theme/darkTheme.ts @@ -8,4 +8,5 @@ import type { ThemeType } from './types' export const darkTheme: ThemeType = { theme: { ...darkThemeAssets, InputSize, ModalSize, custom: customDark }, ...commonTheme, + fonts: { primary: 'TT Fellows', secondary: 'PT Sans' }, } diff --git a/src/theme/index.ts b/src/theme/index.ts index b5184e0..2420961 100644 --- a/src/theme/index.ts +++ b/src/theme/index.ts @@ -1,9 +1,24 @@ -export { darkTheme } from './darkTheme' -export { lightTheme } from './lightTheme' -export { ThemeContextProvider, ThemeContext } from './ThemeContext' +import { StyleSheet } from 'react-native-unistyles' + +import { darkTheme } from './darkTheme' +import { lightTheme } from './lightTheme' + +StyleSheet.configure({ + settings: { initialTheme: 'light' }, + themes: { light: lightTheme, dark: darkTheme }, +}) + +export { darkTheme, lightTheme } export { type ThemeType, ThemeVariant, type FontsConfigType, type FontsConfig, } from './types' + +declare module 'react-native-unistyles' { + export interface UnistylesThemes { + light: typeof lightTheme + dark: typeof darkTheme + } +} diff --git a/src/theme/lightTheme.ts b/src/theme/lightTheme.ts index 68d86d2..eecf593 100644 --- a/src/theme/lightTheme.ts +++ b/src/theme/lightTheme.ts @@ -8,4 +8,5 @@ import type { ThemeType } from './types' export const lightTheme: ThemeType = { theme: { ...lightThemeAssets, InputSize, ModalSize, custom: customLight }, ...commonTheme, + fonts: { primary: 'TT Fellows', secondary: 'PT Sans' }, } diff --git a/src/theme/types.ts b/src/theme/types.ts index 1545a96..250836a 100644 --- a/src/theme/types.ts +++ b/src/theme/types.ts @@ -29,6 +29,7 @@ export interface ThemeType { typography: typeof typography custom: typeof customCommon shadow: typeof shadow + fonts: FontsConfig } export enum ThemeVariant { diff --git a/src/utils/makeStyles.ts b/src/utils/makeStyles.ts index 64fc814..cf9ac78 100644 --- a/src/utils/makeStyles.ts +++ b/src/utils/makeStyles.ts @@ -1,38 +1,23 @@ import { useMemo } from 'react' import type { ImageStyle, TextStyle, ViewStyle } from 'react-native' - -import { useFonts } from '../hooks/useFonts' -import { useTheme } from '../hooks/useTheme' -import { - type FontsConfigType, - type ThemeType, - darkTheme, - lightTheme, - ThemeVariant, -} from '../theme' - +import { useUnistyles } from 'react-native-unistyles' + +import type { ThemeType } from '../theme' + +/** + * @deprecated Используйте `StyleSheet.create(...)` из SDK. + * + * `makeStyles` использует `useUnistyles()`, что вызывает React-ререндеры при смене + * темы. `StyleSheet.create(...)` — нативный путь, обновляет стили **без** ререндеров. + * + * Будет удалён в следующей minor версии. + */ export const makeStyles = (createStyles: CreateStyles): (() => T) => - () => { - const fonts = useFonts() - const theme = useTheme() - const themeValues = useMemo(() => { - switch (theme) { - case ThemeVariant.Light: - return lightTheme - - case ThemeVariant.Dark: - return darkTheme - - default: - return lightTheme - } - }, [theme]) - - return useMemo( - () => createStyles({ ...themeValues, fonts }) as T, - [fonts, themeValues] - ) + (): T => { + const { theme } = useUnistyles() + + return useMemo(() => createStyles(theme as ThemeType) as T, [theme]) } type StylesItem = ViewStyle | ImageStyle | TextStyle @@ -40,7 +25,7 @@ type StylesItem = ViewStyle | ImageStyle | TextStyle type StylesObject = Record type CreateStyles = ( - theme: ThemeType & FontsConfigType + theme: ThemeType ) => CheckInvalidProps extends never ? T : 'TypeError. Invalid key of style property was used.' diff --git a/yarn.lock b/yarn.lock index 5b49e04..4bb2e48 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2682,6 +2682,16 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:7.29.0": + version: 7.29.0 + resolution: "@babel/types@npm:7.29.0" + dependencies: + "@babel/helper-string-parser": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.28.5" + checksum: 10/bfc2b211210f3894dcd7e6a33b2d1c32c93495dc1e36b547376aa33441abe551ab4bc1640d4154ee2acd8e46d3bbc925c7224caae02fcaf0e6a771e97fccc661 + languageName: node + linkType: hard + "@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.25.7, @babel/types@npm:^7.25.8, @babel/types@npm:^7.3.3": version: 7.25.8 resolution: "@babel/types@npm:7.25.8" @@ -2778,7 +2788,7 @@ __metadata: "@storybook/preview-api": "npm:8.6.14" "@storybook/react-native": "npm:10.1.1" "@stylistic/eslint-plugin": "npm:5.6.1" - "@tabler/icons-react-native": "npm:^3.36.0" + "@tabler/icons-react-native": "npm:3.36.1" "@testing-library/react-native": "npm:13.3.3" "@types/jest": "npm:29.5.14" "@types/lodash.map": "npm:4.6.13" @@ -2820,9 +2830,11 @@ __metadata: react-native: "npm:0.81.5" react-native-advanced-input-mask: "npm:1.4.6" react-native-gesture-handler: "npm:2.29.1" + react-native-nitro-modules: "npm:0.35.2" react-native-reanimated: "npm:4.1.1" react-native-safe-area-context: "npm:5.6.2" react-native-svg: "npm:15.15.1" + react-native-unistyles: "npm:3.2.3" react-native-worklets: "npm:0.5.1" release-it: "npm:19.1.0" standard-version: "npm:9.5.0" @@ -5578,21 +5590,21 @@ __metadata: languageName: node linkType: hard -"@tabler/icons-react-native@npm:^3.36.0": - version: 3.36.0 - resolution: "@tabler/icons-react-native@npm:3.36.0" +"@tabler/icons-react-native@npm:3.36.1": + version: 3.36.1 + resolution: "@tabler/icons-react-native@npm:3.36.1" dependencies: - "@tabler/icons": "npm:3.36.0" + "@tabler/icons": "npm:" peerDependencies: react: ">= 16.5.1" - checksum: 10/aa3f9075074b6eedadfa5ebb97f2cfb96fd4eaacab79e8f0615f3247b34537a31c2df7ed53f461af8f69c35b478748f82ffdb60e7afbc79fec4f17a4616f0635 + checksum: 10/34e944ebd0376bdbe5c7e1a845ec19e34423ecbf8390eea1c1ef6b47ace09bbd9831c51b7bbec00e0e96c1468be302f4fe67a001137ad31ee151d3939f2e1dcd languageName: node linkType: hard -"@tabler/icons@npm:3.36.0": - version: 3.36.0 - resolution: "@tabler/icons@npm:3.36.0" - checksum: 10/007ec7ddad617f784a81f792d2bddfc11e76541c011bc50e9ebed9ce5210278076c8688cd73d081ac1c54e42a8bc7437bbcda41638204d43edd069a0c8387516 +"@tabler/icons@npm:": + version: 3.41.1 + resolution: "@tabler/icons@npm:3.41.1" + checksum: 10/9a1af1a659c9e997a37e83acfd408fc32486687107b1a71e8fe03472274490c92c4512a1e62fd68d23e8d2d5f2c57feea2d22245605a07c8eeed67fcc683c544 languageName: node linkType: hard @@ -16113,6 +16125,16 @@ __metadata: languageName: node linkType: hard +"react-native-nitro-modules@npm:0.35.2": + version: 0.35.2 + resolution: "react-native-nitro-modules@npm:0.35.2" + peerDependencies: + react: "*" + react-native: "*" + checksum: 10/22977e5e22c63fb91526c90fa59fcfe5c7f97978c583400f433a1d3ef8ee32fbceb0ad1b009b5bf0d1794e94a11716ad752fd7702c2d67fc808a0256c6e35488 + languageName: node + linkType: hard + "react-native-reanimated@npm:4.1.1": version: 4.1.1 resolution: "react-native-reanimated@npm:4.1.1" @@ -16152,6 +16174,27 @@ __metadata: languageName: node linkType: hard +"react-native-unistyles@npm:3.2.3": + version: 3.2.3 + resolution: "react-native-unistyles@npm:3.2.3" + dependencies: + "@babel/types": "npm:7.29.0" + peerDependencies: + "@react-native/normalize-colors": "*" + react: "*" + react-native: ">=0.76.0" + react-native-edge-to-edge: "*" + react-native-nitro-modules: "*" + react-native-reanimated: "*" + peerDependenciesMeta: + react-native-edge-to-edge: + optional: true + react-native-reanimated: + optional: true + checksum: 10/217875c9993f88bd5b183e2341feff115777872c3ea6dc0aac493f01bcb7cb2f34a3f4dabd1f0ba613553cad8a42395eda62f448f71e7f8b64fcd9fb262cb99b + languageName: node + linkType: hard + "react-native-url-polyfill@npm:^3.0.0": version: 3.0.0 resolution: "react-native-url-polyfill@npm:3.0.0" From 5ae413bc1e0b1aa5b30c4824567f13210cf9019e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D0=B0=D0=B3=D0=BE=D0=B7=D0=B8=D0=BD=20=D0=9D=D0=B8?= =?UTF-8?q?=D0=BA=D0=B8=D1=82=D0=B0=20=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0?= =?UTF-8?q?=D0=BD=D0=B4=D1=80=D0=BE=D0=B2=D0=B8=D1=87?= Date: Mon, 13 Apr 2026 15:16:35 +0500 Subject: [PATCH 6/6] fix: eslint --- .../{useChangeTheme.test.tsx => useChangeTheme.test.ts} | 0 src/hooks/__tests__/{useTheme.test.tsx => useTheme.test.ts} | 0 src/index.ts | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) rename src/hooks/__tests__/{useChangeTheme.test.tsx => useChangeTheme.test.ts} (100%) rename src/hooks/__tests__/{useTheme.test.tsx => useTheme.test.ts} (100%) diff --git a/src/hooks/__tests__/useChangeTheme.test.tsx b/src/hooks/__tests__/useChangeTheme.test.ts similarity index 100% rename from src/hooks/__tests__/useChangeTheme.test.tsx rename to src/hooks/__tests__/useChangeTheme.test.ts diff --git a/src/hooks/__tests__/useTheme.test.tsx b/src/hooks/__tests__/useTheme.test.ts similarity index 100% rename from src/hooks/__tests__/useTheme.test.tsx rename to src/hooks/__tests__/useTheme.test.ts diff --git a/src/index.ts b/src/index.ts index 64e8feb..0f3f9a6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ export * from './components' +/* eslint-disable @typescript-eslint/no-deprecated -- Deprecated API сохраняем в публичном интерфейсе для обратной совместимости. */ export { useChangeTheme } from './hooks/useChangeTheme' export { ThemeVariant, @@ -14,7 +15,6 @@ export { useUnistyles, withUnistyles, } from 'react-native-unistyles' -/* eslint-disable @typescript-eslint/no-deprecated -- Deprecated API сохраняем в публичном интерфейсе для обратной совместимости. */ export { useFonts } from './hooks/useFonts' export { makeStyles } from './utils/makeStyles' export { useTheme } from './hooks/useTheme'