Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d7e2dce
perf: ускорение eslint
ragozin-nikita Apr 13, 2026
5e2e726
perf: ускорение jest
ragozin-nikita Apr 13, 2026
53e7390
test: обновил снапшоты, добавил новые моки
ragozin-nikita Apr 13, 2026
55bca58
chore: обновлены eslint/prettier конфиги
ragozin-nikita Apr 13, 2026
7cd5b48
feat(unistyles): миграция стилей на unistyles
ragozin-nikita Apr 13, 2026
ce4408e
fix: eslint
ragozin-nikita Apr 13, 2026
2f51d50
feat(unistyles): перевод ui kit на unistyles
ragozin-nikita Apr 13, 2026
770526a
build: фиксация обновленных зависимостей
ragozin-nikita Apr 14, 2026
0e0c89b
build: обновление зависимостей
ragozin-nikita Apr 14, 2026
883dcf2
fix: исправлены проблемы после локального тестирования
ragozin-nikita Apr 15, 2026
2bb8102
fix: исправлено нереактивное поведение Svg ассетов
ragozin-nikita Apr 15, 2026
2c6757b
fix: eslint
ragozin-nikita Apr 15, 2026
ca89f23
fix: анимация skeleton
ragozin-nikita Apr 15, 2026
435a41a
fix: исправлены замечания по code review
ragozin-nikita Apr 16, 2026
1043202
fix: themecontext стал тупым в целях безопасности
ragozin-nikita Apr 16, 2026
c786d7b
fix: code review
ragozin-nikita Apr 21, 2026
17bed58
style: визуальное уменьшение diff в pr
ragozin-nikita Apr 21, 2026
c92f9d8
fix: локальный код ревью
ragozin-nikita Apr 21, 2026
8abe938
fix: небольшое изменение нейминга
ragozin-nikita Apr 22, 2026
da052c3
fix: code review
ragozin-nikita Apr 22, 2026
1a5fa35
fix: зачистка diff
ragozin-nikita Apr 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@
/.yarn/plugins/**/* binary
/.pnp.* binary linguist-generated
*.pbxproj -text

**/*.snap linguist-generated
yarn.lock linguist-generated
ios/Podfile.lock linguist-generated
6 changes: 5 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
ios/Pods
ios
.github/workflows
.yarn
android/app/.cxx
CHANGELOG.md
android/app/build
android/build
CHANGELOG.md
39 changes: 15 additions & 24 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
import type { Preview } from '@storybook/react'
import {
makeStyles,
ThemeContextProvider,
ThemeVariant,
useChangeTheme,
} from '../src'
import { StyleSheet } from 'react-native-unistyles'
import { ThemeContextProvider, ThemeVariant } from '../src'
import { View } from 'react-native'
import React, { type FunctionComponent, type ReactNode, useEffect } from 'react'
import React, { type FunctionComponent, type ReactNode } from 'react'

const preview: Preview = {
decorators: [
(Story, { args }) => {
return (
<ThemeContextProvider initialTheme={args.theme}>
<Container theme={args.theme}>
<View style={{ padding: 16, flex: 1 }}>
<Story />
</View>
</Container>
</ThemeContextProvider>
<Container theme={args.theme}>
<View style={{ padding: 16, flex: 1 }}>
<Story />
</View>
</Container>
)
},
],
Expand All @@ -41,20 +35,17 @@ const Container: FunctionComponent<{
children: ReactNode
theme: ThemeVariant
}> = ({ children, theme }) => {
const styles = useStyles()
const changeTheme = useChangeTheme()

useEffect(() => {
changeTheme(theme)
}, [theme, changeTheme])

return <View style={styles.container}>{children}</View>
return (
<ThemeContextProvider initialTheme={theme}>
<View style={styles.container}>{children}</View>
</ThemeContextProvider>
)
}

const useStyles = makeStyles((theme) => ({
const styles = StyleSheet.create(({ theme }) => ({
container: {
width: '100%',
height: '100%',
backgroundColor: theme.theme.Surface['surface-card'],
backgroundColor: theme.Surface['surface-card'],
},
}))
49 changes: 27 additions & 22 deletions .storybook/storybook.requires.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,52 @@
/* do not change this file, it is auto generated by storybook. */
import { start, updateView, View } from '@storybook/react-native';

