diff --git a/.prettierignore b/.prettierignore index 72079094..b109670a 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/.storybook/preview.tsx b/.storybook/preview.tsx index d4317acb..146026b8 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -1,10 +1,5 @@ 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' @@ -12,13 +7,11 @@ const preview: Preview = { decorators: [ (Story, { args }) => { return ( - - - - - - - + + + + + ) }, ], diff --git a/.storybook/storybook.requires.ts b/.storybook/storybook.requires.ts index 21bd048f..00985b4f 100644 --- a/.storybook/storybook.requires.ts +++ b/.storybook/storybook.requires.ts @@ -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 - 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 = global.view +export const view: View = global.view; diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 00000000..be96cdbb --- /dev/null +++ b/MIGRATION.md @@ -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 +// ❌ Не рекомендуется + + +// ✅ Рекомендуется +const color = styles.icon.color +const width = styles.icon.width + +``` + +### Почему это важно + +`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/) diff --git a/babel.config.js b/babel.config.js index 46fba684..ae8e3b2f 100644 --- a/babel.config.js +++ b/babel.config.js @@ -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', + ], } } diff --git a/configs/eslint/index.ts b/configs/eslint/index.ts index 52274ad4..b909dcf8 100644 --- a/configs/eslint/index.ts +++ b/configs/eslint/index.ts @@ -9,6 +9,7 @@ import { reactConfig, reactNativeConfig, prettierConfig, + unistylesConfig, } from './rules' export const MobileConfig = defineConfig([ @@ -20,6 +21,7 @@ export const MobileConfig = defineConfig([ ...importConfig, ...reactConfig, ...reactNativeConfig, + ...unistylesConfig, globalIgnores([ 'dist/', '.yarn/', diff --git a/configs/eslint/rules/index.ts b/configs/eslint/rules/index.ts index ab304c6b..75a60b25 100644 --- a/configs/eslint/rules/index.ts +++ b/configs/eslint/rules/index.ts @@ -6,3 +6,4 @@ export { reactConfig } from './react' export { reactNativeConfig } from './reactNative' export { stylisticConfig } from './stylistic' export { prettierConfig } from './prettier' +export { unistylesConfig } from './unistyles' diff --git a/configs/eslint/rules/unistyles.ts b/configs/eslint/rules/unistyles.ts new file mode 100644 index 00000000..93af5e9e --- /dev/null +++ b/configs/eslint/rules/unistyles.ts @@ -0,0 +1,8 @@ +/** + * ESLint правила для unistyles + * + * Этот файл переиспортирует конфиг из папки unistyles/. + * Полная документация и реализация в configs/eslint/rules/unistyles/ + */ + +export { unistylesConfig } from './unistyles/index' diff --git a/configs/eslint/rules/unistyles/README.md b/configs/eslint/rules/unistyles/README.md new file mode 100644 index 00000000..42474787 --- /dev/null +++ b/configs/eslint/rules/unistyles/README.md @@ -0,0 +1,262 @@ +# ESLint Rules for Unistyles + +Custom ESLint rules для защиты от потери скрытого `unistyles_*` payload при +работе со стилями из `StyleSheet.create()`. + +## Проблема + +`react-native-unistyles` добавляет скрытый payload в каждый объект из +`StyleSheet.create()`. Этот payload содержит информацию о: + +- **Теме** (light/dark и т.д.) +- **Responsive breakpoint** (текущий размер экрана) +- **Unistyles runtime configuration** + +Когда ты распаковываешь объект через spread или деструктуризируешь его, этот +payload **теряется**. Нативная часть больше не может: + +- Применить правильную тему +- Обновить стиль при смене темы/breakpoint +- Корректно интерпретировать значения + +## Правила + +### 1. `unistyles/no-spread-unistyles` (error) + +**Запрещает** распаковывать объекты из `StyleSheet.create()` через spread +оператор. + +#### ❌ Неправильно + +```typescript +const styles = StyleSheet.create({ button: { padding: 12 } }) + +// Spread теряет скрытый payload +const myStyle = { ...styles.button } +const btn = { ...styles.button, marginTop: 10 } + +// Object.assign тоже теряет payload +Object.assign({}, styles.button) +``` + +#### ✅ Правильно + +```typescript +const styles = StyleSheet.create({ button: { padding: 12 } }) + +// Передавай массив стилей (самый безопасный способ) +style={[styles.button, extraStyle]} + +// Или используй напрямую +style={styles.button} + +// Для динамических стилей — массив +style={[ + styles.button, + isActive && styles.buttonActive, +]} +``` + +--- + +### 2. `unistyles/no-unistyles-in-worklet` (error) + +**Запрещает** захватывать переменную `styles` в worklet closures +(`useAnimatedStyle`, `runOnJS` и т.д.). + +Причина: worklet функции передаются в native код, и весь unistyles объект +потеряет скрытый payload при этой передаче. + +#### ❌ Неправильно + +```typescript +const styles = useStyles() + +// ❌ styles захвачена целиком в worklet +const animStyle = useAnimatedStyle(() => ({ color: styles.text.color })) + +// ❌ styles передана в worklet +withSpring(styles.animConfig) + +// ❌ styles в runOnJS +runOnJS(() => console.log(styles.debug)) +``` + +#### ✅ Правильно + +```typescript +const styles = useStyles() + +// ✅ Вытащи примитив ДО worklet +const color = styles.text.color as string +const animConfig = styles.animConfig + +const animStyle = useAnimatedStyle(() => ({ + color, // Теперь это просто строка +})) + +withSpring(animConfig) // Примитив передан + +// Если нужны разные типы — распакуй явно +const { width, height } = styles.icon +runOnJS(() => { + console.log(width, height) // Примитивы +}) +``` + +--- + +### 3. `unistyles/no-spread-icon-styles` (warn) + +**Предупреждает** о spread unistyles объектов при передаче в Icon компоненты. + +Лучше передавать явные props для понимаемости и безопасности. + +#### ❌ Не рекомендуется + +```typescript +const styles = StyleSheet.create({ + icon: { width: 24, height: 24, color: 'red' } +}) + +// Spread скрывает, какие props передаются + + +``` + +#### ✅ Рекомендуется + +```typescript +const styles = StyleSheet.create({ + icon: { width: 24, height: 24, color: 'red' } +}) + +// Явные props — лучше видна структура + + +// Или если нужны переменные +const color = styles.icon.color +const width = styles.icon.width + +``` + +--- + +## Пример потери payload + +```typescript +const styles = StyleSheet.create({ + text: useColorScheme() === 'dark' + ? { color: '#fff' } + : { color: '#000' } +}) + +// ❌ Потеря payload при spread +const myStyle = { ...styles.text } // payload потерян, цвет не обновится при смене темы + +// ✅ Payload сохранён +const myStyle = styles.text // нет payload потерь +style={styles.text} // payload сохранён +``` + +--- + +## Как исправить существующий код + +### 1. Spread в объектах → используй массив + +```typescript +// ❌ Было +{ ...styles.button, marginTop: 10 } + +// ✅ Стало +[styles.button, { marginTop: 10 }] +``` + +### 2. Worklets → вытащи примитив перед worklet + +```typescript +// ❌ Было +useAnimatedStyle(() => ({ color: styles.text.color })) + +// ✅ Стало +const color = styles.text.color +useAnimatedStyle(() => ({ color })) +``` + +### 3. Icon spreads → явные props + +```typescript +// ❌ Было + + +// ✅ Стало + +``` + +--- + +## Использование + +Правила автоматически включены в конфиг ESLint для всех файлов в +`src/**/*.{ts,tsx}`. + +### Конфигурация + +```typescript +rules: { + 'unistyles/no-spread-unistyles': 'error', // ⛔ Критичная ошибка + 'unistyles/no-unistyles-in-worklet': 'error', // ⛔ Критичная ошибка + 'unistyles/no-spread-icon-styles': 'warn', // ⚠️ Рекомендация +} +``` + +### Проверить нарушения + +```bash +npm run lint:check +``` + +### Автоматическое исправление + +```bash +npm run lint:fix +``` + +--- + +## Как это работает + +Правила используют **ESLint AST (Abstract Syntax Tree)** для отслеживания: + +1. **SpreadElement** — ловит `{ ...styles.foo }` +2. **CallExpression** — ловит `Object.assign({}, styles.foo)` +3. **Identifier / MemberExpression** — проверяет захват `styles` в worklet + closures +4. **JSXSpreadAttribute** — ловит `{...styles}` в JSX + +Это гарантирует, что скрытый `unistyles_*` payload не будет случайно потерян при +refactoring или во время разработки. + +--- + +## Структура + +``` +configs/eslint/rules/unistyles/ +├── index.ts - Все правила и конфиг +├── types.ts - Типы для AST узлов +└── README.md - Эта документация +``` + +--- + +## Ссылки + +- [Unistyles Documentation](https://www.unistyl.es) +- [ESLint Custom Rules Guide](https://eslint.org/docs/developer-guide/working-with-rules) +- [Unistyles with Reanimated](https://www.unistyl.es/v3/guides/reanimated/) diff --git a/configs/eslint/rules/unistyles/index.ts b/configs/eslint/rules/unistyles/index.ts new file mode 100644 index 00000000..a13523f7 --- /dev/null +++ b/configs/eslint/rules/unistyles/index.ts @@ -0,0 +1,218 @@ +import { defineConfig } from 'eslint/config' + +import type { ASTNode, RuleContext } from './types' + +/** + * Правило: не распаковывать стили из unistyles + * ❌ { ...styles.button } + * ❌ const btn = { ...styles.button, marginTop: 10 } + * ✅ [styles.button, customStyle] + */ +const noSpreadUnistyles = { + meta: { + type: 'problem' as const, + docs: { + description: + 'Запретить spread операции на стилях из StyleSheet.create() — теряется unistyles metadata', + category: 'Best Practices', + recommended: 'error' as const, + }, + messages: { + noSpread: + 'Не распаковывай стили через spread ({...styles}). Это теряет unistyles metadata. Используй массив: [styles.button, customStyle]', + }, + }, + create(context: RuleContext) { + return { + SpreadElement(node: ASTNode) { + // Проверяем: { ...styles.foo } + const arg = node.argument + + if ( + arg?.type === 'MemberExpression' && + arg.object?.type === 'Identifier' && + arg.object.name?.includes('styles') + ) { + context.report({ node, messageId: 'noSpread' }) + } + }, + + CallExpression(node: ASTNode) { + // Проверяем Object.assign({}, styles.foo) + if ( + node.callee?.object?.name === 'Object' && + node.callee?.property?.name === 'assign' + ) { + for (const arg of node.arguments || []) { + if ( + arg?.type === 'MemberExpression' && + arg.object?.name?.includes('styles') + ) { + context.report({ node, messageId: 'noSpread' }) + + return + } + } + } + }, + } + }, +} + +/** + * Правило: не захватывать styles в worklet closures + * ❌ useAnimatedStyle(() => ({ color: styles.text.color })) + */ +const noUnistylesInWorklet = { + meta: { + type: 'problem' as const, + docs: { + description: 'Запретить захват styles переменных в worklet closures', + category: 'Best Practices', + recommended: 'error' as const, + }, + messages: { + noCapture: + 'Не захватывай styles переменную в worklet. Вытащи примитив перед: const color = styles.text.color', + }, + }, + create(context: RuleContext) { + const workletNames = new Set([ + 'useAnimatedStyle', + 'useAnimatedReaction', + 'runOnJS', + 'runOnUIThread', + 'withTiming', + 'withSpring', + 'withDecay', + 'withDelay', + 'withSequence', + 'withRepeat', + ]) + + const skipKeys = new Set(['parent', 'loc', 'range', 'start', 'end']) + + const hasStylesReference = (node: ASTNode): boolean => { + if (!node) return false + + if (node.type === 'Identifier' && node.name && node.name === 'styles') { + return true + } + + if ( + node.type === 'MemberExpression' && + node.object?.type === 'Identifier' && + node.object.name === 'styles' + ) { + return true + } + + for (const key of Object.keys(node)) { + if (skipKeys.has(key)) { + // eslint-disable-next-line no-continue + continue + } + + const child = node[key] + + if (Array.isArray(child)) { + if (child.some(hasStylesReference)) return true + } else if (child && typeof child === 'object') { + if (hasStylesReference(child)) return true + } + } + + return false + } + + return { + CallExpression(node: ASTNode) { + const funcName = node.callee?.name + + if (!funcName || !workletNames.has(funcName)) return + + const fn = node.arguments?.[0] + + if ( + !fn || + (fn.type !== 'ArrowFunctionExpression' && + fn.type !== 'FunctionExpression') + ) { + return + } + + if (hasStylesReference(fn.body)) { + context.report({ node: fn, messageId: 'noCapture' }) + } + }, + } + }, +} + +/** + * Правило: не спредить styles в Icon компонентах + * ❌ + * ✅ + */ +const noSpreadIconStyles = { + meta: { + type: 'suggestion' as const, + docs: { + description: 'Передавай явные props для Icon вместо spread', + category: 'Best Practices', + recommended: 'warn' as const, + }, + messages: { + noSpread: + 'Не спредь styles в Icon. Передавай явные props: width, height, color', + }, + }, + create(context: RuleContext) { + return { + JSXSpreadAttribute(node: ASTNode) { + // Проверяем, что это styles.something + const arg = node.argument + + if ( + arg?.type === 'MemberExpression' && + arg.object?.name && + arg.object.name.includes('styles') + ) { + // Проверяем, что мы в Icon компоненте + const parent = node.parent + + if (parent?.type === 'JSXOpeningElement') { + const tagName = parent.name?.name || '' + + if (tagName.includes('Icon')) { + context.report({ node, messageId: 'noSpread' }) + } + } + } + }, + } + }, +} + +/** + * Интегрируем все правила в конфиг + */ +export const unistylesConfig = defineConfig([ + { + files: ['src/**/*.{ts,tsx}'], + plugins: { + unistyles: { + rules: { + 'no-spread-unistyles': noSpreadUnistyles, + 'no-unistyles-in-worklet': noUnistylesInWorklet, + 'no-spread-icon-styles': noSpreadIconStyles, + }, + }, + }, + rules: { + 'unistyles/no-spread-unistyles': 'error', + 'unistyles/no-unistyles-in-worklet': 'error', + 'unistyles/no-spread-icon-styles': 'warn', + }, + }, +]) diff --git a/configs/eslint/rules/unistyles/types.ts b/configs/eslint/rules/unistyles/types.ts new file mode 100644 index 00000000..31f4a7a6 --- /dev/null +++ b/configs/eslint/rules/unistyles/types.ts @@ -0,0 +1,10 @@ +import type { Rule } from 'eslint' + +export type RuleContext = Rule.RuleContext + +/** + * ESLint AST узлы имеют сложную типизацию с дискриминированными типами. + * Используем more practical подход с type guards и indexed access. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type ASTNode = any diff --git a/eslint.config.ts b/eslint.config.ts index 509fd717..063ecab3 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -5,9 +5,28 @@ 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/**/*', + '.expo/**/*', + '.git/**/*', + '.idea/**/*', 'dist/**/*', + 'build/**/*', + 'coverage/**/*', + '**/*.min.js', + '.gradle/**/*', + 'android/**/*', + 'ios/**/*', + '.yarn/**/*', + '.vscode/**/*', + '.jest/**/*', + '.gemini/**/*', '.storybook/**/*', 'configs/cz-conventional-mobile/**/*', ], diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 4f3054a0..69c230d6 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -64,6 +64,29 @@ PODS: - hermes-engine (0.81.5): - hermes-engine/Pre-built (= 0.81.5) - hermes-engine/Pre-built (0.81.5) + - NitroModules (0.35.2): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React-callinvoker + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga - RCTDeprecation (0.81.5) - RCTRequired (0.81.5) - RCTTypeSafety (0.81.5): @@ -1896,7 +1919,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - RNReanimated (4.1.1): + - RNReanimated (4.2.1): - hermes-engine - RCTRequired - RCTTypeSafety @@ -1918,10 +1941,10 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - ReactNativeDependencies - - RNReanimated/reanimated (= 4.1.1) + - RNReanimated/reanimated (= 4.2.1) - RNWorklets - Yoga - - RNReanimated/reanimated (4.1.1): + - RNReanimated/reanimated (4.2.1): - hermes-engine - RCTRequired - RCTTypeSafety @@ -1943,10 +1966,10 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - ReactNativeDependencies - - RNReanimated/reanimated/apple (= 4.1.1) + - RNReanimated/reanimated/apple (= 4.2.1) - RNWorklets - Yoga - - RNReanimated/reanimated/apple (4.1.1): + - RNReanimated/reanimated/apple (4.2.1): - hermes-engine - RCTRequired - RCTTypeSafety @@ -2015,7 +2038,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga - - RNWorklets (0.5.1): + - RNWorklets (0.7.1): - hermes-engine - RCTRequired - RCTTypeSafety @@ -2037,9 +2060,9 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - ReactNativeDependencies - - RNWorklets/worklets (= 0.5.1) + - RNWorklets/worklets (= 0.7.1) - Yoga - - RNWorklets/worklets (0.5.1): + - RNWorklets/worklets (0.7.1): - hermes-engine - RCTRequired - RCTTypeSafety @@ -2061,9 +2084,9 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - ReactNativeDependencies - - RNWorklets/worklets/apple (= 0.5.1) + - RNWorklets/worklets/apple (= 0.7.1) - Yoga - - RNWorklets/worklets/apple (0.5.1): + - RNWorklets/worklets/apple (0.7.1): - hermes-engine - RCTRequired - RCTTypeSafety @@ -2086,6 +2109,29 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga + - Unistyles (3.2.3): + - hermes-engine + - NitroModules + - RCTRequired + - RCTTypeSafety + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga - Yoga (0.0.0) DEPENDENCIES: @@ -2099,6 +2145,7 @@ DEPENDENCIES: - ExpoModulesCore (from `../node_modules/expo-modules-core`) - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) + - NitroModules (from `../node_modules/react-native-nitro-modules`) - RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) - RCTRequired (from `../node_modules/react-native/Libraries/Required`) - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) @@ -2173,6 +2220,7 @@ DEPENDENCIES: - RNReanimated (from `../node_modules/react-native-reanimated`) - RNSVG (from `../node_modules/react-native-svg`) - RNWorklets (from `../node_modules/react-native-worklets`) + - Unistyles (from `../node_modules/react-native-unistyles`) - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) SPEC REPOS: @@ -2201,6 +2249,8 @@ EXTERNAL SOURCES: hermes-engine: :podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" :tag: hermes-2025-07-07-RNv0.81.0-e0fc67142ec0763c6b6153ca2bf96df815539782 + NitroModules: + :path: "../node_modules/react-native-nitro-modules" RCTDeprecation: :path: "../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation" RCTRequired: @@ -2347,6 +2397,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native-svg" RNWorklets: :path: "../node_modules/react-native-worklets" + Unistyles: + :path: "../node_modules/react-native-unistyles" Yoga: :path: "../node_modules/react-native/ReactCommon/yoga" @@ -2362,6 +2414,7 @@ SPEC CHECKSUMS: FBLazyVector: e95a291ad2dadb88e42b06e0c5fb8262de53ec12 ForkInputMask: 55e3fbab504b22da98483e9f9a6514b98fdd2f3c hermes-engine: 9f4dfe93326146a1c99eb535b1cb0b857a3cd172 + NitroModules: 76063cb7bc1a21cf46d11b25abfcf1759bf0be47 RCTDeprecation: 943572d4be82d480a48f4884f670135ae30bf990 RCTRequired: 8f3cfc90cc25cf6e420ddb3e7caaaabc57df6043 RCTTypeSafety: 16a4144ca3f959583ab019b57d5633df10b5e97c @@ -2432,9 +2485,10 @@ SPEC CHECKSUMS: RNCAsyncStorage: 3a4f5e2777dae1688b781a487923a08569e27fe4 RNDateTimePicker: 19ffa303c4524ec0a2dfdee2658198451c16b7f1 RNGestureHandler: 723f29dac55e25f109d263ed65cecc4b9c4bd46a - RNReanimated: 6e0147e13f8906f63703143f40237f84347e6ca1 + RNReanimated: 8a7182314bb7afc01041a529e409a9112c007a50 RNSVG: cf9ae78f2edf2988242c71a6392d15ff7dd62522 - RNWorklets: 76fce72926e28e304afb44f0da23b2d24f2c1fa0 + RNWorklets: 9eb6d567fa43984e96b6924a6df504b8a15980cd + Unistyles: 6bb7e273c90d75b4f6bcb68fd1b94fb7b246145b Yoga: 5934998fbeaef7845dbf698f698518695ab4cd1a PODFILE CHECKSUM: 64cb709f656081a8373e391252a6bd658b57e3c1 diff --git a/jest.config.ts b/jest.config.ts index b5a706d8..4a42d615 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -14,9 +14,12 @@ 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' }, + moduleNameMapper: { + '\\.svg': '/__mocks__/svgMock.js', + '^react-native-worklets$': 'react-native-worklets/lib/module/mock', + }, setupFiles: ['/jest.setup.ts'], setupFilesAfterEnv: ['jest-extended/all'], } diff --git a/jest.d.ts b/jest.d.ts index a86a4041..d41f79d0 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 5fbb78c5..fd4df2ef 100644 --- a/jest.setup.ts +++ b/jest.setup.ts @@ -1,9 +1,143 @@ 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 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, +})) + +const normalizeVariantValue = (value: unknown) => { + if (typeof value === 'boolean') { + return value ? 'true' : 'false' + } + + return value +} + +const resolveStyle = ( + style: Record, + activeVariants: Record +) => { + const { variants, compoundVariants, ...baseStyle } = style as { + variants?: Record>> + compoundVariants?: Array> + } + + const resolvedStyle: Record = { ...baseStyle } + + if (variants) { + for (const [variantName, variantMap] of Object.entries(variants)) { + const activeValue = normalizeVariantValue(activeVariants[variantName]) + + if ( + activeValue !== undefined && + variantMap[activeValue as keyof typeof variantMap] + ) { + Object.assign( + resolvedStyle, + variantMap[activeValue as keyof typeof variantMap] + ) + } + } + } + + if (compoundVariants) { + for (const compoundVariant of compoundVariants) { + const { styles, ...conditions } = compoundVariant + const matches = Object.entries(conditions).every( + ([variantName, expectedValue]) => + normalizeVariantValue(activeVariants[variantName]) === + normalizeVariantValue(expectedValue) + ) + + if (matches && styles && typeof styles === 'object') { + Object.assign(resolvedStyle, styles) + } + } + } + + return resolvedStyle +} + +unistyles.StyleSheet.create = jest.fn( + (stylesheet: ((theme: ReturnType) => unknown) | unknown) => { + const styleDefinitions = + typeof stylesheet === 'function' + ? stylesheet(getTheme(runtime.themeName)) + : stylesheet + + const activeVariants: Record = {} + const resolvedStyles: Record = { + useVariants: (variants: Record) => { + Object.keys(activeVariants).forEach((key) => { + activeVariants[key] = undefined + }) + + Object.assign(activeVariants, variants) + }, + } + + for (const [styleName, styleValue] of Object.entries( + styleDefinitions as Record + )) { + Object.defineProperty(resolvedStyles, styleName, { + enumerable: true, + get() { + if ( + styleValue && + typeof styleValue === 'object' && + !Array.isArray(styleValue) + ) { + return resolveStyle( + styleValue as Record, + activeVariants + ) + } + + return styleValue + }, + }) + } + + return resolvedStyles + } +) + +unistyles.withUnistyles = jest.fn((Component: T) => Component) + generatePropsCombinations = (properties: PropertyCombinations): T[] => { const keys = Object.keys(properties) as Array diff --git a/package.json b/package.json index 9f0494ed..3115e47f 100644 --- a/package.json +++ b/package.json @@ -27,14 +27,14 @@ "scripts": { "build": "rm -rf dist && tsc -p tsconfig.build.json", "test": "jest", - "start": "expo start --dev-client", + "start": "expo start --dev-client --clear", "android": "expo run:android", "ios": "expo run:ios --no-install", "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}}\"", @@ -42,7 +42,7 @@ "pod-install": "bundle exec pod install --project-directory=ios" }, "dependencies": { - "@tabler/icons-react-native": "^3.36.0" + "@tabler/icons-react-native": "3.36.1" }, "devDependencies": { "@babel/core": "7.28.5", @@ -106,10 +106,12 @@ "react-native": "0.81.5", "react-native-advanced-input-mask": "1.4.6", "react-native-gesture-handler": "2.29.1", - "react-native-reanimated": "4.1.1", + "react-native-nitro-modules": "0.35.2", + "react-native-reanimated": "4.2.1", "react-native-safe-area-context": "5.6.2", "react-native-svg": "15.15.1", - "react-native-worklets": "0.5.1", + "react-native-unistyles": "3.2.3", + "react-native-worklets": "0.7.1", "release-it": "19.1.0", "standard-version": "9.5.0", "storybook": "10.1.10", @@ -123,8 +125,10 @@ "expo": ">=54.x.x", "react": ">=19.1", "react-native": ">=0.81.5", - "react-native-reanimated": ">=4.1.1", - "react-native-svg": ">=15.15.1" + "react-native-reanimated": ">=4.2.1", + "react-native-svg": ">=15.15.1", + "react-native-unistyles": ">=3.2.3", + "react-native-worklets": ">=0.7.0" }, "peerDependenciesMeta": { "expo": { diff --git a/src/components/Accordion/Accordion.tsx b/src/components/Accordion/Accordion.tsx index 9af5bde7..f596b0d5 100644 --- a/src/components/Accordion/Accordion.tsx +++ b/src/components/Accordion/Accordion.tsx @@ -10,8 +10,8 @@ import Animated, { import type { ViewProps } from 'react-native-svg/lib/typescript/fabric/utils' +import { StyleSheet } from '../../utils' import { type SvgSource, SvgUniversal } from '../../utils/SvgUniversal' -import { makeStyles } from '../../utils/makeStyles' export interface AccordionProps extends ViewProps { /** Иконка слева от заголовка */ @@ -50,8 +50,6 @@ export const Accordion: React.FC = ({ children, ...rest }) => { - const styles = useStyles() - const contentHeight = useSharedValue(0) const contentOpenFraction = useSharedValue(initiallyExpanded ? 1 : 0) const [isExpanded, setIsExpanded] = useState(initiallyExpanded) @@ -107,13 +105,20 @@ export const Accordion: React.FC = ({ style={arrowAnimatedStyle} testID={AccordionTestIds.arrow} > - + {Icon ? ( ) : null} {title} @@ -151,7 +156,7 @@ export const AccordionTestIds = { separator: 'Separator', } -const useStyles = makeStyles(({ theme, fonts }) => ({ +const styles = StyleSheet.create(({ theme, fonts }) => ({ component: { width: '100%' }, header: { paddingVertical: theme.Panel.Accordion.accordionHeaderPaddingTopBottom, diff --git a/src/components/Accordion/__tests__/__snapshots__/Accordion.test.tsx.snap b/src/components/Accordion/__tests__/__snapshots__/Accordion.test.tsx.snap index 163abe4c..88883a29 100644 --- a/src/components/Accordion/__tests__/__snapshots__/Accordion.test.tsx.snap +++ b/src/components/Accordion/__tests__/__snapshots__/Accordion.test.tsx.snap @@ -121,6 +121,7 @@ exports[`Accordion Header elements maximal 1`] = ` }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -168,6 +169,7 @@ exports[`Accordion Header elements maximal 1`] = ` strokeLinecap={1} strokeLinejoin={1} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -344,7 +346,7 @@ exports[`Accordion Header elements maximal 1`] = ` strokeWidth={2} > diff --git a/src/components/Avatar/Avatar.tsx b/src/components/Avatar/Avatar.tsx index bfae1fba..755c3a28 100644 --- a/src/components/Avatar/Avatar.tsx +++ b/src/components/Avatar/Avatar.tsx @@ -21,8 +21,8 @@ import { type ViewStyle, } from 'react-native' +import { StyleSheet } from '../../utils' import { type SvgSource, SvgUniversal } from '../../utils/SvgUniversal' -import { makeStyles } from '../../utils/makeStyles' export type AvatarSize = 'xlarge' | 'large' | 'normal' @@ -137,7 +137,6 @@ export const Avatar = memo( onError, iconColor, }) => { - const styles = useStyles() const window = useWindowDimensions() const [badgeLayout, setBadgeLayout] = useState() @@ -192,7 +191,7 @@ export const Avatar = memo( width={iconSize} /> ) - }, [Icon, calculatedSize, iconColor, size, styles, type]) + }, [Icon, calculatedSize, iconColor, size, type]) useEffect(() => { if (badge) { @@ -255,7 +254,7 @@ export const Avatar = memo( } ) -const useStyles = makeStyles(({ theme, border, typography, fonts }) => ({ +const styles = StyleSheet.create(({ theme, border, typography, fonts }) => ({ container: { justifyContent: 'center', alignItems: 'center', diff --git a/src/components/Avatar/__tests__/__snapshots__/Avatar.test.tsx.snap b/src/components/Avatar/__tests__/__snapshots__/Avatar.test.tsx.snap index 734dacdd..87f18c6d 100644 --- a/src/components/Avatar/__tests__/__snapshots__/Avatar.test.tsx.snap +++ b/src/components/Avatar/__tests__/__snapshots__/Avatar.test.tsx.snap @@ -1701,17 +1701,13 @@ exports[`Avatar component tests With badge, showBadge: true 1`] = ` > ( - ({ children, dot, severity = 'basic', style, testID, ...rest }) => { - const styles = useStyles() +export const Badge = memo( + ({ + children, + dot, + severity = 'basic', + style, + testID, + ...rest + }: BadgeProps) => { + badgeStyles.useVariants({ severity }) const [textLayout, setTextLayout] = useState() const onTextLayout = useCallback((e: LayoutChangeEvent) => { @@ -60,18 +67,15 @@ export const Badge = memo( }, []) return ( - + {dot ? ( - + ) : ( <> - + {children} @@ -81,12 +85,12 @@ export const Badge = memo( {children} @@ -100,41 +104,70 @@ export const Badge = memo( } ) -const useStyles = makeStyles(({ theme, border, typography, fonts }) => ({ - container: { alignItems: 'flex-start' }, - dot: { - width: theme.Misc.Badge.badgeDotSize, - height: theme.Misc.Badge.badgeDotSize, - borderRadius: border.Radius['rounded-full'], - }, - textBadgeContainer: { - height: theme.Misc.Badge.badgeHeight, - paddingHorizontal: theme.Misc.Tag.tagPadding, - justifyContent: 'center', - borderRadius: border.Radius['rounded-full'], - }, - textBadge: { - color: theme.Misc.Badge.badgeTextColor, - fontSize: typography.Size['text-xs'], - includeFontPadding: false, - verticalAlign: 'middle', - fontFamily: fonts.primary, - }, - basic: { backgroundColor: theme.Misc.Badge.badgeBg }, - info: { backgroundColor: theme.Button.Severity.Info.Basic.infoButtonBg }, - success: { - backgroundColor: theme.Button.Severity.Success.Basic.successButtonBg, - }, - warning: { - backgroundColor: theme.Button.Severity.Warning.Basic.warningButtonBg, - }, - danger: { - backgroundColor: theme.Button.Severity.Danger.Basic.dangerButtonBg, - }, - hiddenContainer: { - width: Dimensions.get('window').width, - height: 0, - flexDirection: 'row', - position: 'absolute', - }, -})) +const badgeStyles = StyleSheet.create( + ({ theme, border, typography, fonts }) => ({ + container: { alignItems: 'flex-start' }, + dot: { + width: theme.Misc.Badge.badgeDotSize, + height: theme.Misc.Badge.badgeDotSize, + borderRadius: border.Radius['rounded-full'], + variants: { + severity: { + basic: { backgroundColor: theme.Misc.Badge.badgeBg }, + info: { + backgroundColor: theme.Button.Severity.Info.Basic.infoButtonBg, + }, + success: { + backgroundColor: + theme.Button.Severity.Success.Basic.successButtonBg, + }, + warning: { + backgroundColor: + theme.Button.Severity.Warning.Basic.warningButtonBg, + }, + danger: { + backgroundColor: theme.Button.Severity.Danger.Basic.dangerButtonBg, + }, + }, + }, + }, + textBadgeContainer: { + height: theme.Misc.Badge.badgeHeight, + paddingHorizontal: theme.Misc.Tag.tagPadding, + justifyContent: 'center', + borderRadius: border.Radius['rounded-full'], + variants: { + severity: { + basic: { backgroundColor: theme.Misc.Badge.badgeBg }, + info: { + backgroundColor: theme.Button.Severity.Info.Basic.infoButtonBg, + }, + success: { + backgroundColor: + theme.Button.Severity.Success.Basic.successButtonBg, + }, + warning: { + backgroundColor: + theme.Button.Severity.Warning.Basic.warningButtonBg, + }, + danger: { + backgroundColor: theme.Button.Severity.Danger.Basic.dangerButtonBg, + }, + }, + }, + }, + textBadge: { + color: theme.Misc.Badge.badgeTextColor, + fontSize: typography.Size['text-xs'], + includeFontPadding: false, + verticalAlign: 'middle', + fontFamily: fonts.primary, + }, + hiddenContainer: { + width: Dimensions.get('window').width, + height: 0, + flexDirection: 'row', + position: 'absolute', + }, + }) +) diff --git a/src/components/Badge/__tests__/__snapshots__/Badge.test.tsx.snap b/src/components/Badge/__tests__/__snapshots__/Badge.test.tsx.snap index e5b425a8..0cac9d85 100644 --- a/src/components/Badge/__tests__/__snapshots__/Badge.test.tsx.snap +++ b/src/components/Badge/__tests__/__snapshots__/Badge.test.tsx.snap @@ -13,16 +13,12 @@ exports[`Badge component tests dot, severity: basic 1`] = ` > @@ -41,16 +37,12 @@ exports[`Badge component tests dot, severity: danger 1`] = ` > @@ -69,16 +61,12 @@ exports[`Badge component tests dot, severity: info 1`] = ` > @@ -97,16 +85,12 @@ exports[`Badge component tests dot, severity: success 1`] = ` > @@ -125,16 +109,12 @@ exports[`Badge component tests dot, severity: warning 1`] = ` > @@ -153,17 +133,13 @@ exports[`Badge component tests severity: basic 1`] = ` > diff --git a/src/components/Button/BaseButton.tsx b/src/components/Button/BaseButton.tsx index 4b604e3b..5e752b27 100644 --- a/src/components/Button/BaseButton.tsx +++ b/src/components/Button/BaseButton.tsx @@ -1,21 +1,21 @@ import { useCallback, useState } from 'react' - import type { GestureResponderEvent } from 'react-native' import { genericMemo } from '../../utils/genericMemo' -import type { ButtonProps, ButtonVariant, VariantStyles } from './types' +import type { ButtonProps, ButtonVariant } from './types' import { ButtonLeftArea, ButtonRightArea, ButtonLabel, ButtonContainer, } from './utils' +import { ButtonPressedContext } from './utils/ButtonPressedContext' export type BaseButtonComponentProps = Omit< ButtonProps, 'variant' -> & { readonly variant: Variant } & VariantStyles +> & { readonly variant: Variant } const BaseButtonComponent = ({ size = 'base', @@ -28,15 +28,11 @@ const BaseButtonComponent = ({ Icon, label, style, - containerVariantStyles, - labelVariantStyles, - pressedVariantStyles, - iconVariantStyles, - pressedLabelVariantStyles, onPressIn: onPressInProp, onPressOut: onPressOutProp, ...props }: BaseButtonComponentProps) => { + const isDisabled = !!disabled const [pressed, setPressed] = useState(false) const onPressIn = useCallback( @@ -56,62 +52,31 @@ const BaseButtonComponent = ({ ) return ( - - - - + - + {...props} + > + + + + + ) } diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx index 1ac8c549..539561d4 100644 --- a/src/components/Button/Button.tsx +++ b/src/components/Button/Button.tsx @@ -1,8 +1,8 @@ -import { memo } from 'react' +import { memo, useMemo } from 'react' import { BaseButton } from './BaseButton' -import { useBasicButtonStyles } from './styles' import type { ButtonBaseVariant, ButtonProps } from './types' +import { ButtonVariantContext } from './utils/ButtonVariantContext' /** * Button component @@ -18,10 +18,14 @@ import type { ButtonBaseVariant, ButtonProps } from './types' * @param style - external style control for component * @see BaseButton */ -export const Button = memo>( - ({ variant = 'primary', ...props }) => { - const buttonStyles = useBasicButtonStyles() +export const Button = memo( + ({ variant = 'primary', ...props }: ButtonProps) => { + const variantContextValue = useMemo(() => ({ variant }), [variant]) - return + return ( + + + + ) } ) diff --git a/src/components/Button/ButtonBadge.tsx b/src/components/Button/ButtonBadge.tsx index 1dfad821..8e9ab6d0 100644 --- a/src/components/Button/ButtonBadge.tsx +++ b/src/components/Button/ButtonBadge.tsx @@ -6,12 +6,12 @@ import { type ViewStyle, } from 'react-native' -import { makeStyles } from '../../utils/makeStyles' +import { StyleSheet } from '../../utils' import { Badge } from '../Badge' import { BaseButton } from './BaseButton' -import { useBasicButtonStyles } from './styles' import type { ButtonBadgeProps, ButtonBaseVariant, ButtonProps } from './types' +import { ButtonVariantContext } from './utils/ButtonVariantContext' /** * Button component with badge @@ -29,59 +29,65 @@ import type { ButtonBadgeProps, ButtonBaseVariant, ButtonProps } from './types' * @param badgeLabel - text label inside badge * @see BaseButton */ -export const ButtonBadge = memo< - ButtonProps & ButtonBadgeProps ->(({ badgeLabel, badgeSeverity, variant = 'primary', ...props }) => { - const buttonStyles = useBasicButtonStyles() - const styles = useStyles() - const [badgeLayout, setBadgeLayout] = useState() +export const ButtonBadge = memo( + ({ + badgeLabel, + badgeSeverity, + variant = 'primary', + ...props + }: ButtonProps & ButtonBadgeProps) => { + const [badgeLayout, setBadgeLayout] = useState() + const variantContextValue = useMemo(() => ({ variant }), [variant]) - const badgeContainerStyle = useMemo( - () => ({ - position: 'absolute', - top: badgeLayout ? -Math.round(badgeLayout.height / 2) : 0, - right: badgeLayout ? -Math.round(badgeLayout.width / 2) : 0, - }), - [badgeLayout] - ) + const badgeContainerStyle = useMemo( + () => ({ + position: 'absolute', + top: badgeLayout ? -Math.round(badgeLayout.height / 2) : 0, + right: badgeLayout ? -Math.round(badgeLayout.width / 2) : 0, + }), + [badgeLayout] + ) - const onLayout = useCallback( - (e: LayoutChangeEvent) => setBadgeLayout(e.nativeEvent.layout), - [] - ) + const onLayout = useCallback( + (e: LayoutChangeEvent) => setBadgeLayout(e.nativeEvent.layout), + [] + ) - const badgeCommonProps = useMemo( - () => ({ severity: badgeSeverity, testID: ButtonBadgeTestId.badge }), - [badgeSeverity] - ) + const badgeCommonProps = useMemo( + () => ({ severity: badgeSeverity, testID: ButtonBadgeTestId.badge }), + [badgeSeverity] + ) - return ( - - - - - {badgeLabel ? ( - + + - {badgeLabel} - - ) : ( - - )} - - - ) -}) + + + {badgeLabel ? ( + + {badgeLabel} + + ) : ( + + )} + + + + ) + } +) -const useStyles = makeStyles(() => ({ +const styles = StyleSheet.create(() => ({ root: { flexDirection: 'row' }, contentContainer: { flex: 1 }, iconOnlyContainer: { flex: 0 }, diff --git a/src/components/Button/ButtonSeverity.tsx b/src/components/Button/ButtonSeverity.tsx index 0f017a3d..8bd9780d 100644 --- a/src/components/Button/ButtonSeverity.tsx +++ b/src/components/Button/ButtonSeverity.tsx @@ -1,12 +1,12 @@ -import { memo } from 'react' +import { memo, useMemo } from 'react' import { BaseButton } from './BaseButton' -import { useSeverityButtonStyles } from './styles' import type { ButtonProps, ButtonSeverityProps, ButtonSeverityVariant, } from './types' +import { ButtonVariantContext } from './utils/ButtonVariantContext' /** * Button component @@ -23,10 +23,21 @@ import type { * @param severity - severity button styling variant * @see BaseButton */ -export const ButtonSeverity = memo< - ButtonProps & ButtonSeverityProps ->(({ severity, variant = 'basic', ...props }) => { - const buttonStyles = useSeverityButtonStyles(severity) +export const ButtonSeverity = memo( + ({ + severity, + variant = 'basic', + ...props + }: ButtonProps & ButtonSeverityProps) => { + const variantContextValue = useMemo( + () => ({ variant, severity }), + [severity, variant] + ) - return -}) + return ( + + + + ) + } +) diff --git a/src/components/Button/__tests__/__snapshots__/Button.test.tsx.snap b/src/components/Button/__tests__/__snapshots__/Button.test.tsx.snap index 58dda768..cc40367f 100644 --- a/src/components/Button/__tests__/__snapshots__/Button.test.tsx.snap +++ b/src/components/Button/__tests__/__snapshots__/Button.test.tsx.snap @@ -33,21 +33,24 @@ exports[`Button component tests Button default props 1`] = ` onResponderTerminationRequest={[Function]} onStartShouldSetResponder={[Function]} style={ - { - "alignItems": "center", - "backgroundColor": "#44e858", - "borderColor": "rgba(255, 255, 255, 0.0001)", - "borderRadius": 10.5, - "borderWidth": 1, - "flexDirection": "row", - "gap": 7, - "height": 35, - "justifyContent": "center", - "maxHeight": 35, - "minHeight": 35, - "paddingHorizontal": 14, - "paddingVertical": 0, - } + [ + { + "alignItems": "center", + "backgroundColor": "#44e858", + "borderColor": "rgba(255, 255, 255, 0.0001)", + "borderRadius": 10.5, + "borderWidth": 1, + "flexDirection": "row", + "gap": 7, + "height": 35, + "justifyContent": "center", + "maxHeight": 35, + "minHeight": 35, + "paddingHorizontal": 14, + "paddingVertical": 0, + }, + false, + ] } > + + + + + + + + Button + + +`; + +exports[`Button component tests Button with icon on left, size - base, shape - circle, variant - secondary, loading - true, disabled - true 2`] = ` + - - - - Button - - -`; - -exports[`Button component tests Button with icon on left, size - base, shape - circle, variant - secondary, loading - true, disabled - true 2`] = ` - - - - - + + + + + + + Button + + +`; + +exports[`Button component tests Button with icon on left, size - base, shape - square, variant - text, loading - false, disabled - false 1`] = ` + + `; -exports[`Button component tests Button with icon on left, size - base, shape - square, variant - text, loading - false, disabled - false 1`] = ` +exports[`Button component tests Button with icon on left, size - base, shape - square, variant - text, loading - false, disabled - false 2`] = ` `; -exports[`Button component tests Button with icon on left, size - base, shape - square, variant - text, loading - false, disabled - false 2`] = ` +exports[`Button component tests Button with icon on left, size - base, shape - square, variant - text, loading - false, disabled - true 1`] = ` `; -exports[`Button component tests Button with icon on left, size - base, shape - square, variant - text, loading - false, disabled - true 1`] = ` +exports[`Button component tests Button with icon on left, size - base, shape - square, variant - text, loading - false, disabled - true 2`] = ` `; -exports[`Button component tests Button with icon on left, size - base, shape - square, variant - text, loading - false, disabled - true 2`] = ` +exports[`Button component tests Button with icon on left, size - base, shape - square, variant - text, loading - true, disabled - false 1`] = ` + + + Button + + +`; + +exports[`Button component tests Button with icon on left, size - base, shape - square, variant - text, loading - true, disabled - false 2`] = ` + + + + Button + + +`; + +exports[`Button component tests Button with icon on left, size - base, shape - square, variant - text, loading - true, disabled - true 1`] = ` + `; -exports[`Button component tests Button with icon on left, size - base, shape - square, variant - text, loading - true, disabled - false 1`] = ` +exports[`Button component tests Button with icon on left, size - base, shape - square, variant - text, loading - true, disabled - true 2`] = ` - + + + + + + `; -exports[`Button component tests Button with icon on left, size - base, shape - square, variant - text, loading - true, disabled - false 2`] = ` +exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - link, loading - false, disabled - false 1`] = ` - - - Button - - -`; - -exports[`Button component tests Button with icon on left, size - base, shape - square, variant - text, loading - true, disabled - true 1`] = ` - `; -exports[`Button component tests Button with icon on left, size - base, shape - square, variant - text, loading - true, disabled - true 2`] = ` +exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - link, loading - false, disabled - false 2`] = ` `; -exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - link, loading - false, disabled - false 1`] = ` +exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - link, loading - false, disabled - true 1`] = ` `; -exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - link, loading - false, disabled - false 2`] = ` +exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - link, loading - false, disabled - true 2`] = ` `; -exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - link, loading - false, disabled - true 1`] = ` +exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - link, loading - true, disabled - false 1`] = ` + + + Button + + +`; + +exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - link, loading - true, disabled - false 2`] = ` + + + + Button + + +`; + +exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - link, loading - true, disabled - true 1`] = ` + `; -exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - link, loading - false, disabled - true 2`] = ` +exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - link, loading - true, disabled - true 2`] = ` - - - - - - - Button - - -`; - -exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - link, loading - true, disabled - false 1`] = ` - - - - Button - - -`; - -exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - link, loading - true, disabled - false 2`] = ` - - - - Button - - -`; - -exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - link, loading - true, disabled - true 1`] = ` - - - - - - - - - Button - - -`; - -exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - link, loading - true, disabled - true 2`] = ` - - - - - - - - - Button - - -`; - -exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - primary, loading - false, disabled - true 2`] = ` - - - - - - - - - Button - - -`; - -exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - primary, loading - true, disabled - false 1`] = ` - - - - Button - - -`; - -exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - primary, loading - true, disabled - false 2`] = ` - - - - Button - - -`; - -exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - primary, loading - true, disabled - true 1`] = ` - - - - - - - - - Button - - -`; - -exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - primary, loading - true, disabled - true 2`] = ` - - - - - - - - - Button - - -`; - -exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - secondary, loading - false, disabled - false 1`] = ` - - `; -exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - secondary, loading - false, disabled - false 2`] = ` +exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - primary, loading - false, disabled - true 2`] = ` `; -exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - secondary, loading - false, disabled - true 1`] = ` +exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - primary, loading - true, disabled - false 1`] = ` + + + + Button + + +`; + +exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - primary, loading - true, disabled - false 2`] = ` + + + Button + + +`; + +exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - primary, loading - true, disabled - true 1`] = ` + `; -exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - secondary, loading - false, disabled - true 2`] = ` +exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - primary, loading - true, disabled - true 2`] = ` `; -exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - secondary, loading - true, disabled - false 1`] = ` +exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - secondary, loading - false, disabled - false 1`] = ` - + + + + + + `; -exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - secondary, loading - true, disabled - false 2`] = ` +exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - secondary, loading - false, disabled - false 2`] = ` - + + + + + + `; -exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - secondary, loading - true, disabled - true 1`] = ` +exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - secondary, loading - false, disabled - true 1`] = ` `; -exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - secondary, loading - true, disabled - true 2`] = ` +exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - secondary, loading - false, disabled - true 2`] = ` `; -exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - tertiary, loading - false, disabled - false 1`] = ` +exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - secondary, loading - true, disabled - false 1`] = ` + + + Button + + +`; + +exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - secondary, loading - true, disabled - false 2`] = ` + + + + Button + + +`; + +exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - secondary, loading - true, disabled - true 1`] = ` + `; -exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - tertiary, loading - false, disabled - false 2`] = ` +exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - secondary, loading - true, disabled - true 2`] = ` `; -exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - tertiary, loading - false, disabled - true 1`] = ` +exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - tertiary, loading - false, disabled - false 1`] = ` `; -exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - tertiary, loading - false, disabled - true 2`] = ` +exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - tertiary, loading - false, disabled - false 2`] = ` `; -exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - tertiary, loading - true, disabled - false 1`] = ` +exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - tertiary, loading - false, disabled - true 1`] = ` - - - Button - - -`; - -exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - tertiary, loading - true, disabled - false 2`] = ` - - + + + + + `; -exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - tertiary, loading - true, disabled - true 1`] = ` +exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - tertiary, loading - false, disabled - true 2`] = ` `; -exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - tertiary, loading - true, disabled - true 2`] = ` +exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - tertiary, loading - true, disabled - false 1`] = ` + + + + Button + + +`; + +exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - tertiary, loading - true, disabled - false 2`] = ` + + + Button + + +`; + +exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - tertiary, loading - true, disabled - true 1`] = ` + + + + + + + + Button + + +`; + +exports[`Button component tests Button with icon on left, size - large, shape - circle, variant - tertiary, loading - true, disabled - true 2`] = ` + + - - Button - - - - - - - - -`; - -exports[`Button component tests Button with icon on right, size - large, shape - circle, variant - secondary, loading - false, disabled - false 2`] = ` - `; -exports[`Button component tests Button with icon on right, size - large, shape - circle, variant - secondary, loading - false, disabled - true 1`] = ` +exports[`Button component tests Button with icon on right, size - large, shape - circle, variant - secondary, loading - false, disabled - false 2`] = ` `; -exports[`Button component tests Button with icon on right, size - large, shape - circle, variant - secondary, loading - false, disabled - true 2`] = ` +exports[`Button component tests Button with icon on right, size - large, shape - circle, variant - secondary, loading - false, disabled - true 1`] = ` `; -exports[`Button component tests Button with icon on right, size - large, shape - circle, variant - secondary, loading - true, disabled - false 1`] = ` +exports[`Button component tests Button with icon on right, size - large, shape - circle, variant - secondary, loading - false, disabled - true 2`] = ` Button - + + + + + + `; -exports[`Button component tests Button with icon on right, size - large, shape - circle, variant - secondary, loading - true, disabled - false 2`] = ` +exports[`Button component tests Button with icon on right, size - large, shape - circle, variant - secondary, loading - true, disabled - false 1`] = ` `; -exports[`Button component tests Button with icon on right, size - large, shape - circle, variant - secondary, loading - true, disabled - true 1`] = ` +exports[`Button component tests Button with icon on right, size - large, shape - circle, variant - secondary, loading - true, disabled - false 2`] = ` Button - - - - - - + `; -exports[`Button component tests Button with icon on right, size - large, shape - circle, variant - secondary, loading - true, disabled - true 2`] = ` +exports[`Button component tests Button with icon on right, size - large, shape - circle, variant - secondary, loading - true, disabled - true 1`] = ` `; -exports[`Button component tests Button with icon on right, size - large, shape - circle, variant - tertiary, loading - false, disabled - false 1`] = ` +exports[`Button component tests Button with icon on right, size - large, shape - circle, variant - secondary, loading - true, disabled - true 2`] = ` + + + + + + +`; + +exports[`Button component tests Button with icon on right, size - large, shape - circle, variant - tertiary, loading - false, disabled - false 1`] = ` + + + Button + + + + +`; + +exports[`Button component tests Button with only icon, size - base, shape - circle, variant - secondary, loading - true, disabled - true 1`] = ` + - - -`; - -exports[`Button component tests Button with only icon, size - base, shape - circle, variant - secondary, loading - true, disabled - true 1`] = ` - - - - - - - - -`; - -exports[`Button component tests Button with only icon, size - large, shape - square, variant - primary, loading - false, disabled - true 1`] = ` - `; -exports[`Button component tests Button with only icon, size - large, shape - square, variant - primary, loading - true, disabled - false 1`] = ` +exports[`Button component tests Button with only icon, size - large, shape - square, variant - primary, loading - false, disabled - true 1`] = ` + + + + + + + +`; + +exports[`Button component tests Button with only icon, size - large, shape - square, variant - primary, loading - true, disabled - false 1`] = ` + - - - - - - - -`; - -exports[`Button component tests Button with only icon, size - small, shape - square, variant - link, loading - false, disabled - true 1`] = ` - `; -exports[`Button component tests Button with only icon, size - small, shape - square, variant - link, loading - true, disabled - false 1`] = ` +exports[`Button component tests Button with only icon, size - small, shape - square, variant - link, loading - false, disabled - true 1`] = ` + + + + + + + +`; + +exports[`Button component tests Button with only icon, size - small, shape - square, variant - link, loading - true, disabled - false 1`] = ` + - - Button - - -`; - -exports[`Button component tests Button with text, size - large, shape - square, variant - tertiary, loading - false, disabled - true 2`] = ` - `; -exports[`Button component tests Button with text, size - large, shape - square, variant - tertiary, loading - true, disabled - false 1`] = ` +exports[`Button component tests Button with text, size - large, shape - square, variant - tertiary, loading - false, disabled - true 2`] = ` + + Button + + +`; + +exports[`Button component tests Button with text, size - large, shape - square, variant - tertiary, loading - true, disabled - false 1`] = ` + + + Button + + +`; + +exports[`Button component tests Button with text, size - small, shape - circle, variant - text, loading - true, disabled - true 2`] = ` + - - Button - - -`; - -exports[`Button component tests Button with text, size - small, shape - circle, variant - text, loading - true, disabled - true 2`] = ` - + + Button + + +`; + +exports[`Button component tests Button with text, size - xlarge, shape - circle, variant - primary, loading - false, disabled - true 2`] = ` + - - Button - - -`; - -exports[`Button component tests Button with text, size - xlarge, shape - circle, variant - primary, loading - false, disabled - true 2`] = ` - @@ -221,21 +220,24 @@ exports[`ButtonSeverity component tests badge - dot 1`] = ` onResponderTerminationRequest={[Function]} onStartShouldSetResponder={[Function]} style={ - { - "alignItems": "center", - "backgroundColor": "#44e858", - "borderColor": "rgba(255, 255, 255, 0.0001)", - "borderRadius": 10.5, - "borderWidth": 1, - "flexDirection": "row", - "gap": 7, - "height": 35, - "justifyContent": "center", - "maxHeight": 35, - "minHeight": 35, - "paddingHorizontal": 14, - "paddingVertical": 0, - } + [ + { + "alignItems": "center", + "backgroundColor": "#44e858", + "borderColor": "rgba(255, 255, 255, 0.0001)", + "borderRadius": 10.5, + "borderWidth": 1, + "flexDirection": "row", + "gap": 7, + "height": 35, + "justifyContent": "center", + "maxHeight": 35, + "minHeight": 35, + "paddingHorizontal": 14, + "paddingVertical": 0, + }, + false, + ] } > diff --git a/src/components/Button/__tests__/__snapshots__/ButtonSeverity.test.tsx.snap b/src/components/Button/__tests__/__snapshots__/ButtonSeverity.test.tsx.snap index da1b450c..57b1b5e8 100644 --- a/src/components/Button/__tests__/__snapshots__/ButtonSeverity.test.tsx.snap +++ b/src/components/Button/__tests__/__snapshots__/ButtonSeverity.test.tsx.snap @@ -33,21 +33,24 @@ exports[`ButtonSeverity component tests severity - danger 1`] = ` onResponderTerminationRequest={[Function]} onStartShouldSetResponder={[Function]} style={ - { - "alignItems": "center", - "backgroundColor": "#fbacaa", - "borderColor": "rgba(255, 255, 255, 0.0001)", - "borderRadius": 10.5, - "borderWidth": 1, - "flexDirection": "row", - "gap": 7, - "height": 35, - "justifyContent": "center", - "maxHeight": 35, - "minHeight": 35, - "paddingHorizontal": 14, - "paddingVertical": 0, - } + [ + { + "alignItems": "center", + "backgroundColor": "#fbacaa", + "borderColor": "rgba(255, 255, 255, 0.0001)", + "borderRadius": 10.5, + "borderWidth": 1, + "flexDirection": "row", + "gap": 7, + "height": 35, + "justifyContent": "center", + "maxHeight": 35, + "minHeight": 35, + "paddingHorizontal": 14, + "paddingVertical": 0, + }, + false, + ] } > { - const labelVariantStyles = useLabelVariantStyles() - const pressedVariantStyles = usePressedVariantStyles() - const containerVariantStyles = useContainerVariantStyles() - const iconVariantStyles = useIconVariantStyles() - const pressedLabelVariantStyles = usePressedLabelVariantStyles() - - return { - containerVariantStyles, - labelVariantStyles, - pressedVariantStyles, - iconVariantStyles, - pressedLabelVariantStyles, - } -} - -const useLabelVariantStyles = makeStyles(({ theme }) => ({ - primary: { color: theme.Button.Brand.buttonTextColor }, - - secondary: { color: theme.Button.Primary.secondaryButtonTextColor }, - - tertiary: { color: theme.Button.Secondary.helpButtonTextColor }, - - text: { color: theme.Button.Text.textButtonTextColor }, - - link: { color: theme.Button.Text.textButtonTextColor }, -})) - -const usePressedVariantStyles = makeStyles(({ theme }) => ({ - primary: { - borderColor: theme.Button.Brand.buttonBorderColor, - backgroundColor: theme.Button.Brand.buttonHoverBg, - }, - - secondary: { - borderColor: theme.Button.Primary.secondaryButtonHoverBorderColor, - backgroundColor: theme.Button.Primary.secondaryButtonHoverBg, - }, - - tertiary: { - borderColor: theme.Button.Secondary.helpButtonHoverBorderColor, - backgroundColor: theme.Button.Secondary.helpButtonHoverBg, - }, - - text: { - borderColor: theme.Button.Brand.buttonBorderColor, - backgroundColor: theme.Button.Text.textButtonHoverBg, - }, - - link: {}, -})) - -const useContainerVariantStyles = makeStyles(({ theme, spacing }) => ({ - primary: { - borderColor: theme.Button.Brand.buttonBorderColor, - backgroundColor: theme.Button.Brand.buttonBg, - }, - - secondary: { - borderColor: theme.Button.Primary.secondaryButtonBorderColor, - backgroundColor: theme.Button.Primary.secondaryButtonBg, - }, - - tertiary: { - borderColor: theme.Button.Secondary.helpButtonBorderColor, - backgroundColor: theme.Button.Secondary.helpButtonBg, - }, - - text: { - borderColor: theme.Button.Brand.buttonBorderColor, - backgroundColor: theme.Button.Text.textButtonBg, - }, - - link: { - paddingHorizontal: 0, - paddingVertical: spacing.Padding['p-1'], - height: 'auto', - minHeight: 'auto', - borderColor: theme.Button.Brand.buttonBorderColor, - backgroundColor: theme.Button.Text.textButtonBg, - }, -})) - -const useIconVariantStyles = makeStyles(({ theme }) => ({ - primary: { color: theme.Button.Brand.buttonTextColor }, - - secondary: { color: theme.Button.Primary.secondaryButtonTextColor }, - - tertiary: { color: theme.Button.Secondary.helpButtonTextColor }, - - text: { color: theme.Button.Text.textButtonTextColor }, - - link: { color: theme.Button.Text.textButtonTextColor }, -})) - -const usePressedLabelVariantStyles = makeStyles(({ theme, typography }) => ({ - primary: { color: theme.Button.Brand.buttonTextColor }, - - secondary: { color: theme.Button.Primary.secondaryButtonTextColor }, - - tertiary: { color: theme.Button.Secondary.helpButtonTextColor }, - - text: { color: theme.Button.Text.textButtonTextColor }, - - link: { color: typography.Color.Common['text-color-secondary'] }, -})) diff --git a/src/components/Button/styles/useDangerButtonStyles.ts b/src/components/Button/styles/useDangerButtonStyles.ts deleted file mode 100644 index 396c7e45..00000000 --- a/src/components/Button/styles/useDangerButtonStyles.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { makeStyles } from '../../../utils/makeStyles' - -export const useDangerButtonStyles = () => { - const labelVariantStyles = useLabelVariantStyles() - const pressedVariantStyles = usePressedVariantStyles() - const containerVariantStyles = useContainerVariantStyles() - const iconVariantStyles = useIconVariantStyles() - - return { - containerVariantStyles, - labelVariantStyles, - pressedVariantStyles, - iconVariantStyles, - pressedLabelVariantStyles: labelVariantStyles, - } -} - -const useLabelVariantStyles = makeStyles(({ theme }) => ({ - basic: { color: theme.Button.Severity.Danger.Basic.dangerButtonTextColor }, - - outlined: { - color: theme.Button.Severity.Danger.Outlined.dangerOutlinedButtonTextColor, - }, - - text: { color: theme.Button.Severity.Danger.Text.dangerTextButtonTextColor }, -})) - -const usePressedVariantStyles = makeStyles(({ theme }) => ({ - basic: { - borderColor: theme.Button.Severity.Danger.Basic.dangerButtonBorderColor, - backgroundColor: theme.Button.Severity.Danger.Basic.dangerButtonHoverBg, - }, - - outlined: { - borderColor: - theme.Button.Severity.Danger.Outlined - .dangerOutlinedButtonHoverBorderColor, - backgroundColor: - theme.Button.Severity.Danger.Outlined.dangerOutlinedButtonHoverBg, - }, - - text: { - borderColor: theme.Surface['surface-transparent'], - backgroundColor: theme.Button.Severity.Danger.Text.dangerTextButtonHoverBg, - }, -})) - -const useContainerVariantStyles = makeStyles(({ theme }) => ({ - basic: { - borderColor: theme.Button.Severity.Danger.Basic.dangerButtonBorderColor, - backgroundColor: theme.Button.Severity.Danger.Basic.dangerButtonBg, - }, - - outlined: { - borderColor: - theme.Button.Severity.Danger.Outlined.dangerOutlinedButtonBorderColor, - backgroundColor: - theme.Button.Severity.Danger.Outlined.dangerOutlinedButtonBg, - }, - - text: { - borderColor: theme.Button.Severity.Danger.Basic.dangerButtonBorderColor, - backgroundColor: theme.Button.Severity.Danger.Text.dangerTextButtonBg, - }, -})) - -const useIconVariantStyles = makeStyles(({ theme }) => ({ - basic: { color: theme.Button.Severity.Danger.Basic.dangerButtonTextColor }, - - outlined: { - color: theme.Button.Severity.Danger.Outlined.dangerOutlinedButtonTextColor, - }, - - text: { color: theme.Button.Severity.Danger.Text.dangerTextButtonTextColor }, -})) diff --git a/src/components/Button/styles/useInfoButtonStyles.ts b/src/components/Button/styles/useInfoButtonStyles.ts deleted file mode 100644 index 83c102d0..00000000 --- a/src/components/Button/styles/useInfoButtonStyles.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { makeStyles } from '../../../utils/makeStyles' - -export const useInfoButtonStyles = () => { - const labelVariantStyles = useLabelVariantStyles() - const pressedVariantStyles = usePressedVariantStyles() - const containerVariantStyles = useContainerVariantStyles() - const iconVariantStyles = useIconVariantStyles() - - return { - containerVariantStyles, - labelVariantStyles, - pressedVariantStyles, - iconVariantStyles, - pressedLabelVariantStyles: labelVariantStyles, - } -} - -const useLabelVariantStyles = makeStyles(({ theme }) => ({ - basic: { color: theme.Button.Severity.Info.Basic.infoButtonTextColor }, - - outlined: { - color: theme.Button.Severity.Info.Outlined.infoOutlinedButtonTextColor, - }, - - text: { color: theme.Button.Severity.Info.Text.infoTextButtonTextColor }, -})) - -const usePressedVariantStyles = makeStyles(({ theme }) => ({ - basic: { - borderColor: theme.Button.Severity.Info.Basic.infoButtonBorderColor, - backgroundColor: theme.Button.Severity.Info.Basic.infoButtonHoverBg, - }, - - outlined: { - borderColor: - theme.Button.Severity.Info.Outlined.infoOutlinedButtonHoverBorderColor, - backgroundColor: - theme.Button.Severity.Info.Outlined.infoOutlinedButtonHoverBg, - }, - - text: { - borderColor: theme.Surface['surface-transparent'], - backgroundColor: theme.Button.Severity.Info.Text.infoTextButtonHoverBg, - }, -})) - -const useContainerVariantStyles = makeStyles(({ theme }) => ({ - basic: { - borderColor: theme.Button.Severity.Info.Basic.infoButtonBorderColor, - backgroundColor: theme.Button.Severity.Info.Basic.infoButtonBg, - }, - - outlined: { - borderColor: - theme.Button.Severity.Info.Outlined.infoOutlinedButtonBorderColor, - backgroundColor: theme.Button.Severity.Info.Outlined.infoOutlinedButtonBg, - }, - - text: { - borderColor: theme.Button.Severity.Info.Basic.infoButtonBorderColor, - backgroundColor: theme.Button.Severity.Info.Text.infoTextButtonBg, - }, -})) - -const useIconVariantStyles = makeStyles(({ theme }) => ({ - basic: { color: theme.Button.Severity.Info.Basic.infoButtonTextColor }, - - outlined: { - color: theme.Button.Severity.Info.Outlined.infoOutlinedButtonTextColor, - }, - - text: { color: theme.Button.Severity.Info.Text.infoTextButtonTextColor }, -})) diff --git a/src/components/Button/styles/useSeverityButtonStyles.ts b/src/components/Button/styles/useSeverityButtonStyles.ts deleted file mode 100644 index 331249de..00000000 --- a/src/components/Button/styles/useSeverityButtonStyles.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { useMemo } from 'react' - -import type { ButtonSeverityProps } from '../types' - -import { useDangerButtonStyles } from './useDangerButtonStyles' -import { useInfoButtonStyles } from './useInfoButtonStyles' -import { useSuccessButtonStyles } from './useSuccessButtonStyles' -import { useWarningButtonStyles } from './useWarningButtonStyles' - -export const useSeverityButtonStyles = ( - severity: ButtonSeverityProps['severity'] -) => { - const dangerButtonStyles = useDangerButtonStyles() - const warningButtonStyles = useWarningButtonStyles() - const infoButtonStyles = useInfoButtonStyles() - const successButtonStyles = useSuccessButtonStyles() - - return useMemo(() => { - switch (severity) { - case 'danger': - return dangerButtonStyles - - case 'warning': - return warningButtonStyles - - case 'info': - return infoButtonStyles - - case 'success': - return successButtonStyles - } - }, [ - dangerButtonStyles, - infoButtonStyles, - severity, - successButtonStyles, - warningButtonStyles, - ]) -} diff --git a/src/components/Button/styles/useSuccessButtonStyles.ts b/src/components/Button/styles/useSuccessButtonStyles.ts deleted file mode 100644 index a80acc6b..00000000 --- a/src/components/Button/styles/useSuccessButtonStyles.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { makeStyles } from '../../../utils/makeStyles' - -export const useSuccessButtonStyles = () => { - const labelVariantStyles = useLabelVariantStyles() - const pressedVariantStyles = usePressedVariantStyles() - const containerVariantStyles = useContainerVariantStyles() - const iconVariantStyles = useIconVariantStyles() - - return { - containerVariantStyles, - labelVariantStyles, - pressedVariantStyles, - iconVariantStyles, - pressedLabelVariantStyles: labelVariantStyles, - } -} - -const useLabelVariantStyles = makeStyles(({ theme }) => ({ - basic: { color: theme.Button.Severity.Success.Basic.successButtonTextColor }, - - outlined: { - color: - theme.Button.Severity.Success.Outlined.successOutlinedButtonTextColor, - }, - - text: { - color: theme.Button.Severity.Success.Text.successTextButtonTextColor, - }, -})) - -const usePressedVariantStyles = makeStyles(({ theme }) => ({ - basic: { - borderColor: theme.Button.Severity.Success.Basic.successButtonBorderColor, - backgroundColor: theme.Button.Severity.Success.Basic.successButtonHoverBg, - }, - - outlined: { - borderColor: - theme.Button.Severity.Success.Outlined - .successOutlinedButtonHoverBorderColor, - backgroundColor: - theme.Button.Severity.Success.Outlined.successOutlinedButtonHoverBg, - }, - - text: { - borderColor: theme.Surface['surface-transparent'], - backgroundColor: - theme.Button.Severity.Success.Text.successTextButtonHoverBg, - }, -})) - -const useContainerVariantStyles = makeStyles(({ theme }) => ({ - basic: { - borderColor: theme.Button.Severity.Success.Basic.successButtonBorderColor, - backgroundColor: theme.Button.Severity.Success.Basic.successButtonBg, - }, - - outlined: { - borderColor: - theme.Button.Severity.Success.Outlined.successOutlinedButtonBorderColor, - backgroundColor: - theme.Button.Severity.Success.Outlined.successOutlinedButtonBg, - }, - - text: { - borderColor: theme.Button.Severity.Success.Basic.successButtonBorderColor, - backgroundColor: theme.Button.Severity.Success.Text.successTextButtonBg, - }, -})) - -const useIconVariantStyles = makeStyles(({ theme }) => ({ - basic: { color: theme.Button.Severity.Success.Basic.successButtonTextColor }, - - outlined: { - color: - theme.Button.Severity.Success.Outlined.successOutlinedButtonTextColor, - }, - - text: { - color: theme.Button.Severity.Success.Text.successTextButtonTextColor, - }, -})) diff --git a/src/components/Button/styles/useWarningButtonStyles.ts b/src/components/Button/styles/useWarningButtonStyles.ts deleted file mode 100644 index 6b588341..00000000 --- a/src/components/Button/styles/useWarningButtonStyles.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { makeStyles } from '../../../utils/makeStyles' - -export const useWarningButtonStyles = () => { - const labelVariantStyles = useLabelVariantStyles() - const pressedVariantStyles = usePressedVariantStyles() - const containerVariantStyles = useContainerVariantStyles() - const iconVariantStyles = useIconVariantStyles() - - return { - containerVariantStyles, - labelVariantStyles, - pressedVariantStyles, - iconVariantStyles, - pressedLabelVariantStyles: labelVariantStyles, - } -} - -const useLabelVariantStyles = makeStyles(({ theme }) => ({ - basic: { color: theme.Button.Severity.Warning.Basic.warningButtonTextColor }, - - outlined: { - color: - theme.Button.Severity.Warning.Outlined.warningOutlinedButtonTextColor, - }, - - text: { - color: theme.Button.Severity.Warning.Text.warningTextButtonTextColor, - }, -})) - -const usePressedVariantStyles = makeStyles(({ theme }) => ({ - basic: { - borderColor: theme.Button.Severity.Warning.Basic.warningButtonBorderColor, - backgroundColor: theme.Button.Severity.Warning.Basic.warningButtonHoverBg, - }, - - outlined: { - borderColor: - theme.Button.Severity.Warning.Outlined - .warningOutlinedButtonHoverBorderColor, - backgroundColor: - theme.Button.Severity.Warning.Outlined.warningOutlinedButtonHoverBg, - }, - - text: { - borderColor: theme.Surface['surface-transparent'], - backgroundColor: - theme.Button.Severity.Warning.Text.warningTextButtonHoverBg, - }, -})) - -const useContainerVariantStyles = makeStyles(({ theme }) => ({ - basic: { - borderColor: theme.Button.Severity.Warning.Basic.warningButtonBorderColor, - backgroundColor: theme.Button.Severity.Warning.Basic.warningButtonBg, - }, - - outlined: { - borderColor: - theme.Button.Severity.Warning.Outlined.warningOutlinedButtonBorderColor, - backgroundColor: - theme.Button.Severity.Warning.Outlined.warningOutlinedButtonBg, - }, - - text: { - borderColor: theme.Button.Severity.Warning.Basic.warningButtonBorderColor, - backgroundColor: theme.Button.Severity.Warning.Text.warningTextButtonBg, - }, -})) - -const useIconVariantStyles = makeStyles(({ theme }) => ({ - basic: { color: theme.Button.Severity.Warning.Basic.warningButtonTextColor }, - - outlined: { - color: - theme.Button.Severity.Warning.Outlined.warningOutlinedButtonTextColor, - }, - - text: { - color: theme.Button.Severity.Warning.Text.warningTextButtonTextColor, - }, -})) diff --git a/src/components/Button/types.ts b/src/components/Button/types.ts index e5768c1e..81bf64a4 100644 --- a/src/components/Button/types.ts +++ b/src/components/Button/types.ts @@ -97,27 +97,33 @@ export type ButtonProps = | IconTextButton | IconOnlyButtonProps +/** @deprecated */ export type LabelVariantStyles = Record< Required>['variant'], TextStyle > +/** @deprecated */ export type PressedVariantStyles = Record< Required>['variant'], ViewStyle > +/** @deprecated */ export type ContainerVariantStyles = Record< Required>['variant'], ViewStyle > +/** @deprecated */ export type IconVariantStyles = Record< Required>['variant'], { color: ColorValue } > +/** @deprecated */ export type PressedLabelVariantStyles = Record< Required>['variant'], { color: ColorValue } > +/** @deprecated */ export interface VariantStyles { containerVariantStyles: ContainerVariantStyles pressedVariantStyles: PressedVariantStyles diff --git a/src/components/Button/utils/ButtonActivityIndicator.tsx b/src/components/Button/utils/ButtonActivityIndicator.tsx index 441f080c..bcca0a18 100644 --- a/src/components/Button/utils/ButtonActivityIndicator.tsx +++ b/src/components/Button/utils/ButtonActivityIndicator.tsx @@ -1,44 +1,47 @@ import { ActivityIndicator } from 'react-native' +import { StyleSheet } from '../../../utils' import { genericMemo } from '../../../utils/genericMemo' -import { makeStyles } from '../../../utils/makeStyles' -import type { BaseButtonProps, ButtonVariant } from '../types' +import type { BaseButtonProps, ButtonSize } from '../types' -import { useTypeBasedStyle } from './useTypeBasedStyle' - -export type ButtonActivityIndicatorProps = Pick< - Required>, +export type ButtonActivityIndicatorProps = Pick< + Required>, 'size' -> +> & { readonly size: ButtonSize } -const ButtonActivityIndicatorComponent = ({ +const ButtonActivityIndicatorComponent = ({ size, -}: ButtonActivityIndicatorProps) => { - const styles = useStyles() - - const sizeBasedStyle = useTypeBasedStyle(size, styles) as { height: number } +}: ButtonActivityIndicatorProps) => { + buttonActivityIndicatorStyles.useVariants({ size }) return ( ) } -const useStyles = makeStyles(({ theme }) => ({ - xlarge: { height: 21 }, - - large: { height: 21 }, - - base: { height: 17.5 }, - - small: { height: 14 }, - - activityIndicator: { color: theme.Button.Disabled.disabledButtonTextColor }, -})) - export const ButtonActivityIndicator = genericMemo( ButtonActivityIndicatorComponent ) + +const buttonActivityIndicatorStyles = StyleSheet.create(({ theme }) => ({ + indicator: { + color: theme.Button.Disabled.disabledButtonTextColor, + variants: { + size: { + xlarge: { height: 21 }, + large: { height: 21 }, + base: { height: 17.5 }, + small: { height: 14 }, + }, + }, + }, +})) diff --git a/src/components/Button/utils/ButtonContainer.tsx b/src/components/Button/utils/ButtonContainer.tsx index b68219b4..53aad548 100644 --- a/src/components/Button/utils/ButtonContainer.tsx +++ b/src/components/Button/utils/ButtonContainer.tsx @@ -1,62 +1,77 @@ -import type { ReactNode } from 'react' -import { Pressable } from 'react-native' +/* eslint-disable max-lines */ +/* eslint-disable max-lines-per-function */ +import { type ReactNode, useContext } from 'react' +import { Pressable, type PressableStateCallbackType } from 'react-native' + +import { StyleSheet } from '../../../utils' import { genericMemo } from '../../../utils/genericMemo' -import type { BaseButtonProps, ButtonVariant, VariantStyles } from '../types' +import type { BaseButtonProps, ButtonShape, ButtonSize } from '../types' -import { useButtonContainerCallbackStyle } from './useButtonContainerCallbackStyle' +import { ButtonPressedContext } from './ButtonPressedContext' +import { ButtonVariantContext } from './ButtonVariantContext' -export type ButtonContainerComponentProps = Omit< - BaseButtonProps, - | 'size' - | 'variant' - | 'disabled' - | 'loading' - | 'shape' - | 'Icon' - | 'iconPosition' - | 'label' -> & - Pick< - Required>, - 'size' | 'variant' | 'disabled' | 'loading' | 'shape' - > & { readonly children: ReactNode } & Pick< - VariantStyles, - 'containerVariantStyles' | 'pressedVariantStyles' - > +export interface ButtonContainerProps { + readonly size: ButtonSize + readonly shape: ButtonShape + readonly disabled: boolean + readonly loading: boolean + readonly isIconOnly: boolean + readonly style?: BaseButtonProps['style'] + readonly pressableRef?: BaseButtonProps['pressableRef'] + readonly children: ReactNode + readonly onPressIn?: BaseButtonProps['onPressIn'] + readonly onPressOut?: BaseButtonProps['onPressOut'] + [key: string]: unknown +} -export const ButtonContainerComponent = ({ +const ButtonContainerComponent = ({ style, size, disabled, loading, - variant, shape, - iconOnly, + isIconOnly, children, - containerVariantStyles, - pressedVariantStyles, pressableRef, + onPressIn, + onPressOut, ...props -}: ButtonContainerComponentProps) => { - const containerCallbackStyle = useButtonContainerCallbackStyle( +}: ButtonContainerProps) => { + const pressed = useContext(ButtonPressedContext) + const { variant, severity } = useContext(ButtonVariantContext) + + buttonContainerStyles.useVariants({ size, - variant, shape, - disabled, - loading, - iconOnly, - style, - containerVariantStyles, - pressedVariantStyles - ) + variant, + severity, + pressed: pressed ? 'true' : 'false', + disabled: disabled || loading ? 'true' : 'false', + iconOnly: isIconOnly ? 'true' : 'false', + }) + + const resolvedStyle = + typeof style === 'function' + ? (state: PressableStateCallbackType) => [ + buttonContainerStyles.container, + isIconOnly && buttonContainerStyles.iconOnly, + style(state), + ] + : [ + buttonContainerStyles.container, + isIconOnly && buttonContainerStyles.iconOnly, + ...(style ? [style] : []), + ] return ( {children} @@ -65,3 +80,442 @@ export const ButtonContainerComponent = ({ } export const ButtonContainer = genericMemo(ButtonContainerComponent) + +const buttonContainerStyles = StyleSheet.create( + ({ theme, border, spacing, sizing }) => ({ + container: { + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + borderWidth: border.Width.border, + variants: { + size: { + xlarge: { + paddingHorizontal: spacing.Padding['p-6'], + paddingVertical: theme.Button.Common.buttonPaddingTopBottom, + height: theme.Button.Common.buttonHeightXL, + minHeight: theme.Button.Common.buttonHeightXL, + maxHeight: theme.Button.Common.buttonHeightXL, + gap: spacing.Gap['gap-3'], + borderRadius: theme.General.borderRadius2XL, + }, + large: { + paddingHorizontal: spacing.Padding['p-6'], + paddingVertical: theme.Button.Common.buttonPaddingTopBottom, + height: theme.Button.Common.buttonHeightLG, + minHeight: theme.Button.Common.buttonHeightLG, + maxHeight: theme.Button.Common.buttonHeightLG, + gap: spacing.Gap['gap-3'], + borderRadius: theme.General.borderRadius2XL, + }, + base: { + paddingHorizontal: theme.Button.Common.buttonPaddingLeftRight, + paddingVertical: theme.Button.Common.buttonPaddingTopBottom, + height: theme.Button.Common.buttonHeight, + minHeight: theme.Button.Common.buttonHeight, + maxHeight: theme.Button.Common.buttonHeight, + gap: theme.General.inlineSpacing, + borderRadius: theme.General.borderRadiusXL, + }, + small: { + paddingHorizontal: spacing.Padding['p-3'], + paddingVertical: theme.Button.Common.buttonPaddingTopBottom, + height: theme.Button.Common.buttonHeightSM, + minHeight: theme.Button.Common.buttonHeightSM, + maxHeight: theme.Button.Common.buttonHeightSM, + gap: theme.General.inlineSpacing, + borderRadius: theme.General.borderRadiusXL, + }, + }, + shape: { + square: {}, + circle: { borderRadius: border.Radius['rounded-full'] }, + }, + variant: { + primary: { + borderColor: theme.Button.Brand.buttonBorderColor, + backgroundColor: theme.Button.Brand.buttonBg, + }, + secondary: { + borderColor: theme.Button.Primary.secondaryButtonBorderColor, + backgroundColor: theme.Button.Primary.secondaryButtonBg, + }, + tertiary: { + borderColor: theme.Button.Secondary.helpButtonBorderColor, + backgroundColor: theme.Button.Secondary.helpButtonBg, + }, + text: { + borderColor: theme.Button.Brand.buttonBorderColor, + backgroundColor: theme.Button.Text.textButtonBg, + }, + link: { + paddingHorizontal: 0, + paddingVertical: spacing.Padding['p-1'], + height: 'auto', + minHeight: 'auto', + borderColor: theme.Button.Brand.buttonBorderColor, + backgroundColor: theme.Button.Text.textButtonBg, + }, + basic: { + borderColor: theme.Button.Brand.buttonBorderColor, + backgroundColor: theme.Button.Brand.buttonBg, + }, + outlined: { + borderColor: theme.Button.Brand.buttonBorderColor, + backgroundColor: theme.Button.Text.textButtonBg, + }, + }, + severity: { info: {}, success: {}, warning: {}, danger: {} }, + pressed: { true: {}, false: {} }, + disabled: { + true: { + backgroundColor: theme.Button.Disabled.disabledButtonBg, + borderColor: theme.Button.Disabled.disabledButtonBorderColor, + }, + false: {}, + }, + iconOnly: { true: {}, false: {} }, + }, + compoundVariants: [ + // basic button pressed + { + variant: 'primary', + pressed: 'true', + styles: { + borderColor: theme.Button.Brand.buttonBorderColor, + backgroundColor: theme.Button.Brand.buttonHoverBg, + }, + }, + { + variant: 'secondary', + pressed: 'true', + styles: { + borderColor: theme.Button.Primary.secondaryButtonHoverBorderColor, + backgroundColor: theme.Button.Primary.secondaryButtonHoverBg, + }, + }, + { + variant: 'tertiary', + pressed: 'true', + styles: { + borderColor: theme.Button.Secondary.helpButtonHoverBorderColor, + backgroundColor: theme.Button.Secondary.helpButtonHoverBg, + }, + }, + { + variant: 'text', + pressed: 'true', + styles: { + borderColor: theme.Button.Brand.buttonBorderColor, + backgroundColor: theme.Button.Text.textButtonHoverBg, + }, + }, + + // severity container (override placeholder variant styles) + { + variant: 'basic', + severity: 'info', + styles: { + borderColor: theme.Button.Severity.Info.Basic.infoButtonBorderColor, + backgroundColor: theme.Button.Severity.Info.Basic.infoButtonBg, + }, + }, + { + variant: 'outlined', + severity: 'info', + styles: { + borderColor: + theme.Button.Severity.Info.Outlined.infoOutlinedButtonBorderColor, + backgroundColor: + theme.Button.Severity.Info.Outlined.infoOutlinedButtonBg, + }, + }, + { + variant: 'text', + severity: 'info', + styles: { + borderColor: theme.Button.Severity.Info.Basic.infoButtonBorderColor, + backgroundColor: theme.Button.Severity.Info.Text.infoTextButtonBg, + }, + }, + { + variant: 'basic', + severity: 'success', + styles: { + borderColor: + theme.Button.Severity.Success.Basic.successButtonBorderColor, + backgroundColor: + theme.Button.Severity.Success.Basic.successButtonBg, + }, + }, + { + variant: 'outlined', + severity: 'success', + styles: { + borderColor: + theme.Button.Severity.Success.Outlined + .successOutlinedButtonBorderColor, + backgroundColor: + theme.Button.Severity.Success.Outlined.successOutlinedButtonBg, + }, + }, + { + variant: 'text', + severity: 'success', + styles: { + borderColor: + theme.Button.Severity.Success.Basic.successButtonBorderColor, + backgroundColor: + theme.Button.Severity.Success.Text.successTextButtonBg, + }, + }, + { + variant: 'basic', + severity: 'warning', + styles: { + borderColor: + theme.Button.Severity.Warning.Basic.warningButtonBorderColor, + backgroundColor: + theme.Button.Severity.Warning.Basic.warningButtonBg, + }, + }, + { + variant: 'outlined', + severity: 'warning', + styles: { + borderColor: + theme.Button.Severity.Warning.Outlined + .warningOutlinedButtonBorderColor, + backgroundColor: + theme.Button.Severity.Warning.Outlined.warningOutlinedButtonBg, + }, + }, + { + variant: 'text', + severity: 'warning', + styles: { + borderColor: + theme.Button.Severity.Warning.Basic.warningButtonBorderColor, + backgroundColor: + theme.Button.Severity.Warning.Text.warningTextButtonBg, + }, + }, + { + variant: 'basic', + severity: 'danger', + styles: { + borderColor: + theme.Button.Severity.Danger.Basic.dangerButtonBorderColor, + backgroundColor: theme.Button.Severity.Danger.Basic.dangerButtonBg, + }, + }, + { + variant: 'outlined', + severity: 'danger', + styles: { + borderColor: + theme.Button.Severity.Danger.Outlined + .dangerOutlinedButtonBorderColor, + backgroundColor: + theme.Button.Severity.Danger.Outlined.dangerOutlinedButtonBg, + }, + }, + { + variant: 'text', + severity: 'danger', + styles: { + borderColor: + theme.Button.Severity.Danger.Basic.dangerButtonBorderColor, + backgroundColor: + theme.Button.Severity.Danger.Text.dangerTextButtonBg, + }, + }, + + // severity pressed + { + variant: 'basic', + severity: 'info', + pressed: 'true', + styles: { + borderColor: theme.Button.Severity.Info.Basic.infoButtonBorderColor, + backgroundColor: theme.Button.Severity.Info.Basic.infoButtonHoverBg, + }, + }, + { + variant: 'outlined', + severity: 'info', + pressed: 'true', + styles: { + borderColor: + theme.Button.Severity.Info.Outlined + .infoOutlinedButtonHoverBorderColor, + backgroundColor: + theme.Button.Severity.Info.Outlined.infoOutlinedButtonHoverBg, + }, + }, + { + variant: 'text', + severity: 'info', + pressed: 'true', + styles: { + borderColor: theme.Surface['surface-transparent'], + backgroundColor: + theme.Button.Severity.Info.Text.infoTextButtonHoverBg, + }, + }, + { + variant: 'basic', + severity: 'success', + pressed: 'true', + styles: { + borderColor: + theme.Button.Severity.Success.Basic.successButtonBorderColor, + backgroundColor: + theme.Button.Severity.Success.Basic.successButtonHoverBg, + }, + }, + { + variant: 'outlined', + severity: 'success', + pressed: 'true', + styles: { + borderColor: + theme.Button.Severity.Success.Outlined + .successOutlinedButtonHoverBorderColor, + backgroundColor: + theme.Button.Severity.Success.Outlined + .successOutlinedButtonHoverBg, + }, + }, + { + variant: 'text', + severity: 'success', + pressed: 'true', + styles: { + borderColor: theme.Surface['surface-transparent'], + backgroundColor: + theme.Button.Severity.Success.Text.successTextButtonHoverBg, + }, + }, + { + variant: 'basic', + severity: 'warning', + pressed: 'true', + styles: { + borderColor: + theme.Button.Severity.Warning.Basic.warningButtonBorderColor, + backgroundColor: + theme.Button.Severity.Warning.Basic.warningButtonHoverBg, + }, + }, + { + variant: 'outlined', + severity: 'warning', + pressed: 'true', + styles: { + borderColor: + theme.Button.Severity.Warning.Outlined + .warningOutlinedButtonHoverBorderColor, + backgroundColor: + theme.Button.Severity.Warning.Outlined + .warningOutlinedButtonHoverBg, + }, + }, + { + variant: 'text', + severity: 'warning', + pressed: 'true', + styles: { + borderColor: theme.Surface['surface-transparent'], + backgroundColor: + theme.Button.Severity.Warning.Text.warningTextButtonHoverBg, + }, + }, + { + variant: 'basic', + severity: 'danger', + pressed: 'true', + styles: { + borderColor: + theme.Button.Severity.Danger.Basic.dangerButtonBorderColor, + backgroundColor: + theme.Button.Severity.Danger.Basic.dangerButtonHoverBg, + }, + }, + { + variant: 'outlined', + severity: 'danger', + pressed: 'true', + styles: { + borderColor: + theme.Button.Severity.Danger.Outlined + .dangerOutlinedButtonHoverBorderColor, + backgroundColor: + theme.Button.Severity.Danger.Outlined.dangerOutlinedButtonHoverBg, + }, + }, + { + variant: 'text', + severity: 'danger', + pressed: 'true', + styles: { + borderColor: theme.Surface['surface-transparent'], + backgroundColor: + theme.Button.Severity.Danger.Text.dangerTextButtonHoverBg, + }, + }, + + // link + iconOnly special sizes + { + variant: 'link', + iconOnly: 'true', + size: 'xlarge', + styles: { + paddingHorizontal: spacing.Gap['gap-1'], + paddingVertical: spacing.Gap['gap-1'], + height: sizing.Height['h-2'], + minHeight: sizing.Height['h-2'], + maxHeight: sizing.Height['h-2'], + }, + }, + { + variant: 'link', + iconOnly: 'true', + size: 'large', + styles: { + paddingHorizontal: spacing.Gap['gap-0'], + paddingVertical: spacing.Gap['gap-0'], + height: 24.5, + minHeight: 24.5, + maxHeight: 24.5, + }, + }, + { + variant: 'link', + iconOnly: 'true', + size: 'base', + styles: { + paddingHorizontal: spacing.Gap['gap-1'], + paddingVertical: spacing.Gap['gap-1'], + height: 21.5, + minHeight: 21.5, + maxHeight: 21.5, + }, + }, + { + variant: 'link', + iconOnly: 'true', + size: 'small', + styles: { + paddingHorizontal: spacing.Gap['gap-1'], + paddingVertical: spacing.Gap['gap-1'], + height: sizing.Height['h-1'], + minHeight: sizing.Height['h-2'], + maxHeight: sizing.Height['h-2'], + }, + }, + ], + }, + iconOnly: { aspectRatio: 1 }, + }) +) diff --git a/src/components/Button/utils/ButtonIcon.tsx b/src/components/Button/utils/ButtonIcon.tsx index a9bbfae4..cd66566e 100644 --- a/src/components/Button/utils/ButtonIcon.tsx +++ b/src/components/Button/utils/ButtonIcon.tsx @@ -1,31 +1,36 @@ +import { useContext } from 'react' + +import { StyleSheet } from '../../../utils' import { SvgUniversal } from '../../../utils/SvgUniversal' import { genericMemo } from '../../../utils/genericMemo' -import type { BaseButtonProps, ButtonVariant, VariantStyles } from '../types' +import type { BaseButtonProps, ButtonSize } from '../types' -import { useIconStyle } from './useIconStyle' +import { ButtonPressedContext } from './ButtonPressedContext' +import { ButtonVariantContext } from './ButtonVariantContext' -export type ButtonIconComponentProps = Pick< - Required>, - 'size' | 'variant' | 'disabled' | 'loading' -> & - Pick, 'Icon'> & - Pick, 'iconVariantStyles'> +export interface ButtonIconProps { + readonly size: ButtonSize + readonly disabled: boolean + readonly loading: boolean + readonly Icon?: BaseButtonProps['Icon'] +} -export const ButtonIconComponent = ({ +const ButtonIconComponent = ({ size, - variant, disabled, loading, Icon, - iconVariantStyles, -}: ButtonIconComponentProps) => { - const iconStyle = useIconStyle( +}: ButtonIconProps) => { + const pressed = useContext(ButtonPressedContext) + const { variant, severity } = useContext(ButtonVariantContext) + + buttonIconStyles.useVariants({ size, variant, - disabled, - loading, - iconVariantStyles - ) + severity, + pressed: pressed ? 'true' : 'false', + disabled: disabled || loading ? 'true' : 'false', + }) if (!Icon) { return null @@ -33,13 +38,151 @@ export const ButtonIconComponent = ({ return ( ) } export const ButtonIcon = genericMemo(ButtonIconComponent) + +const buttonIconStyles = StyleSheet.create(({ theme, typography }) => ({ + icon: { + variants: { + size: { + xlarge: { + height: typography.Size['text-2xl'], + width: typography.Size['text-2xl'], + }, + large: { + height: typography.Size['text-2xl'], + width: typography.Size['text-2xl'], + }, + base: { + height: typography.Size['text-xl'], + width: typography.Size['text-xl'], + }, + small: { + height: typography.Size['text-base'], + width: typography.Size['text-base'], + }, + }, + variant: { + primary: { color: theme.Button.Brand.buttonTextColor }, + secondary: { color: theme.Button.Primary.secondaryButtonTextColor }, + tertiary: { color: theme.Button.Secondary.helpButtonTextColor }, + text: { color: theme.Button.Text.textButtonTextColor }, + link: { color: theme.Button.Text.textButtonTextColor }, + basic: { color: theme.Button.Brand.buttonTextColor }, + outlined: { color: theme.Button.Brand.buttonTextColor }, + }, + severity: { info: {}, success: {}, warning: {}, danger: {} }, + pressed: { true: {}, false: {} }, + disabled: { + true: { color: theme.Button.Disabled.disabledButtonTextColor }, + false: {}, + }, + }, + compoundVariants: [ + // link pressed + { + variant: 'link', + pressed: 'true', + styles: { color: typography.Color.Common['text-color-secondary'] }, + }, + + // severity icon colors + { + variant: 'basic', + severity: 'info', + styles: { color: theme.Button.Severity.Info.Basic.infoButtonTextColor }, + }, + { + variant: 'outlined', + severity: 'info', + styles: { + color: + theme.Button.Severity.Info.Outlined.infoOutlinedButtonTextColor, + }, + }, + { + variant: 'text', + severity: 'info', + styles: { + color: theme.Button.Severity.Info.Text.infoTextButtonTextColor, + }, + }, + { + variant: 'basic', + severity: 'success', + styles: { + color: theme.Button.Severity.Success.Basic.successButtonTextColor, + }, + }, + { + variant: 'outlined', + severity: 'success', + styles: { + color: + theme.Button.Severity.Success.Outlined + .successOutlinedButtonTextColor, + }, + }, + { + variant: 'text', + severity: 'success', + styles: { + color: theme.Button.Severity.Success.Text.successTextButtonTextColor, + }, + }, + { + variant: 'basic', + severity: 'warning', + styles: { + color: theme.Button.Severity.Warning.Basic.warningButtonTextColor, + }, + }, + { + variant: 'outlined', + severity: 'warning', + styles: { + color: + theme.Button.Severity.Warning.Outlined + .warningOutlinedButtonTextColor, + }, + }, + { + variant: 'text', + severity: 'warning', + styles: { + color: theme.Button.Severity.Warning.Text.warningTextButtonTextColor, + }, + }, + { + variant: 'basic', + severity: 'danger', + styles: { + color: theme.Button.Severity.Danger.Basic.dangerButtonTextColor, + }, + }, + { + variant: 'outlined', + severity: 'danger', + styles: { + color: + theme.Button.Severity.Danger.Outlined.dangerOutlinedButtonTextColor, + }, + }, + { + variant: 'text', + severity: 'danger', + styles: { + color: theme.Button.Severity.Danger.Text.dangerTextButtonTextColor, + }, + }, + ], + }, +})) diff --git a/src/components/Button/utils/ButtonLabel.tsx b/src/components/Button/utils/ButtonLabel.tsx index 9971bd96..235e423d 100644 --- a/src/components/Button/utils/ButtonLabel.tsx +++ b/src/components/Button/utils/ButtonLabel.tsx @@ -1,46 +1,178 @@ +import { useContext } from 'react' import { Text } from 'react-native' +import { StyleSheet } from '../../../utils' import { genericMemo } from '../../../utils/genericMemo' -import type { BaseButtonProps, ButtonVariant, VariantStyles } from '../types' +import type { BaseButtonProps, ButtonSize } from '../types' -import { useButtonLabelStyle } from './useButtonLabelStyle' +import { ButtonPressedContext } from './ButtonPressedContext' +import { ButtonVariantContext } from './ButtonVariantContext' -export type ButtonLabelComponentProps = Pick< - BaseButtonProps, - 'iconOnly' | 'label' -> & - Pick< - Required>, - 'size' | 'variant' | 'disabled' | 'loading' - > & - Pick, 'labelVariantStyles'> +export interface ButtonLabelProps { + readonly iconOnly?: BaseButtonProps['iconOnly'] + readonly label?: string + readonly size: ButtonSize + readonly disabled: boolean + readonly loading: boolean +} -export const ButtonLabelComponent = ({ +const ButtonLabelComponent = ({ label, iconOnly, size, disabled, loading, - variant, - labelVariantStyles, -}: ButtonLabelComponentProps) => { - const labelStyle = useButtonLabelStyle( +}: ButtonLabelProps) => { + const pressed = useContext(ButtonPressedContext) + const { variant, severity } = useContext(ButtonVariantContext) + + buttonLabelStyles.useVariants({ size, variant, - disabled, - loading, - labelVariantStyles - ) + severity, + pressed: pressed ? 'true' : 'false', + disabled: disabled || loading ? 'true' : 'false', + }) if (iconOnly) { return null } return ( - + {label} ) } export const ButtonLabel = genericMemo(ButtonLabelComponent) + +const buttonLabelStyles = StyleSheet.create(({ theme, typography, fonts }) => ({ + label: { + fontWeight: 600, + includeFontPadding: false, + verticalAlign: 'middle', + fontFamily: fonts.primary, + variants: { + size: { + xlarge: { fontSize: typography.Size['text-xl'] }, + large: { fontSize: typography.Size['text-xl'] }, + base: { fontSize: typography.Size['text-base'] }, + small: { fontSize: typography.Size['text-sm'] }, + }, + variant: { + primary: { color: theme.Button.Brand.buttonTextColor }, + secondary: { color: theme.Button.Primary.secondaryButtonTextColor }, + tertiary: { color: theme.Button.Secondary.helpButtonTextColor }, + text: { color: theme.Button.Text.textButtonTextColor }, + link: { color: theme.Button.Text.textButtonTextColor }, + basic: { color: theme.Button.Brand.buttonTextColor }, + outlined: { color: theme.Button.Brand.buttonTextColor }, + }, + severity: { info: {}, success: {}, warning: {}, danger: {} }, + pressed: { true: {}, false: {} }, + disabled: { + true: { color: theme.Button.Disabled.disabledButtonTextColor }, + false: {}, + }, + }, + compoundVariants: [ + // link pressed color change + { + variant: 'link', + pressed: 'true', + styles: { color: typography.Color.Common['text-color-secondary'] }, + }, + + // severity label colors + { + variant: 'basic', + severity: 'info', + styles: { color: theme.Button.Severity.Info.Basic.infoButtonTextColor }, + }, + { + variant: 'outlined', + severity: 'info', + styles: { + color: + theme.Button.Severity.Info.Outlined.infoOutlinedButtonTextColor, + }, + }, + { + variant: 'text', + severity: 'info', + styles: { + color: theme.Button.Severity.Info.Text.infoTextButtonTextColor, + }, + }, + { + variant: 'basic', + severity: 'success', + styles: { + color: theme.Button.Severity.Success.Basic.successButtonTextColor, + }, + }, + { + variant: 'outlined', + severity: 'success', + styles: { + color: + theme.Button.Severity.Success.Outlined + .successOutlinedButtonTextColor, + }, + }, + { + variant: 'text', + severity: 'success', + styles: { + color: theme.Button.Severity.Success.Text.successTextButtonTextColor, + }, + }, + { + variant: 'basic', + severity: 'warning', + styles: { + color: theme.Button.Severity.Warning.Basic.warningButtonTextColor, + }, + }, + { + variant: 'outlined', + severity: 'warning', + styles: { + color: + theme.Button.Severity.Warning.Outlined + .warningOutlinedButtonTextColor, + }, + }, + { + variant: 'text', + severity: 'warning', + styles: { + color: theme.Button.Severity.Warning.Text.warningTextButtonTextColor, + }, + }, + { + variant: 'basic', + severity: 'danger', + styles: { + color: theme.Button.Severity.Danger.Basic.dangerButtonTextColor, + }, + }, + { + variant: 'outlined', + severity: 'danger', + styles: { + color: + theme.Button.Severity.Danger.Outlined.dangerOutlinedButtonTextColor, + }, + }, + { + variant: 'text', + severity: 'danger', + styles: { + color: theme.Button.Severity.Danger.Text.dangerTextButtonTextColor, + }, + }, + ], + }, +})) diff --git a/src/components/Button/utils/ButtonLeftArea.tsx b/src/components/Button/utils/ButtonLeftArea.tsx index 5ea7b3ed..55569a2b 100644 --- a/src/components/Button/utils/ButtonLeftArea.tsx +++ b/src/components/Button/utils/ButtonLeftArea.tsx @@ -1,38 +1,30 @@ import { genericMemo } from '../../../utils/genericMemo' -import type { BaseButtonProps, ButtonVariant, VariantStyles } from '../types' +import type { BaseButtonProps, ButtonSize } from '../types' import { ButtonActivityIndicator } from './ButtonActivityIndicator' import { ButtonIcon } from './ButtonIcon' -export type ButtonLeftAreaComponentProps = Pick< - BaseButtonProps, - 'iconPosition' | 'Icon' -> & - Pick< - Required>, - 'size' | 'variant' | 'loading' | 'disabled' - > & - Pick, 'iconVariantStyles'> +export interface ButtonLeftAreaProps { + readonly size: ButtonSize + readonly iconPosition?: BaseButtonProps['iconPosition'] + readonly loading: boolean + readonly disabled: boolean + readonly Icon?: BaseButtonProps['Icon'] +} -export const ButtonLeftAreaComponent = ({ +const ButtonLeftAreaComponent = ({ size, iconPosition, - variant, Icon, loading, disabled, - iconVariantStyles, -}: ButtonLeftAreaComponentProps) => { +}: ButtonLeftAreaProps) => { if (iconPosition === 'left' || iconPosition === 'prefix') { if (loading && !disabled) { return } - return ( - - ) + return } return null diff --git a/src/components/Button/utils/ButtonPressedContext.ts b/src/components/Button/utils/ButtonPressedContext.ts new file mode 100644 index 00000000..0a8bfcc6 --- /dev/null +++ b/src/components/Button/utils/ButtonPressedContext.ts @@ -0,0 +1,3 @@ +import { createContext } from 'react' + +export const ButtonPressedContext = createContext(false) diff --git a/src/components/Button/utils/ButtonRightArea.tsx b/src/components/Button/utils/ButtonRightArea.tsx index 0a98ad87..5967b751 100644 --- a/src/components/Button/utils/ButtonRightArea.tsx +++ b/src/components/Button/utils/ButtonRightArea.tsx @@ -1,38 +1,30 @@ import { genericMemo } from '../../../utils/genericMemo' -import type { BaseButtonProps, ButtonVariant, VariantStyles } from '../types' +import type { BaseButtonProps, ButtonSize } from '../types' import { ButtonActivityIndicator } from './ButtonActivityIndicator' import { ButtonIcon } from './ButtonIcon' -export type ButtonRightAreaComponentProps = Pick< - BaseButtonProps, - 'iconPosition' | 'Icon' -> & - Pick< - Required>, - 'size' | 'variant' | 'loading' | 'disabled' - > & - Pick, 'iconVariantStyles'> +export interface ButtonRightAreaProps { + readonly size: ButtonSize + readonly iconPosition?: BaseButtonProps['iconPosition'] + readonly loading: boolean + readonly disabled: boolean + readonly Icon?: BaseButtonProps['Icon'] +} -const ButtonRightAreaComponent = ({ +const ButtonRightAreaComponent = ({ size, iconPosition, - variant, Icon, loading, disabled, - iconVariantStyles, -}: ButtonRightAreaComponentProps) => { +}: ButtonRightAreaProps) => { if (iconPosition === 'right' || iconPosition === 'postfix') { if (loading && !disabled) { return } - return ( - - ) + return } return null diff --git a/src/components/Button/utils/ButtonVariantContext.ts b/src/components/Button/utils/ButtonVariantContext.ts new file mode 100644 index 00000000..82cafe05 --- /dev/null +++ b/src/components/Button/utils/ButtonVariantContext.ts @@ -0,0 +1,12 @@ +import { createContext } from 'react' + +import type { ButtonSeverity, ButtonVariant } from '../types' + +export interface ButtonVariantContextValue { + variant: ButtonVariant + severity?: ButtonSeverity +} + +export const ButtonVariantContext = createContext({ + variant: 'primary', +}) diff --git a/src/components/Button/utils/useButtonContainerCallbackStyle.ts b/src/components/Button/utils/useButtonContainerCallbackStyle.ts deleted file mode 100644 index db447592..00000000 --- a/src/components/Button/utils/useButtonContainerCallbackStyle.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { useCallback, useMemo } from 'react' -import { - type PressableStateCallbackType, - type StyleProp, - StyleSheet, - type ViewStyle, -} from 'react-native' - -import { makeStyles } from '../../../utils/makeStyles' -import type { - BaseButtonProps, - ButtonVariant, - ContainerVariantStyles, - PressedVariantStyles, -} from '../types' - -import { useTypeBasedStyle } from './useTypeBasedStyle' - -export const useButtonContainerCallbackStyle = ( - size: Required>['size'], - variant: Required>['variant'], - shape: Required>['shape'], - disabled: Required>['disabled'], - loading: Required>['loading'], - iconOnly: Required>['iconOnly'], - style: BaseButtonProps['style'], - containerVariantStyles: ContainerVariantStyles, - pressedVariantStyles: PressedVariantStyles -) => { - const styles = useButtonContainerStyle() - - const sizeBasedStyle = useTypeBasedStyle(size, styles) - const variantBasedStyle = useTypeBasedStyle(variant, containerVariantStyles) - const shapeBasedStyle = useTypeBasedStyle(shape, styles) - const pressedStyle = useTypeBasedStyle(variant, pressedVariantStyles) - const iconOnlyLinkContainerStyle = useIconOnlyLinkContainerStyle() - const disabledStyle = useMemo(() => { - if (variant === 'link') { - return - } - - return styles.disabled - }, [styles.disabled, variant]) - - return useCallback( - ({ pressed }: PressableStateCallbackType) => { - const containerStyle: Array> = [ - styles.container, - sizeBasedStyle, - variantBasedStyle, - shapeBasedStyle, - ] - - if (iconOnly) { - containerStyle.push(styles.iconOnly) - - if (variant === 'link') { - containerStyle.push(iconOnlyLinkContainerStyle[size]) - } - } - - if (disabled || loading) { - containerStyle.push(disabledStyle) - } - - if (pressed) { - containerStyle.push(pressedStyle) - } - - if (typeof style === 'function') { - containerStyle.push(style({ pressed })) - } else { - containerStyle.push(style) - } - - return StyleSheet.flatten(containerStyle) - }, - [ - disabled, - disabledStyle, - iconOnly, - iconOnlyLinkContainerStyle, - loading, - pressedStyle, - shapeBasedStyle, - size, - sizeBasedStyle, - style, - styles.container, - styles.iconOnly, - variant, - variantBasedStyle, - ] - ) -} - -const useButtonContainerStyle = makeStyles(({ theme, border, spacing }) => ({ - container: { - flexDirection: 'row', - justifyContent: 'center', - alignItems: 'center', - borderWidth: border.Width.border, - }, - - xlarge: { - paddingHorizontal: spacing.Padding['p-6'], - paddingVertical: theme.Button.Common.buttonPaddingTopBottom, - height: theme.Button.Common.buttonHeightXL, - minHeight: theme.Button.Common.buttonHeightXL, - maxHeight: theme.Button.Common.buttonHeightXL, - gap: spacing.Gap['gap-3'], - borderRadius: theme.General.borderRadius2XL, - }, - - large: { - paddingHorizontal: spacing.Padding['p-6'], - paddingVertical: theme.Button.Common.buttonPaddingTopBottom, - height: theme.Button.Common.buttonHeightLG, - minHeight: theme.Button.Common.buttonHeightLG, - maxHeight: theme.Button.Common.buttonHeightLG, - gap: spacing.Gap['gap-3'], - borderRadius: theme.General.borderRadius2XL, - }, - - base: { - paddingHorizontal: theme.Button.Common.buttonPaddingLeftRight, - paddingVertical: theme.Button.Common.buttonPaddingTopBottom, - height: theme.Button.Common.buttonHeight, - minHeight: theme.Button.Common.buttonHeight, - maxHeight: theme.Button.Common.buttonHeight, - gap: theme.General.inlineSpacing, - borderRadius: theme.General.borderRadiusXL, - }, - - small: { - paddingHorizontal: spacing.Padding['p-3'], - paddingVertical: theme.Button.Common.buttonPaddingTopBottom, - height: theme.Button.Common.buttonHeightSM, - minHeight: theme.Button.Common.buttonHeightSM, - maxHeight: theme.Button.Common.buttonHeightSM, - gap: theme.General.inlineSpacing, - borderRadius: theme.General.borderRadiusXL, - }, - - square: {}, - - circle: { borderRadius: border.Radius['rounded-full'] }, - - disabled: { - backgroundColor: theme.Button.Disabled.disabledButtonBg, - borderColor: theme.Button.Disabled.disabledButtonBorderColor, - }, - - iconOnly: { aspectRatio: 1 }, -})) - -const useIconOnlyLinkContainerStyle = makeStyles(({ spacing, sizing }) => ({ - xlarge: { - paddingHorizontal: spacing.Gap['gap-1'], - paddingVertical: spacing.Gap['gap-1'], - height: sizing.Height['h-2'], - minHeight: sizing.Height['h-2'], - maxHeight: sizing.Height['h-2'], - }, - - large: { - paddingHorizontal: spacing.Gap['gap-0'], - paddingVertical: spacing.Gap['gap-0'], - height: 24.5, - minHeight: 24.5, - maxHeight: 24.5, - }, - - base: { - paddingHorizontal: spacing.Gap['gap-1'], - paddingVertical: spacing.Gap['gap-1'], - height: 21.5, - minHeight: 21.5, - maxHeight: 21.5, - }, - - small: { - paddingHorizontal: spacing.Gap['gap-1'], - paddingVertical: spacing.Gap['gap-1'], - height: sizing.Height['h-1'], - minHeight: sizing.Height['h-2'], - maxHeight: sizing.Height['h-2'], - }, -})) diff --git a/src/components/Button/utils/useButtonLabelStyle.ts b/src/components/Button/utils/useButtonLabelStyle.ts deleted file mode 100644 index e403303c..00000000 --- a/src/components/Button/utils/useButtonLabelStyle.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { useMemo } from 'react' -import { type StyleProp, StyleSheet, type TextStyle } from 'react-native' - -import { makeStyles } from '../../../utils/makeStyles' -import type { - BaseButtonProps, - ButtonVariant, - LabelVariantStyles, -} from '../types' - -import { useTypeBasedStyle } from './useTypeBasedStyle' - -export const useButtonLabelStyle = ( - size: Required>['size'], - variant: Required>['variant'], - disabled: Required>['disabled'], - loading: Required>['loading'], - labelVariantStyles: LabelVariantStyles -) => { - const styles = useButtonLabelStyles() - - const sizeBasedStyle = useTypeBasedStyle(size, styles) - const variantBasedStyle = useTypeBasedStyle(variant, labelVariantStyles) - - return useMemo(() => { - const containerStyle: Array> = [ - styles.font, - sizeBasedStyle, - variantBasedStyle, - ] - - if (disabled || loading) { - containerStyle.push(styles.disabled) - } - - return StyleSheet.flatten(containerStyle) - }, [ - disabled, - loading, - sizeBasedStyle, - styles.disabled, - styles.font, - variantBasedStyle, - ]) -} - -const useButtonLabelStyles = makeStyles(({ theme, typography, fonts }) => ({ - font: { - fontWeight: 600, - includeFontPadding: false, - verticalAlign: 'middle', - fontFamily: fonts.primary, - }, - - xlarge: { fontSize: typography.Size['text-xl'] }, - - large: { fontSize: typography.Size['text-xl'] }, - - base: { fontSize: typography.Size['text-base'] }, - - small: { fontSize: typography.Size['text-sm'] }, - - disabled: { color: theme.Button.Disabled.disabledButtonTextColor }, -})) diff --git a/src/components/Button/utils/useIconStyle.ts b/src/components/Button/utils/useIconStyle.ts deleted file mode 100644 index f319ac27..00000000 --- a/src/components/Button/utils/useIconStyle.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { useMemo } from 'react' -import { type ColorValue, StyleSheet, type TextStyle } from 'react-native' - -import { makeStyles } from '../../../utils/makeStyles' -import type { - BaseButtonProps, - ButtonVariant, - IconVariantStyles, -} from '../types' - -import { useTypeBasedStyle } from './useTypeBasedStyle' - -export const useIconStyle = ( - size: Required>['size'], - variant: Required>['variant'], - disabled: Required>['disabled'], - loading: Required>['loading'], - iconVariantStyles: IconVariantStyles -) => { - const styles = useStyles() - - const sizeBasedStyle = useTypeBasedStyle(size, styles) - const variantBasedStyle = useTypeBasedStyle(variant, iconVariantStyles) - - return useMemo(() => { - const containerStyle = [sizeBasedStyle, variantBasedStyle] as TextStyle[] - - if (disabled || loading) { - containerStyle.push(styles.disabled) - } - - return StyleSheet.flatten(containerStyle) as { - width: number - height: number - color: ColorValue - } - }, [disabled, loading, sizeBasedStyle, styles.disabled, variantBasedStyle]) -} - -const useStyles = makeStyles(({ theme, typography }) => ({ - xlarge: { - height: typography.Size['text-2xl'], - width: typography.Size['text-2xl'], - }, - - large: { - height: typography.Size['text-2xl'], - width: typography.Size['text-2xl'], - }, - - base: { - height: typography.Size['text-xl'], - width: typography.Size['text-xl'], - }, - - small: { - height: typography.Size['text-base'], - width: typography.Size['text-base'], - }, - - disabled: { color: theme.Button.Disabled.disabledButtonTextColor }, -})) diff --git a/src/components/Button/utils/useTypeBasedStyle.ts b/src/components/Button/utils/useTypeBasedStyle.ts deleted file mode 100644 index afcdcaf7..00000000 --- a/src/components/Button/utils/useTypeBasedStyle.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { useMemo } from 'react' -import type { StyleProp, ViewStyle, TextStyle, ImageStyle } from 'react-native' - -export const useTypeBasedStyle = < - Type extends string, - Style extends TextStyle | ViewStyle | ImageStyle | object, ->( - type: Type, - styles: Record> -) => { - return useMemo(() => styles[type], [styles, type]) -} diff --git a/src/components/Checkbox/Checkbox.tsx b/src/components/Checkbox/Checkbox.tsx index c402c4af..6f3acc39 100644 --- a/src/components/Checkbox/Checkbox.tsx +++ b/src/components/Checkbox/Checkbox.tsx @@ -1,16 +1,14 @@ import { IconCheck, IconMinus } from '@tabler/icons-react-native' -import React, { memo, useCallback, useMemo, useState } from 'react' +import { memo, useCallback, useMemo, useState } from 'react' import { type AccessibilityProps, + StyleSheet as RNStyleSheet, Pressable, - StyleSheet, View, } from 'react-native' import type { ViewProps } from 'react-native-svg/lib/typescript/fabric/utils' -import { makeStyles } from '../../utils/makeStyles' - -import { usePressableStyles } from './hooks/usePressableStyles' +import { StyleSheet, SvgUniversal } from '../../utils' type CheckboxState = 'default' | 'danger' @@ -41,7 +39,7 @@ export interface CheckboxProps * Используется для множественного выбора элементов * @see https://www.figma.com/design/4TYeki0MDLhfPGJstbIicf/UI-kit-PrimeFace-(DS)?node-id=484-5316 */ -export const Checkbox = memo( +export const Checkbox = memo( ({ onPress, checked = false, @@ -52,6 +50,15 @@ export const Checkbox = memo( }: CheckboxProps) => { const [isPressed, setIsPressed] = useState(false) + const filled = checked || indeterminate + + styles.useVariants({ + filled: filled ? 'true' : 'false', + pressed: isPressed ? 'true' : 'false', + state: state === 'danger' ? 'danger' : undefined, + disabled: disabled ? 'true' : 'false', + }) + const Icon = useMemo(() => { if (indeterminate) { return IconMinus @@ -64,15 +71,6 @@ export const Checkbox = memo( return null }, [indeterminate, checked]) - const getPressableStyles = usePressableStyles({ - checked, - indeterminate, - disabled, - state, - }) - - const styles = useStyles() - const onPressIn = useCallback(() => { setIsPressed(true) }, []) @@ -81,10 +79,6 @@ export const Checkbox = memo( setIsPressed(false) }, []) - const pressableStyles = useMemo(() => { - return getPressableStyles(isPressed) - }, [getPressableStyles, isPressed]) - return ( ( onPressIn={onPressIn} onPressOut={onPressOut} > - + {Icon ? ( - @@ -109,14 +104,71 @@ export const Checkbox = memo( } ) -const useStyles = makeStyles(({ theme, sizing }) => ({ +const styles = StyleSheet.create(({ theme, border, sizing }) => ({ container: { justifyContent: 'center', alignItems: 'center', width: theme.Form.Checkbox.checkboxWidth, height: theme.Form.Checkbox.checkboxHeight, }, - background: { ...StyleSheet.absoluteFillObject }, + background: { ...RNStyleSheet.absoluteFillObject }, + backgroundState: { + borderRadius: border.Radius['rounded-lg'], + borderWidth: border.Width.border, + variants: { + filled: { + true: { + backgroundColor: theme.Form.Checkbox.checkboxActiveBg, + borderColor: theme.Form.Checkbox.checkboxActiveBorderColor, + }, + false: { + backgroundColor: theme.Form.InputText.inputBg, + borderColor: theme.Form.InputText.inputBorderColor, + }, + }, + pressed: { + true: { borderColor: theme.Form.InputText.inputHoverBorderColor }, + false: {}, + }, + state: { + danger: { + borderColor: theme.Form.InputText.inputErrorBorderColor, + outlineStyle: 'solid', + outlineColor: theme.General.focusOutlineErrorColor, + outlineWidth: Math.round(theme.General.focusShadowWidth), + }, + }, + disabled: { true: { outlineWidth: 0 }, false: {} }, + }, + compoundVariants: [ + { + filled: 'true', + pressed: 'true', + styles: { + backgroundColor: theme.Form.Checkbox.checkboxActiveHoverBg, + borderColor: theme.Form.Checkbox.checkboxActiveHoverBorderColor, + }, + }, + { + filled: 'false', + disabled: 'true', + styles: { + backgroundColor: theme.Button.Disabled.disabledButtonBg, + borderColor: theme.Form.InputText.inputBorderColor, + mixBlendMode: 'luminosity', + }, + }, + { + filled: 'true', + disabled: 'true', + styles: { + borderColor: theme.Form.Checkbox.checkboxActiveBorderColor, + opacity: 0.2, + mixBlendMode: 'luminosity', + }, + }, + ], + }, icon: { height: sizing.Height['h-1'], width: sizing.Width['w-1'], diff --git a/src/components/Checkbox/__tests__/__snapshots__/Checkbox.test.tsx.snap b/src/components/Checkbox/__tests__/__snapshots__/Checkbox.test.tsx.snap index 3a300dc4..5cbb35bb 100644 --- a/src/components/Checkbox/__tests__/__snapshots__/Checkbox.test.tsx.snap +++ b/src/components/Checkbox/__tests__/__snapshots__/Checkbox.test.tsx.snap @@ -53,23 +53,15 @@ exports[`Checkbox snapshots checked = false, indeterminate = false, disabled = f "right": 0, "top": 0, }, - [ - { - "borderRadius": 7, - "borderWidth": 1, - "height": 21, - "width": 21, - }, - { - "backgroundColor": "#ffffff", - "borderColor": "#db3424", - "outlineColor": "#fff0f0", - "outlineStyle": "solid", - "outlineWidth": 4, - }, - false, - false, - ], + { + "backgroundColor": "#ffffff", + "borderColor": "#db3424", + "borderRadius": 7, + "borderWidth": 1, + "outlineColor": "#fff0f0", + "outlineStyle": "solid", + "outlineWidth": 4, + }, ] } /> @@ -129,20 +121,12 @@ exports[`Checkbox snapshots checked = false, indeterminate = false, disabled = f "right": 0, "top": 0, }, - [ - { - "borderRadius": 7, - "borderWidth": 1, - "height": 21, - "width": 21, - }, - { - "backgroundColor": "#ffffff", - "borderColor": "#cecfd2", - }, - false, - false, - ], + { + "backgroundColor": "#ffffff", + "borderColor": "#cecfd2", + "borderRadius": 7, + "borderWidth": 1, + }, ] } /> @@ -202,23 +186,15 @@ exports[`Checkbox snapshots checked = true, indeterminate = false, disabled = fa "right": 0, "top": 0, }, - [ - { - "borderRadius": 7, - "borderWidth": 1, - "height": 21, - "width": 21, - }, - { - "backgroundColor": "#2b2e33", - "borderColor": "#db3424", - "outlineColor": "#fff0f0", - "outlineStyle": "solid", - "outlineWidth": 4, - }, - false, - false, - ], + { + "backgroundColor": "#2b2e33", + "borderColor": "#db3424", + "borderRadius": 7, + "borderWidth": 1, + "outlineColor": "#fff0f0", + "outlineStyle": "solid", + "outlineWidth": 4, + }, ] } /> @@ -254,6 +230,7 @@ exports[`Checkbox snapshots checked = true, indeterminate = false, disabled = fa }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={14} @@ -301,6 +278,7 @@ exports[`Checkbox snapshots checked = true, indeterminate = false, disabled = fa strokeLinecap={1} strokeLinejoin={1} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -360,20 +338,12 @@ exports[`Checkbox snapshots checked = true, indeterminate = false, disabled = fa "right": 0, "top": 0, }, - [ - { - "borderRadius": 7, - "borderWidth": 1, - "height": 21, - "width": 21, - }, - { - "backgroundColor": "#2b2e33", - "borderColor": "#2b2e33", - }, - false, - false, - ], + { + "backgroundColor": "#2b2e33", + "borderColor": "#2b2e33", + "borderRadius": 7, + "borderWidth": 1, + }, ] } /> @@ -409,6 +379,7 @@ exports[`Checkbox snapshots checked = true, indeterminate = false, disabled = fa }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={14} @@ -456,6 +427,7 @@ exports[`Checkbox snapshots checked = true, indeterminate = false, disabled = fa strokeLinecap={1} strokeLinejoin={1} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -515,23 +487,15 @@ exports[`Checkbox snapshots checked = true, indeterminate = true, disabled = fal "right": 0, "top": 0, }, - [ - { - "borderRadius": 7, - "borderWidth": 1, - "height": 21, - "width": 21, - }, - { - "backgroundColor": "#2b2e33", - "borderColor": "#db3424", - "outlineColor": "#fff0f0", - "outlineStyle": "solid", - "outlineWidth": 4, - }, - false, - false, - ], + { + "backgroundColor": "#2b2e33", + "borderColor": "#db3424", + "borderRadius": 7, + "borderWidth": 1, + "outlineColor": "#fff0f0", + "outlineStyle": "solid", + "outlineWidth": 4, + }, ] } /> @@ -567,6 +531,7 @@ exports[`Checkbox snapshots checked = true, indeterminate = true, disabled = fal }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={14} @@ -614,6 +579,7 @@ exports[`Checkbox snapshots checked = true, indeterminate = true, disabled = fal strokeLinecap={1} strokeLinejoin={1} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -673,20 +639,12 @@ exports[`Checkbox snapshots checked = true, indeterminate = true, disabled = fal "right": 0, "top": 0, }, - [ - { - "borderRadius": 7, - "borderWidth": 1, - "height": 21, - "width": 21, - }, - { - "backgroundColor": "#2b2e33", - "borderColor": "#2b2e33", - }, - false, - false, - ], + { + "backgroundColor": "#2b2e33", + "borderColor": "#2b2e33", + "borderRadius": 7, + "borderWidth": 1, + }, ] } /> @@ -722,6 +680,7 @@ exports[`Checkbox snapshots checked = true, indeterminate = true, disabled = fal }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={14} @@ -769,6 +728,7 @@ exports[`Checkbox snapshots checked = true, indeterminate = true, disabled = fal strokeLinecap={1} strokeLinejoin={1} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -828,28 +788,17 @@ exports[`Checkbox snapshots checked = true, indeterminate = true, disabled = tru "right": 0, "top": 0, }, - [ - { - "borderRadius": 7, - "borderWidth": 1, - "height": 21, - "width": 21, - }, - { - "backgroundColor": "#2b2e33", - "borderColor": "#db3424", - "outlineColor": "#fff0f0", - "outlineStyle": "solid", - "outlineWidth": 4, - }, - { - "borderColor": "#2b2e33", - "mixBlendMode": "luminosity", - "opacity": 0.2, - "outlineWidth": 0, - }, - false, - ], + { + "backgroundColor": "#2b2e33", + "borderColor": "#2b2e33", + "borderRadius": 7, + "borderWidth": 1, + "mixBlendMode": "luminosity", + "opacity": 0.2, + "outlineColor": "#fff0f0", + "outlineStyle": "solid", + "outlineWidth": 0, + }, ] } /> @@ -885,6 +834,7 @@ exports[`Checkbox snapshots checked = true, indeterminate = true, disabled = tru }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={14} @@ -932,6 +882,7 @@ exports[`Checkbox snapshots checked = true, indeterminate = true, disabled = tru strokeLinecap={1} strokeLinejoin={1} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -991,25 +942,15 @@ exports[`Checkbox snapshots checked = true, indeterminate = true, disabled = tru "right": 0, "top": 0, }, - [ - { - "borderRadius": 7, - "borderWidth": 1, - "height": 21, - "width": 21, - }, - { - "backgroundColor": "#2b2e33", - "borderColor": "#2b2e33", - }, - { - "borderColor": "#2b2e33", - "mixBlendMode": "luminosity", - "opacity": 0.2, - "outlineWidth": 0, - }, - false, - ], + { + "backgroundColor": "#2b2e33", + "borderColor": "#2b2e33", + "borderRadius": 7, + "borderWidth": 1, + "mixBlendMode": "luminosity", + "opacity": 0.2, + "outlineWidth": 0, + }, ] } /> @@ -1045,6 +986,7 @@ exports[`Checkbox snapshots checked = true, indeterminate = true, disabled = tru }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={14} @@ -1092,6 +1034,7 @@ exports[`Checkbox snapshots checked = true, indeterminate = true, disabled = tru strokeLinecap={1} strokeLinejoin={1} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -1151,20 +1094,12 @@ exports[`Checkbox snapshots default 1`] = ` "right": 0, "top": 0, }, - [ - { - "borderRadius": 7, - "borderWidth": 1, - "height": 21, - "width": 21, - }, - { - "backgroundColor": "#ffffff", - "borderColor": "#cecfd2", - }, - false, - false, - ], + { + "backgroundColor": "#ffffff", + "borderColor": "#cecfd2", + "borderRadius": 7, + "borderWidth": 1, + }, ] } /> diff --git a/src/components/Checkbox/hooks/usePressableStyles.ts b/src/components/Checkbox/hooks/usePressableStyles.ts deleted file mode 100644 index 900a1b46..00000000 --- a/src/components/Checkbox/hooks/usePressableStyles.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { useCallback, useMemo } from 'react' - -import { makeStyles } from '../../../utils/makeStyles' -import type { CheckboxProps } from '../Checkbox' - -export const usePressableStyles = ({ - checked, - indeterminate = false, - disabled = false, - state, -}: Pick) => { - const styles = useStyles() - - const styleMap = useMemo( - () => ({ - default: { filled: styles.defaultFilled, clean: styles.defaultClean }, - disabled: { filled: styles.disabledFilled, clean: styles.disabledClean }, - danger: { - filled: { ...styles.dangerFilled, ...styles.dangerOutline }, - clean: { ...styles.dangerClean, ...styles.dangerOutline }, - }, - hover: { filled: styles.hoverFilled, clean: styles.hoverClean }, - }), - [ - styles.defaultFilled, - styles.defaultClean, - styles.disabledFilled, - styles.disabledClean, - styles.dangerFilled, - styles.dangerClean, - styles.hoverFilled, - styles.hoverClean, - styles.dangerOutline, - ] - ) - - return useCallback( - (pressed: boolean) => { - const isFilled = checked || indeterminate - const stateStyles = - state in styleMap && isFilled - ? styleMap[state].filled - : styleMap[state].clean - const disabledStyles = isFilled - ? styleMap.disabled.filled - : styleMap.disabled.clean - const pressedStyles = isFilled - ? styleMap.hover.filled - : styleMap.hover.clean - - return [ - styles.container, - stateStyles, - disabled && disabledStyles, - pressed && pressedStyles, - ] - }, - [checked, indeterminate, disabled, state, styles.container, styleMap] - ) -} - -const useStyles = makeStyles(({ theme, border }) => ({ - container: { - width: theme.Form.Checkbox.checkboxWidth, - height: theme.Form.Checkbox.checkboxHeight, - borderRadius: border.Radius['rounded-lg'], - borderWidth: border.Width.border, - }, - defaultClean: { - backgroundColor: theme.Form.InputText.inputBg, - borderColor: theme.Form.InputText.inputBorderColor, - }, - defaultFilled: { - backgroundColor: theme.Form.Checkbox.checkboxActiveBg, - borderColor: theme.Form.Checkbox.checkboxActiveBorderColor, - }, - hoverClean: { - backgroundColor: theme.Form.InputText.inputBg, - borderColor: theme.Form.InputText.inputHoverBorderColor, - }, - hoverFilled: { - backgroundColor: theme.Form.Checkbox.checkboxActiveHoverBg, - borderColor: theme.Form.Checkbox.checkboxActiveHoverBorderColor, - }, - dangerClean: { - backgroundColor: theme.Form.InputText.inputBg, - borderColor: theme.Form.InputText.inputErrorBorderColor, - }, - dangerFilled: { - backgroundColor: theme.Form.Checkbox.checkboxActiveBg, - borderColor: theme.Form.InputText.inputErrorBorderColor, - }, - dangerOutline: { - outlineStyle: 'solid', - outlineColor: theme.General.focusOutlineErrorColor, - outlineWidth: Math.round(theme.General.focusShadowWidth), - }, - disabledClean: { - backgroundColor: theme.Button.Disabled.disabledButtonBg, - borderColor: theme.Form.InputText.inputBorderColor, - outlineWidth: 0, - mixBlendMode: 'luminosity', - }, - disabledFilled: { - borderColor: theme.Form.Checkbox.checkboxActiveBorderColor, - opacity: 0.2, - outlineWidth: 0, - mixBlendMode: 'luminosity', - }, -})) diff --git a/src/components/Chip/Chip.tsx b/src/components/Chip/Chip.tsx index ffc52bce..6280ca0e 100644 --- a/src/components/Chip/Chip.tsx +++ b/src/components/Chip/Chip.tsx @@ -2,8 +2,8 @@ import { IconX } from '@tabler/icons-react-native' import { memo } from 'react' import { Text, Pressable, type PressableProps } from 'react-native' +import { StyleSheet } from '../../utils' import { type SvgSource, SvgUniversal } from '../../utils/SvgUniversal' -import { makeStyles } from '../../utils/makeStyles' export interface ChipProps extends PressableProps { /** SVG-иконка */ @@ -40,8 +40,6 @@ export const Chip = memo( showIcon = true, ...rest }) => { - const styles = useStyles() - return ( ( onPress={onClose} > {({ pressed }) => ( - @@ -86,7 +85,7 @@ export const Chip = memo( } ) -const useStyles = makeStyles(({ theme, typography, border, fonts }) => ({ +const styles = StyleSheet.create(({ theme, typography, border, fonts }) => ({ chip: { height: theme.Misc.Chip.chipHeight, alignSelf: 'flex-start', diff --git a/src/components/Chip/__tests__/__snapshots__/Chip.test.tsx.snap b/src/components/Chip/__tests__/__snapshots__/Chip.test.tsx.snap index 3c03cc52..40c37e8c 100644 --- a/src/components/Chip/__tests__/__snapshots__/Chip.test.tsx.snap +++ b/src/components/Chip/__tests__/__snapshots__/Chip.test.tsx.snap @@ -392,6 +392,7 @@ exports[`Chip component tests { }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={14} @@ -439,6 +440,7 @@ exports[`Chip component tests { strokeLinecap={1} strokeLinejoin={1} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -704,6 +707,7 @@ exports[`Chip component tests { }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={14} @@ -751,6 +755,7 @@ exports[`Chip component tests { strokeLinecap={1} strokeLinejoin={1} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -1070,6 +1076,7 @@ exports[`Chip component tests { }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={14} @@ -1117,6 +1124,7 @@ exports[`Chip component tests { strokeLinecap={1} strokeLinejoin={1} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -1280,6 +1289,7 @@ exports[`Chip component tests { }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={14} @@ -1327,6 +1337,7 @@ exports[`Chip component tests { strokeLinecap={1} strokeLinejoin={1} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -1748,6 +1760,7 @@ exports[`Chip component tests { }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={14} @@ -1795,6 +1808,7 @@ exports[`Chip component tests { strokeLinecap={1} strokeLinejoin={1} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -2060,6 +2075,7 @@ exports[`Chip component tests { }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={14} @@ -2107,6 +2123,7 @@ exports[`Chip component tests { strokeLinecap={1} strokeLinejoin={1} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -2426,6 +2444,7 @@ exports[`Chip component tests { }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={14} @@ -2473,6 +2492,7 @@ exports[`Chip component tests { strokeLinecap={1} strokeLinejoin={1} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -2636,6 +2657,7 @@ exports[`Chip component tests { }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={14} @@ -2683,6 +2705,7 @@ exports[`Chip component tests { strokeLinecap={1} strokeLinejoin={1} strokeWidth={2} + testID="SvgUniversalComponent" /> diff --git a/src/components/Dialog/DialogComponent.tsx b/src/components/Dialog/DialogComponent.tsx index fd963ad4..0f985c68 100644 --- a/src/components/Dialog/DialogComponent.tsx +++ b/src/components/Dialog/DialogComponent.tsx @@ -1,7 +1,7 @@ import React, { type ReactNode } from 'react' import { View, useWindowDimensions } from 'react-native' -import { makeStyles } from '../../utils/makeStyles' +import { StyleSheet } from '../../utils' export interface DialogComponentProps { readonly body?: React.ComponentType @@ -14,7 +14,6 @@ export const DialogComponent: React.FC = ({ footer: Footer, header = null, }) => { - const styles = useStyles() const { width, height } = useWindowDimensions() const maxSize = { maxWidth: width - 40, maxHeight: height - 100 } @@ -37,7 +36,7 @@ export const DialogComponent: React.FC = ({ ) } -const useStyles = makeStyles(({ theme }) => ({ +const styles = StyleSheet.create(({ theme }) => ({ root: { backgroundColor: theme.Overlay.Dialog.Header.dialogHeaderBg, borderColor: theme.Overlay.Overlay.overlayContentBorderColor, diff --git a/src/components/Dialog/DialogHeader.tsx b/src/components/Dialog/DialogHeader.tsx index dfffd1de..3d25180f 100644 --- a/src/components/Dialog/DialogHeader.tsx +++ b/src/components/Dialog/DialogHeader.tsx @@ -1,5 +1,4 @@ import { - type Icon, IconAlertTriangle, IconCircleCheck, IconCircleX, @@ -10,7 +9,7 @@ import { import { useMemo } from 'react' import { TouchableOpacity, View } from 'react-native' -import { makeStyles } from '../../utils/makeStyles' +import { StyleSheet, SvgUniversal, type SvgSource } from '../../utils' import { Title } from '../Typography' type Severity = 'danger' | 'warning' | 'info' | 'success' | 'help' @@ -21,7 +20,7 @@ export interface DialogHeaderProps { readonly severity?: Severity } -const iconsMap: Record = { +const iconsMap: Record = { danger: IconCircleX, warning: IconAlertTriangle, info: IconInfoCircle, @@ -34,16 +33,22 @@ export const DialogHeader = ({ onClose, severity, }: DialogHeaderProps) => { - const styles = useStyles() const tids = DialogHeaderTestId const icon = useMemo(() => { if (!severity) return null - const Icon = iconsMap[severity] + const source = iconsMap[severity] - return - }, [severity, styles]) + return ( + + ) + }, [severity]) return ( <> @@ -58,7 +63,12 @@ export const DialogHeader = ({ testID={tids.closeButton} onPress={onClose} > - + ) : null} @@ -68,7 +78,7 @@ export const DialogHeader = ({ ) } -const useStyles = makeStyles(({ theme, spacing, typography, border }) => ({ +const styles = StyleSheet.create(({ theme, spacing, typography, border }) => ({ text: { flex: 1 }, header: { flexDirection: 'row', diff --git a/src/components/Dialog/__tests__/__snapshots__/DialogHeader.test.tsx.snap b/src/components/Dialog/__tests__/__snapshots__/DialogHeader.test.tsx.snap index 2331d729..793348ec 100644 --- a/src/components/Dialog/__tests__/__snapshots__/DialogHeader.test.tsx.snap +++ b/src/components/Dialog/__tests__/__snapshots__/DialogHeader.test.tsx.snap @@ -40,6 +40,7 @@ exports[`DialogHeader {"onClose": [Function mockConstructor], "severity": "dange }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={21} @@ -67,7 +68,7 @@ exports[`DialogHeader {"onClose": [Function mockConstructor], "severity": "dange strokeWidth={2} > @@ -197,6 +200,7 @@ exports[`DialogHeader {"onClose": [Function mockConstructor], "severity": "dange }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={14} @@ -244,6 +248,7 @@ exports[`DialogHeader {"onClose": [Function mockConstructor], "severity": "dange strokeLinecap={1} strokeLinejoin={1} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -322,6 +328,7 @@ exports[`DialogHeader {"onClose": [Function mockConstructor], "severity": "help" }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={21} @@ -369,6 +376,7 @@ exports[`DialogHeader {"onClose": [Function mockConstructor], "severity": "help" strokeLinecap={1} strokeLinejoin={1} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -501,6 +511,7 @@ exports[`DialogHeader {"onClose": [Function mockConstructor], "severity": "help" }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={14} @@ -548,6 +559,7 @@ exports[`DialogHeader {"onClose": [Function mockConstructor], "severity": "help" strokeLinecap={1} strokeLinejoin={1} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -626,6 +639,7 @@ exports[`DialogHeader {"onClose": [Function mockConstructor], "severity": "info" }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={21} @@ -673,6 +687,7 @@ exports[`DialogHeader {"onClose": [Function mockConstructor], "severity": "info" strokeLinecap={1} strokeLinejoin={1} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -805,6 +822,7 @@ exports[`DialogHeader {"onClose": [Function mockConstructor], "severity": "info" }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={14} @@ -852,6 +870,7 @@ exports[`DialogHeader {"onClose": [Function mockConstructor], "severity": "info" strokeLinecap={1} strokeLinejoin={1} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -930,6 +950,7 @@ exports[`DialogHeader {"onClose": [Function mockConstructor], "severity": "succe }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={21} @@ -957,7 +978,7 @@ exports[`DialogHeader {"onClose": [Function mockConstructor], "severity": "succe strokeWidth={2} > @@ -1087,6 +1110,7 @@ exports[`DialogHeader {"onClose": [Function mockConstructor], "severity": "succe }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={14} @@ -1134,6 +1158,7 @@ exports[`DialogHeader {"onClose": [Function mockConstructor], "severity": "succe strokeLinecap={1} strokeLinejoin={1} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -1212,6 +1238,7 @@ exports[`DialogHeader {"onClose": [Function mockConstructor], "severity": "warni }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={21} @@ -1259,9 +1286,10 @@ exports[`DialogHeader {"onClose": [Function mockConstructor], "severity": "warni strokeLinecap={1} strokeLinejoin={1} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -1391,6 +1421,7 @@ exports[`DialogHeader {"onClose": [Function mockConstructor], "severity": "warni }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={14} @@ -1438,6 +1469,7 @@ exports[`DialogHeader {"onClose": [Function mockConstructor], "severity": "warni strokeLinecap={1} strokeLinejoin={1} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -1574,6 +1607,7 @@ exports[`DialogHeader {"onClose": [Function mockConstructor], "severity": undefi }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={14} @@ -1621,6 +1655,7 @@ exports[`DialogHeader {"onClose": [Function mockConstructor], "severity": undefi strokeLinecap={1} strokeLinejoin={1} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -1699,6 +1735,7 @@ exports[`DialogHeader {"onClose": undefined, "severity": "danger", "title": "Dia }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={21} @@ -1726,7 +1763,7 @@ exports[`DialogHeader {"onClose": undefined, "severity": "danger", "title": "Dia strokeWidth={2} > @@ -1845,6 +1884,7 @@ exports[`DialogHeader {"onClose": undefined, "severity": "help", "title": "Dialo }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={21} @@ -1892,6 +1932,7 @@ exports[`DialogHeader {"onClose": undefined, "severity": "help", "title": "Dialo strokeLinecap={1} strokeLinejoin={1} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -2013,6 +2056,7 @@ exports[`DialogHeader {"onClose": undefined, "severity": "info", "title": "Dialo }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={21} @@ -2060,6 +2104,7 @@ exports[`DialogHeader {"onClose": undefined, "severity": "info", "title": "Dialo strokeLinecap={1} strokeLinejoin={1} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -2181,6 +2228,7 @@ exports[`DialogHeader {"onClose": undefined, "severity": "success", "title": "Di }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={21} @@ -2208,7 +2256,7 @@ exports[`DialogHeader {"onClose": undefined, "severity": "success", "title": "Di strokeWidth={2} > @@ -2327,6 +2377,7 @@ exports[`DialogHeader {"onClose": undefined, "severity": "warning", "title": "Di }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={21} @@ -2374,9 +2425,10 @@ exports[`DialogHeader {"onClose": undefined, "severity": "warning", "title": "Di strokeLinecap={1} strokeLinejoin={1} strokeWidth={2} + testID="SvgUniversalComponent" /> diff --git a/src/components/Divider/Divider.tsx b/src/components/Divider/Divider.tsx index 52406f5b..4fae623c 100644 --- a/src/components/Divider/Divider.tsx +++ b/src/components/Divider/Divider.tsx @@ -1,8 +1,8 @@ import { memo, useMemo } from 'react' import { View, type ViewStyle, type StyleProp } from 'react-native' +import { StyleSheet } from '../../utils' import { type SvgSource, SvgUniversal } from '../../utils/SvgUniversal' -import { makeStyles } from '../../utils/makeStyles' import { Subtitle } from '../Typography' export interface DividerProps { @@ -63,7 +63,6 @@ export const Divider = memo( style, Icon, }) => { - const styles = useStyles() const isVertical = useMemo(() => layout === 'vertical', [layout]) const showIcon = useMemo( () => !!(showIconProp && Icon), @@ -77,7 +76,7 @@ export const Divider = memo( const lineStyle = useMemo( () => [styles.line, type === 'dash' && styles.lineDash], - [styles.line, styles.lineDash, type] + [type] ) return ( @@ -132,7 +131,7 @@ export const Divider = memo( } ) -const useStyles = makeStyles(({ spacing, theme, sizing, typography }) => ({ +const styles = StyleSheet.create(({ spacing, theme, sizing, typography }) => ({ container: { minHeight: sizing.Height['h-2'], flexDirection: 'row', diff --git a/src/components/Input/InputGroup.tsx b/src/components/Input/InputGroup.tsx index b8379437..dd1e8e75 100644 --- a/src/components/Input/InputGroup.tsx +++ b/src/components/Input/InputGroup.tsx @@ -2,12 +2,12 @@ import { memo, useCallback, useImperativeHandle, useMemo, useRef } from 'react' import { type TextInput, View, - StyleSheet, + StyleSheet as RNStyleSheet, type ViewStyle, type StyleProp, } from 'react-native' -import { makeStyles } from '../../utils/makeStyles' +import { StyleSheet } from '../../utils' import { InputGroupAddon, type InputGroupAddonProps } from './InputGroupAddon' import { InputTextBase } from './InputTextBase/InputTextBase' @@ -40,7 +40,6 @@ export const InputGroup = memo( inputRef: propsInputRef, ...otherProps }) => { - const styles = useStyles() const inputRef = useRef(null) const focus = useCallback(() => inputRef.current?.focus(), [inputRef]) @@ -51,18 +50,12 @@ export const InputGroup = memo( ) const containerStyle = useMemo(() => { - return StyleSheet.flatten([ + return RNStyleSheet.flatten([ styles.inputContainer, !!left && styles.inputContainerForLeftAddon, !!right && styles.inputContainerForRightAddon, ]) - }, [ - left, - right, - styles.inputContainer, - styles.inputContainerForLeftAddon, - styles.inputContainerForRightAddon, - ]) + }, [left, right]) return ( @@ -97,7 +90,7 @@ export const InputGroup = memo( } ) -const useStyles = makeStyles(() => ({ +const styles = StyleSheet.create(() => ({ container: { flexDirection: 'row' }, inputWrapper: { flexGrow: 1, zIndex: 10 }, inputContainer: { flexGrow: 1 }, diff --git a/src/components/Input/InputGroupAddon.tsx b/src/components/Input/InputGroupAddon.tsx index 6ad2456e..14e5cba4 100644 --- a/src/components/Input/InputGroupAddon.tsx +++ b/src/components/Input/InputGroupAddon.tsx @@ -1,8 +1,8 @@ import { memo } from 'react' import { Pressable, Text } from 'react-native' +import { StyleSheet } from '../../utils' import { SvgUniversal, type SvgSource } from '../../utils/SvgUniversal' -import { makeStyles } from '../../utils/makeStyles' export interface InputGroupAddonProps { /** Содержимое аддона инпут группы, текст или SVG-иконка */ @@ -22,8 +22,6 @@ export interface InputGroupAddonProps { */ export const InputGroupAddon = memo( ({ content, onPress, position, disabled }) => { - const styles = useStyles() - return ( ( } ) -const useStyles = makeStyles(({ theme, typography, fonts }) => ({ +const styles = StyleSheet.create(({ theme, typography, fonts }) => ({ container: { paddingVertical: theme.Form.InputText.inputPaddingTopBottom, paddingHorizontal: theme.Form.InputText.inputPaddingLeftRight, diff --git a/src/components/Input/InputOtp/InputOtp.tsx b/src/components/Input/InputOtp/InputOtp.tsx index e747ccfa..0a6f6530 100644 --- a/src/components/Input/InputOtp/InputOtp.tsx +++ b/src/components/Input/InputOtp/InputOtp.tsx @@ -18,7 +18,7 @@ import { type BlurEvent, } from 'react-native' -import { makeStyles } from '../../../utils/makeStyles' +import { StyleSheet } from '../../../utils' import { InputOtpItem } from './InputOtpItem' @@ -50,7 +50,6 @@ export const InputOtp = memo( onBlur, ...rest }) => { - const styles = useStyles() const [isFocused, setIsFocused] = useState(false) const inputRef = useRef(null) @@ -137,7 +136,7 @@ export const InputOtp = memo( } ) -const useStyles = makeStyles(({ spacing }) => ({ +const styles = StyleSheet.create(({ spacing }) => ({ container: {}, content: { flexDirection: 'row', gap: spacing.Gap['gap-2'] }, diff --git a/src/components/Input/InputOtp/InputOtpItem.tsx b/src/components/Input/InputOtp/InputOtpItem.tsx index f40c1239..1af3b844 100644 --- a/src/components/Input/InputOtp/InputOtpItem.tsx +++ b/src/components/Input/InputOtp/InputOtpItem.tsx @@ -9,7 +9,7 @@ import Animated, { withTiming, } from 'react-native-reanimated' -import { makeStyles } from '../../../utils/makeStyles' +import { StyleSheet } from '../../../utils' export interface InputOtpItemProps extends Pick { value?: string @@ -23,8 +23,6 @@ const CURSOR_ANIMATION_DURATION = 500 export const InputOtpItem = memo( ({ value, error, pressed, disabled, focused, testID }) => { - const styles = useStyles() - const opacity = useSharedValue(1) useEffect(() => { @@ -66,7 +64,7 @@ export const InputOtpItem = memo( } ) -const useStyles = makeStyles(({ theme, border, fonts, typography }) => ({ +const styles = StyleSheet.create(({ theme, border, fonts, typography }) => ({ container: { minHeight: theme.Button.Common.buttonHeight, minWidth: theme.Button.Common.buttonHeight, diff --git a/src/components/Input/InputSwitch/InputSwitch.tsx b/src/components/Input/InputSwitch/InputSwitch.tsx index a2a52412..bb06abf2 100644 --- a/src/components/Input/InputSwitch/InputSwitch.tsx +++ b/src/components/Input/InputSwitch/InputSwitch.tsx @@ -19,11 +19,8 @@ export const InputSwitch = memo( danger = false, ...props }) => { - const { sliderStyle, onPressedChange } = useSliderStyles( - checked, - disabled, - danger - ) + const { containerStyle, sliderStyle, onPressIn, onPressOut } = + useSliderStyles(checked, disabled, danger) const { handleStyle } = useHandleStyles(checked) const handlePress = useCallback(() => { @@ -33,8 +30,10 @@ export const InputSwitch = memo( return ( diff --git a/src/components/Input/InputSwitch/__tests__/__snapshots__/InputSwitch.test.tsx.snap b/src/components/Input/InputSwitch/__tests__/__snapshots__/InputSwitch.test.tsx.snap index 24be13a9..a860e6b6 100644 --- a/src/components/Input/InputSwitch/__tests__/__snapshots__/InputSwitch.test.tsx.snap +++ b/src/components/Input/InputSwitch/__tests__/__snapshots__/InputSwitch.test.tsx.snap @@ -170,7 +170,7 @@ exports[`InputSwitch component tests checked - false, danger - false, disabled - } jestInlineStyle={ { - "backgroundColor": ""rgba(133, 136, 142, 1)"", + "backgroundColor": ""#85888e"", "borderColor": ""transparent"", "borderRadius": 100, "borderWidth": 1, @@ -183,7 +183,7 @@ exports[`InputSwitch component tests checked - false, danger - false, disabled - style={ [ { - "backgroundColor": "rgba(133, 136, 142, 1)", + "backgroundColor": "#85888e", "borderColor": "transparent", "borderRadius": 100, "borderWidth": 1, @@ -642,7 +642,7 @@ exports[`InputSwitch component tests checked - false, danger - true, disabled - } jestInlineStyle={ { - "backgroundColor": ""rgba(133, 136, 142, 1)"", + "backgroundColor": ""#85888e"", "borderColor": ""#db3424"", "borderRadius": 100, "borderWidth": 1, @@ -655,7 +655,7 @@ exports[`InputSwitch component tests checked - false, danger - true, disabled - style={ [ { - "backgroundColor": "rgba(133, 136, 142, 1)", + "backgroundColor": "#85888e", "borderColor": "#db3424", "borderRadius": 100, "borderWidth": 1, @@ -1114,7 +1114,7 @@ exports[`InputSwitch component tests checked - true, danger - false, disabled - } jestInlineStyle={ { - "backgroundColor": ""rgba(43, 46, 51, 1)"", + "backgroundColor": ""#2b2e33"", "borderColor": ""transparent"", "borderRadius": 100, "borderWidth": 1, @@ -1127,7 +1127,7 @@ exports[`InputSwitch component tests checked - true, danger - false, disabled - style={ [ { - "backgroundColor": "rgba(43, 46, 51, 1)", + "backgroundColor": "#2b2e33", "borderColor": "transparent", "borderRadius": 100, "borderWidth": 1, @@ -1586,7 +1586,7 @@ exports[`InputSwitch component tests checked - true, danger - true, disabled - f } jestInlineStyle={ { - "backgroundColor": ""rgba(43, 46, 51, 1)"", + "backgroundColor": ""#2b2e33"", "borderColor": ""#db3424"", "borderRadius": 100, "borderWidth": 1, @@ -1599,7 +1599,7 @@ exports[`InputSwitch component tests checked - true, danger - true, disabled - f style={ [ { - "backgroundColor": "rgba(43, 46, 51, 1)", + "backgroundColor": "#2b2e33", "borderColor": "#db3424", "borderRadius": 100, "borderWidth": 1, diff --git a/src/components/Input/InputSwitch/styles/useHandleStyles.ts b/src/components/Input/InputSwitch/styles/useHandleStyles.ts index f33b53d1..6fb5af9e 100644 --- a/src/components/Input/InputSwitch/styles/useHandleStyles.ts +++ b/src/components/Input/InputSwitch/styles/useHandleStyles.ts @@ -1,16 +1,17 @@ import { useCallback, useEffect, useMemo } from 'react' -import { StyleSheet } from 'react-native' +import { StyleSheet as RNStyleSheet } from 'react-native' import { useSharedValue, withTiming } from 'react-native-reanimated' -import { makeStyles } from '../../../../utils/makeStyles' +import { StyleSheet } from '../../../../utils' export const useHandleStyles = (checked: boolean) => { - const styles = useStyles() + const styles = handleStyles + const handleOnLeft = styles.handleOn.left + const handleOffLeft = styles.handleOff.left const calculateHandleLeftPosition = useCallback( - (checked: boolean) => - checked ? styles.handleOn.left : styles.handleOff.left, - [styles.handleOff.left, styles.handleOn.left] + (checked: boolean) => (checked ? handleOnLeft : handleOffLeft), + [handleOffLeft, handleOnLeft] ) const calculateHandleBackground = useCallback( @@ -22,32 +23,32 @@ export const useHandleStyles = (checked: boolean) => { const handleLeftPosition = useSharedValue( calculateHandleLeftPosition(checked) ) - const handlerBackgrouind = useSharedValue(calculateHandleBackground(checked)) + const handleBackground = useSharedValue(calculateHandleBackground(checked)) useEffect(() => { handleLeftPosition.value = withTiming(calculateHandleLeftPosition(checked)) - handlerBackgrouind.value = withTiming(calculateHandleBackground(checked)) + handleBackground.value = withTiming(calculateHandleBackground(checked)) }, [ calculateHandleBackground, calculateHandleLeftPosition, checked, handleLeftPosition, - handlerBackgrouind, + handleBackground, ]) const handleStyle = useMemo( () => - StyleSheet.flatten([ + RNStyleSheet.flatten([ styles.handle, - { left: handleLeftPosition, backgroundColor: handlerBackgrouind }, + { left: handleLeftPosition, backgroundColor: handleBackground }, ]), - [handleLeftPosition, handlerBackgrouind, styles.handle] + [handleLeftPosition, handleBackground, styles.handle] ) return { handleStyle } } -const useStyles = makeStyles(({ theme, border }) => ({ +const handleStyles = StyleSheet.create(({ theme, border }) => ({ handle: { height: theme.Form.inputSwitch.inputSwitchHandleHeight, width: theme.Form.inputSwitch.inputSwitchHandleWidth, diff --git a/src/components/Input/InputSwitch/styles/useSliderStyles.ts b/src/components/Input/InputSwitch/styles/useSliderStyles.ts index a5f40150..abe7473d 100644 --- a/src/components/Input/InputSwitch/styles/useSliderStyles.ts +++ b/src/components/Input/InputSwitch/styles/useSliderStyles.ts @@ -1,47 +1,54 @@ -import { useCallback, useEffect, useMemo } from 'react' -import { type PressableStateCallbackType, StyleSheet } from 'react-native' +import { useCallback, useEffect, useMemo, useState } from 'react' +import { StyleSheet as RNStyleSheet } from 'react-native' import { useSharedValue, withTiming } from 'react-native-reanimated' -import { makeStyles } from '../../../../utils/makeStyles' +import { StyleSheet } from '../../../../utils' export const useSliderStyles = ( checked: boolean, disabled: boolean, danger: boolean ) => { - const styles = useStyles() - + const styles = switchStyles + const [pressed, setPressed] = useState(false) + + const sliderDisabledBg = styles.sliderDisabled.backgroundColor + const sliderOffBg = styles.sliderOff.backgroundColor + const sliderOnBg = styles.sliderOn.backgroundColor + const sliderOnDisabledBg = styles.sliderOnDisabled.backgroundColor + const sliderOnPressedBg = styles.sliderOnPressed.backgroundColor + const sliderPressedBg = styles.sliderPressed.backgroundColor const calculateSliderBackground = useCallback( - (checked: boolean, disabled: boolean, pressed: boolean) => { + (checked: boolean, disabled: boolean, pressed: boolean): string => { if (disabled) { if (checked) { - return styles.sliderOnDisabled.backgroundColor + return sliderOnDisabledBg } - return styles.sliderDisabled.backgroundColor + return sliderDisabledBg } if (pressed) { if (checked) { - return styles.sliderOnPressed.backgroundColor + return sliderOnPressedBg } - return styles.sliderPressed.backgroundColor + return sliderPressedBg } if (checked) { - return styles.sliderOn.backgroundColor + return sliderOnBg } - return styles.sliderOff.backgroundColor + return sliderOffBg }, [ - styles.sliderDisabled.backgroundColor, - styles.sliderOff.backgroundColor, - styles.sliderOn.backgroundColor, - styles.sliderOnDisabled.backgroundColor, - styles.sliderOnPressed.backgroundColor, - styles.sliderPressed.backgroundColor, + sliderDisabledBg, + sliderOffBg, + sliderOnBg, + sliderOnDisabledBg, + sliderOnPressedBg, + sliderPressedBg, ] ) @@ -65,40 +72,37 @@ export const useSliderStyles = ( ) const sliderBorderColor = useSharedValue(calculateSliderBorderColor(danger)) + useEffect(() => { + sliderBackground.value = withTiming( + calculateSliderBackground(checked, disabled, pressed) + ) + }, [calculateSliderBackground, checked, disabled, pressed, sliderBackground]) + useEffect(() => { sliderBorderColor.value = withTiming(calculateSliderBorderColor(danger)) }, [calculateSliderBorderColor, danger, sliderBorderColor]) const sliderStyle = useMemo( () => - StyleSheet.flatten([ + RNStyleSheet.flatten([ styles.slider, { backgroundColor: sliderBackground, borderColor: sliderBorderColor }, ]), [sliderBackground, sliderBorderColor, styles.slider] ) - const onPressedChange = useCallback( - ({ pressed }: PressableStateCallbackType) => { - sliderBackground.value = withTiming( - calculateSliderBackground(checked, disabled, pressed) - ) - - return styles.container - }, - [ - calculateSliderBackground, - checked, - disabled, - sliderBackground, - styles.container, - ] - ) + const onPressIn = useCallback(() => setPressed(true), []) + const onPressOut = useCallback(() => setPressed(false), []) - return { sliderStyle, onPressedChange } + return { + containerStyle: styles.container, + sliderStyle, + onPressIn, + onPressOut, + } } -const useStyles = makeStyles(({ theme, border }) => ({ +const switchStyles = StyleSheet.create(({ theme, border }) => ({ container: { height: theme.Form.inputSwitch.inputSwitchHeight, width: theme.Form.inputSwitch.inputSwitchWidth, diff --git a/src/components/Input/InputTextBase/InputTextBase.tsx b/src/components/Input/InputTextBase/InputTextBase.tsx index 55b13b2b..5c0962e0 100644 --- a/src/components/Input/InputTextBase/InputTextBase.tsx +++ b/src/components/Input/InputTextBase/InputTextBase.tsx @@ -35,10 +35,11 @@ import Animated, { import { useLoadingRotationAnimation } from '../../../hooks/useLoadingRotationAnimation' import { useMakeTestId } from '../../../hooks/useMakeTestId' +import { SvgUniversal } from '../../../utils/SvgUniversal' import { InputTextBaseTestId } from './testIds' import type { InputTextBaseProps, RenderTextInputArgs } from './types' -import { useInputStyle } from './useInputStyles' +import { inputStyles, useInputContainerMinHeight } from './useInputStyles' interface PrivateInputTextBaseProps { loading?: boolean @@ -71,35 +72,40 @@ export const InputTextBase = memo< // TODO: разделить float label и обычный инпут -> добавить во float label поддержку font scale // eslint-disable-next-line complexity }) => { - const styles = useInputStyle(size) + const styles = inputStyles + const containerMinHeightStyle = useInputContainerMinHeight(size) const inputRef = useRef(null) const [valueState, setValueState] = useState('') const [isFocused, setIsFocused] = useState(otherProps.autoFocus || false) const labelAnimation = useSharedValue(0) + const propsOnFocus = otherProps.onFocus + const propsOnBlur = otherProps.onBlur + const propsOnChangeText = otherProps.onChangeText + const onFocus = useCallback( (e: FocusEvent) => { setIsFocused(true) - otherProps.onFocus?.(e) + propsOnFocus?.(e) }, - [otherProps] + [propsOnFocus] ) const onBlur = useCallback( (e: BlurEvent) => { setIsFocused(false) - otherProps.onBlur?.(e) + propsOnBlur?.(e) }, - [otherProps] + [propsOnBlur] ) const onChangeText = useCallback( (nextValue: string) => { - otherProps.onChangeText?.(nextValue) + propsOnChangeText?.(nextValue) setValueState(nextValue) }, - [otherProps] + [propsOnChangeText] ) const clear = useCallback(() => { @@ -122,26 +128,35 @@ export const InputTextBase = memo< const loadingAnimatedStyle = useLoadingRotationAnimation(loading) + // Extract primitive values before the worklet closure to avoid + // capturing the unistyles HostObject (non-serializable) in the worklet. + const labelTop = styles.label.top + const labelReducedTop = styles.labelReducedSize.top + const labelPaddingVertical = styles.label.paddingVertical + const labelReducedPaddingVertical = styles.labelReducedSize.paddingVertical + const labelFontSize = styles.label.fontSize + const labelReducedFontSize = styles.labelReducedSize.fontSize + const labelFontFamily = styles.label.fontFamily + const labelReducedFontFamily = styles.labelReducedSize.fontFamily + const labelAnimatedStyle = useAnimatedStyle(() => ({ top: interpolate( labelAnimation.value, [0, 1], - [styles.label.top, styles.labelReducedSize.top] + [labelTop, labelReducedTop] ), paddingVertical: interpolate( labelAnimation.value, [0, 1], - [styles.label.paddingVertical, styles.labelReducedSize.paddingVertical] + [labelPaddingVertical, labelReducedPaddingVertical] ), fontSize: interpolate( labelAnimation.value, [0, 1], - [styles.label.fontSize, styles.labelReducedSize.fontSize] + [labelFontSize, labelReducedFontSize] ), fontFamily: - labelAnimation.value > 0.5 - ? styles.labelReducedSize.fontFamily - : styles.label.fontFamily, + labelAnimation.value > 0.5 ? labelReducedFontFamily : labelFontFamily, })) useEffect(() => { @@ -235,15 +250,15 @@ export const InputTextBase = memo< disabled, editable, secureTextEntry, - styles.inputFont, - styles.floatLabelInput, - styles.input, - styles.inputWithRightContent, hasRightContent, value, onBlur, onChangeText, onFocus, + styles.inputFont, + styles.floatLabelInput, + styles.input, + styles.inputWithRightContent, ] ) @@ -263,6 +278,7 @@ export const InputTextBase = memo< disabled={disabled} style={[ styles.container, + containerMinHeightStyle, floatLabel && styles.containerFloatLabel, isFocused && styles.containerFocused, containerStyle, @@ -315,9 +331,10 @@ export const InputTextBase = memo< style={[styles.rightButtonContainer, loadingAnimatedStyle]} testID={makeTestId(InputTextBaseTestId.loading)} > - @@ -331,9 +348,10 @@ export const InputTextBase = memo< testID={makeTestId(InputTextBaseTestId.clearButton)} onPress={clear} > - @@ -346,26 +364,20 @@ export const InputTextBase = memo< testID={makeTestId(InputTextBaseTestId.secureInputButton)} onPress={toggleUserDefinedSecureTextEntry} > - {userDefinedSecureTextEntry ? ( - - ) : ( - - )} + ) : null} {disabled ? ( - diff --git a/src/components/Input/InputTextBase/useInputStyles.ts b/src/components/Input/InputTextBase/useInputStyles.ts index 8b0b7224..c2fc2574 100644 --- a/src/components/Input/InputTextBase/useInputStyles.ts +++ b/src/components/Input/InputTextBase/useInputStyles.ts @@ -1,31 +1,30 @@ import { useMemo } from 'react' -import { makeStyles } from '../../../utils/makeStyles' +import { StyleSheet } from '../../../utils' import type { InputTextBaseProps } from './types' -export const useInputStyle = (size: InputTextBaseProps['size'] = 'base') => { - const styles = useStyles() - const containerMinHeight = useContainerMinHeight() - +export const useInputContainerMinHeight = ( + size: InputTextBaseProps['size'] = 'base' +) => { const minHeight = useMemo(() => { if (typeof size === 'number') { return Math.max(size, containerMinHeight.base.minHeight) } return containerMinHeight[size].minHeight - }, [size, containerMinHeight]) + }, [size]) - return { ...styles, container: { ...styles.container, minHeight } } + return { minHeight } } -const useContainerMinHeight = makeStyles(({ theme }) => ({ +const containerMinHeight = StyleSheet.create(({ theme }) => ({ base: { minHeight: theme.InputSize.base['min-height'] }, large: { minHeight: theme.InputSize.large['min-height'] }, xlarge: { minHeight: theme.InputSize.xlarge['min-height'] }, })) -const useStyles = makeStyles( +export const inputStyles = StyleSheet.create( ({ theme, border, typography, spacing, fonts }) => ({ container: { flexDirection: 'row', diff --git a/src/components/List/Base/ListBase.tsx b/src/components/List/Base/ListBase.tsx index d27c2ba8..f7817856 100644 --- a/src/components/List/Base/ListBase.tsx +++ b/src/components/List/Base/ListBase.tsx @@ -1,14 +1,8 @@ import React, { memo, useMemo } from 'react' -import { - View, - type ViewStyle, - type ViewProps, - Pressable, - type ColorValue, -} from 'react-native' +import { View, type ViewProps, Pressable, type ColorValue } from 'react-native' +import { StyleSheet } from '../../../utils' import { type SvgSource, SvgUniversal } from '../../../utils/SvgUniversal' -import { makeStyles } from '../../../utils/makeStyles' import { Subtitle, Body, Caption } from '../../Typography' /** Свойства ListBase */ @@ -58,14 +52,13 @@ export const ListBase = memo( testID, ...rest }) => { - const styles = useStyles() - - const leftIconStyle: ViewStyle = useMemo(() => { - return { - ...styles.leftIcon, - alignSelf: iconAlignment === 'top' ? 'flex-start' : undefined, - } - }, [styles.leftIcon, iconAlignment]) + const leftIconStyle = useMemo( + () => [ + styles.leftIcon, + iconAlignment === 'top' ? { alignSelf: 'flex-start' as const } : null, + ], + [iconAlignment] + ) const fullDivider = divider === 'full' ? styles.divider : {} const contentDivider = divider === 'content' ? styles.divider : {} @@ -98,9 +91,10 @@ export const ListBase = memo( {LeftIcon ? ( ) : null} @@ -122,9 +116,10 @@ export const ListBase = memo( ) : null} {RightIcon ? ( ) : null} @@ -136,43 +131,45 @@ export const ListBase = memo( } ) -const useStyles = makeStyles(({ spacing, typography, theme, background }) => ({ - container: { - flexDirection: 'row', - paddingLeft: spacing.Padding['p-4'], - gap: spacing.Padding['p-4'], - alignItems: 'center', - }, - pressed: { backgroundColor: background.Common['bg-surface-ground-hover'] }, - disabled: { opacity: 0.6 }, - leftIcon: { paddingVertical: spacing.Padding['p-4'] }, - content: { - flex: 1, - flexDirection: 'row', - justifyContent: 'space-between', - paddingVertical: spacing.Padding['p-2'], - paddingEnd: spacing.Padding['p-4'], - gap: spacing.Gap['gap-4'], - }, - labelContainer: { - paddingVertical: spacing.Padding['p-2'], - gap: spacing.Gap['gap-2'], - flex: 1, - }, - titleContainer: { gap: spacing.Gap['gap-1'] }, - extraContainer: { paddingVertical: spacing.Padding['p-2'] }, - icon: { - width: typography.Size['text-2xl'], - height: typography.Size['text-2xl'], - }, - rightSection: { - flexDirection: 'row', - alignItems: 'center', - paddingVertical: spacing.Padding['p-2'], - gap: spacing.Gap['gap-4'], - }, - divider: { - borderTopColor: theme.Surface['surface-border'], - borderTopWidth: 1, - }, -})) +const styles = StyleSheet.create( + ({ spacing, typography, theme, background }) => ({ + container: { + flexDirection: 'row', + paddingLeft: spacing.Padding['p-4'], + gap: spacing.Padding['p-4'], + alignItems: 'center', + }, + pressed: { backgroundColor: background.Common['bg-surface-ground-hover'] }, + disabled: { opacity: 0.6 }, + leftIcon: { paddingVertical: spacing.Padding['p-4'] }, + content: { + flex: 1, + flexDirection: 'row', + justifyContent: 'space-between', + paddingVertical: spacing.Padding['p-2'], + paddingEnd: spacing.Padding['p-4'], + gap: spacing.Gap['gap-4'], + }, + labelContainer: { + paddingVertical: spacing.Padding['p-2'], + gap: spacing.Gap['gap-2'], + flex: 1, + }, + titleContainer: { gap: spacing.Gap['gap-1'] }, + extraContainer: { paddingVertical: spacing.Padding['p-2'] }, + icon: { + width: typography.Size['text-2xl'], + height: typography.Size['text-2xl'], + }, + rightSection: { + flexDirection: 'row', + alignItems: 'center', + paddingVertical: spacing.Padding['p-2'], + gap: spacing.Gap['gap-4'], + }, + divider: { + borderTopColor: theme.Surface['surface-border'], + borderTopWidth: 1, + }, + }) +) diff --git a/src/components/List/Base/__tests__/__snapshots__/ListBase.test.tsx.snap b/src/components/List/Base/__tests__/__snapshots__/ListBase.test.tsx.snap index ddeb7e7a..4f2ceaa8 100644 --- a/src/components/List/Base/__tests__/__snapshots__/ListBase.test.tsx.snap +++ b/src/components/List/Base/__tests__/__snapshots__/ListBase.test.tsx.snap @@ -56,10 +56,12 @@ exports[`ListBase tests ListBase centered and full divider 1`] = ` > ( ({ Icon, disabled }) => { - const styles = useStyles() - const IconComponent = useMemo( () => (disabled ? IconLock : Icon), [Icon, disabled] @@ -27,13 +25,18 @@ export const MenuItemAccessory = memo( return ( - + ) } ) -const useStyles = makeStyles(({ theme }) => ({ +const styles = StyleSheet.create(({ theme }) => ({ container: { justifyContent: 'center' }, icon: { width: theme.Menu.Item.menuitemSubmenuIconFontSize, diff --git a/src/components/MenuItem/MenuItemIcon.tsx b/src/components/MenuItem/MenuItemIcon.tsx index a2355900..6458b07d 100644 --- a/src/components/MenuItem/MenuItemIcon.tsx +++ b/src/components/MenuItem/MenuItemIcon.tsx @@ -4,11 +4,10 @@ import { type DimensionValue, type LayoutChangeEvent, View, - StyleSheet, } from 'react-native' +import { StyleSheet } from '../../utils' import { type SvgSource, SvgUniversal } from '../../utils/SvgUniversal' -import { makeStyles } from '../../utils/makeStyles' import { Badge, type BadgeSeverity } from '../Badge/Badge' interface MenuItemIconStyle { @@ -49,8 +48,6 @@ export const MenuItemIcon = ({ style, badgeSeverity, }: MenuItemIconProps) => { - const styles = useStyles() - const [badgePosition, setBadgePosition] = useState<{ top: DimensionValue right: DimensionValue @@ -67,7 +64,7 @@ export const MenuItemIcon = ({ styles.badge, { top: badgePosition.top, right: badgePosition.right }, ]), - [badgePosition.right, badgePosition.top, styles.badge] + [badgePosition.right, badgePosition.top] ) return ( @@ -88,7 +85,7 @@ export const MenuItemIcon = ({ ) } -const useStyles = makeStyles(() => ({ +const styles = StyleSheet.create(() => ({ container: { justifyContent: 'center', position: 'relative' }, badge: { position: 'absolute' }, diff --git a/src/components/MenuItem/Template/MenuItemTemplate.tsx b/src/components/MenuItem/Template/MenuItemTemplate.tsx index e31c2595..aa0b8d07 100644 --- a/src/components/MenuItem/Template/MenuItemTemplate.tsx +++ b/src/components/MenuItem/Template/MenuItemTemplate.tsx @@ -9,8 +9,8 @@ import { type ViewStyle, } from 'react-native' +import { StyleSheet } from '../../../utils' import type { SvgSource } from '../../../utils/SvgUniversal' -import { makeStyles } from '../../../utils/makeStyles' import type { BadgeSeverity } from '../../Badge/Badge' import { Body, Caption } from '../../Typography' import { MenuItemAccessory } from '../MenuItemAccessory' @@ -71,11 +71,13 @@ export const MenuItemTemplate = memo( style, ...rest }) => { - const styles = useStyles() - const iconStyle = useMemo( - () => ({ ...styles.icon, color: iconColor || styles.icon.color }), - [iconColor, styles.icon] + () => ({ + width: styles.icon.width, + height: styles.icon.height, + color: iconColor || styles.icon.color, + }), + [iconColor] ) const pressableStyle = useCallback( @@ -85,7 +87,7 @@ export const MenuItemTemplate = memo( style, disabled && styles.containerDisabled, ], - [disabled, styles, style] + [disabled, style] ) return ( @@ -130,7 +132,7 @@ export const MenuItemTemplate = memo( } ) -const useStyles = makeStyles(({ theme, spacing, typography, border }) => ({ +const styles = StyleSheet.create(({ theme, spacing, typography, border }) => ({ container: { borderColor: theme.Menu.Item.menuitemBorderColor, borderWidth: border.Width.border, diff --git a/src/components/MenuItem/Template/__tests__/__snapshots__/MenuItemTemplate.test.tsx.snap b/src/components/MenuItem/Template/__tests__/__snapshots__/MenuItemTemplate.test.tsx.snap index e9abfe73..8402de7d 100644 --- a/src/components/MenuItem/Template/__tests__/__snapshots__/MenuItemTemplate.test.tsx.snap +++ b/src/components/MenuItem/Template/__tests__/__snapshots__/MenuItemTemplate.test.tsx.snap @@ -199,26 +199,26 @@ exports[`MenuItemTemplate tests MenuItemTemplate full disabled 1`] = ` { "alignItems": "flex-start", }, - { - "position": "absolute", - "right": 0, - "top": 0, - }, - ] - } - > - + @@ -439,7 +439,7 @@ exports[`MenuItemTemplate tests MenuItemTemplate full disabled 1`] = ` strokeWidth={2} > - + diff --git a/src/components/Message/Message.tsx b/src/components/Message/Message.tsx index 656c3f1a..4cc70722 100644 --- a/src/components/Message/Message.tsx +++ b/src/components/Message/Message.tsx @@ -5,7 +5,7 @@ import { IconInfoCircle, IconX, } from '@tabler/icons-react-native' -import { type ComponentProps, memo, type ReactNode, useMemo } from 'react' +import { memo, type ComponentProps, type ReactNode, useMemo } from 'react' import { type AccessibilityProps, type StyleProp, @@ -14,8 +14,8 @@ import { type ViewStyle, } from 'react-native' +import { StyleSheet } from '../../utils' import { type SvgSource, SvgUniversal } from '../../utils/SvgUniversal' -import { makeStyles } from '../../utils/makeStyles' import { ButtonSeverity } from '../Button/ButtonSeverity' import { Timer } from '../Timer/Timer' import { Body, Caption } from '../Typography' @@ -23,44 +23,44 @@ import { Body, Caption } from '../Typography' export interface MessageProps extends AccessibilityProps, Pick { /** Текст заголовка */ - title: string + readonly title: string /** Тело сообщения */ - body?: ReactNode + readonly body?: ReactNode /** Текст подписи */ - caption?: string + readonly caption?: string /** Футер сообщения */ - footer?: ReactNode + readonly footer?: ReactNode /** * Обработчик нажатия на кнопку закрытия. * Кнопка не отображается, если обработчик не передан. */ - onClose?: () => void + readonly onClose?: () => void /** * Текст на кнопке закрытия тоста * Если не указан, в кнопке отображается иконка "крестик" * Это свойство игнорируется если onClose = undefined */ - closeLabel?: string + readonly closeLabel?: string /** Срабатывает при истечении таймера */ - onTimerFinish?: () => void + readonly onTimerFinish?: () => void /** * Выбор варианта стиля компонента * @default 'info' */ - severity?: 'info' | 'success' | 'warning' | 'danger' + readonly severity?: 'info' | 'success' | 'warning' | 'danger' /** Дополнительная стилизация для контейнера компонента */ - style?: StyleProp + readonly style?: StyleProp /** Значение таймера, если нужно отображать таймер вместо иконки */ - timerValue?: number + readonly timerValue?: number /** * SVG-иконка. @@ -72,21 +72,21 @@ export interface MessageProps * IconCircleX для severity='danger' * */ - Icon?: SvgSource + readonly Icon?: SvgSource /** * Скрыть иконку. * Позволяет скрывать установленные или дефолтные иконки * Дефолтное значение: false */ - hiddenIcon?: boolean + readonly hiddenIcon?: boolean } /** * Унифицированный компонент, который используется для отображения информационных сообщений * @see https://www.figma.com/design/4TYeki0MDLhfPGJstbIicf/UI-kit-PrimeFace-(DS)?node-id=562-2947 */ -export const Message = memo( +export const Message = memo( ({ title, body, @@ -102,8 +102,9 @@ export const Message = memo( timerValue, Icon: IconProp, ...rest - }) => { - const styles = useStyles() + }: MessageProps) => { + messageStyles.useVariants({ severity }) + const Icon = useMemo(() => { if (IconProp) { return IconProp @@ -152,7 +153,7 @@ export const Message = memo( testID={TestId.CloseButton} /> ) - }, [closeLabel, severity, onClose]) + }, [closeLabel, onClose, severity]) const LeftContent = useMemo(() => { if (timerValue) { @@ -162,29 +163,29 @@ export const Message = memo( if (!hiddenIcon) { return ( ) } return undefined - }, [timerValue, hiddenIcon, onTimerFinish, Icon, styles, severity]) + }, [hiddenIcon, Icon, onTimerFinish, timerValue]) return ( - - + + {LeftContent} - + {title} @@ -202,58 +203,73 @@ export const Message = memo( } ) -const useStyles = makeStyles(({ theme, typography, spacing, border }) => ({ - container: { - borderRadius: theme.General.borderRadiusXL, - borderWidth: border.Width.border, - overflow: 'hidden', - }, - content: { - flexGrow: 1, - borderLeftWidth: border.Width['border-3'] - border.Width.border, - padding: spacing.Padding['p-4'], - paddingLeft: spacing.Padding['p-5'], - gap: spacing.Gap['gap-4'], - }, - info: { - borderColor: theme.Message.Severities.Info.infoMessageBorderColor, - backgroundColor: theme.Message.Severities.Info.infoMessageBg, - }, - contentinfo: { - borderColor: theme.Message.Severities.Info.infoMessageIconColor, - }, - success: { - borderColor: theme.Message.Severities.Success.successMessageBorderColor, - backgroundColor: theme.Message.Severities.Success.successMessageBg, - }, - contentsuccess: { - borderColor: theme.Message.Severities.Success.successMessageIconColor, - }, - warning: { - borderColor: theme.Message.Severities.Warning.warningMessageBorderColor, - backgroundColor: theme.Message.Severities.Warning.warningMessageBg, - }, - contentwarning: { - borderColor: theme.Message.Severities.Warning.warningMessageIconColor, - }, - danger: { - borderColor: theme.Message.Severities.Danger.dangerMessageBorderColor, - backgroundColor: theme.Message.Severities.Danger.dangerMessageBg, - }, - contentdanger: { - borderColor: theme.Message.Severities.Danger.dangerMessageIconColor, - }, - titleRow: { flexDirection: 'row', gap: spacing.Gap['gap-4'] }, - titleTextContainer: { - flex: 1, - alignSelf: 'center', - gap: spacing.Gap['gap-1'], - }, - iconSize: { - width: typography.Size['text-4xl'], - height: typography.Size['text-4xl'], - }, -})) +const messageStyles = StyleSheet.create( + ({ theme, typography, spacing, border }) => ({ + container: { + borderRadius: theme.General.borderRadiusXL, + borderWidth: border.Width.border, + overflow: 'hidden', + variants: { + severity: { + info: { + borderColor: theme.Message.Severities.Info.infoMessageBorderColor, + backgroundColor: theme.Message.Severities.Info.infoMessageBg, + }, + success: { + borderColor: + theme.Message.Severities.Success.successMessageBorderColor, + backgroundColor: theme.Message.Severities.Success.successMessageBg, + }, + warning: { + borderColor: + theme.Message.Severities.Warning.warningMessageBorderColor, + backgroundColor: theme.Message.Severities.Warning.warningMessageBg, + }, + danger: { + borderColor: + theme.Message.Severities.Danger.dangerMessageBorderColor, + backgroundColor: theme.Message.Severities.Danger.dangerMessageBg, + }, + }, + }, + }, + content: { + flexGrow: 1, + borderLeftWidth: border.Width['border-3'] - border.Width.border, + padding: spacing.Padding['p-4'], + paddingLeft: spacing.Padding['p-5'], + gap: spacing.Gap['gap-4'], + variants: { + severity: { + info: { + borderColor: theme.Message.Severities.Info.infoMessageIconColor, + }, + success: { + borderColor: + theme.Message.Severities.Success.successMessageIconColor, + }, + warning: { + borderColor: + theme.Message.Severities.Warning.warningMessageIconColor, + }, + danger: { + borderColor: theme.Message.Severities.Danger.dangerMessageIconColor, + }, + }, + }, + }, + titleRow: { flexDirection: 'row', gap: spacing.Gap['gap-4'] }, + titleTextContainer: { + flex: 1, + alignSelf: 'center', + gap: spacing.Gap['gap-1'], + }, + iconSize: { + width: typography.Size['text-4xl'], + height: typography.Size['text-4xl'], + }, + }) +) export enum TestId { Container = 'MessageContainer', diff --git a/src/components/Message/__tests__/__snapshots__/Message.test.tsx.snap b/src/components/Message/__tests__/__snapshots__/Message.test.tsx.snap index f31241c7..06eed4f8 100644 --- a/src/components/Message/__tests__/__snapshots__/Message.test.tsx.snap +++ b/src/components/Message/__tests__/__snapshots__/Message.test.tsx.snap @@ -6,14 +6,12 @@ exports[`Message {"Icon": [Object], "timerValue": 5} 1`] = ` style={ [ { + "backgroundColor": "#fafdff", + "borderColor": "#d4ecfe", "borderRadius": 10.5, "borderWidth": 1, "overflow": "hidden", }, - { - "backgroundColor": "#fafdff", - "borderColor": "#d4ecfe", - }, undefined, ] } @@ -21,18 +19,14 @@ exports[`Message {"Icon": [Object], "timerValue": 5} 1`] = ` > @@ -881,14 +854,12 @@ exports[`Message {"footer": [Object], "onClose": [Function mockConstructor], "se style={ [ { + "backgroundColor": "#fafffb", + "borderColor": "#d4fedc", "borderRadius": 10.5, "borderWidth": 1, "overflow": "hidden", }, - { - "backgroundColor": "#fafffb", - "borderColor": "#d4fedc", - }, undefined, ] } @@ -896,18 +867,14 @@ exports[`Message {"footer": [Object], "onClose": [Function mockConstructor], "se > @@ -1098,7 +1069,7 @@ exports[`Message {"footer": [Object], "onClose": [Function mockConstructor], "se meetOrSlice={0} minX={0} minY={0} - stroke="currentColor" + stroke="#44e858" strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} @@ -1108,11 +1079,6 @@ exports[`Message {"footer": [Object], "onClose": [Function mockConstructor], "se "backgroundColor": "transparent", "borderWidth": 0, }, - { - "color": "#44e858", - "height": 14, - "width": 14, - }, { "flex": 0, "height": 14, @@ -1127,7 +1093,6 @@ exports[`Message {"footer": [Object], "onClose": [Function mockConstructor], "se xmlns="http://www.w3.org/2000/svg" > { */ export const ProgressBar = memo( ({ value: propsValue, showValue = false, style }) => { - const styles = useStyles() - const [containerWidth, setContainerWidth] = useState(0) const indicatorWidth = useSharedValue(0) @@ -91,7 +89,7 @@ export const ProgressBar = memo( } ) -const useStyles = makeStyles(({ theme, typography, border, fonts }) => ({ +const styles = StyleSheet.create(({ theme, typography, border, fonts }) => ({ container: { borderRadius: border.Radius['rounded-full'], backgroundColor: theme.Misc.ProgressBar.progressBarBg, diff --git a/src/components/ProgressSpinner/ProgressSpinner.tsx b/src/components/ProgressSpinner/ProgressSpinner.tsx index 0658ac5d..66a757f6 100644 --- a/src/components/ProgressSpinner/ProgressSpinner.tsx +++ b/src/components/ProgressSpinner/ProgressSpinner.tsx @@ -10,7 +10,7 @@ import Animated, { } from 'react-native-reanimated' import Svg, { Circle } from 'react-native-svg' -import { makeStyles } from '../../utils/makeStyles' +import { StyleSheet } from '../../utils' const AnimatedSvg = Animated.createAnimatedComponent(Svg) const AnimatedCircle = Animated.createAnimatedComponent(Circle) @@ -37,7 +37,6 @@ export interface ProgressSpinnerProps { */ export const ProgressSpinner = memo( ({ size = 'md', fill = 'primary' }) => { - const styles = useStyles() const circleAnimation = useSharedValue(0) const containerAnimation = useSharedValue(0) @@ -65,7 +64,7 @@ export const ProgressSpinner = memo( case 'white': return styles.white.color } - }, [fill, styles.primary.color, styles.white.color]) + }, [fill]) const center = useMemo(() => sizeInDp / 2, [sizeInDp]) const radius = useMemo(() => center - STROKE_WIDTH / 2, [center]) @@ -126,7 +125,7 @@ export const ProgressSpinner = memo( } ) -const useStyles = makeStyles(({ theme, global }) => ({ +const styles = StyleSheet.create(({ theme, global }) => ({ primary: { color: theme.General.primaryColor }, white: { color: global.Neutrals.White['white-100'] }, })) diff --git a/src/components/RadioButton/RadioButton.tsx b/src/components/RadioButton/RadioButton.tsx index 3bb7fc03..d9eef004 100644 --- a/src/components/RadioButton/RadioButton.tsx +++ b/src/components/RadioButton/RadioButton.tsx @@ -1,35 +1,33 @@ -import { memo, useCallback, useMemo } from 'react' +import { memo, useCallback, useState } from 'react' import { type AccessibilityProps, Pressable, - type PressableStateCallbackType, - StyleSheet, View, type ViewProps, } from 'react-native' import Animated, { LinearTransition } from 'react-native-reanimated' -import { makeStyles } from '../../utils/makeStyles' +import { StyleSheet } from '../../utils' export interface RadioButtonProps extends AccessibilityProps, Pick { /** Обработчик нажатия на кнопку */ - onPress: () => void + readonly onPress: () => void /** * true, если необходим компонент в активном состоянии * @default false */ - checked?: boolean + readonly checked?: boolean /** * Управление доступностью компонента * @default false */ - disabled?: boolean + readonly disabled?: boolean /** Выбор состояния компонента */ - state?: 'default' | 'danger' + readonly state?: 'default' | 'danger' } -export const RadioButton = memo( +export const RadioButton = memo( ({ onPress, checked = false, @@ -37,92 +35,44 @@ export const RadioButton = memo( state = 'default', testID, ...rest - }) => { - const styles = useStyles() + }: RadioButtonProps) => { + const [pressed, setPressed] = useState(false) - const centerViewBackground = useMemo( - () => [styles.defaultView, disabled && !checked && styles.disabledView], - [disabled, checked, styles.defaultView, styles.disabledView] - ) - - const pressableStyles = useCallback( - ({ pressed }: PressableStateCallbackType) => { - const result = [styles.container, styles.default] - - if (checked) { - result.push(styles.checked) - } - - if (pressed) { - result.push(styles.pressed) - - if (checked) { - result.push(styles.checkedPressed) - } - } - - if (state === 'danger') { - result.push(styles.danger) - - if (checked) { - result.push(styles.dangerChecked) - - if (pressed) { - result.push(styles.dangerCheckedPressed) - } - } - } - - if (disabled) { - result.push(styles.disabled) + radioStyles.useVariants({ + checked: checked ? 'true' : 'false', + pressed: pressed ? 'true' : 'false', + state: state === 'danger' ? 'danger' : undefined, + disabled: disabled ? 'true' : 'false', + }) - if (checked) { - result.push(styles.disabledChecked) - } - } - - return StyleSheet.flatten(result) - }, - [ - checked, - disabled, - state, - styles.container, - styles.default, - styles.pressed, - styles.checked, - styles.checkedPressed, - styles.danger, - styles.dangerChecked, - styles.dangerCheckedPressed, - styles.disabled, - styles.disabledChecked, - ] - ) + const onPressIn = useCallback(() => setPressed(true), []) + const onPressOut = useCallback(() => setPressed(false), []) return ( {!disabled && state === 'danger' && ( )} - + ) } ) -const useStyles = makeStyles(({ theme }) => ({ +const radioStyles = StyleSheet.create(({ theme }) => ({ container: { width: theme.Form.RadioButton.radiobuttonWidth, height: theme.Form.RadioButton.radiobuttonHeight, @@ -130,11 +80,83 @@ const useStyles = makeStyles(({ theme }) => ({ alignItems: 'center', justifyContent: 'center', margin: theme.General.focusShadowWidth, + variants: { + checked: { + true: { + borderColor: theme.Form.RadioButton.radiobuttonActiveBorderColor, + backgroundColor: theme.Form.RadioButton.radiobuttonActiveBorderColor, + borderWidth: 5, + }, + false: { + borderColor: theme.Form.InputText.inputBorderColor, + borderWidth: 1, + backgroundColor: theme.Form.InputText.inputBg, + }, + }, + pressed: { + true: { borderColor: theme.Form.InputText.inputHoverBorderColor }, + false: {}, + }, + state: { + danger: { borderColor: theme.Form.InputText.inputErrorBorderColor }, + }, + disabled: { + true: { + borderColor: theme.Form.InputText.inputBorderColor, + backgroundColor: theme.Button.Disabled.disabledButtonBg, + opacity: 0.6, + borderWidth: 1, + }, + false: {}, + }, + }, + compoundVariants: [ + { + checked: 'true', + pressed: 'true', + styles: { + borderColor: theme.Form.RadioButton.radiobuttonActiveHoverBorderColor, + backgroundColor: + theme.Form.RadioButton.radiobuttonActiveHoverBorderColor, + borderWidth: 5, + }, + }, + { + state: 'danger', + checked: 'true', + styles: { + borderColor: theme.Form.InputText.inputErrorBorderColor, + backgroundColor: theme.Form.RadioButton.radiobuttonActiveBorderColor, + borderWidth: 1, + }, + }, + { + checked: 'true', + disabled: 'true', + styles: { + borderColor: theme.Form.RadioButton.radiobuttonActiveBorderColor, + backgroundColor: theme.Form.RadioButton.radiobuttonActiveBorderColor, + borderWidth: 5, + }, + }, + ], }, center: { width: theme.Form.RadioButton.radiobuttonIconSize, height: theme.Form.RadioButton.radiobuttonIconSize, borderRadius: theme.Form.RadioButton.radiobuttonIconSize, + backgroundColor: theme.Form.InputText.inputBg, + variants: { + checked: { true: {}, false: {} }, + disabled: { true: {}, false: {} }, + }, + compoundVariants: [ + { + disabled: 'true', + checked: 'false', + styles: { backgroundColor: 'transparent' }, + }, + ], }, outline: { position: 'absolute', @@ -149,59 +171,4 @@ const useStyles = makeStyles(({ theme }) => ({ theme.General.focusShadowWidth * 2, backgroundColor: theme.General.focusOutlineErrorColor, }, - - // centerView - defaultView: { backgroundColor: theme.Form.InputText.inputBg }, - disabledView: { backgroundColor: 'transparent' }, - - // container viewState - default: { - borderColor: theme.Form.InputText.inputBorderColor, - borderWidth: 1, - backgroundColor: theme.Form.InputText.inputBg, - }, - pressed: { - borderColor: theme.Form.InputText.inputHoverBorderColor, - backgroundColor: theme.Form.InputText.inputBg, - borderWidth: 1, - }, - checked: { - borderColor: theme.Form.RadioButton.radiobuttonActiveBorderColor, - backgroundColor: theme.Form.RadioButton.radiobuttonActiveBorderColor, - borderWidth: 5, - }, - checkedPressed: { - borderColor: theme.Form.RadioButton.radiobuttonActiveHoverBorderColor, - backgroundColor: theme.Form.RadioButton.radiobuttonActiveHoverBorderColor, - borderWidth: 5, - }, - // danger viewState - danger: { - borderColor: theme.Form.InputText.inputErrorBorderColor, - backgroundColor: theme.Form.InputText.inputBg, - borderWidth: 1, - }, - dangerChecked: { - borderColor: theme.Form.InputText.inputErrorBorderColor, - backgroundColor: theme.Form.RadioButton.radiobuttonActiveBorderColor, - borderWidth: 1, - }, - dangerCheckedPressed: { - borderColor: theme.Form.InputText.inputErrorBorderColor, - backgroundColor: theme.Form.RadioButton.radiobuttonActiveBorderColor, - borderWidth: 1, - }, - // disabled viewState - disabled: { - borderColor: theme.Form.InputText.inputBorderColor, - backgroundColor: theme.Button.Disabled.disabledButtonBg, - opacity: 0.6, - borderWidth: 1, - mixBlendMode: 'luminosity', - }, - disabledChecked: { - borderColor: theme.Form.RadioButton.radiobuttonActiveBorderColor, - backgroundColor: theme.Form.RadioButton.radiobuttonActiveBorderColor, - borderWidth: 5, - }, })) diff --git a/src/components/RadioButton/__tests__/__snapshots__/RadioButton.test.tsx.snap b/src/components/RadioButton/__tests__/__snapshots__/RadioButton.test.tsx.snap index a75154f3..80f0aff7 100644 --- a/src/components/RadioButton/__tests__/__snapshots__/RadioButton.test.tsx.snap +++ b/src/components/RadioButton/__tests__/__snapshots__/RadioButton.test.tsx.snap @@ -91,19 +91,12 @@ exports[`RadioButton snapshots checked = false, disabled = false, state = danger > @@ -159,19 +152,12 @@ exports[`RadioButton snapshots checked = false, disabled = false, state = defaul > @@ -220,7 +206,6 @@ exports[`RadioButton snapshots checked = false, disabled = true, state = default "height": 21, "justifyContent": "center", "margin": 3.5, - "mixBlendMode": "luminosity", "opacity": 0.6, "width": 21, } @@ -229,21 +214,12 @@ exports[`RadioButton snapshots checked = false, disabled = true, state = default > @@ -341,19 +317,12 @@ exports[`RadioButton snapshots checked = true, disabled = false, state = danger > @@ -409,19 +378,12 @@ exports[`RadioButton snapshots checked = true, disabled = false, state = default > @@ -470,7 +432,6 @@ exports[`RadioButton snapshots checked = true, disabled = true, state = default "height": 21, "justifyContent": "center", "margin": 3.5, - "mixBlendMode": "luminosity", "opacity": 0.6, "width": 21, } @@ -479,19 +440,12 @@ exports[`RadioButton snapshots checked = true, disabled = true, state = default > @@ -547,19 +501,12 @@ exports[`RadioButton snapshots default 1`] = ` > diff --git a/src/components/Rating/Rating.tsx b/src/components/Rating/Rating.tsx index db533e57..19356e45 100644 --- a/src/components/Rating/Rating.tsx +++ b/src/components/Rating/Rating.tsx @@ -1,7 +1,7 @@ import { memo, useCallback, useMemo } from 'react' import { type AccessibilityProps, View, type ViewProps } from 'react-native' -import { makeStyles } from '../../utils/makeStyles' +import { StyleSheet } from '../../utils' import { RatingClear } from './RatingClear' import { RatingItem } from './RatingItem' @@ -65,8 +65,6 @@ export const Rating = memo( testID, ...rest }) => { - const styles = useStyles() - const handleItemPress = useCallback( (index: number) => () => { onChange(index + 1) @@ -102,7 +100,7 @@ export const Rating = memo( } ) -const useStyles = makeStyles(({ theme }) => ({ +const styles = StyleSheet.create(({ theme }) => ({ container: { flexDirection: 'row', gap: theme.General.inlineSpacing, diff --git a/src/components/Rating/RatingClear.tsx b/src/components/Rating/RatingClear.tsx index 889585c5..5383b6b3 100644 --- a/src/components/Rating/RatingClear.tsx +++ b/src/components/Rating/RatingClear.tsx @@ -1,7 +1,7 @@ import { IconBan } from '@tabler/icons-react-native' import { memo } from 'react' -import { makeStyles } from '../../utils/makeStyles' +import { StyleSheet, SvgUniversal } from '../../utils' import { RatingItemContainer, @@ -24,12 +24,10 @@ export interface RatingClearProps extends Omit< * @see RatingItemContainer - компонент контейнер для элемента */ export const RatingClear = memo(({ ...rest }) => { - const styles = useStyles() - return ( {({ disabled, pressed }) => ( - (({ ...rest }) => { : styles.icon.color } height={styles.icon.height} + source={IconBan} width={styles.icon.width} /> )} @@ -45,7 +44,7 @@ export const RatingClear = memo(({ ...rest }) => { ) }) -const useStyles = makeStyles(({ theme }) => ({ +const styles = StyleSheet.create(({ theme }) => ({ icon: { height: theme.Form.Rating.ratingIconFontSize, width: theme.Form.Rating.ratingIconFontSize, diff --git a/src/components/Rating/RatingItem.tsx b/src/components/Rating/RatingItem.tsx index c079c7c7..54e967e7 100644 --- a/src/components/Rating/RatingItem.tsx +++ b/src/components/Rating/RatingItem.tsx @@ -1,7 +1,7 @@ import { IconStar, IconStarFilled } from '@tabler/icons-react-native' import { memo, useMemo } from 'react' -import { makeStyles } from '../../utils/makeStyles' +import { StyleSheet, SvgUniversal } from '../../utils' import { RatingItemContainer, @@ -30,14 +30,12 @@ export interface RatingItemProps extends Omit< * @see RatingItemContainer - компонент контейнер для элемента */ export const RatingItem = memo(({ checked, ...rest }) => { - const styles = useStyles() - const Icon = useMemo(() => (checked ? IconStarFilled : IconStar), [checked]) return ( {({ pressed, disabled }) => ( - (({ checked, ...rest }) => { } fillOpacity={checked ? 1 : 0} height={styles.icon.height} + source={Icon} width={styles.icon.width} /> )} @@ -67,7 +66,7 @@ export const RatingItem = memo(({ checked, ...rest }) => { ) }) -const useStyles = makeStyles(({ theme }) => ({ +const styles = StyleSheet.create(({ theme }) => ({ icon: { height: theme.Form.Rating.ratingIconFontSize, width: theme.Form.Rating.ratingIconFontSize, diff --git a/src/components/Rating/RatingItemContainer.tsx b/src/components/Rating/RatingItemContainer.tsx index e0cd066c..5f20722c 100644 --- a/src/components/Rating/RatingItemContainer.tsx +++ b/src/components/Rating/RatingItemContainer.tsx @@ -1,7 +1,7 @@ import { memo, type ReactNode } from 'react' import { Pressable, type PressableProps } from 'react-native' -import { makeStyles } from '../../utils/makeStyles' +import { StyleSheet } from '../../utils' /** * Свойста компонента контейнера для элемента в компоненте рейтинга @@ -43,8 +43,6 @@ export interface RatingItemContainerProps extends Omit< */ export const RatingItemContainer = memo( ({ disabled = false, paddings = false, children, ...rest }) => { - const styles = useStyles() - return ( ( } ) -const useStyles = makeStyles(({ theme, sizing }) => ({ +const styles = StyleSheet.create(({ theme, sizing }) => ({ container: { alignItems: 'center', justifyContent: 'center', diff --git a/src/components/Rating/__tests__/__snapshots__/Rating.test.tsx.snap b/src/components/Rating/__tests__/__snapshots__/Rating.test.tsx.snap index 6251a5fe..9a97587d 100644 --- a/src/components/Rating/__tests__/__snapshots__/Rating.test.tsx.snap +++ b/src/components/Rating/__tests__/__snapshots__/Rating.test.tsx.snap @@ -80,6 +80,7 @@ exports[`Rating component tests renders correctly with default props 1`] = ` }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -107,7 +108,7 @@ exports[`Rating component tests renders correctly with default props 1`] = ` strokeWidth={2} > @@ -222,6 +225,7 @@ exports[`Rating component tests renders correctly with default props 1`] = ` }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -265,6 +269,7 @@ exports[`Rating component tests renders correctly with default props 1`] = ` } stroke={null} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -338,6 +343,7 @@ exports[`Rating component tests renders correctly with default props 1`] = ` }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -381,6 +387,7 @@ exports[`Rating component tests renders correctly with default props 1`] = ` } stroke={null} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -454,6 +461,7 @@ exports[`Rating component tests renders correctly with default props 1`] = ` }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -497,6 +505,7 @@ exports[`Rating component tests renders correctly with default props 1`] = ` } stroke={null} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -571,6 +580,7 @@ exports[`Rating component tests renders correctly with default props 1`] = ` }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -604,7 +614,7 @@ exports[`Rating component tests renders correctly with default props 1`] = ` strokeWidth={2} > @@ -704,6 +715,7 @@ exports[`Rating component tests renders correctly with default props 1`] = ` }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -737,7 +749,7 @@ exports[`Rating component tests renders correctly with default props 1`] = ` strokeWidth={2} > @@ -850,6 +863,7 @@ exports[`Rating component tests renders the correct number of checked items - $r }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -877,7 +891,7 @@ exports[`Rating component tests renders the correct number of checked items - $r strokeWidth={2} > @@ -992,6 +1008,7 @@ exports[`Rating component tests renders the correct number of checked items - $r }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -1035,6 +1052,7 @@ exports[`Rating component tests renders the correct number of checked items - $r } stroke={null} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -1108,6 +1126,7 @@ exports[`Rating component tests renders the correct number of checked items - $r }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -1151,6 +1170,7 @@ exports[`Rating component tests renders the correct number of checked items - $r } stroke={null} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -1224,6 +1244,7 @@ exports[`Rating component tests renders the correct number of checked items - $r }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -1267,6 +1288,7 @@ exports[`Rating component tests renders the correct number of checked items - $r } stroke={null} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -1341,6 +1363,7 @@ exports[`Rating component tests renders the correct number of checked items - $r }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -1374,7 +1397,7 @@ exports[`Rating component tests renders the correct number of checked items - $r strokeWidth={2} > @@ -1474,6 +1498,7 @@ exports[`Rating component tests renders the correct number of checked items - $r }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -1507,7 +1532,7 @@ exports[`Rating component tests renders the correct number of checked items - $r strokeWidth={2} > @@ -1620,6 +1646,7 @@ exports[`Rating component tests renders the correct number of checked items - $r }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -1647,7 +1674,7 @@ exports[`Rating component tests renders the correct number of checked items - $r strokeWidth={2} > @@ -1762,6 +1791,7 @@ exports[`Rating component tests renders the correct number of checked items - $r }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -1805,6 +1835,7 @@ exports[`Rating component tests renders the correct number of checked items - $r } stroke={null} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -1878,6 +1909,7 @@ exports[`Rating component tests renders the correct number of checked items - $r }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -1921,6 +1953,7 @@ exports[`Rating component tests renders the correct number of checked items - $r } stroke={null} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -1994,6 +2027,7 @@ exports[`Rating component tests renders the correct number of checked items - $r }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -2037,6 +2071,7 @@ exports[`Rating component tests renders the correct number of checked items - $r } stroke={null} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -2110,6 +2145,7 @@ exports[`Rating component tests renders the correct number of checked items - $r }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -2153,6 +2189,7 @@ exports[`Rating component tests renders the correct number of checked items - $r } stroke={null} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -2227,6 +2264,7 @@ exports[`Rating component tests renders the correct number of checked items - $r }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -2260,7 +2298,7 @@ exports[`Rating component tests renders the correct number of checked items - $r strokeWidth={2} > diff --git a/src/components/Rating/__tests__/__snapshots__/RatingClear.test.tsx.snap b/src/components/Rating/__tests__/__snapshots__/RatingClear.test.tsx.snap index 915d8b51..798f0ac9 100644 --- a/src/components/Rating/__tests__/__snapshots__/RatingClear.test.tsx.snap +++ b/src/components/Rating/__tests__/__snapshots__/RatingClear.test.tsx.snap @@ -70,6 +70,7 @@ exports[`RatingItem component tests checked - $checked, disabled - false, presse }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -97,7 +98,7 @@ exports[`RatingItem component tests checked - $checked, disabled - false, presse strokeWidth={2} > @@ -218,6 +221,7 @@ exports[`RatingItem component tests checked - $checked, disabled - false, presse }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -245,7 +249,7 @@ exports[`RatingItem component tests checked - $checked, disabled - false, presse strokeWidth={2} > @@ -363,6 +369,7 @@ exports[`RatingItem component tests checked - $checked, disabled - false, presse }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -390,7 +397,7 @@ exports[`RatingItem component tests checked - $checked, disabled - false, presse strokeWidth={2} > @@ -511,6 +520,7 @@ exports[`RatingItem component tests checked - $checked, disabled - false, presse }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -538,7 +548,7 @@ exports[`RatingItem component tests checked - $checked, disabled - false, presse strokeWidth={2} > @@ -656,6 +668,7 @@ exports[`RatingItem component tests checked - $checked, disabled - true, pressed }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -683,7 +696,7 @@ exports[`RatingItem component tests checked - $checked, disabled - true, pressed strokeWidth={2} > @@ -804,6 +819,7 @@ exports[`RatingItem component tests checked - $checked, disabled - true, pressed }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -831,7 +847,7 @@ exports[`RatingItem component tests checked - $checked, disabled - true, pressed strokeWidth={2} > @@ -949,6 +967,7 @@ exports[`RatingItem component tests checked - $checked, disabled - true, pressed }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -976,7 +995,7 @@ exports[`RatingItem component tests checked - $checked, disabled - true, pressed strokeWidth={2} > @@ -1097,6 +1118,7 @@ exports[`RatingItem component tests checked - $checked, disabled - true, pressed }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -1124,7 +1146,7 @@ exports[`RatingItem component tests checked - $checked, disabled - true, pressed strokeWidth={2} > diff --git a/src/components/Rating/__tests__/__snapshots__/RatingItem.test.tsx.snap b/src/components/Rating/__tests__/__snapshots__/RatingItem.test.tsx.snap index 38b68329..85b90c7c 100644 --- a/src/components/Rating/__tests__/__snapshots__/RatingItem.test.tsx.snap +++ b/src/components/Rating/__tests__/__snapshots__/RatingItem.test.tsx.snap @@ -70,6 +70,7 @@ exports[`RatingItem component tests checked - false, disabled - false, pressed - }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -103,7 +104,7 @@ exports[`RatingItem component tests checked - false, disabled - false, pressed - strokeWidth={2} > @@ -208,6 +210,7 @@ exports[`RatingItem component tests checked - false, disabled - false, pressed - }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -241,7 +244,7 @@ exports[`RatingItem component tests checked - false, disabled - false, pressed - strokeWidth={2} > @@ -343,6 +347,7 @@ exports[`RatingItem component tests checked - false, disabled - false, pressed - }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -376,7 +381,7 @@ exports[`RatingItem component tests checked - false, disabled - false, pressed - strokeWidth={2} > @@ -481,6 +487,7 @@ exports[`RatingItem component tests checked - false, disabled - false, pressed - }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -514,7 +521,7 @@ exports[`RatingItem component tests checked - false, disabled - false, pressed - strokeWidth={2} > @@ -616,6 +624,7 @@ exports[`RatingItem component tests checked - false, disabled - true, pressed - }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -649,7 +658,7 @@ exports[`RatingItem component tests checked - false, disabled - true, pressed - strokeWidth={2} > @@ -754,6 +764,7 @@ exports[`RatingItem component tests checked - false, disabled - true, pressed - }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -787,7 +798,7 @@ exports[`RatingItem component tests checked - false, disabled - true, pressed - strokeWidth={2} > @@ -889,6 +901,7 @@ exports[`RatingItem component tests checked - false, disabled - true, pressed - }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -922,7 +935,7 @@ exports[`RatingItem component tests checked - false, disabled - true, pressed - strokeWidth={2} > @@ -1027,6 +1041,7 @@ exports[`RatingItem component tests checked - false, disabled - true, pressed - }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -1060,7 +1075,7 @@ exports[`RatingItem component tests checked - false, disabled - true, pressed - strokeWidth={2} > @@ -1161,6 +1177,7 @@ exports[`RatingItem component tests checked - true, disabled - false, pressed - }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -1204,6 +1221,7 @@ exports[`RatingItem component tests checked - true, disabled - false, pressed - } stroke={null} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -1282,6 +1300,7 @@ exports[`RatingItem component tests checked - true, disabled - false, pressed - }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -1325,6 +1344,7 @@ exports[`RatingItem component tests checked - true, disabled - false, pressed - } stroke={null} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -1400,6 +1420,7 @@ exports[`RatingItem component tests checked - true, disabled - false, pressed - }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -1443,6 +1464,7 @@ exports[`RatingItem component tests checked - true, disabled - false, pressed - } stroke={null} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -1521,6 +1543,7 @@ exports[`RatingItem component tests checked - true, disabled - false, pressed - }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -1564,6 +1587,7 @@ exports[`RatingItem component tests checked - true, disabled - false, pressed - } stroke={null} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -1639,6 +1663,7 @@ exports[`RatingItem component tests checked - true, disabled - true, pressed - f }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -1682,6 +1707,7 @@ exports[`RatingItem component tests checked - true, disabled - true, pressed - f } stroke={null} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -1760,6 +1786,7 @@ exports[`RatingItem component tests checked - true, disabled - true, pressed - f }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -1803,6 +1830,7 @@ exports[`RatingItem component tests checked - true, disabled - true, pressed - f } stroke={null} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -1878,6 +1906,7 @@ exports[`RatingItem component tests checked - true, disabled - true, pressed - t }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -1921,6 +1950,7 @@ exports[`RatingItem component tests checked - true, disabled - true, pressed - t } stroke={null} strokeWidth={2} + testID="SvgUniversalComponent" /> @@ -1999,6 +2029,7 @@ exports[`RatingItem component tests checked - true, disabled - true, pressed - t }, ] } + testID="SvgUniversalComponent" vbHeight={24} vbWidth={24} width={17.5} @@ -2042,6 +2073,7 @@ exports[`RatingItem component tests checked - true, disabled - true, pressed - t } stroke={null} strokeWidth={2} + testID="SvgUniversalComponent" /> diff --git a/src/components/SelectButton/SelectButton.tsx b/src/components/SelectButton/SelectButton.tsx index 7b9753a1..cf32efe7 100644 --- a/src/components/SelectButton/SelectButton.tsx +++ b/src/components/SelectButton/SelectButton.tsx @@ -20,7 +20,7 @@ import Animated, { import { scheduleOnRN } from 'react-native-worklets' -import { makeStyles } from '../../utils/makeStyles' +import { StyleSheet } from '../../utils' import { SelectButtonItem, @@ -77,7 +77,6 @@ export const SelectButton = memo( position: positionProp, ...rest }) => { - const styles = useStyles() const buttonsLayoutListRef = useRef([]) const buttonsLayoutList = useSharedValue([]) const [frameKey, setFrameKey] = useState(Date.now()) @@ -189,7 +188,7 @@ export const SelectButton = memo( } ) -const useStyles = makeStyles(({ theme }) => ({ +const styles = StyleSheet.create(({ theme }) => ({ container: { flexDirection: 'row', padding: theme.Form.SelectButton.selectButtonGroupPadding, diff --git a/src/components/SelectButton/SelectButtonItem.tsx b/src/components/SelectButton/SelectButtonItem.tsx index 0ee17b6f..09f828fe 100644 --- a/src/components/SelectButton/SelectButtonItem.tsx +++ b/src/components/SelectButton/SelectButtonItem.tsx @@ -9,8 +9,8 @@ import Animated, { import { scheduleOnRN } from 'react-native-worklets' +import { StyleSheet } from '../../utils' import { type SvgSource, SvgUniversal } from '../../utils/SvgUniversal' -import { makeStyles } from '../../utils/makeStyles' export interface SelectButtonItemProps extends Pick< ViewProps, @@ -66,62 +66,30 @@ export const SelectButtonItem = memo( showIcon = true, Icon, }) => { - const styles = useStyles() - - const iconSize = useMemo(() => { - switch (size) { - case 'small': - return styles.iconSmall - - case 'base': - return styles.iconBase + const sizeMap = useMemo( + () => ({ + small: { icon: styles.iconSmall, label: styles.labelSmall }, + base: { icon: styles.iconBase, label: styles.labelBase }, + large: { icon: styles.iconLarge, label: styles.labelLarge }, + xlarge: { icon: styles.iconXLarge, label: styles.labelXLarge }, + }), + [] + ) - case 'large': - return styles.iconLarge + const iconSize = sizeMap[size].icon + const labelFontSize = sizeMap[size].label - case 'xlarge': - return styles.iconXLarge - } - }, [ - size, - styles.iconBase, - styles.iconLarge, - styles.iconSmall, - styles.iconXLarge, - ]) - - const labelFontSize = useMemo(() => { - switch (size) { - case 'small': - return styles.labelSmall - - case 'base': - return styles.labelBase - - case 'large': - return styles.labelLarge - - case 'xlarge': - return styles.labelXLarge - } - }, [ - size, - styles.labelBase, - styles.labelLarge, - styles.labelSmall, - styles.labelXLarge, - ]) + // Extract primitive color values before the worklet closure to avoid + // capturing the unistyles HostObject (non-serializable) in the worklet. + const textColor = styles.textColor.color + const checkedTextColor = styles.checkedTextColor.color const animatedColorStyle = useAnimatedStyle(() => { return { color: interpolateColor( position.value, [index - 1, index, index + 1], - [ - styles.textColor.color, - styles.checkedTextColor.color, - styles.textColor.color, - ] + [textColor, checkedTextColor, textColor] ), } }) @@ -181,7 +149,7 @@ export const SelectButtonItem = memo( } ) -const useStyles = makeStyles( +const styles = StyleSheet.create( ({ theme, typography, border, spacing, fonts }) => ({ container: { flex: 1, diff --git a/src/components/Skeleton/Skeleton.stories.tsx b/src/components/Skeleton/Skeleton.stories.tsx index ef1ade57..c3603c6e 100644 --- a/src/components/Skeleton/Skeleton.stories.tsx +++ b/src/components/Skeleton/Skeleton.stories.tsx @@ -2,6 +2,8 @@ import type { Meta, StoryObj } from '@storybook/react' import { StyleSheet, View } from 'react-native' +import { SkeletonContextProvider } from '../../utils/SkeletonContext' + import { Skeleton } from './Skeleton' const styles = StyleSheet.create({ @@ -13,27 +15,29 @@ const meta: Meta = { title: 'Misc/Skeleton', component: Skeleton, render: () => ( - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - + ), } diff --git a/src/components/Skeleton/Skeleton.tsx b/src/components/Skeleton/Skeleton.tsx index 091bfaff..7682d80f 100644 --- a/src/components/Skeleton/Skeleton.tsx +++ b/src/components/Skeleton/Skeleton.tsx @@ -7,8 +7,8 @@ import Animated, { import Svg, { Defs, LinearGradient, Rect, Stop } from 'react-native-svg' import type { ViewProps } from 'react-native-svg/lib/typescript/fabric/utils' +import { StyleSheet, useUnistyles } from '../../utils' import { SkeletonContext } from '../../utils/SkeletonContext' -import { makeStyles } from '../../utils/makeStyles' interface SkeletonProps extends ViewProps {} @@ -17,13 +17,17 @@ interface SkeletonProps extends ViewProps {} * @see https://www.figma.com/design/4TYeki0MDLhfPGJstbIicf/UI-kit-PrimeFace-(DS)?node-id=5241-3731 */ export const Skeleton = memo(({ style, testID, ...rest }) => { - const styles = useStyles() const { globalTranslateX, registerSkeleton, unregisterSkeleton, skeletonWidth, } = useContext(SkeletonContext) + const { + theme: { + theme: { Misc }, + }, + } = useUnistyles() const skeletonRef = useRef(null) const skeletonX = useSharedValue(0) @@ -65,17 +69,17 @@ export const Skeleton = memo(({ style, testID, ...rest }) => { @@ -87,14 +91,13 @@ export const Skeleton = memo(({ style, testID, ...rest }) => { ) }) -const useStyles = makeStyles(({ border, theme }) => ({ +const styles = StyleSheet.create(({ border, theme }) => ({ container: { borderRadius: border.Radius['rounded-lg'], overflow: 'hidden', backgroundColor: theme.Misc.Skeleton.skeletonBg, }, gradientContainer: { position: 'absolute', height: '100%' }, - gradientColor: { backgroundColor: theme.Misc.Skeleton.skeletonAnimationBg }, })) export const SkeletonTestId = { diff --git a/src/components/Slider/Slider.tsx b/src/components/Slider/Slider.tsx index f7be8e32..ea38c144 100644 --- a/src/components/Slider/Slider.tsx +++ b/src/components/Slider/Slider.tsx @@ -15,7 +15,7 @@ import Animated, { import { scheduleOnRN } from 'react-native-worklets' -import { makeStyles } from '../../utils/makeStyles' +import { StyleSheet } from '../../utils' export interface SliderProps extends AccessibilityProps, Pick { @@ -64,7 +64,7 @@ const clamp = (val: number, min: number, max: number) => { */ export const Slider = memo( // TODO: refactor component to fix max-statements - // eslint-disable-next-line max-statements + ({ disabled = false, range = false, @@ -75,7 +75,6 @@ export const Slider = memo( testID, ...rest }) => { - const styles = useStyles() const minPointX = useSharedValue(0) const maxPointX = useSharedValue(0) @@ -89,12 +88,12 @@ export const Slider = memo( const pointerStyle = useMemo( () => [styles.point, isPressed && styles.hovered], - [styles.point, isPressed, styles.hovered] + [isPressed] ) const lineStyle = useMemo( () => [styles.line, isPressed && styles.hovered], - [styles.line, isPressed, styles.hovered] + [isPressed] ) const interpolateInitVal = useCallback( @@ -254,7 +253,7 @@ export const Slider = memo( } ) -const useStyles = makeStyles(({ theme, border }) => { +const styles = StyleSheet.create(({ theme, border }) => { return { container: { width: '100%', diff --git a/src/components/Tabs/TabItem/TabItem.tsx b/src/components/Tabs/TabItem/TabItem.tsx index 0b6e1466..8c6ee02c 100644 --- a/src/components/Tabs/TabItem/TabItem.tsx +++ b/src/components/Tabs/TabItem/TabItem.tsx @@ -1,8 +1,8 @@ import { memo, useCallback, type ReactNode } from 'react' import { Text, Pressable, View, type ViewProps } from 'react-native' +import { StyleSheet } from '../../../utils' import { type SvgSource, SvgUniversal } from '../../../utils/SvgUniversal' -import { makeStyles } from '../../../utils/makeStyles' export interface TabItemProps { /** SVG-иконка */ @@ -34,8 +34,6 @@ export interface TabItemProps { // export const TabItem = memo( ({ Icon, label, badge, index, onPress, disabled, active, onLayout }) => { - const styles = useStyles() - const getIconColor = useCallback( (pressed: boolean) => { if (disabled) { @@ -52,7 +50,7 @@ export const TabItem = memo( return styles.icon.color }, - [disabled, active, styles] + [disabled, active] ) return ( @@ -99,7 +97,7 @@ export const TabItem = memo( } ) -const useStyles = makeStyles(({ theme, typography, fonts }) => ({ +const styles = StyleSheet.create(({ theme, typography, fonts }) => ({ container: { alignItems: 'center', flexDirection: 'row', diff --git a/src/components/Tabs/Tabs.tsx b/src/components/Tabs/Tabs.tsx index 3f6529d2..646cd8d9 100644 --- a/src/components/Tabs/Tabs.tsx +++ b/src/components/Tabs/Tabs.tsx @@ -13,7 +13,7 @@ import Animated, { withTiming, } from 'react-native-reanimated' -import { makeStyles } from '../../utils/makeStyles' +import { StyleSheet } from '../../utils' import { TabItem, type TabItemProps } from './TabItem/TabItem' @@ -37,8 +37,6 @@ export interface TabsProps // export const Tabs = memo( ({ items, disabled = false, activeIndex, onChange, testID, ...rest }) => { - const styles = useStyles() - const [tabsLayouts, setTabsLayouts] = useState< Record >({}) @@ -93,7 +91,7 @@ export const Tabs = memo( } ) -const useStyles = makeStyles(({ theme, border }) => ({ +const styles = StyleSheet.create(({ theme, border }) => ({ container: { flexDirection: 'row', gap: theme.Panel.TabView.tabviewHeaderSpacing, diff --git a/src/components/Tabs/__tests__/__snapshots__/Tabs.test.tsx.snap b/src/components/Tabs/__tests__/__snapshots__/Tabs.test.tsx.snap index c3c1e6a2..753038b8 100644 --- a/src/components/Tabs/__tests__/__snapshots__/Tabs.test.tsx.snap +++ b/src/components/Tabs/__tests__/__snapshots__/Tabs.test.tsx.snap @@ -166,17 +166,13 @@ exports[`TabItem component tests active: label, badge 1`] = ` > { /** Текст */ - text: string + readonly text: string /** true, если необходимо полное скругление углов компонента */ - rounded?: boolean + readonly rounded?: boolean /** * Выбор варианта стиля компонента * @default 'basic' */ - severity?: 'basic' | 'info' | 'success' | 'warning' | 'danger' | 'secondary' + readonly severity?: + | 'basic' + | 'info' + | 'success' + | 'warning' + | 'danger' + | 'secondary' /** * Показать или скрыть иконку внутри компонента * @default true */ - showIcon?: boolean + readonly showIcon?: boolean /** Дополнительная стилизация для контейнера компонента */ - style?: StyleProp + readonly style?: StyleProp /** SVG-иконка */ - Icon?: SvgSource + readonly Icon?: SvgSource } /** * Используется для маркировки элементов интерфейса * @see https://www.figma.com/design/4TYeki0MDLhfPGJstbIicf/UI-kit-PrimeFace-(DS)?node-id=484-4921 */ -export const Tag = memo( +export const Tag = memo( ({ text, rounded, @@ -52,31 +58,27 @@ export const Tag = memo( Icon, testID, ...rest - }) => { - const styles = useStyles() + }: TagProps) => { + tagStyles.useVariants({ severity }) return ( {showIcon && Icon ? ( ) : null} {text} @@ -87,7 +89,7 @@ export const Tag = memo( } ) -const useStyles = makeStyles( +const tagStyles = StyleSheet.create( ({ theme, border, spacing, typography, fonts }) => ({ container: { alignSelf: 'flex-start', @@ -97,12 +99,31 @@ const useStyles = makeStyles( paddingHorizontal: theme.Misc.Tag.tagPadding, height: theme.Misc.Tag.tagHeight, borderRadius: border.Radius['rounded-lg'], + variants: { + severity: { + basic: { backgroundColor: theme.Misc.Badge.badgeBg }, + info: { + backgroundColor: theme.Button.Severity.Info.Basic.infoButtonBg, + }, + success: { + backgroundColor: + theme.Button.Severity.Success.Basic.successButtonBg, + }, + warning: { + backgroundColor: + theme.Button.Severity.Warning.Basic.warningButtonBg, + }, + danger: { + backgroundColor: theme.Button.Severity.Danger.Basic.dangerButtonBg, + }, + secondary: { backgroundColor: theme.Surface['surface-border'] }, + }, + }, }, roundedContainer: { borderRadius: border.Radius['rounded-full'] }, icon: { width: theme.Misc.Tag.tagFontSize, height: theme.Misc.Tag.tagFontSize, - color: theme.Misc.Badge.badgeTextColor, }, text: { flexShrink: 1, @@ -110,25 +131,17 @@ const useStyles = makeStyles( includeFontPadding: false, verticalAlign: 'middle', fontFamily: fonts.primary, + variants: { + severity: { + basic: { color: theme.Misc.Badge.badgeTextColor }, + info: { color: theme.Misc.Badge.badgeInfoTextColor }, + success: { color: theme.Misc.Badge.badgeSuccessTextColor }, + warning: { color: theme.Misc.Badge.badgeWarningTextColor }, + danger: { color: theme.Misc.Badge.badgeDangerTextColor }, + secondary: { color: theme.Misc.Badge.badgeTextColor }, + }, + }, }, - textbasic: { color: theme.Misc.Badge.badgeTextColor }, - textinfo: { color: theme.Misc.Badge.badgeInfoTextColor }, - textwarning: { color: theme.Misc.Badge.badgeWarningTextColor }, - textsuccess: { color: theme.Misc.Badge.badgeSuccessTextColor }, - textdanger: { color: theme.Misc.Badge.badgeDangerTextColor }, - textsecondary: { color: theme.Misc.Badge.badgeTextColor }, - basic: { backgroundColor: theme.Misc.Badge.badgeBg }, - info: { backgroundColor: theme.Button.Severity.Info.Basic.infoButtonBg }, - success: { - backgroundColor: theme.Button.Severity.Success.Basic.successButtonBg, - }, - warning: { - backgroundColor: theme.Button.Severity.Warning.Basic.warningButtonBg, - }, - danger: { - backgroundColor: theme.Button.Severity.Danger.Basic.dangerButtonBg, - }, - secondary: { backgroundColor: theme.Surface['surface-border'] }, }) ) diff --git a/src/components/Tag/__tests__/__snapshots__/Tag.test.tsx.snap b/src/components/Tag/__tests__/__snapshots__/Tag.test.tsx.snap index 2704eec7..d41f7d70 100644 --- a/src/components/Tag/__tests__/__snapshots__/Tag.test.tsx.snap +++ b/src/components/Tag/__tests__/__snapshots__/Tag.test.tsx.snap @@ -10,15 +10,13 @@ exports[`Tag snapshot: default 1`] = ` { "alignItems": "center", "alignSelf": "flex-start", + "backgroundColor": "#44e858", "borderRadius": 7, "flexDirection": "row", "gap": 3.5, "height": 21, "paddingHorizontal": 7, }, - { - "backgroundColor": "#44e858", - }, undefined, ] } @@ -27,18 +25,14 @@ exports[`Tag snapshot: default 1`] = ` @@ -58,15 +52,13 @@ exports[`Tag snapshot: severity = basic, rounded = true, with Icon 1`] = ` { "alignItems": "center", "alignSelf": "flex-start", + "backgroundColor": "#44e858", "borderRadius": 7, "flexDirection": "row", "gap": 3.5, "height": 21, "paddingHorizontal": 7, }, - { - "backgroundColor": "#44e858", - }, { "borderRadius": 100, }, @@ -156,18 +148,14 @@ exports[`Tag snapshot: severity = basic, rounded = true, with Icon 1`] = ` @@ -187,15 +175,13 @@ exports[`Tag snapshot: severity = danger 1`] = ` { "alignItems": "center", "alignSelf": "flex-start", + "backgroundColor": "#fbacaa", "borderRadius": 7, "flexDirection": "row", "gap": 3.5, "height": 21, "paddingHorizontal": 7, }, - { - "backgroundColor": "#fbacaa", - }, undefined, ] } @@ -204,18 +190,14 @@ exports[`Tag snapshot: severity = danger 1`] = ` @@ -235,15 +217,13 @@ exports[`Tag snapshot: severity = info, showIcon = false, with Icon 1`] = ` { "alignItems": "center", "alignSelf": "flex-start", + "backgroundColor": "#aad7fb", "borderRadius": 7, "flexDirection": "row", "gap": 3.5, "height": 21, "paddingHorizontal": 7, }, - { - "backgroundColor": "#aad7fb", - }, undefined, ] } @@ -331,18 +311,14 @@ exports[`Tag snapshot: severity = info, showIcon = false, with Icon 1`] = ` @@ -362,15 +338,13 @@ exports[`Tag snapshot: severity = secondary, rounded = false, showIcon = true, w { "alignItems": "center", "alignSelf": "flex-start", + "backgroundColor": "#e2e2e4", "borderRadius": 7, "flexDirection": "row", "gap": 3.5, "height": 21, "paddingHorizontal": 7, }, - { - "backgroundColor": "#e2e2e4", - }, false, ] } @@ -458,18 +432,14 @@ exports[`Tag snapshot: severity = secondary, rounded = false, showIcon = true, w @@ -494,15 +464,13 @@ exports[`Tag snapshot: severity = success, with custom styles 1`] = ` { "alignItems": "center", "alignSelf": "flex-start", + "backgroundColor": "#aafbb7", "borderRadius": 7, "flexDirection": "row", "gap": 3.5, "height": 21, "paddingHorizontal": 7, }, - { - "backgroundColor": "#aafbb7", - }, undefined, ] } @@ -511,18 +479,14 @@ exports[`Tag snapshot: severity = success, with custom styles 1`] = ` @@ -542,15 +506,13 @@ exports[`Tag snapshot: severity = warning 1`] = ` { "alignItems": "center", "alignSelf": "flex-start", + "backgroundColor": "#fddeaa", "borderRadius": 7, "flexDirection": "row", "gap": 3.5, "height": 21, "paddingHorizontal": 7, }, - { - "backgroundColor": "#fddeaa", - }, undefined, ] } @@ -559,18 +521,14 @@ exports[`Tag snapshot: severity = warning 1`] = ` diff --git a/src/components/Timer/Timer.tsx b/src/components/Timer/Timer.tsx index a56e4269..ab333469 100644 --- a/src/components/Timer/Timer.tsx +++ b/src/components/Timer/Timer.tsx @@ -9,7 +9,7 @@ import Animated, { } from 'react-native-reanimated' import Svg, { Circle } from 'react-native-svg' -import { makeStyles } from '../../utils/makeStyles' +import { StyleSheet } from '../../utils' import { TimerFlip } from './TimerFlip' import { COUNTER_SIZE } from './constants' @@ -33,7 +33,6 @@ const BORDER_WIDTH = 2 * @see https://www.figma.com/design/2ZnL6XPKEpxAHvrlbRvnMu/Template-Tailwind-CSS-(DS)?node-id=270-1514 */ export const Timer = memo(({ countFrom, onFinish }) => { - const styles = useStyles() const circleAnimation = useSharedValue(0) const [currentTimerValue, setCurrentTimerValue] = useState(countFrom) const circumferenceRadius = COUNTER_SIZE / 2 - BORDER_WIDTH @@ -97,7 +96,7 @@ export const Timer = memo(({ countFrom, onFinish }) => { ) }) -const useStyles = makeStyles(({ typography }) => ({ +const styles = StyleSheet.create(({ typography }) => ({ container: { overflow: 'hidden', width: COUNTER_SIZE, diff --git a/src/components/Timer/TimerFlip.tsx b/src/components/Timer/TimerFlip.tsx index bf8089af..1c213841 100644 --- a/src/components/Timer/TimerFlip.tsx +++ b/src/components/Timer/TimerFlip.tsx @@ -8,7 +8,7 @@ import Animated, { import { scheduleOnRN } from 'react-native-worklets' -import { makeStyles } from '../../utils/makeStyles' +import { StyleSheet } from '../../utils' import { COUNTER_SIZE } from './constants' @@ -18,7 +18,6 @@ interface TimerFlipProps { } export const TimerFlip = memo(({ value, duration = 300 }) => { - const styles = useStyles() const [currentValue, setCurrentValue] = useState(value) const [nextValue, setNextValue] = useState(value) const progress = useSharedValue(0) @@ -66,7 +65,7 @@ export const TimerFlip = memo(({ value, duration = 300 }) => { ) }) -const useStyles = makeStyles(({ typography, fonts }) => ({ +const styles = StyleSheet.create(({ typography, fonts }) => ({ container: { overflow: 'hidden', width: COUNTER_SIZE, diff --git a/src/components/ToggleButton/ToggleButton.tsx b/src/components/ToggleButton/ToggleButton.tsx index 8ec530a4..4f1b53aa 100644 --- a/src/components/ToggleButton/ToggleButton.tsx +++ b/src/components/ToggleButton/ToggleButton.tsx @@ -9,56 +9,52 @@ import { type ViewStyle, } from 'react-native' +import { StyleSheet } from '../../utils' import { type SvgSource, SvgUniversal } from '../../utils/SvgUniversal' -import { makeStyles } from '../../utils/makeStyles' - -import { useIconSize } from './hooks/useIconSize' -import { useLabelSize } from './hooks/useLabelSize' -import { useStateStyles } from './hooks/useStateStyles' export interface ToggleButtonProps extends AccessibilityProps, Pick { /** Обработчик нажатия на кнопку */ - onPress: () => void + readonly onPress: () => void /** * true, если необходим компонент в активном состоянии * @default false */ - checked?: boolean + readonly checked?: boolean /** * Управление доступностью компонента * @default false */ - disabled?: boolean + readonly disabled?: boolean /** Отображение только иконки без текста */ - iconOnly?: boolean + readonly iconOnly?: boolean /** * Выбор позиции иконки. 'left' - иконка слева, 'right' - иконка справа, null - иконка скрыта * @default 'left' */ - iconPos?: 'left' | 'right' | null + readonly iconPos?: 'left' | 'right' | null /** Текст на кнопке */ - label?: string + readonly label?: string /** * Выбор размера элемента * @default 'base' */ - size?: 'xlarge' | 'large' | 'base' | 'small' + readonly size?: 'xlarge' | 'large' | 'base' | 'small' /** Дополнительная стилизация для контейнера компонента */ - style?: StyleProp + readonly style?: StyleProp /** SVG-иконка */ - Icon?: SvgSource + readonly Icon?: SvgSource } /** * Используется для выбора нескольких значений с помощью кнопки * @see https://www.figma.com/design/4TYeki0MDLhfPGJstbIicf/UI-kit-PrimeFace-(DS)?node-id=484-4821 */ -export const ToggleButton = memo( +export const ToggleButton = memo( ({ onPress, - checked, - disabled, + checked = false, + disabled = false, iconOnly: iconOnlyProp, iconPos = 'left', label, @@ -67,33 +63,30 @@ export const ToggleButton = memo( Icon, testID, ...rest - }) => { - const styles = useStyles() - const labelSize = useLabelSize(size) - const iconSize = useIconSize(size) + }: ToggleButtonProps) => { const [pressed, setPressed] = useState(false) - const stateStyles = useStateStyles(checked, disabled, pressed) + + toggleStyles.useVariants({ + size, + checked: checked ? 'true' : 'false', + pressed: pressed ? 'true' : 'false', + disabled: disabled ? 'true' : 'false', + }) const iconOnly = useMemo( () => iconOnlyProp || !label, [iconOnlyProp, label] ) - const icon = useMemo(() => { - if (!Icon) { - return null - } - - return ( - - ) - }, [Icon, iconSize.height, iconSize.width, stateStyles.label]) + const icon = Icon ? ( + + ) : null const onPressIn = useCallback(() => setPressed(true), []) const onPressOut = useCallback(() => setPressed(false), []) @@ -101,7 +94,7 @@ export const ToggleButton = memo( return ( ( > @@ -122,10 +113,7 @@ export const ToggleButton = memo( ) : ( <> {iconPos === 'left' && icon} - + {label} {Icon && iconPos === 'right' ? icon : null} @@ -137,42 +125,182 @@ export const ToggleButton = memo( } ) -const useStyles = makeStyles(({ theme, spacing, border, fonts }) => ({ - container: { - alignSelf: 'flex-start', - borderRadius: border.Radius['rounded-full'], - borderWidth: border.Width.border, - overflow: 'hidden', - }, - contentContainer: { - flexDirection: 'row', - alignItems: 'center', - alignSelf: 'flex-start', - borderRadius: border.Radius['rounded-full'], - paddingVertical: theme.Button.Common.buttonPaddingTopBottom, - paddingHorizontal: spacing.Padding['p-6'], - gap: spacing.Gap['gap-3'], - }, - xlarge: { minHeight: theme.Button.Common.buttonHeightXL }, - large: { minHeight: theme.Button.Common.buttonHeightLG }, - base: { - minHeight: theme.Button.Common.buttonHeight, - paddingHorizontal: theme.Button.Common.buttonPaddingLeftRight, - gap: theme.General.inlineSpacing, - }, - small: { - minHeight: theme.Button.Common.buttonHeightSM, - paddingHorizontal: spacing.Padding['p-3'], - gap: theme.General.inlineSpacing, - }, - iconOnly: { - aspectRatio: 1, - paddingHorizontal: 0, - paddingVertical: 0, - justifyContent: 'center', - }, - label: { flexShrink: 1, fontFamily: fonts.primary }, -})) +const toggleStyles = StyleSheet.create( + ({ theme, spacing, border, fonts, typography }) => ({ + container: { + alignSelf: 'flex-start', + borderRadius: border.Radius['rounded-full'], + borderWidth: border.Width.border, + overflow: 'hidden', + variants: { + checked: { + true: { + borderColor: theme.Form.ToggleButton.toggleButtonActiveBorderColor, + }, + false: { + borderColor: theme.Form.ToggleButton.toggleButtonBorderColor, + }, + }, + pressed: { + true: { borderColor: theme.Form.ToggleButton.toggleButtonHoverBg }, + false: {}, + }, + disabled: { + true: { + borderColor: theme.Button.Disabled.disabledButtonBorderColor, + opacity: 0.6, + }, + false: {}, + }, + }, + compoundVariants: [ + { + checked: 'true', + pressed: 'true', + styles: { + borderColor: + theme.Form.ToggleButton.toggleButtonActiveHoverBorderColor, + }, + }, + ], + }, + contentContainer: { + flexDirection: 'row', + alignItems: 'center', + alignSelf: 'flex-start', + borderRadius: border.Radius['rounded-full'], + paddingVertical: theme.Button.Common.buttonPaddingTopBottom, + paddingHorizontal: spacing.Padding['p-6'], + gap: spacing.Gap['gap-3'], + variants: { + size: { + xlarge: { minHeight: theme.Button.Common.buttonHeightXL }, + large: { minHeight: theme.Button.Common.buttonHeightLG }, + base: { + minHeight: theme.Button.Common.buttonHeight, + paddingHorizontal: theme.Button.Common.buttonPaddingLeftRight, + gap: theme.General.inlineSpacing, + }, + small: { + minHeight: theme.Button.Common.buttonHeightSM, + paddingHorizontal: spacing.Padding['p-3'], + gap: theme.General.inlineSpacing, + }, + }, + checked: { + true: { + backgroundColor: theme.Form.ToggleButton.toggleButtonActiveBg, + }, + false: { backgroundColor: theme.Form.ToggleButton.toggleButtonBg }, + }, + pressed: { + true: { + backgroundColor: theme.Form.ToggleButton.toggleButtonHoverBg, + }, + false: {}, + }, + disabled: { + true: { backgroundColor: theme.Button.Disabled.disabledButtonBg }, + false: {}, + }, + }, + compoundVariants: [ + { + checked: 'true', + pressed: 'true', + styles: { + backgroundColor: theme.Form.ToggleButton.toggleButtonActiveHoverBg, + }, + }, + ], + }, + iconOnly: { + aspectRatio: 1, + paddingHorizontal: 0, + paddingVertical: 0, + justifyContent: 'center', + }, + icon: { + variants: { + size: { + xlarge: { + width: typography.Size['text-4xl'], + height: typography.Size['text-4xl'], + }, + large: { + width: typography.Size['text-2xl'], + height: typography.Size['text-2xl'], + }, + base: { + width: typography.Size['text-xl'], + height: typography.Size['text-xl'], + }, + small: { + width: typography.Size['text-base'], + height: typography.Size['text-base'], + }, + }, + checked: { + true: { color: theme.Form.ToggleButton.toggleButtonActiveTextColor }, + false: { color: theme.Form.ToggleButton.toggleButtonTextColor }, + }, + pressed: { + true: { color: theme.Form.ToggleButton.toggleButtonHoverTextColor }, + false: {}, + }, + disabled: { + true: { color: theme.Button.Disabled.disabledButtonTextColor }, + false: {}, + }, + }, + compoundVariants: [ + { + checked: 'true', + pressed: 'true', + styles: { + color: theme.Form.ToggleButton.toggleButtonTextActiveHoverColor, + }, + }, + ], + }, + label: { + flexShrink: 1, + fontFamily: fonts.primary, + fontWeight: '600', + includeFontPadding: false, + verticalAlign: 'middle', + variants: { + size: { + xlarge: { fontSize: typography.Size['text-2xl'] }, + large: { fontSize: typography.Size['text-xl'] }, + base: { fontSize: typography.Size['text-base'] }, + small: { fontSize: typography.Size['text-sm'] }, + }, + checked: { + true: { color: theme.Form.ToggleButton.toggleButtonActiveTextColor }, + false: { color: theme.Form.ToggleButton.toggleButtonTextColor }, + }, + pressed: { + true: { color: theme.Form.ToggleButton.toggleButtonHoverTextColor }, + false: {}, + }, + disabled: { + true: { color: theme.Button.Disabled.disabledButtonTextColor }, + false: {}, + }, + }, + compoundVariants: [ + { + checked: 'true', + pressed: 'true', + styles: { + color: theme.Form.ToggleButton.toggleButtonTextActiveHoverColor, + }, + }, + ], + }, + }) +) export const ToggleButtonTestId = { root: 'ToggleButton', diff --git a/src/components/ToggleButton/__tests__/__snapshots__/ToggleButton.test.tsx.snap b/src/components/ToggleButton/__tests__/__snapshots__/ToggleButton.test.tsx.snap index 0f327a30..9c25a385 100644 --- a/src/components/ToggleButton/__tests__/__snapshots__/ToggleButton.test.tsx.snap +++ b/src/components/ToggleButton/__tests__/__snapshots__/ToggleButton.test.tsx.snap @@ -7,24 +7,13 @@ exports[`ToggleButton snapshots checked = false, disabled = false, iconOnly = fa onPressIn={[Function]} onPressOut={[Function]} style={ - [ - { - "alignSelf": "flex-start", - "borderRadius": 100, - "borderWidth": 1, - "overflow": "hidden", - }, - undefined, - [ - { - "borderColor": "rgba(255, 255, 255, 0.0001)", - }, - false, - false, - false, - false, - ], - ] + { + "alignSelf": "flex-start", + "borderColor": "rgba(255, 255, 255, 0.0001)", + "borderRadius": 100, + "borderWidth": 1, + "overflow": "hidden", + } } testID="ToggleButton" > @@ -34,27 +23,15 @@ exports[`ToggleButton snapshots checked = false, disabled = false, iconOnly = fa { "alignItems": "center", "alignSelf": "flex-start", + "backgroundColor": "#e2e2e4", "borderRadius": 100, "flexDirection": "row", - "gap": 10.5, - "paddingHorizontal": 21, - "paddingVertical": 0, - }, - { "gap": 7, "minHeight": 28, "paddingHorizontal": 10.5, + "paddingVertical": 0, }, - false, - [ - { - "backgroundColor": "#e2e2e4", - }, - false, - false, - false, - false, - ], + {}, ] } testID="ToggleButton.container" @@ -69,7 +46,7 @@ exports[`ToggleButton snapshots checked = false, disabled = false, iconOnly = fa meetOrSlice={0} minX={0} minY={0} - stroke="currentColor" + stroke="#181a1f" strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} @@ -79,15 +56,6 @@ exports[`ToggleButton snapshots checked = false, disabled = false, iconOnly = fa "backgroundColor": "transparent", "borderWidth": 0, }, - [ - { - "color": "#181a1f", - }, - false, - false, - false, - false, - ], { "flex": 0, "height": 14, @@ -102,7 +70,6 @@ exports[`ToggleButton snapshots checked = false, disabled = false, iconOnly = fa xmlns="http://www.w3.org/2000/svg" > @@ -213,24 +165,13 @@ exports[`ToggleButton snapshots checked = false, disabled = false, iconOnly = fa onPressIn={[Function]} onPressOut={[Function]} style={ - [ - { - "alignSelf": "flex-start", - "borderRadius": 100, - "borderWidth": 1, - "overflow": "hidden", - }, - undefined, - [ - { - "borderColor": "rgba(255, 255, 255, 0.0001)", - }, - false, - false, - false, - false, - ], - ] + { + "alignSelf": "flex-start", + "borderColor": "rgba(255, 255, 255, 0.0001)", + "borderRadius": 100, + "borderWidth": 1, + "overflow": "hidden", + } } testID="ToggleButton" > @@ -240,58 +181,30 @@ exports[`ToggleButton snapshots checked = false, disabled = false, iconOnly = fa { "alignItems": "center", "alignSelf": "flex-start", + "backgroundColor": "#e2e2e4", "borderRadius": 100, "flexDirection": "row", - "gap": 10.5, - "paddingHorizontal": 21, - "paddingVertical": 0, - }, - { "gap": 7, "minHeight": 28, "paddingHorizontal": 10.5, + "paddingVertical": 0, }, - false, - [ - { - "backgroundColor": "#e2e2e4", - }, - false, - false, - false, - false, - ], + {}, ] } testID="ToggleButton.container" > @@ -307,7 +220,7 @@ exports[`ToggleButton snapshots checked = false, disabled = false, iconOnly = fa meetOrSlice={0} minX={0} minY={0} - stroke="currentColor" + stroke="#181a1f" strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} @@ -317,15 +230,6 @@ exports[`ToggleButton snapshots checked = false, disabled = false, iconOnly = fa "backgroundColor": "transparent", "borderWidth": 0, }, - [ - { - "color": "#181a1f", - }, - false, - false, - false, - false, - ], { "flex": 0, "height": 14, @@ -340,7 +244,6 @@ exports[`ToggleButton snapshots checked = false, disabled = false, iconOnly = fa xmlns="http://www.w3.org/2000/svg" > @@ -446,16 +339,13 @@ exports[`ToggleButton snapshots checked = false, disabled = false, iconOnly = tr { "alignItems": "center", "alignSelf": "flex-start", + "backgroundColor": "#e2e2e4", "borderRadius": 100, "flexDirection": "row", - "gap": 10.5, - "paddingHorizontal": 21, - "paddingVertical": 0, - }, - { "gap": 7, "minHeight": 35, "paddingHorizontal": 14, + "paddingVertical": 0, }, { "aspectRatio": 1, @@ -463,15 +353,6 @@ exports[`ToggleButton snapshots checked = false, disabled = false, iconOnly = tr "paddingHorizontal": 0, "paddingVertical": 0, }, - [ - { - "backgroundColor": "#e2e2e4", - }, - false, - false, - false, - false, - ], ] } testID="ToggleButton.container" @@ -486,7 +367,7 @@ exports[`ToggleButton snapshots checked = false, disabled = false, iconOnly = tr meetOrSlice={0} minX={0} minY={0} - stroke="currentColor" + stroke="#181a1f" strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} @@ -496,15 +377,6 @@ exports[`ToggleButton snapshots checked = false, disabled = false, iconOnly = tr "backgroundColor": "transparent", "borderWidth": 0, }, - [ - { - "color": "#181a1f", - }, - false, - false, - false, - false, - ], { "flex": 0, "height": 17, @@ -519,7 +391,6 @@ exports[`ToggleButton snapshots checked = false, disabled = false, iconOnly = tr xmlns="http://www.w3.org/2000/svg" > @@ -628,27 +487,15 @@ exports[`ToggleButton snapshots checked = false, disabled = true, iconOnly = fal { "alignItems": "center", "alignSelf": "flex-start", + "backgroundColor": "#e2e2e4", "borderRadius": 100, "flexDirection": "row", "gap": 10.5, + "minHeight": 49, "paddingHorizontal": 21, "paddingVertical": 0, }, - { - "minHeight": 49, - }, - false, - [ - { - "backgroundColor": "#e2e2e4", - }, - false, - false, - false, - { - "backgroundColor": "#e2e2e4", - }, - ], + {}, ] } testID="ToggleButton.container" @@ -663,7 +510,7 @@ exports[`ToggleButton snapshots checked = false, disabled = true, iconOnly = fal meetOrSlice={0} minX={0} minY={0} - stroke="currentColor" + stroke="#85888e" strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} @@ -673,17 +520,6 @@ exports[`ToggleButton snapshots checked = false, disabled = true, iconOnly = fal "backgroundColor": "transparent", "borderWidth": 0, }, - [ - { - "color": "#181a1f", - }, - false, - false, - false, - { - "color": "#85888e", - }, - ], { "flex": 0, "height": 21, @@ -698,7 +534,6 @@ exports[`ToggleButton snapshots checked = false, disabled = true, iconOnly = fal xmlns="http://www.w3.org/2000/svg" > @@ -811,28 +629,13 @@ exports[`ToggleButton snapshots checked = true, disabled = false, iconOnly = fal onPressIn={[Function]} onPressOut={[Function]} style={ - [ - { - "alignSelf": "flex-start", - "borderRadius": 100, - "borderWidth": 1, - "overflow": "hidden", - }, - { - "margin": 10, - }, - [ - { - "borderColor": "rgba(255, 255, 255, 0.0001)", - }, - false, - { - "borderColor": "rgba(255, 255, 255, 0.0001)", - }, - false, - false, - ], - ] + { + "alignSelf": "flex-start", + "borderColor": "rgba(255, 255, 255, 0.0001)", + "borderRadius": 100, + "borderWidth": 1, + "overflow": "hidden", + } } testID="ToggleButton" > @@ -842,32 +645,20 @@ exports[`ToggleButton snapshots checked = true, disabled = false, iconOnly = fal { "alignItems": "center", "alignSelf": "flex-start", + "backgroundColor": "#2b2e33", "borderRadius": 100, "flexDirection": "row", "gap": 10.5, + "minHeight": 56, "paddingHorizontal": 21, "paddingVertical": 0, }, - { - "minHeight": 56, - }, { "aspectRatio": 1, "justifyContent": "center", "paddingHorizontal": 0, "paddingVertical": 0, }, - [ - { - "backgroundColor": "#e2e2e4", - }, - false, - { - "backgroundColor": "#2b2e33", - }, - false, - false, - ], ] } testID="ToggleButton.container" @@ -882,7 +673,7 @@ exports[`ToggleButton snapshots checked = true, disabled = false, iconOnly = fal meetOrSlice={0} minX={0} minY={0} - stroke="currentColor" + stroke="#ffffff" strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} @@ -892,17 +683,6 @@ exports[`ToggleButton snapshots checked = true, disabled = false, iconOnly = fal "backgroundColor": "transparent", "borderWidth": 0, }, - [ - { - "color": "#181a1f", - }, - false, - { - "color": "#ffffff", - }, - false, - false, - ], { "flex": 0, "height": 31, @@ -917,7 +697,6 @@ exports[`ToggleButton snapshots checked = true, disabled = false, iconOnly = fal xmlns="http://www.w3.org/2000/svg" > @@ -1022,16 +792,13 @@ exports[`ToggleButton snapshots default 1`] = ` { "alignItems": "center", "alignSelf": "flex-start", + "backgroundColor": "#e2e2e4", "borderRadius": 100, "flexDirection": "row", - "gap": 10.5, - "paddingHorizontal": 21, - "paddingVertical": 0, - }, - { "gap": 7, "minHeight": 35, "paddingHorizontal": 14, + "paddingVertical": 0, }, { "aspectRatio": 1, @@ -1039,15 +806,6 @@ exports[`ToggleButton snapshots default 1`] = ` "paddingHorizontal": 0, "paddingVertical": 0, }, - [ - { - "backgroundColor": "#e2e2e4", - }, - false, - false, - false, - false, - ], ] } testID="ToggleButton.container" diff --git a/src/components/ToggleButton/hooks/useIconSize.ts b/src/components/ToggleButton/hooks/useIconSize.ts deleted file mode 100644 index a5e679d8..00000000 --- a/src/components/ToggleButton/hooks/useIconSize.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { useMemo } from 'react' - -import { makeStyles } from '../../../utils/makeStyles' -import type { ToggleButtonProps } from '../ToggleButton' - -export const useIconSize = (size: ToggleButtonProps['size'] = 'base') => { - const styles = useStyles() - - return useMemo(() => { - return styles[size] - }, [size, styles]) -} - -const useStyles = makeStyles(({ typography }) => ({ - xlarge: { - width: typography.Size['text-4xl'], - height: typography.Size['text-4xl'], - }, - large: { - width: typography.Size['text-2xl'], - height: typography.Size['text-2xl'], - }, - base: { - width: typography.Size['text-xl'], - height: typography.Size['text-xl'], - }, - small: { - width: typography.Size['text-base'], - height: typography.Size['text-base'], - }, -})) diff --git a/src/components/ToggleButton/hooks/useLabelSize.ts b/src/components/ToggleButton/hooks/useLabelSize.ts deleted file mode 100644 index 99e68212..00000000 --- a/src/components/ToggleButton/hooks/useLabelSize.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { useMemo } from 'react' - -import { makeStyles } from '../../../utils/makeStyles' -import type { ToggleButtonProps } from '../ToggleButton' - -export const useLabelSize = (size: ToggleButtonProps['size'] = 'base') => { - const styles = useStyles() - - return useMemo(() => { - return [styles.common, styles[size]] - }, [size, styles]) -} - -const useStyles = makeStyles(({ typography }) => ({ - common: { - fontWeight: '600', - includeFontPadding: false, - verticalAlign: 'middle', - }, - xlarge: { fontSize: typography.Size['text-2xl'] }, - large: { fontSize: typography.Size['text-xl'] }, - base: { fontSize: typography.Size['text-base'] }, - small: { fontSize: typography.Size['text-sm'] }, -})) diff --git a/src/components/ToggleButton/hooks/useStateStyles.ts b/src/components/ToggleButton/hooks/useStateStyles.ts deleted file mode 100644 index f0a47504..00000000 --- a/src/components/ToggleButton/hooks/useStateStyles.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { useMemo } from 'react' - -import { makeStyles } from '../../../utils/makeStyles' - -export const useStateStyles = ( - checked = false, - disabled = false, - pressed = false -) => { - const styles = useStyles() - - const borderContainer = useMemo( - () => [ - styles.borderContainer, - pressed && styles.pressedBorderContainer, - checked && styles.checkedBorderContainer, - checked && pressed && styles.checkedPressedBorderContainer, - disabled && styles.disabledBorderContainer, - ], - [ - checked, - disabled, - pressed, - styles.borderContainer, - styles.checkedBorderContainer, - styles.checkedPressedBorderContainer, - styles.disabledBorderContainer, - styles.pressedBorderContainer, - ] - ) - - const contentContainer = useMemo( - () => [ - styles.contentContainer, - pressed && styles.pressedContentContainer, - checked && styles.checkedContentContainer, - checked && pressed && styles.checkedPressedContentContainer, - disabled && styles.disabledContentContainer, - ], - [ - checked, - disabled, - pressed, - styles.checkedContentContainer, - styles.checkedPressedContentContainer, - styles.contentContainer, - styles.disabledContentContainer, - styles.pressedContentContainer, - ] - ) - - const label = useMemo( - () => [ - styles.label, - pressed && styles.pressedLabel, - checked && styles.checkedLabel, - checked && pressed && styles.checkedPressedLabel, - disabled && styles.disabledLabel, - ], - [ - checked, - disabled, - pressed, - styles.checkedLabel, - styles.checkedPressedLabel, - styles.disabledLabel, - styles.label, - styles.pressedLabel, - ] - ) - - return useMemo( - () => ({ borderContainer, contentContainer, label }), - [borderContainer, contentContainer, label] - ) -} - -const useStyles = makeStyles(({ theme }) => ({ - borderContainer: { - borderColor: theme.Form.ToggleButton.toggleButtonBorderColor, - }, - pressedBorderContainer: { - borderColor: theme.Form.ToggleButton.toggleButtonHoverBg, - }, - checkedBorderContainer: { - borderColor: theme.Form.ToggleButton.toggleButtonActiveBorderColor, - }, - checkedPressedBorderContainer: { - borderColor: theme.Form.ToggleButton.toggleButtonActiveHoverBorderColor, - }, - disabledBorderContainer: { - borderColor: theme.Button.Disabled.disabledButtonBorderColor, - opacity: 0.6, - }, - - contentContainer: { backgroundColor: theme.Form.ToggleButton.toggleButtonBg }, - pressedContentContainer: { - backgroundColor: theme.Form.ToggleButton.toggleButtonHoverBg, - }, - checkedContentContainer: { - backgroundColor: theme.Form.ToggleButton.toggleButtonActiveBg, - }, - checkedPressedContentContainer: { - backgroundColor: theme.Form.ToggleButton.toggleButtonActiveHoverBg, - }, - disabledContentContainer: { - backgroundColor: theme.Button.Disabled.disabledButtonBg, - }, - - label: { color: theme.Form.ToggleButton.toggleButtonTextColor }, - pressedLabel: { color: theme.Form.ToggleButton.toggleButtonHoverTextColor }, - checkedLabel: { color: theme.Form.ToggleButton.toggleButtonActiveTextColor }, - checkedPressedLabel: { - color: theme.Form.ToggleButton.toggleButtonTextActiveHoverColor, - }, - disabledLabel: { color: theme.Button.Disabled.disabledButtonTextColor }, -})) diff --git a/src/components/Typography/Anchor.tsx b/src/components/Typography/Anchor.tsx index 9d193ce4..e2b3bdcf 100644 --- a/src/components/Typography/Anchor.tsx +++ b/src/components/Typography/Anchor.tsx @@ -8,8 +8,8 @@ import { View, } from 'react-native' +import { StyleSheet } from '../../utils' import { type SvgSource, SvgUniversal } from '../../utils/SvgUniversal' -import { makeStyles } from '../../utils/makeStyles' const WORD_JOINER = '\u2060' // символ невидимого пробела, чтобы избежать разрыва строки между текстом и иконкой @@ -50,8 +50,6 @@ export const Anchor = memo( style, ...other }: AnchorProps) => { - const styles = useStyles() - const [pressed, setPressed] = useState(false) const onPressIn = useCallback(() => setPressed(true), []) const onPressOut = useCallback(() => setPressed(false), []) @@ -63,12 +61,14 @@ export const Anchor = memo( } return { style: styles.container, testID: testID || AnchorTestId.root } - }, [noWrapper, styles.container, testID]) + }, [noWrapper, testID]) const iconColor = useMemo( () => (visited ? styles.visited.color : styles.text.color), - [styles.text.color, styles.visited.color, visited] + [visited] ) + const iconWidth = base ? styles.iconBase.width : styles.icon.width + const iconHeight = base ? styles.iconBase.height : styles.icon.height return ( @@ -81,11 +81,11 @@ export const Anchor = memo( onPressOut={onPressOut} > ) : null} @@ -118,11 +118,11 @@ export const Anchor = memo( onPressOut={onPressOut} > ) : null} @@ -131,7 +131,7 @@ export const Anchor = memo( } ) -const useStyles = makeStyles(({ spacing, typography, fonts }) => ({ +const styles = StyleSheet.create(({ spacing, typography, fonts }) => ({ container: { flexDirection: 'row', alignItems: 'center' }, text: { flexShrink: 1, diff --git a/src/components/Typography/Body.tsx b/src/components/Typography/Body.tsx index 9d311e39..11dab59c 100644 --- a/src/components/Typography/Body.tsx +++ b/src/components/Typography/Body.tsx @@ -1,6 +1,6 @@ import { Text, type TextProps } from 'react-native' -import { makeStyles } from '../../utils/makeStyles' +import { StyleSheet } from '../../utils' export interface BodyProps extends TextProps { readonly base?: boolean @@ -19,8 +19,6 @@ export const Body = ({ style, ...other }: BodyProps) => { - const styles = useStyles() - return ( ({ +const styles = StyleSheet.create(({ theme, typography, fonts }) => ({ text: { fontSize: typography.Size['text-lg'], includeFontPadding: false, diff --git a/src/components/Typography/Caption.tsx b/src/components/Typography/Caption.tsx index edffa2b9..5b2471c3 100644 --- a/src/components/Typography/Caption.tsx +++ b/src/components/Typography/Caption.tsx @@ -1,7 +1,7 @@ import { Text, type TextProps, View } from 'react-native' +import { StyleSheet } from '../../utils' import { type SvgSource, SvgUniversal } from '../../utils/SvgUniversal' -import { makeStyles } from '../../utils/makeStyles' export interface CaptionProps extends TextProps { readonly color?: 'default' | 'secondary' | 'primary' @@ -17,7 +17,6 @@ export const Caption = ({ Icon, ...other }: CaptionProps) => { - const styles = useStyles() const text = ( - + {text} ) @@ -43,7 +47,7 @@ export const Caption = ({ return text } -const useStyles = makeStyles(({ theme, spacing, typography, fonts }) => ({ +const styles = StyleSheet.create(({ theme, spacing, typography, fonts }) => ({ text: { fontSize: typography.Size['text-sm'], includeFontPadding: false, diff --git a/src/components/Typography/Service.tsx b/src/components/Typography/Service.tsx index 1a0addf0..b7e1b8a9 100644 --- a/src/components/Typography/Service.tsx +++ b/src/components/Typography/Service.tsx @@ -8,8 +8,8 @@ import { import { useMemo } from 'react' import { Text, View, type TextProps } from 'react-native' +import { StyleSheet } from '../../utils' import { type SvgSource, SvgUniversal } from '../../utils/SvgUniversal' -import { makeStyles } from '../../utils/makeStyles' export interface ServiceProps extends TextProps { /** @@ -41,8 +41,6 @@ export const Service = ({ Icon: IconFromProps, ...other }: ServiceProps) => { - const styles = useStyles() - const { Icon, variantStyle, iconSize, textStyles, containerStyle } = useMemo(() => { const iconMap = { @@ -64,7 +62,7 @@ export const Service = ({ ], containerStyle: base ? styles.containerBase : styles.container, } - }, [variant, base, styles, IconFromProps]) + }, [variant, base, IconFromProps]) return ( @@ -81,7 +79,7 @@ export const Service = ({ ) } -const useStyles = makeStyles(({ typography, spacing, fonts }) => ({ +const styles = StyleSheet.create(({ typography, spacing, fonts }) => ({ iconBase: { width: typography.Size['text-xl'], height: typography.Size['text-xl'], diff --git a/src/components/Typography/Subtitle.tsx b/src/components/Typography/Subtitle.tsx index 5fd2542c..e0847f21 100644 --- a/src/components/Typography/Subtitle.tsx +++ b/src/components/Typography/Subtitle.tsx @@ -1,7 +1,7 @@ import { memo } from 'react' import { Text, type TextProps } from 'react-native' -import { makeStyles } from '../../utils/makeStyles' +import { StyleSheet } from '../../utils' export interface SubtitleProps extends TextProps { /** @@ -21,19 +21,15 @@ export interface SubtitleProps extends TextProps { * @see https://www.figma.com/design/2ZnL6XPKEpxAHvrlbRvnMu/Template-Tailwind-CSS-(DS)?node-id=1-245 */ export const Subtitle = memo( - ({ base = false, color = 'default', style, ...other }) => { - const styles = useStyles() - - return ( - - ) - } + ({ base = false, color = 'default', style, ...other }) => ( + + ) ) -const useStyles = makeStyles(({ theme, typography, fonts }) => ({ +const styles = StyleSheet.create(({ theme, typography, fonts }) => ({ text: { fontSize: typography.Size['text-sm'], fontWeight: 700, diff --git a/src/components/Typography/Title.tsx b/src/components/Typography/Title.tsx index a144f9e7..657ac98e 100644 --- a/src/components/Typography/Title.tsx +++ b/src/components/Typography/Title.tsx @@ -1,18 +1,16 @@ import { Text, type TextProps } from 'react-native' -import { makeStyles } from '../../utils/makeStyles' +import { StyleSheet } from '../../utils' export interface TitleProps extends TextProps { readonly level: 'd1' | 'd2' | 'd3' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' } -export const Title = ({ level, style, ...other }: TitleProps) => { - const styles = useStyles() +export const Title = ({ level, style, ...other }: TitleProps) => ( + +) - return -} - -const useStyles = makeStyles(({ theme, typography, fonts }) => ({ +const styles = StyleSheet.create(({ theme, typography, fonts }) => ({ text: { color: theme.General.textColor, fontFamily: fonts.primary, diff --git a/src/components/Typography/__tests__/__snapshots__/Service.test.tsx.snap b/src/components/Typography/__tests__/__snapshots__/Service.test.tsx.snap index 419bd4f2..07505e24 100644 --- a/src/components/Typography/__tests__/__snapshots__/Service.test.tsx.snap +++ b/src/components/Typography/__tests__/__snapshots__/Service.test.tsx.snap @@ -394,7 +394,7 @@ exports[`Service component tests base = false, showIcon = true, variant = danger strokeWidth={2} > { + 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(UnistylesRuntime.setTheme).toHaveBeenCalledWith('dark') + }) + + test('возвращает стабильную ссылку на callback', () => { + const { result, rerender } = renderHook(() => useChangeTheme()) + const first = result.current + + rerender({}) + + expect(result.current).toBe(first) + }) +}) diff --git a/src/hooks/__tests__/useChangeTheme.test.tsx b/src/hooks/__tests__/useChangeTheme.test.tsx deleted file mode 100644 index f1e5c2b4..00000000 --- a/src/hooks/__tests__/useChangeTheme.test.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { renderHook } from '@testing-library/react-native' - -import { ThemeContext, ThemeVariant } from '../../theme' -import { useChangeTheme } from '../useChangeTheme' - -describe('useChangeTheme', () => { - test('returns correct function', () => { - const mockedChangeTheme = jest.fn() - const { result } = renderHook(() => useChangeTheme(), { - wrapper: ({ children }) => ( - - {children} - - ), - }) - - expect(result.current).toStrictEqual(expect.any(Function)) - - result.current?.(ThemeVariant.Dark) - - expect(mockedChangeTheme).toHaveBeenCalledWith(ThemeVariant.Dark) - }) -}) diff --git a/src/hooks/__tests__/useTheme.test.ts b/src/hooks/__tests__/useTheme.test.ts new file mode 100644 index 00000000..c03c94b7 --- /dev/null +++ b/src/hooks/__tests__/useTheme.test.ts @@ -0,0 +1,33 @@ +/* eslint-disable @typescript-eslint/no-deprecated -- Проверяем deprecated API на обратную совместимость. */ +import { renderHook } from '@testing-library/react-native' + +import { ThemeVariant } from '../../theme' +import { UnistylesRuntime } from '../../utils' + +import { useTheme } from '../useTheme' + +const setThemeName = (themeName: 'light' | 'dark') => { + Object.assign(UnistylesRuntime, { themeName }) +} + +describe('useTheme', () => { + 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/__tests__/useTheme.test.tsx b/src/hooks/__tests__/useTheme.test.tsx deleted file mode 100644 index bcb3fcf5..00000000 --- a/src/hooks/__tests__/useTheme.test.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { renderHook } from '@testing-library/react-native' - -import { ThemeContextProvider, ThemeVariant } from '../../theme' -import { useTheme } from '../useTheme' - -describe('useTheme', () => { - test('returns correct function', () => { - const { result } = renderHook(() => useTheme(), { - wrapper: ({ children }) => ( - {children} - ), - }) - - expect(Object.values(ThemeVariant)).toContain(result.current) - }) -}) diff --git a/src/hooks/useChangeTheme.ts b/src/hooks/useChangeTheme.ts index 896e21ff..0922493e 100644 --- a/src/hooks/useChangeTheme.ts +++ b/src/hooks/useChangeTheme.ts @@ -1,7 +1,21 @@ -import { useContext } from 'react' +import { useCallback } from 'react' -import { ThemeContext } from '../theme' +import { ThemeVariant } from '../theme' +import { UnistylesRuntime } from '../utils' +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 9bd519d7..1c8cadc1 100644 --- a/src/hooks/useFonts.ts +++ b/src/hooks/useFonts.ts @@ -1,7 +1,15 @@ -import { useContext } from 'react' +import type { FontsConfig } from '../theme' +import { useUnistyles } from '../utils' -import { ThemeContext } from '../theme' +/** + * Возвращает конфигурацию шрифтов. + * + * @deprecated Используйте `useUnistyles().theme.fonts` или `StyleSheet.create(...)` + * из SDK. + * Будет удалён в следующей minor версии. + */ +export const useFonts = (): FontsConfig => { + const { theme } = useUnistyles() -export const useFonts = () => { - return useContext(ThemeContext).fonts + return theme.fonts } diff --git a/src/hooks/useTheme.ts b/src/hooks/useTheme.ts index 5f9057af..c2d8b2ba 100644 --- a/src/hooks/useTheme.ts +++ b/src/hooks/useTheme.ts @@ -1,7 +1,13 @@ -import { useContext } from 'react' +import { ThemeVariant } from '../theme' +import { UnistylesRuntime } from '../utils' -import { ThemeContext } 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 4b9cc530..9b4b9396 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,23 @@ +import './theme' export * from './components' +/* eslint-disable @typescript-eslint/no-deprecated -- Deprecated API сохраняем в публичном интерфейсе для обратной совместимости. */ 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 './utils' +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 e62e96ce..00000000 --- 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 bf0edf21..46a628d9 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 b5184e05..53fcb197 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 '../utils' + +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 68d86d2c..eecf593d 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 1545a96e..250836a5 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/SvgUniversal.tsx b/src/utils/SvgUniversal.tsx index c270c0b1..f91aab39 100644 --- a/src/utils/SvgUniversal.tsx +++ b/src/utils/SvgUniversal.tsx @@ -1,12 +1,25 @@ -import { type ComponentType, memo } from 'react' +import { + type ComponentType, + type ForwardRefExoticComponent, + type PropsWithoutRef, + type RefAttributes, + memo, +} from 'react' import { SvgUri, SvgXml, type SvgProps } from 'react-native-svg' +import { withUnistyles } from 'react-native-unistyles' + +/** Union type from UnistylesThemes - all possible theme types */ +type UnistylesTheme = unknown + +/** Minimal runtime object from Unistyles */ +type UnistylesMiniRuntime = unknown export type SvgSource = | { uri: string } | { xml: string } | ComponentType -interface SvgUniversalProps extends SvgProps { +export interface SvgUniversalProps extends SvgProps { /** Источник SVG */ source: SvgSource } @@ -17,12 +30,18 @@ interface SvgUniversalProps extends SvgProps { * - uri * - xml * - ComponentType + * + * Подписан на обновления темы Unistyles через uniProps * @example * * ' }} /> * + * ({ color: theme.colors.primary })} + * /> */ -export const SvgUniversal = memo(({ source, ...rest }) => { +const SvgUniversalRaw = memo(({ source, ...rest }) => { if ('uri' in source) { return } @@ -36,6 +55,23 @@ export const SvgUniversal = memo(({ source, ...rest }) => { return }) +type SvgUniversalWithUnistyles = ForwardRefExoticComponent< + PropsWithoutRef< + Partial & { + uniProps?: ( + theme: UnistylesTheme, + rt: UnistylesMiniRuntime + ) => Partial + } + > & + RefAttributes +> + +export const SvgUniversal = withUnistyles< + typeof SvgUniversalRaw, + SvgUniversalProps +>(SvgUniversalRaw) as unknown as SvgUniversalWithUnistyles + export const SvgUniversalTestId = { component: 'SvgUniversalComponent', uri: 'SvgUniversalUri', diff --git a/src/utils/__tests__/SvgUniversal.test.tsx b/src/utils/__tests__/SvgUniversal.test.tsx index b12337b0..9604ad83 100644 --- a/src/utils/__tests__/SvgUniversal.test.tsx +++ b/src/utils/__tests__/SvgUniversal.test.tsx @@ -4,6 +4,12 @@ import { render } from '@testing-library/react-native' import { SvgUniversal, SvgUniversalTestId } from '../SvgUniversal' describe('SvgUniversal', () => { + const svgXmlMock = '' + + afterEach(() => { + jest.restoreAllMocks() + }) + describe('при передаче компонента в качестве источника', () => { test('должен отрендерить компонент', () => { const { getAllByTestId } = render() @@ -15,12 +21,18 @@ describe('SvgUniversal', () => { }) describe('при передаче uri в качестве источника', () => { - test('рендерит компонент SvgUri', () => { - const { getByTestId } = render( + test('рендерит компонент SvgUri', async () => { + jest + .spyOn(global, 'fetch') + .mockResolvedValue(new Response(svgXmlMock, { status: 200 })) + + const { findByTestId } = render( ) - expect(getByTestId(SvgUniversalTestId.uri)).toBeOnTheScreen() + const svgUri = await findByTestId(SvgUniversalTestId.uri) + + expect(svgUri).toBeOnTheScreen() }) }) @@ -34,9 +46,3 @@ describe('SvgUniversal', () => { }) }) }) - -jest.mock('react-native-svg', () => ({ - ...jest.requireActual('react-native-svg'), - SvgXml: 'SvgXml', - SvgUri: 'SvgUri', -})) diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 00000000..6a341288 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,11 @@ +export { + StyleSheet, + UnistylesRuntime, + useUnistyles, + withUnistyles, +} from 'react-native-unistyles' +export { + SvgUniversal, + SvgUniversalTestId, + type SvgSource, +} from './SvgUniversal' diff --git a/src/utils/makeStyles.ts b/src/utils/makeStyles.ts index 64fc8141..d0bbe3e5 100644 --- a/src/utils/makeStyles.ts +++ b/src/utils/makeStyles.ts @@ -1,38 +1,24 @@ 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 type { ThemeType } from '../theme' + +import { useUnistyles } from './index' + +/** + * @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 +26,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 5b49e045..6e0b9d71 100644 --- a/yarn.lock +++ b/yarn.lock @@ -97,6 +97,17 @@ __metadata: languageName: node linkType: hard +"@babel/code-frame@npm:^7.28.6, @babel/code-frame@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/code-frame@npm:7.29.0" + dependencies: + "@babel/helper-validator-identifier": "npm:^7.28.5" + js-tokens: "npm:^4.0.0" + picocolors: "npm:^1.1.1" + checksum: 10/199e15ff89007dd30675655eec52481cb245c9fdf4f81e4dc1f866603b0217b57aff25f5ffa0a95bbc8e31eb861695330cd7869ad52cc211aa63016320ef72c5 + languageName: node + linkType: hard + "@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.25.7": version: 7.25.8 resolution: "@babel/compat-data@npm:7.25.8" @@ -264,6 +275,19 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.29.0": + version: 7.29.1 + resolution: "@babel/generator@npm:7.29.1" + dependencies: + "@babel/parser": "npm:^7.29.0" + "@babel/types": "npm:^7.29.0" + "@jridgewell/gen-mapping": "npm:^0.3.12" + "@jridgewell/trace-mapping": "npm:^0.3.28" + jsesc: "npm:^3.0.2" + checksum: 10/61fe4ddd6e817aa312a14963ccdbb5c9a8c57e8b97b98d19a8a99ccab2215fda1a5f52bc8dd8d2e3c064497ddeb3ab8ceb55c76fa0f58f8169c34679d2256fe0 + languageName: node + linkType: hard + "@babel/helper-annotate-as-pure@npm:^7.25.9": version: 7.25.9 resolution: "@babel/helper-annotate-as-pure@npm:7.25.9" @@ -368,20 +392,20 @@ __metadata: languageName: node linkType: hard -"@babel/helper-create-class-features-plugin@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/helper-create-class-features-plugin@npm:7.28.5" +"@babel/helper-create-class-features-plugin@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-create-class-features-plugin@npm:7.28.6" dependencies: "@babel/helper-annotate-as-pure": "npm:^7.27.3" "@babel/helper-member-expression-to-functions": "npm:^7.28.5" "@babel/helper-optimise-call-expression": "npm:^7.27.1" - "@babel/helper-replace-supers": "npm:^7.27.1" + "@babel/helper-replace-supers": "npm:^7.28.6" "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" - "@babel/traverse": "npm:^7.28.5" + "@babel/traverse": "npm:^7.28.6" semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/0bbf3dfe91875f642fe7ef38f60647f0df8eb9994d4350b19a4d1a9bdc32629e49e56e9a80afb12eeb6f6bcc6666392b37f32231b7c054fc91a0d5251cd67d5b + checksum: 10/11f55607fcf66827ade745c0616aa3c6086aa655c0fab665dd3c4961829752e4c94c942262db30c4831ef9bce37ad444722e85ef1b7136587e28c6b1ef8ad43c languageName: node linkType: hard @@ -608,6 +632,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-plugin-utils@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-plugin-utils@npm:7.28.6" + checksum: 10/21c853bbc13dbdddf03309c9a0477270124ad48989e1ad6524b83e83a77524b333f92edd2caae645c5a7ecf264ec6d04a9ebe15aeb54c7f33c037b71ec521e4a + languageName: node + linkType: hard + "@babel/helper-remap-async-to-generator@npm:^7.25.9": version: 7.25.9 resolution: "@babel/helper-remap-async-to-generator@npm:7.25.9" @@ -660,6 +691,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-replace-supers@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/helper-replace-supers@npm:7.28.6" + dependencies: + "@babel/helper-member-expression-to-functions": "npm:^7.28.5" + "@babel/helper-optimise-call-expression": "npm:^7.27.1" + "@babel/traverse": "npm:^7.28.6" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10/ad2724713a4d983208f509e9607e8f950855f11bd97518a700057eb8bec69d687a8f90dc2da0c3c47281d2e3b79cf1d14ecf1fe3e1ee0a8e90b61aee6759c9a7 + languageName: node + linkType: hard + "@babel/helper-simple-access@npm:^7.25.7": version: 7.25.7 resolution: "@babel/helper-simple-access@npm:7.25.7" @@ -901,6 +945,17 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.28.6, @babel/parser@npm:^7.29.0": + version: 7.29.2 + resolution: "@babel/parser@npm:7.29.2" + dependencies: + "@babel/types": "npm:^7.29.0" + bin: + parser: ./bin/babel-parser.js + checksum: 10/45d050bf75aa5194b3255f156173e8553d615ff5a2434674cc4a10cdc7c261931befb8618c996a1c449b87f0ef32a3407879af2ac967d95dc7b4fdbae7037efa + languageName: node + linkType: hard + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.28.5": version: 7.28.5 resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.28.5" @@ -1279,14 +1334,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-typescript@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/plugin-syntax-typescript@npm:7.27.1" +"@babel/plugin-syntax-typescript@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/plugin-syntax-typescript@npm:7.28.6" dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-plugin-utils": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/87836f7e32af624c2914c73cd6b9803cf324e07d43f61dbb973c6a86f75df725e12540d91fac7141c14b697aa9268fd064220998daced156e96ac3062d7afb41 + checksum: 10/5c55f9c63bd36cf3d7e8db892294c8f85000f9c1526c3a1cc310d47d1e174f5c6f6605e5cc902c4636d885faba7a9f3d5e5edc6b35e4f3b1fd4c2d58d0304fa5 languageName: node linkType: hard @@ -1313,7 +1368,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-arrow-functions@npm:^7.0.0-0, @babel/plugin-transform-arrow-functions@npm:^7.27.1": +"@babel/plugin-transform-arrow-functions@npm:7.27.1, @babel/plugin-transform-arrow-functions@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-arrow-functions@npm:7.27.1" dependencies: @@ -1420,7 +1475,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-class-properties@npm:^7.0.0-0, @babel/plugin-transform-class-properties@npm:^7.27.1": +"@babel/plugin-transform-class-properties@npm:7.27.1, @babel/plugin-transform-class-properties@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-class-properties@npm:7.27.1" dependencies: @@ -1456,7 +1511,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-classes@npm:^7.0.0-0, @babel/plugin-transform-classes@npm:^7.28.4": +"@babel/plugin-transform-classes@npm:7.28.4, @babel/plugin-transform-classes@npm:^7.28.4": version: 7.28.4 resolution: "@babel/plugin-transform-classes@npm:7.28.4" dependencies: @@ -1865,7 +1920,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.0.0-0, @babel/plugin-transform-nullish-coalescing-operator@npm:^7.27.1": +"@babel/plugin-transform-nullish-coalescing-operator@npm:7.27.1, @babel/plugin-transform-nullish-coalescing-operator@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.27.1" dependencies: @@ -1971,15 +2026,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-optional-chaining@npm:^7.0.0-0, @babel/plugin-transform-optional-chaining@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/plugin-transform-optional-chaining@npm:7.28.5" +"@babel/plugin-transform-optional-chaining@npm:7.27.1, @babel/plugin-transform-optional-chaining@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/plugin-transform-optional-chaining@npm:7.27.1" dependencies: "@babel/helper-plugin-utils": "npm:^7.27.1" "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/0bc900bff66d5acc13b057107eaeb6084b4cb0b124654d35b103f71f292d33dba5beac444ab4f92528583585b6e0cf34d64ce9cbb473b15d22375a4a6ed3cbac + checksum: 10/34b0f96400c259a2722740d17a001fe45f78d8ff052c40e29db2e79173be72c1cfe8d9681067e3f5da3989e4a557402df5c982c024c18257587a41e022f95640 languageName: node linkType: hard @@ -1995,15 +2050,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-optional-chaining@npm:^7.27.1": - version: 7.27.1 - resolution: "@babel/plugin-transform-optional-chaining@npm:7.27.1" +"@babel/plugin-transform-optional-chaining@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/plugin-transform-optional-chaining@npm:7.28.5" dependencies: "@babel/helper-plugin-utils": "npm:^7.27.1" "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/34b0f96400c259a2722740d17a001fe45f78d8ff052c40e29db2e79173be72c1cfe8d9681067e3f5da3989e4a557402df5c982c024c18257587a41e022f95640 + checksum: 10/0bc900bff66d5acc13b057107eaeb6084b4cb0b124654d35b103f71f292d33dba5beac444ab4f92528583585b6e0cf34d64ce9cbb473b15d22375a4a6ed3cbac languageName: node linkType: hard @@ -2223,7 +2278,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-shorthand-properties@npm:^7.0.0-0, @babel/plugin-transform-shorthand-properties@npm:^7.27.1": +"@babel/plugin-transform-shorthand-properties@npm:7.27.1, @babel/plugin-transform-shorthand-properties@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-shorthand-properties@npm:7.27.1" dependencies: @@ -2291,7 +2346,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-template-literals@npm:^7.0.0-0, @babel/plugin-transform-template-literals@npm:^7.27.1": +"@babel/plugin-transform-template-literals@npm:7.27.1, @babel/plugin-transform-template-literals@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-template-literals@npm:7.27.1" dependencies: @@ -2343,18 +2398,18 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-typescript@npm:^7.28.5": - version: 7.28.5 - resolution: "@babel/plugin-transform-typescript@npm:7.28.5" +"@babel/plugin-transform-typescript@npm:^7.27.1": + version: 7.28.6 + resolution: "@babel/plugin-transform-typescript@npm:7.28.6" dependencies: "@babel/helper-annotate-as-pure": "npm:^7.27.3" - "@babel/helper-create-class-features-plugin": "npm:^7.28.5" - "@babel/helper-plugin-utils": "npm:^7.27.1" + "@babel/helper-create-class-features-plugin": "npm:^7.28.6" + "@babel/helper-plugin-utils": "npm:^7.28.6" "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.27.1" - "@babel/plugin-syntax-typescript": "npm:^7.27.1" + "@babel/plugin-syntax-typescript": "npm:^7.28.6" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/e4706379df70c2de9d3d3f573ff74a160e05221620a22dd0a64899ab45fddad9e4530fbba33014c75906f13aa78d8044fed80dba85068e11d84ed1f033dea445 + checksum: 10/a0bccc531fa8710a45b0b593140273741e0e4a0721b1ef6ef9dfefae0bbe61528440d65aab7936929551fd76793272257d74f60cf66891352f793294930a4b67 languageName: node linkType: hard @@ -2381,7 +2436,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-unicode-regex@npm:^7.0.0-0, @babel/plugin-transform-unicode-regex@npm:^7.27.1": +"@babel/plugin-transform-unicode-regex@npm:7.27.1, @babel/plugin-transform-unicode-regex@npm:^7.27.1": version: 7.27.1 resolution: "@babel/plugin-transform-unicode-regex@npm:7.27.1" dependencies: @@ -2526,18 +2581,18 @@ __metadata: languageName: node linkType: hard -"@babel/preset-typescript@npm:^7.16.7": - version: 7.28.5 - resolution: "@babel/preset-typescript@npm:7.28.5" +"@babel/preset-typescript@npm:7.27.1": + version: 7.27.1 + resolution: "@babel/preset-typescript@npm:7.27.1" dependencies: "@babel/helper-plugin-utils": "npm:^7.27.1" "@babel/helper-validator-option": "npm:^7.27.1" "@babel/plugin-syntax-jsx": "npm:^7.27.1" "@babel/plugin-transform-modules-commonjs": "npm:^7.27.1" - "@babel/plugin-transform-typescript": "npm:^7.28.5" + "@babel/plugin-transform-typescript": "npm:^7.27.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/72c03e01c34906041b1813542761a283c52da1751e7ddf63191bc5fb2a0354eca30a00537c5a92951688bec3975bdc0e50ef4516b5e94cfd6d4cf947f2125bdc + checksum: 10/9d8e75326b3c93fa016ba7aada652800fc77bc05fcc181888700a049935e8cf1284b549de18a5d62ef3591d02f097ea6de1111f7d71a991aaf36ba74657bd145 languageName: node linkType: hard @@ -2607,6 +2662,17 @@ __metadata: languageName: node linkType: hard +"@babel/template@npm:^7.28.6": + version: 7.28.6 + resolution: "@babel/template@npm:7.28.6" + dependencies: + "@babel/code-frame": "npm:^7.28.6" + "@babel/parser": "npm:^7.28.6" + "@babel/types": "npm:^7.28.6" + checksum: 10/0ad6e32bf1e7e31bf6b52c20d15391f541ddd645cbd488a77fe537a15b280ee91acd3a777062c52e03eedbc2e1f41548791f6a3697c02476ec5daf49faa38533 + languageName: node + linkType: hard + "@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3, @babel/traverse@npm:^7.25.3, @babel/traverse@npm:^7.26.7": version: 7.26.7 resolution: "@babel/traverse@npm:7.26.7" @@ -2682,6 +2748,31 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.28.6": + version: 7.29.0 + resolution: "@babel/traverse@npm:7.29.0" + dependencies: + "@babel/code-frame": "npm:^7.29.0" + "@babel/generator": "npm:^7.29.0" + "@babel/helper-globals": "npm:^7.28.0" + "@babel/parser": "npm:^7.29.0" + "@babel/template": "npm:^7.28.6" + "@babel/types": "npm:^7.29.0" + debug: "npm:^4.3.1" + checksum: 10/3a0d0438f1ba9fed4fbe1706ea598a865f9af655a16ca9517ab57bda526e224569ca1b980b473fb68feea5e08deafbbf2cf9febb941f92f2d2533310c3fc4abc + languageName: node + linkType: hard + +"@babel/types@npm:7.29.0, @babel/types@npm:^7.28.6, @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 +2869,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,10 +2911,12 @@ __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-reanimated: "npm:4.1.1" + react-native-nitro-modules: "npm:0.35.2" + react-native-reanimated: "npm:4.2.1" react-native-safe-area-context: "npm:5.6.2" react-native-svg: "npm:15.15.1" - react-native-worklets: "npm:0.5.1" + react-native-unistyles: "npm:3.2.3" + react-native-worklets: "npm:0.7.1" release-it: "npm:19.1.0" standard-version: "npm:9.5.0" storybook: "npm:10.1.10" @@ -2836,8 +2929,10 @@ __metadata: expo: ">=54.x.x" react: ">=19.1" react-native: ">=0.81.5" - react-native-reanimated: ">=4.1.1" + react-native-reanimated: ">=4.2.1" react-native-svg: ">=15.15.1" + react-native-unistyles: ">=3.2.3" + react-native-worklets: ">=0.7.0" peerDependenciesMeta: expo: optional: true @@ -5578,21 +5673,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 @@ -8505,7 +8600,7 @@ __metadata: languageName: node linkType: hard -"convert-source-map@npm:^2.0.0": +"convert-source-map@npm:2.0.0, convert-source-map@npm:^2.0.0": version: 2.0.0 resolution: "convert-source-map@npm:2.0.0" checksum: 10/c987be3ec061348cdb3c2bfb924bec86dea1eacad10550a85ca23edb0fe3556c3a61c7399114f3331ccb3499d7fd0285ab24566e5745929412983494c3926e15 @@ -16091,7 +16186,7 @@ __metadata: languageName: node linkType: hard -"react-native-is-edge-to-edge@npm:^1.2.1": +"react-native-is-edge-to-edge@npm:1.2.1, react-native-is-edge-to-edge@npm:^1.2.1": version: 1.2.1 resolution: "react-native-is-edge-to-edge@npm:1.2.1" peerDependencies: @@ -16113,18 +16208,27 @@ __metadata: languageName: node linkType: hard -"react-native-reanimated@npm:4.1.1": - version: 4.1.1 - resolution: "react-native-reanimated@npm:4.1.1" +"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.2.1": + version: 4.2.1 + resolution: "react-native-reanimated@npm:4.2.1" dependencies: - react-native-is-edge-to-edge: "npm:^1.2.1" - semver: "npm:7.7.2" + react-native-is-edge-to-edge: "npm:1.2.1" + semver: "npm:7.7.3" peerDependencies: - "@babel/core": ^7.0.0-0 react: "*" react-native: "*" - react-native-worklets: ">=0.5.0" - checksum: 10/09b088d6a927e8139b0265822fd525d5bfd97ef3c654888d4ecafdf20c449d67a813de5edb3c132595ddb895a085474bcf093d080c2ed1bfa9b14df600eee112 + react-native-worklets: ">=0.7.0" + checksum: 10/869eb4c90b5464ac616bf048f77a309fd2c793020a662b80b874022f90256b7c955f43d7e7c4751c15f2f767c306e52c624cc37f3edff4fe0bbf26c2c3f955b7 languageName: node linkType: hard @@ -16152,6 +16256,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" @@ -16163,26 +16288,26 @@ __metadata: languageName: node linkType: hard -"react-native-worklets@npm:0.5.1": - version: 0.5.1 - resolution: "react-native-worklets@npm:0.5.1" - dependencies: - "@babel/plugin-transform-arrow-functions": "npm:^7.0.0-0" - "@babel/plugin-transform-class-properties": "npm:^7.0.0-0" - "@babel/plugin-transform-classes": "npm:^7.0.0-0" - "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.0.0-0" - "@babel/plugin-transform-optional-chaining": "npm:^7.0.0-0" - "@babel/plugin-transform-shorthand-properties": "npm:^7.0.0-0" - "@babel/plugin-transform-template-literals": "npm:^7.0.0-0" - "@babel/plugin-transform-unicode-regex": "npm:^7.0.0-0" - "@babel/preset-typescript": "npm:^7.16.7" - convert-source-map: "npm:^2.0.0" - semver: "npm:7.7.2" +"react-native-worklets@npm:0.7.1": + version: 0.7.1 + resolution: "react-native-worklets@npm:0.7.1" + dependencies: + "@babel/plugin-transform-arrow-functions": "npm:7.27.1" + "@babel/plugin-transform-class-properties": "npm:7.27.1" + "@babel/plugin-transform-classes": "npm:7.28.4" + "@babel/plugin-transform-nullish-coalescing-operator": "npm:7.27.1" + "@babel/plugin-transform-optional-chaining": "npm:7.27.1" + "@babel/plugin-transform-shorthand-properties": "npm:7.27.1" + "@babel/plugin-transform-template-literals": "npm:7.27.1" + "@babel/plugin-transform-unicode-regex": "npm:7.27.1" + "@babel/preset-typescript": "npm:7.27.1" + convert-source-map: "npm:2.0.0" + semver: "npm:7.7.3" peerDependencies: - "@babel/core": ^7.0.0-0 + "@babel/core": "*" react: "*" react-native: "*" - checksum: 10/39e70b64560cc4b031ac329bfbd0686b744b6940bbe1d1768a227bb9e142a0e2bad5ef16752003e83374db49d8d6396c72236b9c279e1465a7177f887ae12d48 + checksum: 10/10015cbfc2c2eeb60d3a10a8f381efbbad9751dda403d5342c68c92960149d18977b230416f14592662941552173c9d4862012622401f1745e5af041248f07b4 languageName: node linkType: hard @@ -17029,6 +17154,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:7.7.3, semver@npm:^7.5.2, semver@npm:^7.6.3, semver@npm:^7.7.2, semver@npm:^7.7.3": + version: 7.7.3 + resolution: "semver@npm:7.7.3" + bin: + semver: bin/semver.js + checksum: 10/8dbc3168e057a38fc322af909c7f5617483c50caddba135439ff09a754b20bdd6482a5123ff543dad4affa488ecf46ec5fb56d61312ad20bb140199b88dfaea9 + languageName: node + linkType: hard + "semver@npm:^6.0.0, semver@npm:^6.3.0, semver@npm:^6.3.1": version: 6.3.1 resolution: "semver@npm:6.3.1" @@ -17056,15 +17190,6 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.5.2, semver@npm:^7.6.3, semver@npm:^7.7.2, semver@npm:^7.7.3": - version: 7.7.3 - resolution: "semver@npm:7.7.3" - bin: - semver: bin/semver.js - checksum: 10/8dbc3168e057a38fc322af909c7f5617483c50caddba135439ff09a754b20bdd6482a5123ff543dad4affa488ecf46ec5fb56d61312ad20bb140199b88dfaea9 - languageName: node - linkType: hard - "semver@npm:^7.7.1": version: 7.7.1 resolution: "semver@npm:7.7.1"