Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
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
19 changes: 6 additions & 13 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
import type { Preview } from '@storybook/react'
import {
makeStyles,
ThemeContextProvider,
ThemeVariant,
useChangeTheme,
} from '../src'
import { makeStyles, ThemeVariant, useChangeTheme } from '../src'
import { View } from 'react-native'
import React, { type FunctionComponent, type ReactNode, useEffect } 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 Down
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;
150 changes: 150 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Миграция на 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')
```

## 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-конфиг вашего приложения.

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

- [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/)
16 changes: 15 additions & 1 deletion babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@ module.exports = (api) => {

return {
presets: ['babel-preset-expo'],
plugins: ['@babel/plugin-transform-class-static-block'],
plugins: [
[
'react-native-unistyles/plugin',
{
root: 'src',
autoProcessImports: [
'../utils',
'../../utils',
'../../../utils',
'../../../../utils',
],
},
],
'@babel/plugin-transform-class-static-block',
],
}
}
2 changes: 2 additions & 0 deletions configs/eslint/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
reactConfig,
reactNativeConfig,
prettierConfig,
unistylesConfig,
} from './rules'

export const MobileConfig = defineConfig([
Expand All @@ -20,6 +21,7 @@ export const MobileConfig = defineConfig([
...importConfig,
...reactConfig,
...reactNativeConfig,
...unistylesConfig,
globalIgnores([
'dist/',
'.yarn/',
Expand Down
1 change: 1 addition & 0 deletions configs/eslint/rules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export { reactConfig } from './react'
export { reactNativeConfig } from './reactNative'
export { stylisticConfig } from './stylistic'
export { prettierConfig } from './prettier'
export { unistylesConfig } from './unistyles'
8 changes: 8 additions & 0 deletions configs/eslint/rules/unistyles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* ESLint правила для unistyles
*
* Этот файл переиспортирует конфиг из папки unistyles/.
* Полная документация и реализация в configs/eslint/rules/unistyles/
*/

export { unistylesConfig } from './unistyles/index'
Loading
Loading