import { start, updateView } from '@storybook/react-native'

import '@storybook/addon-ondevice-notes/register'
import '@storybook/addon-ondevice-controls/register'
import '@storybook/addon-ondevice-actions/register'
import "@storybook/addon-ondevice-notes/register";
import "@storybook/addon-ondevice-controls/register";
import "@storybook/addon-ondevice-actions/register";

const normalizedStories = [
{
titlePrefix: '',
directory: './src',
files: '**/*.stories.?(ts|tsx|js|jsx)',
importPathMatcher:
/^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.stories\.(?:ts|tsx|js|jsx)?)$/,
titlePrefix: "",
directory: "./src",
files: "**/*.stories.?(ts|tsx|js|jsx)",
importPathMatcher: /^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.stories\.(?:ts|tsx|js|jsx)?)$/,
// @ts-ignore
req: require.context(
'../src',
true,
/^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.stories\.(?:ts|tsx|js|jsx)?)$/
),
},
]
}
];


declare global {
var view: ReturnType<typeof start>
var STORIES: typeof normalizedStories
var view: View;
var STORIES: typeof normalizedStories;
}


const annotations = [
require('./preview'),
require('@storybook/react-native/dist/preview'),
require('@storybook/addon-actions/preview'),
]
require("@storybook/react-native/preview")
];

global.STORIES = normalizedStories
global.STORIES = normalizedStories;

// @ts-ignore
module?.hot?.accept?.()
module?.hot?.accept?.();



if (!global.view) {
global.view = start({ annotations, storyEntries: normalizedStories })
global.view = start({
annotations,
storyEntries: normalizedStories,

});
} else {
updateView(global.view, annotations, normalizedStories)
updateView(global.view, annotations, normalizedStories);
}

export const view: ReturnType<typeof start> = global.view
export const view: View = global.view;
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ yarn test -u

**Важные правила:**

- `StyleSheet`, `useUnistyles` и `UnistylesRuntime` **не реэкспортируются** из
кита — импортируй их напрямую из `react-native-unistyles`
- Все компоненты должны быть типизированы
- `testID` должен быть в формате `UpperPascalCase` (например: `ButtonPrimary`,
`InputText`)
Expand Down
216 changes: 216 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
# Миграция на Unistyles V3

Стили переведены на `react-native-unistyles`. `ThemeContextProvider` доступен
как внешний конфигуратор тем и шрифтов, но чтение темы и шрифтов теперь идет
через API `unistyles`.

## Изменения

### `ThemeContextProvider` — доступен

UI kit по-прежнему использует `react-native-unistyles` внутри, но для внешнего
потребителя снова доступен `ThemeContextProvider` как единая точка конфигурации
тем и шрифтов.

Если приложению нужны кастомные шрифты, настройте их через провайдер:

```tsx
import {
ThemeContextProvider,
ThemeVariant,
} from '@cdek-it/react-native-ui-kit'

export const Root = () => (
<ThemeContextProvider
fonts={{ primary: 'MyFont', secondary: 'MySecondaryFont' }}
initialTheme={ThemeVariant.Light}
>
<App />
</ThemeContextProvider>
)
```

Провайдер также принимает `lightTheme` и `darkTheme`, если нужно передать
полностью кастомные темы.

### `useFonts`

Используйте `useUnistyles`:

```tsx
import { useUnistyles } from '@cdek-it/react-native-ui-kit'

const { theme } = useUnistyles()
theme.fonts
```

Или прямо в стилях через `StyleSheet.create(...)`:

```tsx
import { StyleSheet } from 'react-native-unistyles'

const styles = StyleSheet.create(({ fonts }) => ({
Comment thread
grevtsovna marked this conversation as resolved.
title: { fontFamily: fonts.primary },
}))
```

### `useTheme()` / `useChangeTheme()`

`ThemeContextProvider` больше не является источником `theme/fonts` через React
context. Он только конфигурирует `react-native-unistyles`.

- `useTheme()` читает `UnistylesRuntime.themeName`
- `useFonts()` читает `useUnistyles().theme.fonts`
- `ThemeContext` остается пустым и имеет значение `null`
- `useChangeTheme()` всегда вызывает `UnistylesRuntime.setTheme(...)`

### `makeStyles` — removed

Используйте `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` и
Comment thread
isokolovskii marked this conversation as resolved.
`withUnistyles`, поэтому потребителям не нужно импортировать
`react-native-unistyles` напрямую.

### `useTheme()` — removed

```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()` — removed

```tsx
import { UnistylesRuntime } from '@cdek-it/react-native-ui-kit'

UnistylesRuntime.setTheme('dark')
```

## ESLint Правила для Unistyles

Три обязательных ESLint правила защищают от потери скрытого `unistyles` payload:

### ⛔ `unistyles/no-spread-unistyles` (error)

**Проблема**: Spread оператор теряет скрытый payload unistyles, что приводит к
потере темы и реактивности при её смене.

```typescript
// ❌ Неправильно — payload теряется
const myStyle = { ...styles.button }
const btn = { ...styles.button, marginTop: 10 }
Object.assign({}, styles.button)
const { button, text } = styles

// ✅ Правильно — payload сохранится
const myStyle = styles.button
style={[styles.button, { marginTop: 10 }]}
style={[styles.button, isActive && styles.buttonActive]}
```

### ⛔ `unistyles/no-unistyles-in-worklet` (error)

**Проблема**: Worklet функции (`useAnimatedStyle`, `runOnJS`, `withSpring`)
передаются в native код и не могут захватить весь unistyles объект. Нужно
вытащить примитивы.

```typescript
// ❌ Неправильно — styles целиком в worklet
const animStyle = useAnimatedStyle(() => ({ color: styles.text.color }))

// ✅ Правильно — примитив вытащен перед worklet
const color = styles.text.color
const animStyle = useAnimatedStyle(() => ({
color, // Теперь это просто строка
}))
```

### ⚠️ `unistyles/no-spread-icon-styles` (warn)

Рекомендуется передавать явные props для Icon компонентов вместо spread.

```typescript
// ❌ Не рекомендуется
<Icon {...styles.icon} />

// ✅ Рекомендуется
const color = styles.icon.color
const width = styles.icon.width
<Icon width={width} height={24} color={color} />
```

### Почему это важно

`react-native-unistyles` добавляет скрытый payload в каждый объект из
`StyleSheet.create()`. Этот payload содержит информацию о:

- **Активной теме** (light/dark)
- **Responsive breakpoint** (размер экрана)
- **Unistyles runtime configuration**

Если потерять payload, нативная часть больше не сможет:

- Применить правильную тему
- Обновить стиль при смене темы/breakpoint
- Корректно интерпретировать значения

Подробнее:
[ESLint Rules for Unistyles](./configs/eslint/rules/unistyles/README.md)

## Babel конфигурация

Для получения нативного обновления стилей без React-ререндеров:

1. Используйте `StyleSheet.create(...)`.
2. Добавьте `autoProcessPaths` в Babel-конфиг вашего приложения.

Это нужно потому, что UI kit подключается из `node_modules`, а `unistyles` по
умолчанию не обрабатывает такие файлы.

Пример для приложения-потребителя:

```js
module.exports = function (api) {
api.cache(true)

return {
presets: ['babel-preset-expo'],
plugins: [
[
'react-native-unistyles/plugin',
{ root: 'src', autoProcessPaths: ['@cdek-it/react-native-ui-kit'] },
],
],
}
}
```

Если Babel plugin у вас уже настроен, достаточно добавить путь
`@cdek-it/react-native-ui-kit` в существующий `autoProcessPaths`.

Документация:

- [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/)
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ UI kit использует следующие виды шрифтов.
/>
```

После этого шрифты доступны через `useUnistyles().theme.fonts` или прямо в
`StyleSheet.create(({ fonts }) => ...)`.

`ThemeContextProvider` только настраивает темы и шрифты для `unistyles`.
`ThemeContext` остается пустым и всегда имеет значение `null`.

Провайдер также принимает `lightTheme` и `darkTheme`, если нужно переопределить
темы целиком.

### Пример подключения шрифтов с помощью expo-fonts через плагин

```ts
Expand Down
Loading
Loading