From af6332af7d03f23cd22745447da8285deec7d5a5 Mon Sep 17 00:00:00 2001 From: Alesya Volosach Date: Tue, 25 Aug 2026 18:49:50 +0300 Subject: [PATCH 1/7] =?UTF-8?q?feat(input-otp):=20=D1=81=D0=BA=D0=BE=D1=80?= =?UTF-8?q?=D1=80=D0=B5=D0=BA=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=BE=20=D0=BF=D0=BE=D0=B2=D0=B5=D0=B4=D0=B5=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Input/InputOtp/InputOtp.tsx | 131 +- .../Input/InputOtp/InputOtpItem.tsx | 112 +- .../InputOtp/__tests__/InputOtp.test.tsx | 616 +- .../__snapshots__/InputOtp.test.tsx.snap | 16273 ---------------- src/components/Input/InputOtp/testIds.ts | 11 + src/components/Input/index.ts | 6 +- 6 files changed, 670 insertions(+), 16479 deletions(-) delete mode 100644 src/components/Input/InputOtp/__tests__/__snapshots__/InputOtp.test.tsx.snap create mode 100644 src/components/Input/InputOtp/testIds.ts diff --git a/src/components/Input/InputOtp/InputOtp.tsx b/src/components/Input/InputOtp/InputOtp.tsx index 3923463b..9ca52160 100644 --- a/src/components/Input/InputOtp/InputOtp.tsx +++ b/src/components/Input/InputOtp/InputOtp.tsx @@ -3,7 +3,6 @@ import { useCallback, useEffect, useImperativeHandle, - useMemo, useRef, useState, type Ref, @@ -22,12 +21,20 @@ import { import { StyleSheet } from 'react-native-unistyles' import { InputOtpItem } from './InputOtpItem' +import { createInputOtpTestIds, InputOtpTestId } from './testIds' + +export { InputOtpTestId } from './testIds' export interface InputOtpProps extends Omit< TextInputProps, - 'onChangeText' | 'onChange' | 'ref' | 'keyboardType' | 'style' + | 'onChangeText' + | 'onChange' + | 'ref' + | 'style' + | 'inputMode' + | 'keyboardType' >, Pick { length: number @@ -37,6 +44,21 @@ export interface InputOtpProps inputRef?: Ref } +const normalizeOtpValue = (value: string, length: number) => + value.replace(/[^0-9]/g, '').slice(0, length) + +const getDefaultSelection = (value: string) => ({ + start: value.length, + end: value.length, +}) + +const getActiveIndex = (selectionStart: number, length: number) => + Math.min(Math.max(selectionStart, 0), length - 1) + +/** + * Поле для ввода одноразового пароля. + * @link https://www.figma.com/design/Q1BWgZ7zoV5UzlBOnjW0cM/UI-Kit--DS--v2.1?node-id=318-1373 + */ export const InputOtp = memo( ({ length, @@ -49,6 +71,10 @@ export const InputOtp = memo( value = '', onFocus, onBlur, + accessibilityState, + autoComplete = 'one-time-code', + selection, + textContentType = 'oneTimeCode', editable, ...rest }) => { @@ -56,6 +82,7 @@ export const InputOtp = memo( const inputRef = useRef(null) const isInputEditable = !disabled && editable !== false + const inputValue = normalizeOtpValue(value, length) useImperativeHandle( propsInputRef, @@ -80,10 +107,13 @@ export const InputOtp = memo( const handleChange = useCallback( (text: string) => { - const sanitizedText = text.replace(/[^0-9]/g, '') - onChange(sanitizedText) + const nextValue = normalizeOtpValue(text, length) + + if (nextValue !== inputValue) { + onChange(nextValue) + } }, - [onChange] + [inputValue, length, onChange] ) const handleFocus = useCallback( @@ -102,63 +132,68 @@ export const InputOtp = memo( [onBlur] ) - const activeIndex = useMemo( - () => Math.min(value.length, length - 1), - [value.length, length] - ) - - const renderArray = useMemo( - () => Array.from({ length }, (_, i) => `Otp-Item-${i}`), - [length] - ) + const inputSelection = selection ?? getDefaultSelection(inputValue) + const activeIndex = getActiveIndex(inputSelection.start, length) + const testIds = createInputOtpTestIds(testID ?? InputOtpTestId.root) return ( - {({ pressed }) => ( - <> - - {renderArray.map((key, index) => ( - - ))} - - + {Array.from({ length }, (_, index) => ( + - - )} + ))} + + ) } ) -const styles = StyleSheet.create(({ semantic }) => ({ +const styles = StyleSheet.create(({ components }) => ({ container: {}, - content: { flexDirection: 'row', gap: semantic.dimension.space[200] }, + content: { flexDirection: 'row', gap: components.inputotp.root.gap }, input: { position: 'absolute', width: 1, height: 1, opacity: 0 }, })) diff --git a/src/components/Input/InputOtp/InputOtpItem.tsx b/src/components/Input/InputOtp/InputOtpItem.tsx index cb719a5c..50f0706b 100644 --- a/src/components/Input/InputOtp/InputOtpItem.tsx +++ b/src/components/Input/InputOtp/InputOtpItem.tsx @@ -1,5 +1,11 @@ -import { memo } from 'react' -import { View, Text, type TextStyle, type ViewProps } from 'react-native' +import { memo, useState } from 'react' +import { + Pressable, + View, + Text, + type PressableProps, + type TextStyle, +} from 'react-native' import Animated, { type AnimatedStyle } from 'react-native-reanimated' @@ -7,12 +13,17 @@ import { StyleSheet } from 'react-native-unistyles' import effects from '../../../theme/tokens/semantic/effects.json' -export interface InputOtpItemProps extends Pick { +import { createInputOtpTestIds } from './testIds' + +export interface InputOtpItemProps extends Pick< + PressableProps, + 'onPress' | 'testOnly_pressed' +> { value?: string error: boolean - pressed: boolean disabled: boolean focused: boolean + testIdPrefix: string } const CURSOR_ANIMATION_DURATION = 500 @@ -32,52 +43,80 @@ const cursorAnimationStyle = { } satisfies AnimatedStyle export const InputOtpItem = memo( - ({ value, error, pressed, disabled, focused, testID }) => { + ({ + value, + error, + disabled, + focused, + testIdPrefix, + testOnly_pressed, + onPress, + }) => { + const [isHovered, setIsHovered] = useState(false) + const testIds = createInputOtpTestIds(testIdPrefix) + return ( - [ styles.container, + (pressed || isHovered) && styles.hovered, + focused && styles.focused, error && styles.error, - pressed && styles.pressed, + error && focused && styles.errorFocused, disabled && styles.disabled, ]} + testID={testIds.itemContainer} + testOnly_pressed={testOnly_pressed} + onHoverIn={() => setIsHovered(true)} + onHoverOut={() => setIsHovered(false)} + onPress={onPress} > {focused ? ( - + {value ? ( - + {value} ) : null} | ) : ( - + {value} )} - + ) } ) -// Рамка, цвет и отступы намеренно берутся у inputtext: поле OTP должно выглядеть -// как обычное поле ввода. Собственные токены inputotp описывают только отличия — -// тот же приём, что в пресете PrimeUIX lara, где inputotp задаёт лишь gap и width. const styles = StyleSheet.create(({ components, semantic, fonts }) => ({ container: { - minHeight: components.inputotp.extend.height, minWidth: components.inputotp.extend.height, - paddingHorizontal: components.inputtext.root.paddingX, - borderBottomWidth: components.inputotp.extend.borderWidth, - borderColor: components.inputtext.root.borderColor, + minHeight: components.inputotp.extend.height, + borderWidth: components.inputotp.extend.borderWidth, + borderRadius: components.inputtext.root.borderRadius, + borderColor: semantic.colorScheme.color.border.neutral.strong, + backgroundColor: semantic.colorScheme.color.bg.surface.default.default, alignItems: 'center', justifyContent: 'center', }, @@ -85,21 +124,36 @@ const styles = StyleSheet.create(({ components, semantic, fonts }) => ({ textRow: { flexDirection: 'row', alignItems: 'center' }, text: { - fontSize: fonts.fontSize[600], - fontFamily: fonts.fontFamily.heading, + fontSize: fonts.fontSize[200], + lineHeight: fonts.lineHeight[200], + fontFamily: fonts.fontFamily.base, fontWeight: fonts.fontWeight.regular, - color: components.inputtext.root.color, + color: semantic.colorScheme.color.fg.active, includeFontPadding: false, + textAlign: 'center', }, - pressed: { borderColor: components.inputtext.root.hoverBorderColor }, + hovered: { borderColor: semantic.colorScheme.color.border.brand.strong }, - error: { borderColor: components.inputtext.root.invalidBorderColor }, + focused: { + borderColor: semantic.colorScheme.color.border.brand.strong, + boxShadow: `0 0 0 3.5px ${semantic.colorScheme.color.border.focus}`, + }, + + error: { + borderColor: semantic.colorScheme.color.border.status.danger.strong, + }, + + errorFocused: { + boxShadow: `0 0 0 3.5px ${semantic.colorScheme.color.bg.status.danger.weak.hover}`, + }, disabled: { - mixBlendMode: 'luminosity', - opacity: semantic.effects.opacity[60], + backgroundColor: semantic.colorScheme.color.bg.neutral.weak.disabled, + borderColor: semantic.colorScheme.color.border.neutral.strong, + boxShadow: 'none', + opacity: semantic.effects.opacity[50], }, - cursor: { color: components.inputtext.root.color, marginBottom: 3 }, + disabledText: { color: semantic.colorScheme.color.fg.muted }, })) diff --git a/src/components/Input/InputOtp/__tests__/InputOtp.test.tsx b/src/components/Input/InputOtp/__tests__/InputOtp.test.tsx index e743a3d9..17b914e1 100644 --- a/src/components/Input/InputOtp/__tests__/InputOtp.test.tsx +++ b/src/components/Input/InputOtp/__tests__/InputOtp.test.tsx @@ -1,150 +1,510 @@ -import { fireEvent, render } from '@testing-library/react-native' +import { + fireEvent, + isHiddenFromAccessibility, + render, + userEvent, + within, +} from '@testing-library/react-native' import type { TextInput } from 'react-native' -import { InputOtp, type InputOtpProps } from '../InputOtp' - -describe('InputOtp component tests', () => { - const inputSnapshotCases = generatePropsCombinations({ - disabled: [true, false], - error: [true, false], - testOnly_pressed: [true, false], - value: [undefined, '5', '55'], - onChange: [jest.fn()], - length: [2, 4, 8], +import { InputOtp, InputOtpTestId, type InputOtpProps } from '../InputOtp' +import { createInputOtpTestIds } from '../testIds' + +const hiddenElements = { includeHiddenElements: true } + +const renderInputOtp = (props: Partial = {}) => { + const baseProps: InputOtpProps = { length: 4, onChange: jest.fn(), ...props } + const result = render() + + return { + ...result, + rerenderInputOtp: (nextProps: Partial) => + result.rerender(), + } +} + +describe('InputOtp', () => { + describe('отрисовка и конфигурация', () => { + test.each([ + { name: 'две ячейки', length: 2, value: '12' }, + { name: 'четыре ячейки', length: 4, value: '1234' }, + { name: 'восемь ячеек', length: 8, value: '12345678' }, + ])('отображает $name', ({ length, value }) => { + const { getAllByTestId, getByTestId, getByText } = renderInputOtp({ + length, + value, + }) + + expect(getAllByTestId(InputOtpTestId.item, hiddenElements)).toHaveLength( + length + ) + expect(getByTestId(InputOtpTestId.content, hiddenElements)).toHaveStyle({ + gap: 8, + }) + + for (const item of getAllByTestId( + InputOtpTestId.itemContainer, + hiddenElements + )) { + expect(item).toHaveStyle({ minWidth: 40, minHeight: 40 }) + } + + for (const digit of value) { + expect(getByText(digit, hiddenElements)).toBeOnTheScreen() + } + }) + + test('нормализует контролируемое значение и ограничивает его длину', () => { + const { getByTestId, getByText, queryByText } = renderInputOtp({ + value: '1 a2-345', + }) + + expect(getByTestId(InputOtpTestId.hiddenInput)).toHaveProp( + 'value', + '1234' + ) + expect(queryByText('a', hiddenElements)).not.toBeOnTheScreen() + + for (const digit of '1234') { + expect(getByText(digit, hiddenElements)).toBeOnTheScreen() + } + }) + + test('настраивает цифровую клавиатуру и автозаполнение кода', () => { + const { getByTestId } = renderInputOtp() + const input = getByTestId(InputOtpTestId.hiddenInput) + + expect(input).toHaveProp('autoComplete', 'one-time-code') + expect(input).toHaveProp('textContentType', 'oneTimeCode') + expect(input).toHaveProp('inputMode', 'numeric') + expect(input).toHaveProp('keyboardType', 'number-pad') + }) + + test('формирует внутренние testID из пользовательского префикса', () => { + const customTestIds = createInputOtpTestIds('PaymentOtp') + const { getAllByTestId, getByTestId } = renderInputOtp({ + testID: customTestIds.root, + }) + + expect(getByTestId(customTestIds.root)).toBeOnTheScreen() + expect( + getByTestId(customTestIds.content, hiddenElements) + ).toBeOnTheScreen() + expect(getByTestId(customTestIds.hiddenInput)).toBeOnTheScreen() + expect( + getAllByTestId(customTestIds.itemContainer, hiddenElements) + ).toHaveLength(4) + }) + + test('передаёт accessibility props скрытому полю ввода', () => { + const { getAllByTestId, getByTestId, getByText } = renderInputOtp({ + accessibilityHint: 'Введите четыре цифры', + accessibilityLabel: 'Код из SMS', + value: '12', + }) + const input = getByTestId(InputOtpTestId.hiddenInput) + + expect(input).toHaveProp('accessibilityLabel', 'Код из SMS') + expect(input).toHaveProp('accessibilityHint', 'Введите четыре цифры') + expect(input).toHaveProp('accessibilityState', { disabled: false }) + expect(isHiddenFromAccessibility(input)).toBeFalse() + expect( + isHiddenFromAccessibility(getByText('1', hiddenElements)) + ).toBeTrue() + expect(getByTestId(InputOtpTestId.root)).toHaveProp('accessible', false) + + for (const item of getAllByTestId( + InputOtpTestId.itemContainer, + hiddenElements + )) { + expect(item).toHaveProp('accessible', false) + } + }) }) - test.each(inputSnapshotCases)( - 'length - $length, error - $error, disabled - $disabled, pressed - $testOnly_pressed', - (props) => { - const renderInput = render() + describe('контролируемый ввод', () => { + test('игнорирует нецифровой ввод, не изменивший значение', () => { + const mockedOnChange = jest.fn() + const { getByTestId } = renderInputOtp({ + onChange: mockedOnChange, + value: '12', + }) + + fireEvent.changeText(getByTestId(InputOtpTestId.hiddenInput), '12a-') + + expect(mockedOnChange).not.toHaveBeenCalled() + }) + + test('не меняет отображение до обновления контролируемого value', () => { + const mockedOnChange = jest.fn() + const { getByTestId, getByText, queryByText, rerenderInputOtp } = + renderInputOtp({ onChange: mockedOnChange, value: '12' }) + const input = getByTestId(InputOtpTestId.hiddenInput) + + fireEvent.changeText(input, '123') + + expect(mockedOnChange).toHaveBeenCalledWith('123') + expect(input).toHaveProp('value', '12') + expect(queryByText('3', hiddenElements)).not.toBeOnTheScreen() + + rerenderInputOtp({ value: '123' }) + + expect(getByText('3', hiddenElements)).toBeOnTheScreen() + expect(getByTestId(InputOtpTestId.hiddenInput)).toHaveProp('value', '123') + }) + + test('очищает вставленный код от символов и отбрасывает лишние цифры', () => { + const mockedOnChange = jest.fn() + const { getAllByTestId, getByTestId, rerenderInputOtp } = renderInputOtp({ + onChange: mockedOnChange, + value: '', + }) + const input = getByTestId(InputOtpTestId.hiddenInput) + + fireEvent(input, 'focus') + fireEvent.changeText(input, '1 2-3a4 56') + + expect(mockedOnChange).toHaveBeenCalledOnce() + expect(mockedOnChange).toHaveBeenCalledWith('1234') + + rerenderInputOtp({ value: '1234' }) + + expect( + within( + getAllByTestId(InputOtpTestId.itemContainer, hiddenElements)[3] + ).getByTestId(InputOtpTestId.cursor, hiddenElements) + ).toBeOnTheScreen() + }) + + test('после последней цифры передаёт полный код и сохраняет фокус', () => { + const mockedOnChange = jest.fn() + const { getAllByTestId, getByTestId, rerenderInputOtp } = renderInputOtp({ + onChange: mockedOnChange, + value: '123', + }) + const input = getByTestId(InputOtpTestId.hiddenInput) + + fireEvent(input, 'focus') + fireEvent.changeText(input, '1234') + + expect(mockedOnChange).toHaveBeenCalledWith('1234') - expect(renderInput.toJSON()).toMatchSnapshot() - } - ) + rerenderInputOtp({ value: '1234' }) - test('Handle input', async () => { - const mockedOnChange = jest.fn() + expect( + within( + getAllByTestId(InputOtpTestId.itemContainer, hiddenElements)[3] + ).getByTestId(InputOtpTestId.cursor, hiddenElements) + ).toBeOnTheScreen() + }) - const { getByTestId } = render( - - ) - const hiddenInput = getByTestId('InputOtpHiddenInput') + test('backspace очищает заполненную и предыдущую пустую позицию', () => { + const mockedOnChange = jest.fn() + const { getAllByTestId, getByTestId, rerenderInputOtp } = renderInputOtp({ + onChange: mockedOnChange, + value: '1234', + }) + const input = getByTestId(InputOtpTestId.hiddenInput) - expect(mockedOnChange).not.toHaveBeenCalled() + fireEvent(input, 'focus') + fireEvent.changeText(input, '123') - fireEvent.changeText(hiddenInput, '55') + expect(mockedOnChange).toHaveBeenLastCalledWith('123') - expect(mockedOnChange).toHaveBeenCalledWith('55') + rerenderInputOtp({ value: '123' }) - fireEvent.changeText(hiddenInput, '5543') + expect( + within( + getAllByTestId(InputOtpTestId.itemContainer, hiddenElements)[3] + ).getByTestId(InputOtpTestId.cursor, hiddenElements) + ).toBeOnTheScreen() - expect(mockedOnChange).toHaveBeenCalledWith('5543') + fireEvent.changeText(getByTestId(InputOtpTestId.hiddenInput), '12') - fireEvent.changeText(hiddenInput, '55 ') + expect(mockedOnChange).toHaveBeenLastCalledWith('12') + + rerenderInputOtp({ value: '12' }) - expect(mockedOnChange).toHaveBeenCalledWith('55') + expect( + within( + getAllByTestId(InputOtpTestId.itemContainer, hiddenElements)[2] + ).getByTestId(InputOtpTestId.cursor, hiddenElements) + ).toBeOnTheScreen() + }) }) - test('should set hidden input editable prop correctly', () => { - const mockedOnChange = jest.fn() - const { getByTestId, update } = render( - - ) - - expect(getByTestId('InputOtpHiddenInput')).toHaveProp('editable', true) - - update( - - ) - - expect(getByTestId('InputOtpHiddenInput')).toHaveProp('editable', false) - - update( - - ) - - expect(getByTestId('InputOtpHiddenInput')).toHaveProp('editable', false) + describe('фокус и selection', () => { + test('клик по любой ячейке фокусирует первую незаполненную', async () => { + const user = userEvent.setup() + let inputRef: TextInput | null = null + const handleInputRef = (ref: TextInput | null) => { + inputRef = ref + } + const { getAllByTestId, getByTestId } = renderInputOtp({ + inputRef: handleInputRef, + value: '12', + }) + + if (!inputRef) { + throw new Error('Input ref was not set') + } + + const focus = jest.fn() + + Object.assign(inputRef, { focus }) + await user.press( + getAllByTestId(InputOtpTestId.itemContainer, hiddenElements)[3] + ) + + expect(focus).toHaveBeenCalledOnce() + + fireEvent(getByTestId(InputOtpTestId.hiddenInput), 'focus') + + const items = getAllByTestId(InputOtpTestId.itemContainer, hiddenElements) + + expect( + within(items[2]).getByTestId(InputOtpTestId.cursor, hiddenElements) + ).toBeOnTheScreen() + expect( + within(items[3]).queryByTestId(InputOtpTestId.cursor, hiddenElements) + ).not.toBeOnTheScreen() + }) + + test('передаёт focus и blur наружу и скрывает каретку после blur', () => { + const mockedOnBlur = jest.fn() + const mockedOnFocus = jest.fn() + const { getByTestId, getByText, queryByText } = renderInputOtp({ + onBlur: mockedOnBlur, + onFocus: mockedOnFocus, + value: '12', + }) + const input = getByTestId(InputOtpTestId.hiddenInput) + const focusEvent = { nativeEvent: { target: 1 } } + const blurEvent = { nativeEvent: { target: 1 } } + + fireEvent(input, 'focus', focusEvent) + + expect(mockedOnFocus).toHaveBeenCalledWith(focusEvent) + expect(getByText('|', hiddenElements)).toBeOnTheScreen() + + fireEvent(input, 'blur', blurEvent) + + expect(mockedOnBlur).toHaveBeenCalledWith(blurEvent) + expect(queryByText('|', hiddenElements)).not.toBeOnTheScreen() + }) + + test('синхронизирует активную ячейку с переданным selection', () => { + const selection = { start: 2, end: 3 } + const { getAllByTestId, getByTestId } = renderInputOtp({ + selection, + value: '1234', + }) + + fireEvent(getByTestId(InputOtpTestId.hiddenInput), 'focus') + + expect(getByTestId(InputOtpTestId.hiddenInput)).toHaveProp( + 'selection', + selection + ) + expect( + within( + getAllByTestId(InputOtpTestId.itemContainer, hiddenElements)[2] + ).getByTestId(InputOtpTestId.cursor, hiddenElements) + ).toBeOnTheScreen() + }) + + test('при ошибке отражает управляемый снаружи selection', () => { + const mockedOnChange = jest.fn() + const { getAllByTestId, getByTestId, getByText, rerenderInputOtp } = + renderInputOtp({ + error: true, + onChange: mockedOnChange, + selection: { start: 0, end: 1 }, + value: '1234', + }) + const input = getByTestId(InputOtpTestId.hiddenInput) + + fireEvent(input, 'focus') + + expect(input).toHaveProp('selection', { start: 0, end: 1 }) + expect( + within( + getAllByTestId(InputOtpTestId.itemContainer, hiddenElements)[0] + ).getByTestId(InputOtpTestId.cursor, hiddenElements) + ).toBeOnTheScreen() + + for (const digit of '1234') { + expect(getByText(digit, hiddenElements)).toBeOnTheScreen() + } + + for (const item of getAllByTestId( + InputOtpTestId.itemContainer, + hiddenElements + )) { + expect(item).toHaveStyle({ borderColor: '#db3424' }) + } + + fireEvent.changeText(input, '9234') + + expect(mockedOnChange).toHaveBeenCalledWith('9234') + + rerenderInputOtp({ + error: true, + selection: { start: 1, end: 2 }, + value: '9234', + }) + + const updatedInput = getByTestId(InputOtpTestId.hiddenInput) + const items = getAllByTestId(InputOtpTestId.itemContainer, hiddenElements) + + expect(updatedInput).toHaveProp('selection', { start: 1, end: 2 }) + expect( + within(items[1]).getByTestId(InputOtpTestId.cursor, hiddenElements) + ).toBeOnTheScreen() + expect( + within(items[0]).queryByTestId(InputOtpTestId.cursor, hiddenElements) + ).not.toBeOnTheScreen() + + fireEvent.changeText(updatedInput, '9834') + + expect(mockedOnChange).toHaveBeenLastCalledWith('9834') + + rerenderInputOtp({ + error: false, + selection: { start: 2, end: 2 }, + value: '9834', + }) + + expect( + getAllByTestId(InputOtpTestId.itemContainer, hiddenElements)[0] + ).toHaveStyle({ borderColor: '#cecfd2' }) + }) }) - test('should blur and reset focus when input becomes disabled', () => { - const mockedOnChange = jest.fn() - const includeHiddenElements = { includeHiddenElements: true } - let inputRef: TextInput | null = null - const handleInputRef = (ref: TextInput | null) => { - inputRef = ref - } - const { getByTestId, getByText, queryByText, update } = render( - - ) - - fireEvent(getByTestId('InputOtpHiddenInput'), 'focus') - - expect(getByText('|', includeHiddenElements)).toBeOnTheScreen() - expect(queryByText('|')).not.toBeOnTheScreen() - - if (!inputRef) { - throw new Error('Input ref was not set') - } - - const blur = jest.fn() - - Object.assign(inputRef, { blur, isFocused: () => true }) - - update( - - ) - - expect(blur).toHaveBeenCalledOnce() - expect(queryByText('|', includeHiddenElements)).not.toBeOnTheScreen() + describe('визуальные состояния', () => { + test('применяет hover только к ячейке под курсором', () => { + const { getAllByTestId } = renderInputOtp() + const items = getAllByTestId(InputOtpTestId.itemContainer, hiddenElements) + + fireEvent(items[1], 'hoverIn') + + expect(items[0]).toHaveStyle({ borderColor: '#cecfd2' }) + expect(items[1]).toHaveStyle({ borderColor: '#1dc831' }) + + fireEvent(items[1], 'hoverOut') + + expect(items[1]).toHaveStyle({ borderColor: '#cecfd2' }) + }) + + test('показывает pressed у всех ячеек через testOnly_pressed', () => { + const { getAllByTestId } = renderInputOtp({ testOnly_pressed: true }) + + for (const item of getAllByTestId( + InputOtpTestId.itemContainer, + hiddenElements + )) { + expect(item).toHaveStyle({ borderColor: '#1dc831' }) + } + }) + + test('disabled имеет приоритет над error, hover и pressed', () => { + const { getAllByTestId } = renderInputOtp({ + disabled: true, + error: true, + testOnly_pressed: true, + }) + const items = getAllByTestId(InputOtpTestId.itemContainer, hiddenElements) + + fireEvent(items[0], 'hoverIn') + + for (const item of items) { + expect(item).toHaveStyle({ + backgroundColor: '#e2e2e4', + borderColor: '#cecfd2', + boxShadow: 'none', + opacity: 0.5, + }) + } + }) }) - test('should not focus hidden input on press when input is not editable', () => { - const mockedOnChange = jest.fn() - let inputRef: TextInput | null = null - const handleInputRef = (ref: TextInput | null) => { - inputRef = ref - } - const { getByTestId } = render( - - ) - - if (!inputRef) { - throw new Error('Input ref was not set') - } - - const focus = jest.fn() - - Object.assign(inputRef, { focus }) - - fireEvent.press(getByTestId('InputOtp')) - - expect(focus).not.toHaveBeenCalled() + describe('запрещённый ввод', () => { + test('блокирует ячейки и снимает фокус при переходе в disabled', () => { + let inputRef: TextInput | null = null + const handleInputRef = (ref: TextInput | null) => { + inputRef = ref + } + const { getAllByTestId, getByTestId, queryByText, rerenderInputOtp } = + renderInputOtp({ inputRef: handleInputRef, value: '12' }) + + fireEvent(getByTestId(InputOtpTestId.hiddenInput), 'focus') + + if (!inputRef) { + throw new Error('Input ref was not set') + } + + const blur = jest.fn() + + Object.assign(inputRef, { blur, isFocused: () => true }) + rerenderInputOtp({ disabled: true }) + + expect(blur).toHaveBeenCalledOnce() + expect(getByTestId(InputOtpTestId.hiddenInput)).toHaveProp( + 'editable', + false + ) + expect(getByTestId(InputOtpTestId.hiddenInput)).toHaveProp( + 'accessibilityState', + { disabled: true } + ) + expect(queryByText('|', hiddenElements)).not.toBeOnTheScreen() + + for (const item of getAllByTestId( + InputOtpTestId.itemContainer, + hiddenElements + )) { + expect(item).toHaveStyle({ + backgroundColor: '#e2e2e4', + borderColor: '#cecfd2', + boxShadow: 'none', + minHeight: 40, + opacity: 0.5, + }) + } + }) + + test('не фокусирует и визуально блокирует поле при editable=false', async () => { + const user = userEvent.setup() + let inputRef: TextInput | null = null + const handleInputRef = (ref: TextInput | null) => { + inputRef = ref + } + const { getAllByTestId, getByTestId } = renderInputOtp({ + editable: false, + inputRef: handleInputRef, + }) + + if (!inputRef) { + throw new Error('Input ref was not set') + } + + const focus = jest.fn() + + Object.assign(inputRef, { focus }) + await user.press(getByTestId(InputOtpTestId.root)) + + expect(focus).not.toHaveBeenCalled() + expect(getByTestId(InputOtpTestId.hiddenInput)).toHaveProp( + 'editable', + false + ) + + for (const item of getAllByTestId( + InputOtpTestId.itemContainer, + hiddenElements + )) { + expect(item).toHaveStyle({ opacity: 0.5 }) + } + }) }) }) diff --git a/src/components/Input/InputOtp/__tests__/__snapshots__/InputOtp.test.tsx.snap b/src/components/Input/InputOtp/__tests__/__snapshots__/InputOtp.test.tsx.snap deleted file mode 100644 index e07be575..00000000 --- a/src/components/Input/InputOtp/__tests__/__snapshots__/InputOtp.test.tsx.snap +++ /dev/null @@ -1,16273 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`InputOtp component tests length - 2, error - false, disabled - false, pressed - false 1`] = ` - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 2, error - false, disabled - false, pressed - false 2`] = ` - - - - - 5 - - - - - - - - -`; - -exports[`InputOtp component tests length - 2, error - false, disabled - false, pressed - false 3`] = ` - - - - - 5 - - - - - 5 - - - - - -`; - -exports[`InputOtp component tests length - 2, error - false, disabled - false, pressed - true 1`] = ` - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 2, error - false, disabled - false, pressed - true 2`] = ` - - - - - 5 - - - - - - - - -`; - -exports[`InputOtp component tests length - 2, error - false, disabled - false, pressed - true 3`] = ` - - - - - 5 - - - - - 5 - - - - - -`; - -exports[`InputOtp component tests length - 2, error - false, disabled - true, pressed - false 1`] = ` - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 2, error - false, disabled - true, pressed - false 2`] = ` - - - - - 5 - - - - - - - - -`; - -exports[`InputOtp component tests length - 2, error - false, disabled - true, pressed - false 3`] = ` - - - - - 5 - - - - - 5 - - - - - -`; - -exports[`InputOtp component tests length - 2, error - false, disabled - true, pressed - true 1`] = ` - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 2, error - false, disabled - true, pressed - true 2`] = ` - - - - - 5 - - - - - - - - -`; - -exports[`InputOtp component tests length - 2, error - false, disabled - true, pressed - true 3`] = ` - - - - - 5 - - - - - 5 - - - - - -`; - -exports[`InputOtp component tests length - 2, error - true, disabled - false, pressed - false 1`] = ` - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 2, error - true, disabled - false, pressed - false 2`] = ` - - - - - 5 - - - - - - - - -`; - -exports[`InputOtp component tests length - 2, error - true, disabled - false, pressed - false 3`] = ` - - - - - 5 - - - - - 5 - - - - - -`; - -exports[`InputOtp component tests length - 2, error - true, disabled - false, pressed - true 1`] = ` - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 2, error - true, disabled - false, pressed - true 2`] = ` - - - - - 5 - - - - - - - - -`; - -exports[`InputOtp component tests length - 2, error - true, disabled - false, pressed - true 3`] = ` - - - - - 5 - - - - - 5 - - - - - -`; - -exports[`InputOtp component tests length - 2, error - true, disabled - true, pressed - false 1`] = ` - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 2, error - true, disabled - true, pressed - false 2`] = ` - - - - - 5 - - - - - - - - -`; - -exports[`InputOtp component tests length - 2, error - true, disabled - true, pressed - false 3`] = ` - - - - - 5 - - - - - 5 - - - - - -`; - -exports[`InputOtp component tests length - 2, error - true, disabled - true, pressed - true 1`] = ` - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 2, error - true, disabled - true, pressed - true 2`] = ` - - - - - 5 - - - - - - - - -`; - -exports[`InputOtp component tests length - 2, error - true, disabled - true, pressed - true 3`] = ` - - - - - 5 - - - - - 5 - - - - - -`; - -exports[`InputOtp component tests length - 4, error - false, disabled - false, pressed - false 1`] = ` - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 4, error - false, disabled - false, pressed - false 2`] = ` - - - - - 5 - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 4, error - false, disabled - false, pressed - false 3`] = ` - - - - - 5 - - - - - 5 - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 4, error - false, disabled - false, pressed - true 1`] = ` - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 4, error - false, disabled - false, pressed - true 2`] = ` - - - - - 5 - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 4, error - false, disabled - false, pressed - true 3`] = ` - - - - - 5 - - - - - 5 - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 4, error - false, disabled - true, pressed - false 1`] = ` - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 4, error - false, disabled - true, pressed - false 2`] = ` - - - - - 5 - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 4, error - false, disabled - true, pressed - false 3`] = ` - - - - - 5 - - - - - 5 - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 4, error - false, disabled - true, pressed - true 1`] = ` - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 4, error - false, disabled - true, pressed - true 2`] = ` - - - - - 5 - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 4, error - false, disabled - true, pressed - true 3`] = ` - - - - - 5 - - - - - 5 - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 4, error - true, disabled - false, pressed - false 1`] = ` - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 4, error - true, disabled - false, pressed - false 2`] = ` - - - - - 5 - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 4, error - true, disabled - false, pressed - false 3`] = ` - - - - - 5 - - - - - 5 - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 4, error - true, disabled - false, pressed - true 1`] = ` - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 4, error - true, disabled - false, pressed - true 2`] = ` - - - - - 5 - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 4, error - true, disabled - false, pressed - true 3`] = ` - - - - - 5 - - - - - 5 - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 4, error - true, disabled - true, pressed - false 1`] = ` - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 4, error - true, disabled - true, pressed - false 2`] = ` - - - - - 5 - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 4, error - true, disabled - true, pressed - false 3`] = ` - - - - - 5 - - - - - 5 - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 4, error - true, disabled - true, pressed - true 1`] = ` - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 4, error - true, disabled - true, pressed - true 2`] = ` - - - - - 5 - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 4, error - true, disabled - true, pressed - true 3`] = ` - - - - - 5 - - - - - 5 - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 8, error - false, disabled - false, pressed - false 1`] = ` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 8, error - false, disabled - false, pressed - false 2`] = ` - - - - - 5 - - - - - - - - - - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 8, error - false, disabled - false, pressed - false 3`] = ` - - - - - 5 - - - - - 5 - - - - - - - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 8, error - false, disabled - false, pressed - true 1`] = ` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 8, error - false, disabled - false, pressed - true 2`] = ` - - - - - 5 - - - - - - - - - - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 8, error - false, disabled - false, pressed - true 3`] = ` - - - - - 5 - - - - - 5 - - - - - - - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 8, error - false, disabled - true, pressed - false 1`] = ` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 8, error - false, disabled - true, pressed - false 2`] = ` - - - - - 5 - - - - - - - - - - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 8, error - false, disabled - true, pressed - false 3`] = ` - - - - - 5 - - - - - 5 - - - - - - - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 8, error - false, disabled - true, pressed - true 1`] = ` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 8, error - false, disabled - true, pressed - true 2`] = ` - - - - - 5 - - - - - - - - - - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 8, error - false, disabled - true, pressed - true 3`] = ` - - - - - 5 - - - - - 5 - - - - - - - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 8, error - true, disabled - false, pressed - false 1`] = ` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 8, error - true, disabled - false, pressed - false 2`] = ` - - - - - 5 - - - - - - - - - - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 8, error - true, disabled - false, pressed - false 3`] = ` - - - - - 5 - - - - - 5 - - - - - - - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 8, error - true, disabled - false, pressed - true 1`] = ` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 8, error - true, disabled - false, pressed - true 2`] = ` - - - - - 5 - - - - - - - - - - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 8, error - true, disabled - false, pressed - true 3`] = ` - - - - - 5 - - - - - 5 - - - - - - - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 8, error - true, disabled - true, pressed - false 1`] = ` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 8, error - true, disabled - true, pressed - false 2`] = ` - - - - - 5 - - - - - - - - - - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 8, error - true, disabled - true, pressed - false 3`] = ` - - - - - 5 - - - - - 5 - - - - - - - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 8, error - true, disabled - true, pressed - true 1`] = ` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 8, error - true, disabled - true, pressed - true 2`] = ` - - - - - 5 - - - - - - - - - - - - - - - - - - - - - - - - - - -`; - -exports[`InputOtp component tests length - 8, error - true, disabled - true, pressed - true 3`] = ` - - - - - 5 - - - - - 5 - - - - - - - - - - - - - - - - - - - - - - - -`; diff --git a/src/components/Input/InputOtp/testIds.ts b/src/components/Input/InputOtp/testIds.ts new file mode 100644 index 00000000..148513a7 --- /dev/null +++ b/src/components/Input/InputOtp/testIds.ts @@ -0,0 +1,11 @@ +export const createInputOtpTestIds = (prefix: string) => ({ + root: prefix, + content: `${prefix}Content`, + item: `${prefix}Item`, + itemContainer: `${prefix}ItemContainer`, + cursorRow: `${prefix}ItemCursorRow`, + cursor: `${prefix}ItemCursor`, + hiddenInput: `${prefix}HiddenInput`, +}) + +export const InputOtpTestId = createInputOtpTestIds('InputOtp') diff --git a/src/components/Input/index.ts b/src/components/Input/index.ts index c2f1eb5b..fc5a27d3 100644 --- a/src/components/Input/index.ts +++ b/src/components/Input/index.ts @@ -2,4 +2,8 @@ export { InputGroup } from './InputGroup' export { InputText } from './InputText' export { InputSwitch } from './InputSwitch' export type { InputTextBaseProps } from './InputTextBase/types' -export { InputOtp } from './InputOtp/InputOtp' +export { + InputOtp, + InputOtpTestId, + type InputOtpProps, +} from './InputOtp/InputOtp' From 561a8e0eaa727fc581d55e72f961a6439a5a3dae Mon Sep 17 00:00:00 2001 From: Alesya Volosach Date: Wed, 26 Aug 2026 19:53:39 +0300 Subject: [PATCH 2/7] =?UTF-8?q?fix(input-otp):=20=D0=B8=D1=81=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=20=D0=BF=D0=BE=D0=B2=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=BD=D1=8B=D0=B9=20=D0=B2=D0=B2=D0=BE=D0=B4=20=D0=BF?= =?UTF-8?q?=D0=BE=D1=81=D0=BB=D0=B5=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Input/InputOtp/InputOtp.tsx | 9 +- .../Input/InputOtp/InputOtpItem.tsx | 2 +- .../__tests__/InputOtp.scenario.test.tsx | 176 ++++++++++++++++++ 3 files changed, 183 insertions(+), 4 deletions(-) create mode 100644 src/components/Input/InputOtp/__tests__/InputOtp.scenario.test.tsx diff --git a/src/components/Input/InputOtp/InputOtp.tsx b/src/components/Input/InputOtp/InputOtp.tsx index 9ca52160..4109bd92 100644 --- a/src/components/Input/InputOtp/InputOtp.tsx +++ b/src/components/Input/InputOtp/InputOtp.tsx @@ -83,6 +83,9 @@ export const InputOtp = memo( const inputRef = useRef(null) const isInputEditable = !disabled && editable !== false const inputValue = normalizeOtpValue(value, length) + const inputSelection = selection ?? getDefaultSelection(inputValue) + const hasSelectedText = + (inputSelection.end ?? inputSelection.start) > inputSelection.start useImperativeHandle( propsInputRef, @@ -108,12 +111,13 @@ export const InputOtp = memo( const handleChange = useCallback( (text: string) => { const nextValue = normalizeOtpValue(text, length) + const isSameValueReplacement = hasSelectedText && text === inputValue - if (nextValue !== inputValue) { + if (nextValue !== inputValue || isSameValueReplacement) { onChange(nextValue) } }, - [inputValue, length, onChange] + [hasSelectedText, inputValue, length, onChange] ) const handleFocus = useCallback( @@ -132,7 +136,6 @@ export const InputOtp = memo( [onBlur] ) - const inputSelection = selection ?? getDefaultSelection(inputValue) const activeIndex = getActiveIndex(inputSelection.start, length) const testIds = createInputOtpTestIds(testID ?? InputOtpTestId.root) diff --git a/src/components/Input/InputOtp/InputOtpItem.tsx b/src/components/Input/InputOtp/InputOtpItem.tsx index 50f0706b..509a2adc 100644 --- a/src/components/Input/InputOtp/InputOtpItem.tsx +++ b/src/components/Input/InputOtp/InputOtpItem.tsx @@ -111,7 +111,7 @@ export const InputOtpItem = memo( const styles = StyleSheet.create(({ components, semantic, fonts }) => ({ container: { - minWidth: components.inputotp.extend.height, + minWidth: components.inputotp.input.width, minHeight: components.inputotp.extend.height, borderWidth: components.inputotp.extend.borderWidth, borderRadius: components.inputtext.root.borderRadius, diff --git a/src/components/Input/InputOtp/__tests__/InputOtp.scenario.test.tsx b/src/components/Input/InputOtp/__tests__/InputOtp.scenario.test.tsx new file mode 100644 index 00000000..1593b48e --- /dev/null +++ b/src/components/Input/InputOtp/__tests__/InputOtp.scenario.test.tsx @@ -0,0 +1,176 @@ +import { + act, + fireEvent, + render, + userEvent, + waitFor, + within, +} from '@testing-library/react-native' +import { useRef, useState } from 'react' +import { Text, type TextInput } from 'react-native' + +import { InputOtp, InputOtpTestId } from '../InputOtp' + +const hiddenElements = { includeHiddenElements: true } + +interface InputOtpErrorRecoveryMockProps { + readonly validateOtp: (value: string) => Promise +} + +// Валидация и сброс кода намеренно принадлежат mock-consumer: тест проверяет, +// что публичного API InputOtp достаточно без переноса бизнес-логики в библиотеку. +const InputOtpErrorRecoveryMock = ({ + validateOtp, +}: InputOtpErrorRecoveryMockProps) => { + const inputRef = useRef(null) + const [disabled, setDisabled] = useState(false) + const [error, setError] = useState(false) + const [selection, setSelection] = useState({ start: 3, end: 3 }) + const [value, setValue] = useState('123') + + const handleChange = async (nextValue: string) => { + if (error) { + const replacement = nextValue[selection.start] ?? '' + const cursorPosition = replacement.length + + setError(false) + setSelection({ start: cursorPosition, end: cursorPosition }) + setValue(replacement) + + return + } + + const cursorPosition = nextValue.length + + setSelection({ start: cursorPosition, end: cursorPosition }) + setValue(nextValue) + + if (nextValue.length === 4) { + inputRef.current?.blur() + setDisabled(true) + + const isValid = await validateOtp(nextValue) + + setDisabled(false) + + if (!isValid) { + setError(true) + setSelection({ start: 0, end: 1 }) + } + } + } + + return ( + <> + + {error ? Неверный код : null} + + ) +} + +describe('InputOtp: внешний сценарий повторного ввода после ошибки', () => { + test.each([ + { + digit: '5', + name: 'новая цифра отличается от прежней', + nativeValue: '5234', + }, + { + digit: '1', + name: 'новая цифра совпадает с прежней', + nativeValue: '1234', + }, + ])( + '$name: первый ввод снимает ошибку и очищает остальные цифры', + async ({ digit, nativeValue }) => { + const user = userEvent.setup() + let resolveValidation!: (isValid: boolean) => void + const validateOtp = jest.fn( + () => + new Promise((resolve) => { + resolveValidation = resolve + }) + ) + const { getAllByTestId, getByTestId, getByText, queryByText } = render( + + ) + const input = getByTestId(InputOtpTestId.hiddenInput) + + fireEvent(input, 'focus') + fireEvent.changeText(input, '1234') + + expect(validateOtp).toHaveBeenCalledOnce() + expect(validateOtp).toHaveBeenCalledWith('1234') + + await waitFor(() => { + expect(getByTestId(InputOtpTestId.hiddenInput)).toHaveProp( + 'editable', + false + ) + }) + + expect(queryByText('|', hiddenElements)).not.toBeOnTheScreen() + + await act(async () => { + resolveValidation(false) + }) + + await waitFor(() => { + expect(getByText('Неверный код')).toBeOnTheScreen() + }) + + expect(getByTestId(InputOtpTestId.hiddenInput)).toHaveProp('selection', { + start: 0, + end: 1, + }) + expect(getByTestId(InputOtpTestId.hiddenInput)).toHaveProp( + 'value', + '1234' + ) + + await user.press( + getAllByTestId(InputOtpTestId.itemContainer, hiddenElements)[3] + ) + fireEvent(getByTestId(InputOtpTestId.hiddenInput), 'focus') + + expect( + within( + getAllByTestId(InputOtpTestId.itemContainer, hiddenElements)[0] + ).getByTestId(InputOtpTestId.cursor, hiddenElements) + ).toBeOnTheScreen() + + fireEvent.changeText(getByTestId(InputOtpTestId.hiddenInput), nativeValue) + + await waitFor(() => { + expect(getByTestId(InputOtpTestId.hiddenInput)).toHaveProp( + 'value', + digit + ) + }) + + expect(getByTestId(InputOtpTestId.hiddenInput)).toHaveProp('selection', { + start: 1, + end: 1, + }) + expect(queryByText('Неверный код')).not.toBeOnTheScreen() + + for (const clearedDigit of '234') { + expect(queryByText(clearedDigit, hiddenElements)).not.toBeOnTheScreen() + } + + expect( + within( + getAllByTestId(InputOtpTestId.itemContainer, hiddenElements)[1] + ).getByTestId(InputOtpTestId.cursor, hiddenElements) + ).toBeOnTheScreen() + } + ) +}) From 790da8930958228d95d93726861a76c2f1de0ca0 Mon Sep 17 00:00:00 2001 From: Alesya Volosach Date: Wed, 2 Sep 2026 11:04:38 +0300 Subject: [PATCH 3/7] =?UTF-8?q?fix(input-otp):=20=D0=B8=D1=81=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=BA=D0=B0=D1=80=D0=B5?= =?UTF-8?q?=D1=82=D0=BA=D0=B0=20=D0=B8=20=D1=81=D1=86=D0=B5=D0=BD=D0=B0?= =?UTF-8?q?=D1=80=D0=B8=D0=B9=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Input/InputOtp/InputOtp.stories.tsx | 13 ++ src/components/Input/InputOtp/InputOtp.tsx | 6 +- .../InputOtpErrorRecoveryScenario.tsx | 96 ++++++++ .../Input/InputOtp/InputOtpItem.tsx | 23 +- .../__tests__/InputOtp.scenario.test.tsx | 219 +++++------------- .../InputOtp/__tests__/InputOtp.test.tsx | 35 +-- 6 files changed, 217 insertions(+), 175 deletions(-) create mode 100644 src/components/Input/InputOtp/InputOtpErrorRecoveryScenario.tsx diff --git a/src/components/Input/InputOtp/InputOtp.stories.tsx b/src/components/Input/InputOtp/InputOtp.stories.tsx index c4cbcf24..b0600197 100644 --- a/src/components/Input/InputOtp/InputOtp.stories.tsx +++ b/src/components/Input/InputOtp/InputOtp.stories.tsx @@ -4,6 +4,14 @@ import type { Meta, StoryObj } from '@storybook/react' import { useCallback, useEffect, useState } from 'react' import { InputOtp } from './InputOtp' +import { InputOtpErrorRecoveryScenario } from './InputOtpErrorRecoveryScenario' + +const VALIDATION_DELAY = 1000 + +const validateOtp = () => + new Promise((resolve) => { + setTimeout(() => resolve(false), VALIDATION_DELAY) + }) const meta: Meta = { title: 'Form/InputOtp', @@ -36,3 +44,8 @@ type Story = StoryObj const InputOtpStory: Story = {} export { InputOtpStory as InputOtp } + +export const ErrorRecovery: Story = { + name: 'Scenario: Error Recovery', + render: () => , +} diff --git a/src/components/Input/InputOtp/InputOtp.tsx b/src/components/Input/InputOtp/InputOtp.tsx index 4109bd92..e1bb09a9 100644 --- a/src/components/Input/InputOtp/InputOtp.tsx +++ b/src/components/Input/InputOtp/InputOtp.tsx @@ -86,6 +86,7 @@ export const InputOtp = memo( const inputSelection = selection ?? getDefaultSelection(inputValue) const hasSelectedText = (inputSelection.end ?? inputSelection.start) > inputSelection.start + const hasVisibleCursor = inputSelection.start < length useImperativeHandle( propsInputRef, @@ -158,7 +159,10 @@ export const InputOtp = memo( disabled={!isInputEditable} error={error} focused={Boolean( - isFocused && isInputEditable && index === activeIndex + isFocused && + isInputEditable && + hasVisibleCursor && + index === activeIndex )} key={`Otp-Item-${index}`} testIdPrefix={testIds.root} diff --git a/src/components/Input/InputOtp/InputOtpErrorRecoveryScenario.tsx b/src/components/Input/InputOtp/InputOtpErrorRecoveryScenario.tsx new file mode 100644 index 00000000..4d853b70 --- /dev/null +++ b/src/components/Input/InputOtp/InputOtpErrorRecoveryScenario.tsx @@ -0,0 +1,96 @@ +import { + useCallback, + useEffect, + useImperativeHandle, + useRef, + useState, + type Ref, +} from 'react' +import type { TextInput } from 'react-native' + +import { InputOtp } from './InputOtp' + +const OTP_LENGTH = 4 + +export interface InputOtpErrorRecoveryScenarioProps { + readonly inputRef?: Ref + readonly validateOtp: (value: string) => Promise +} + +// Сценарий намеренно имитирует consumer: загрузка, валидация и восстановление +// после ошибки не являются ответственностью библиотечного InputOtp. +export const InputOtpErrorRecoveryScenario = ({ + inputRef: propsInputRef, + validateOtp, +}: InputOtpErrorRecoveryScenarioProps) => { + const inputRef = useRef(null) + const [error, setError] = useState(false) + const [isChecking, setIsChecking] = useState(false) + const [selection, setSelection] = useState({ start: 0, end: 0 }) + const [value, setValue] = useState('') + + useImperativeHandle( + propsInputRef, + () => inputRef.current + ) + + const onChange = useCallback( + async (nextValue: string) => { + if (error) { + const replacement = nextValue[selection.start] ?? '' + const cursorPosition = replacement.length + + setError(false) + setSelection({ start: cursorPosition, end: cursorPosition }) + setValue(replacement) + + return + } + + const cursorPosition = nextValue.length + + setSelection({ start: cursorPosition, end: cursorPosition }) + setValue(nextValue) + + if (nextValue.length === OTP_LENGTH) { + inputRef.current?.blur() + setIsChecking(true) + + const isValid = await validateOtp(nextValue) + + setIsChecking(false) + + if (!isValid) { + setError(true) + } + } + }, + [error, selection.start, validateOtp] + ) + + const onFocus = useCallback(() => { + if (error) { + setSelection({ start: 0, end: 1 }) + } + }, [error]) + + useEffect(() => { + if (error) { + inputRef.current?.focus() + } + }, [error]) + + return ( + + ) +} diff --git a/src/components/Input/InputOtp/InputOtpItem.tsx b/src/components/Input/InputOtp/InputOtpItem.tsx index 509a2adc..ace307fd 100644 --- a/src/components/Input/InputOtp/InputOtpItem.tsx +++ b/src/components/Input/InputOtp/InputOtpItem.tsx @@ -76,12 +76,21 @@ export const InputOtpItem = memo( {focused ? ( {value ? ( - - {value} - + <> + + | + + + {value} + + ) : null} ({ textRow: { flexDirection: 'row', alignItems: 'center' }, + cursorSpacer: { opacity: semantic.effects.opacity[0] }, + text: { fontSize: fonts.fontSize[200], lineHeight: fonts.lineHeight[200], diff --git a/src/components/Input/InputOtp/__tests__/InputOtp.scenario.test.tsx b/src/components/Input/InputOtp/__tests__/InputOtp.scenario.test.tsx index 1593b48e..a6c0b7dd 100644 --- a/src/components/Input/InputOtp/__tests__/InputOtp.scenario.test.tsx +++ b/src/components/Input/InputOtp/__tests__/InputOtp.scenario.test.tsx @@ -2,175 +2,84 @@ import { act, fireEvent, render, - userEvent, waitFor, within, } from '@testing-library/react-native' -import { useRef, useState } from 'react' -import { Text, type TextInput } from 'react-native' +import { createRef } from 'react' +import type { TextInput } from 'react-native' -import { InputOtp, InputOtpTestId } from '../InputOtp' +import { InputOtpTestId } from '../InputOtp' +import { InputOtpErrorRecoveryScenario } from '../InputOtpErrorRecoveryScenario' const hiddenElements = { includeHiddenElements: true } -interface InputOtpErrorRecoveryMockProps { - readonly validateOtp: (value: string) => Promise -} - -// Валидация и сброс кода намеренно принадлежат mock-consumer: тест проверяет, -// что публичного API InputOtp достаточно без переноса бизнес-логики в библиотеку. -const InputOtpErrorRecoveryMock = ({ - validateOtp, -}: InputOtpErrorRecoveryMockProps) => { - const inputRef = useRef(null) - const [disabled, setDisabled] = useState(false) - const [error, setError] = useState(false) - const [selection, setSelection] = useState({ start: 3, end: 3 }) - const [value, setValue] = useState('123') - - const handleChange = async (nextValue: string) => { - if (error) { - const replacement = nextValue[selection.start] ?? '' - const cursorPosition = replacement.length - - setError(false) - setSelection({ start: cursorPosition, end: cursorPosition }) - setValue(replacement) - - return +describe('InputOtp: внешний сценарий повторного ввода после ошибки', () => { + test('после ошибки фокусирует первую ячейку и заменяет код первой новой цифрой', async () => { + const inputRef = createRef() + let resolveValidation!: (isValid: boolean) => void + const validateOtp = jest.fn( + () => + new Promise((resolve) => { + resolveValidation = resolve + }) + ) + const { getAllByTestId, getByTestId } = render( + + ) + const input = getByTestId(InputOtpTestId.hiddenInput) + + if (!inputRef.current) { + throw new Error('Input ref was not set') } - const cursorPosition = nextValue.length + const focus = jest.fn() - setSelection({ start: cursorPosition, end: cursorPosition }) - setValue(nextValue) + Object.assign(inputRef.current, { focus }) - if (nextValue.length === 4) { - inputRef.current?.blur() - setDisabled(true) + fireEvent(input, 'focus') + fireEvent.changeText(input, '1234') - const isValid = await validateOtp(nextValue) + expect(validateOtp).toHaveBeenCalledOnce() + expect(validateOtp).toHaveBeenCalledWith('1234') - setDisabled(false) + await waitFor(() => { + expect(input).toHaveProp('editable', false) + }) - if (!isValid) { - setError(true) - setSelection({ start: 0, end: 1 }) - } - } - } + await act(async () => { + resolveValidation(false) + }) - return ( - <> - - {error ? Неверный код : null} - - ) -} + await waitFor(() => { + expect(focus).toHaveBeenCalledOnce() + }) -describe('InputOtp: внешний сценарий повторного ввода после ошибки', () => { - test.each([ - { - digit: '5', - name: 'новая цифра отличается от прежней', - nativeValue: '5234', - }, - { - digit: '1', - name: 'новая цифра совпадает с прежней', - nativeValue: '1234', - }, - ])( - '$name: первый ввод снимает ошибку и очищает остальные цифры', - async ({ digit, nativeValue }) => { - const user = userEvent.setup() - let resolveValidation!: (isValid: boolean) => void - const validateOtp = jest.fn( - () => - new Promise((resolve) => { - resolveValidation = resolve - }) - ) - const { getAllByTestId, getByTestId, getByText, queryByText } = render( - - ) - const input = getByTestId(InputOtpTestId.hiddenInput) - - fireEvent(input, 'focus') - fireEvent.changeText(input, '1234') - - expect(validateOtp).toHaveBeenCalledOnce() - expect(validateOtp).toHaveBeenCalledWith('1234') - - await waitFor(() => { - expect(getByTestId(InputOtpTestId.hiddenInput)).toHaveProp( - 'editable', - false - ) - }) - - expect(queryByText('|', hiddenElements)).not.toBeOnTheScreen() - - await act(async () => { - resolveValidation(false) - }) - - await waitFor(() => { - expect(getByText('Неверный код')).toBeOnTheScreen() - }) - - expect(getByTestId(InputOtpTestId.hiddenInput)).toHaveProp('selection', { - start: 0, - end: 1, - }) - expect(getByTestId(InputOtpTestId.hiddenInput)).toHaveProp( - 'value', - '1234' - ) - - await user.press( - getAllByTestId(InputOtpTestId.itemContainer, hiddenElements)[3] - ) - fireEvent(getByTestId(InputOtpTestId.hiddenInput), 'focus') - - expect( - within( - getAllByTestId(InputOtpTestId.itemContainer, hiddenElements)[0] - ).getByTestId(InputOtpTestId.cursor, hiddenElements) - ).toBeOnTheScreen() - - fireEvent.changeText(getByTestId(InputOtpTestId.hiddenInput), nativeValue) - - await waitFor(() => { - expect(getByTestId(InputOtpTestId.hiddenInput)).toHaveProp( - 'value', - digit - ) - }) - - expect(getByTestId(InputOtpTestId.hiddenInput)).toHaveProp('selection', { - start: 1, - end: 1, - }) - expect(queryByText('Неверный код')).not.toBeOnTheScreen() - - for (const clearedDigit of '234') { - expect(queryByText(clearedDigit, hiddenElements)).not.toBeOnTheScreen() - } - - expect( - within( - getAllByTestId(InputOtpTestId.itemContainer, hiddenElements)[1] - ).getByTestId(InputOtpTestId.cursor, hiddenElements) - ).toBeOnTheScreen() - } - ) + expect(input).toHaveProp('editable', true) + expect(input).toHaveProp('selection', { start: 4, end: 4 }) + + // RNTL не генерирует native focus event при вызове focus() через ref. + // Отдельно эмулируем ответ платформы на программный фокус consumer-а. + fireEvent(input, 'focus') + + await waitFor(() => { + expect(input).toHaveProp('selection', { start: 0, end: 1 }) + }) + + expect( + within( + getAllByTestId(InputOtpTestId.itemContainer, hiddenElements)[0] + ).getByTestId(InputOtpTestId.cursor, hiddenElements) + ).toBeOnTheScreen() + + fireEvent.changeText(input, '5234') + + await waitFor(() => { + expect(input).toHaveProp('value', '5') + }) + + expect(input).toHaveProp('selection', { start: 1, end: 1 }) + }) }) diff --git a/src/components/Input/InputOtp/__tests__/InputOtp.test.tsx b/src/components/Input/InputOtp/__tests__/InputOtp.test.tsx index 17b914e1..3d6d02ea 100644 --- a/src/components/Input/InputOtp/__tests__/InputOtp.test.tsx +++ b/src/components/Input/InputOtp/__tests__/InputOtp.test.tsx @@ -155,7 +155,7 @@ describe('InputOtp', () => { test('очищает вставленный код от символов и отбрасывает лишние цифры', () => { const mockedOnChange = jest.fn() - const { getAllByTestId, getByTestId, rerenderInputOtp } = renderInputOtp({ + const { getByTestId, queryByText, rerenderInputOtp } = renderInputOtp({ onChange: mockedOnChange, value: '', }) @@ -169,16 +169,14 @@ describe('InputOtp', () => { rerenderInputOtp({ value: '1234' }) - expect( - within( - getAllByTestId(InputOtpTestId.itemContainer, hiddenElements)[3] - ).getByTestId(InputOtpTestId.cursor, hiddenElements) - ).toBeOnTheScreen() + expect(queryByText('|', hiddenElements)).not.toBeOnTheScreen() }) - test('после последней цифры передаёт полный код и сохраняет фокус', () => { + test('после последней цифры сохраняет нативный фокус без визуальной каретки', () => { + const mockedOnBlur = jest.fn() const mockedOnChange = jest.fn() - const { getAllByTestId, getByTestId, rerenderInputOtp } = renderInputOtp({ + const { getByTestId, queryByText, rerenderInputOtp } = renderInputOtp({ + onBlur: mockedOnBlur, onChange: mockedOnChange, value: '123', }) @@ -191,11 +189,8 @@ describe('InputOtp', () => { rerenderInputOtp({ value: '1234' }) - expect( - within( - getAllByTestId(InputOtpTestId.itemContainer, hiddenElements)[3] - ).getByTestId(InputOtpTestId.cursor, hiddenElements) - ).toBeOnTheScreen() + expect(mockedOnBlur).not.toHaveBeenCalled() + expect(queryByText('|', hiddenElements)).not.toBeOnTheScreen() }) test('backspace очищает заполненную и предыдущую пустую позицию', () => { @@ -313,6 +308,20 @@ describe('InputOtp', () => { ).toBeOnTheScreen() }) + test('сообщает повторный ввод выделенной цифры при неизменившемся нативном значении', () => { + const mockedOnChange = jest.fn() + const { getByTestId } = renderInputOtp({ + onChange: mockedOnChange, + selection: { start: 0, end: 1 }, + value: '1234', + }) + + fireEvent.changeText(getByTestId(InputOtpTestId.hiddenInput), '1234') + + expect(mockedOnChange).toHaveBeenCalledOnce() + expect(mockedOnChange).toHaveBeenCalledWith('1234') + }) + test('при ошибке отражает управляемый снаружи selection', () => { const mockedOnChange = jest.fn() const { getAllByTestId, getByTestId, getByText, rerenderInputOtp } = From 4b912249922038aa031b0862c927f57173c15b24 Mon Sep 17 00:00:00 2001 From: Alesya Volosach Date: Wed, 2 Sep 2026 14:26:57 +0300 Subject: [PATCH 4/7] =?UTF-8?q?fix(input-otp):=20=D1=81=D0=B8=D0=BD=D1=85?= =?UTF-8?q?=D1=80=D0=BE=D0=BD=D0=B8=D0=B7=D0=B8=D1=80=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D1=8B=20=D1=81=D1=82=D0=B8=D0=BB=D0=B8=20=D1=81=20=D0=BC?= =?UTF-8?q?=D0=B0=D0=BA=D0=B5=D1=82=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Input/InputOtp/InputOtpItem.tsx | 43 ++++++++----------- .../InputOtp/__tests__/InputOtp.test.tsx | 23 +--------- 2 files changed, 19 insertions(+), 47 deletions(-) diff --git a/src/components/Input/InputOtp/InputOtpItem.tsx b/src/components/Input/InputOtp/InputOtpItem.tsx index ace307fd..2ec53c8e 100644 --- a/src/components/Input/InputOtp/InputOtpItem.tsx +++ b/src/components/Input/InputOtp/InputOtpItem.tsx @@ -84,10 +84,7 @@ export const InputOtpItem = memo( > | - + {value} @@ -95,21 +92,14 @@ export const InputOtpItem = memo( | ) : ( - + {value} )} @@ -120,12 +110,14 @@ export const InputOtpItem = memo( const styles = StyleSheet.create(({ components, semantic, fonts }) => ({ container: { - minWidth: components.inputotp.input.width, - minHeight: components.inputotp.extend.height, + width: components.inputotp.input.width, + height: components.inputotp.extend.height, + paddingHorizontal: components.inputtext.root.paddingX, + paddingVertical: components.inputtext.root.paddingY, borderWidth: components.inputotp.extend.borderWidth, borderRadius: components.inputtext.root.borderRadius, - borderColor: semantic.colorScheme.color.border.neutral.strong, - backgroundColor: semantic.colorScheme.color.bg.surface.default.default, + borderColor: components.inputtext.root.borderColor, + backgroundColor: components.inputtext.root.background, alignItems: 'center', justifyContent: 'center', }, @@ -136,19 +128,20 @@ const styles = StyleSheet.create(({ components, semantic, fonts }) => ({ text: { fontSize: fonts.fontSize[200], - lineHeight: fonts.lineHeight[200], + lineHeight: fonts.fontSize[200], fontFamily: fonts.fontFamily.base, fontWeight: fonts.fontWeight.regular, - color: semantic.colorScheme.color.fg.active, + letterSpacing: fonts.letterSpacing[500], + color: components.inputtext.root.color, includeFontPadding: false, textAlign: 'center', }, - hovered: { borderColor: semantic.colorScheme.color.border.brand.strong }, + hovered: { borderColor: components.inputtext.root.hoverBorderColor }, focused: { - borderColor: semantic.colorScheme.color.border.brand.strong, - boxShadow: `0 0 0 3.5px ${semantic.colorScheme.color.border.focus}`, + borderColor: components.inputtext.root.focusBorderColor, + boxShadow: `0 0 0 ${components.inputtext.root.focusRing.width}px ${components.inputtext.root.focusRing.color}`, }, error: { @@ -160,11 +153,9 @@ const styles = StyleSheet.create(({ components, semantic, fonts }) => ({ }, disabled: { - backgroundColor: semantic.colorScheme.color.bg.neutral.weak.disabled, - borderColor: semantic.colorScheme.color.border.neutral.strong, + backgroundColor: components.inputtext.root.disabledBackground, + borderColor: components.inputtext.root.borderColor, boxShadow: 'none', opacity: semantic.effects.opacity[50], }, - - disabledText: { color: semantic.colorScheme.color.fg.muted }, })) diff --git a/src/components/Input/InputOtp/__tests__/InputOtp.test.tsx b/src/components/Input/InputOtp/__tests__/InputOtp.test.tsx index 3d6d02ea..fcdfd154 100644 --- a/src/components/Input/InputOtp/__tests__/InputOtp.test.tsx +++ b/src/components/Input/InputOtp/__tests__/InputOtp.test.tsx @@ -30,24 +30,11 @@ describe('InputOtp', () => { { name: 'четыре ячейки', length: 4, value: '1234' }, { name: 'восемь ячеек', length: 8, value: '12345678' }, ])('отображает $name', ({ length, value }) => { - const { getAllByTestId, getByTestId, getByText } = renderInputOtp({ - length, - value, - }) + const { getAllByTestId, getByText } = renderInputOtp({ length, value }) expect(getAllByTestId(InputOtpTestId.item, hiddenElements)).toHaveLength( length ) - expect(getByTestId(InputOtpTestId.content, hiddenElements)).toHaveStyle({ - gap: 8, - }) - - for (const item of getAllByTestId( - InputOtpTestId.itemContainer, - hiddenElements - )) { - expect(item).toHaveStyle({ minWidth: 40, minHeight: 40 }) - } for (const digit of value) { expect(getByText(digit, hiddenElements)).toBeOnTheScreen() @@ -472,13 +459,7 @@ describe('InputOtp', () => { InputOtpTestId.itemContainer, hiddenElements )) { - expect(item).toHaveStyle({ - backgroundColor: '#e2e2e4', - borderColor: '#cecfd2', - boxShadow: 'none', - minHeight: 40, - opacity: 0.5, - }) + expect(item).toBeDisabled() } }) From 89fce287d2d1c4ee4687fffa4f4d760c572f8fb9 Mon Sep 17 00:00:00 2001 From: Alesya Volosach Date: Wed, 2 Sep 2026 13:13:30 +0300 Subject: [PATCH 5/7] =?UTF-8?q?fix(tokens):=20=D0=BF=D0=BE=D0=B4=D0=B4?= =?UTF-8?q?=D0=B5=D1=80=D0=B6=D0=B0=D0=BD=D1=8B=20=D0=BD=D0=B5=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D0=BD=D1=8B=D0=B5=20=D1=86=D0=B2=D0=B5=D1=82=D0=BE=D0=B2?= =?UTF-8?q?=D1=8B=D0=B5=20=D1=81=D1=85=D0=B5=D0=BC=D1=8B=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__tests__/compiler.test.ts | 50 +++++++++++++ scripts/token-generator/core/compiler.ts | 75 +++++++++++++++++-- 2 files changed, 120 insertions(+), 5 deletions(-) diff --git a/scripts/token-generator/__tests__/compiler.test.ts b/scripts/token-generator/__tests__/compiler.test.ts index 4094276c..4805ff73 100644 --- a/scripts/token-generator/__tests__/compiler.test.ts +++ b/scripts/token-generator/__tests__/compiler.test.ts @@ -383,6 +383,56 @@ describe('token compiler', () => { expect(compiled.components.dark).toStrictEqual(compiled.components.light) }) + test('достраивает различающиеся component colorScheme из базовых токенов', () => { + const source: TokenTree = { + primitive: { fonts: {} }, + semantic: { + dimension: {}, + effects: {}, + colorScheme: { + light: { color: { base: '#ffffff', override: '#eeeeee' } }, + dark: { color: { base: '#111111', override: '#222222' } }, + }, + }, + components: { + missingScheme: { + root: { background: '{color.base}' }, + colorScheme: { dark: { root: { background: '{color.override}' } } }, + }, + partialScheme: { + root: { background: '{color.base}' }, + overlay: { background: '{color.base}' }, + colorScheme: { + light: { root: { background: '{color.override}' } }, + dark: { + root: { background: '{color.override}' }, + overlay: { background: '{color.override}' }, + }, + }, + }, + }, + } + + const compiled = compileTokens(source) + + expect(compiled.components.light.missingScheme).toHaveProperty( + 'colorScheme.root.background', + '#ffffff' + ) + expect(compiled.components.dark.missingScheme).toHaveProperty( + 'colorScheme.root.background', + '#222222' + ) + expect(compiled.components.light.partialScheme).toHaveProperty( + 'colorScheme.overlay.background', + '#ffffff' + ) + expect(compiled.components.dark.partialScheme).toHaveProperty( + 'colorScheme.overlay.background', + '#222222' + ) + }) + test('отклоняет источник без одной из цветовых схем semantic', () => { const source: TokenTree = { primitive: { fonts: {} }, diff --git a/scripts/token-generator/core/compiler.ts b/scripts/token-generator/core/compiler.ts index 4dfe18c0..387c8171 100644 --- a/scripts/token-generator/core/compiler.ts +++ b/scripts/token-generator/core/compiler.ts @@ -89,6 +89,51 @@ const selectColorScheme = ( ) } +const completeComponentColorScheme = ( + selectedScheme: TokenTree, + alternateScheme: TokenTree, + componentBase: TokenTree, + componentPath: string +): TokenTree => { + const result = mergeTrees({}, selectedScheme) + + for (const [key, alternateValue] of Object.entries(alternateScheme)) { + const selectedValue = result[key] + const baseValue = componentBase[key] + const tokenPath = `${componentPath}.${key}` + + if (selectedValue === undefined) { + if (baseValue === undefined) { + throw new Error(`Missing base token at "${tokenPath}"`) + } + + if (isTokenTree(alternateValue)) { + if (!isTokenTree(baseValue)) { + throw new Error(`Expected an object at "${tokenPath}"`) + } + + result[key] = completeComponentColorScheme( + {}, + alternateValue, + baseValue, + tokenPath + ) + } else { + result[key] = baseValue + } + } else if (isTokenTree(selectedValue) && isTokenTree(alternateValue)) { + result[key] = completeComponentColorScheme( + selectedValue, + alternateValue, + isTokenTree(baseValue) ? baseValue : {}, + tokenPath + ) + } + } + + return result +} + const selectComponentColorSchemes = ( components: TokenTree, scheme: ColorScheme @@ -105,22 +150,42 @@ const selectComponentColorSchemes = ( return [componentName, mergeTrees({}, base)] } - const selectedScheme = isTokenTree(colorScheme) - ? colorScheme[scheme] - : undefined + if (!isTokenTree(colorScheme)) { + throw new Error( + `Expected an object at "components.${componentName}.colorScheme"` + ) + } + + const alternateSchemeName: ColorScheme = + scheme === 'light' ? 'dark' : 'light' + const selectedScheme = colorScheme[scheme] + const alternateScheme = colorScheme[alternateSchemeName] - if (!isTokenTree(selectedScheme)) { + if (selectedScheme !== undefined && !isTokenTree(selectedScheme)) { throw new Error( `Expected an object at "components.${componentName}.colorScheme.${scheme}"` ) } + if (alternateScheme !== undefined && !isTokenTree(alternateScheme)) { + throw new Error( + `Expected an object at "components.${componentName}.colorScheme.${alternateSchemeName}"` + ) + } + + const completedScheme = completeComponentColorScheme( + selectedScheme ?? {}, + alternateScheme ?? {}, + base, + `components.${componentName}` + ) + return [ componentName, Object.fromEntries( Object.entries(component).map(([key, value]) => [ key, - key === 'colorScheme' ? mergeTrees({}, selectedScheme) : value, + key === 'colorScheme' ? completedScheme : value, ]) ), ] From 18a83f98a616e1644b95459b23d48ff11f87fadf Mon Sep 17 00:00:00 2001 From: Alesya Volosach Date: Wed, 2 Sep 2026 13:14:55 +0300 Subject: [PATCH 6/7] =?UTF-8?q?chore(tokens):=20=D0=BE=D0=B1=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=B4=D0=B8=D0=B7=D0=B0=D0=B9?= =?UTF-8?q?=D0=BD-=D1=82=D0=BE=D0=BA=D0=B5=D0=BD=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design-tokens/input/tokens.json | 262 +++++++------- .../__tests__/compiler.test.ts | 2 +- .../Input/InputOtp/InputOtpItem.tsx | 5 +- src/theme/tokens/components/dark.json | 330 +++++++++--------- src/theme/tokens/components/light.json | 278 ++++++++------- .../tokens/semantic/colorScheme/dark.json | 7 +- .../tokens/semantic/colorScheme/light.json | 7 +- src/theme/tokens/semantic/dimensions.json | 2 + 8 files changed, 480 insertions(+), 413 deletions(-) diff --git a/design-tokens/input/tokens.json b/design-tokens/input/tokens.json index 0b157a6d..e05daec3 100644 --- a/design-tokens/input/tokens.json +++ b/design-tokens/input/tokens.json @@ -676,7 +676,7 @@ "default": "{colors.alpha.white.1000}", "hover": "{colors.solid.zinc.50}" }, - "backdrop": "{colors.alpha.black.400}" + "backdrop": "{colors.alpha.black.300}" }, "neutral": { "weak": { @@ -700,7 +700,7 @@ } }, "border": { - "focus": "{colors.solid.green.200}", + "focus": "{colors.alpha.green.200}", "neutral": { "default": "{colors.solid.zinc.200}", "strong": "{colors.solid.zinc.300}", @@ -719,7 +719,8 @@ "danger": { "subtle": "{colors.solid.red.400}", "default": "{colors.solid.red.500}", - "strong": "{colors.solid.red.600}" + "strong": "{colors.solid.red.600}", + "focus": "{colors.alpha.red.200}" }, "info": { "default": "{colors.solid.blue.500}", @@ -937,7 +938,7 @@ "default": "{colors.solid.zinc.800}", "hover": "{colors.solid.zinc.700}" }, - "backdrop": "{colors.alpha.black.600}" + "backdrop": "{colors.alpha.black.300}" }, "neutral": { "weak": { @@ -961,7 +962,7 @@ } }, "border": { - "focus": "{colors.solid.green.200}", + "focus": "{colors.alpha.green.200}", "neutral": { "default": "{colors.solid.zinc.800}", "strong": "{colors.solid.zinc.700}", @@ -980,7 +981,8 @@ "danger": { "subtle": "{colors.solid.red.600}", "default": "{colors.solid.red.500}", - "strong": "{colors.solid.red.400}" + "strong": "{colors.solid.red.400}", + "focus": "{colors.alpha.red.200}" }, "info": { "default": "{colors.solid.blue.500}", @@ -1007,6 +1009,7 @@ "150": "{sizing.3x}", "200": "{sizing.4x}", "300": "{sizing.5x}", + "350": "{sizing.6x}", "400": "{sizing.7x}", "500": "{sizing.9x}", "600": "{sizing.10x}", @@ -1053,6 +1056,7 @@ "100": "{sizing.2x}", "200": "{sizing.4x}", "300": "{sizing.5x}", + "350": "{sizing.6x}", "400": "{sizing.7x}", "500": "{sizing.10x}", "max": "{sizing.max}" @@ -1220,6 +1224,7 @@ }, "autocomplete": { "extend": { + "checkmarkSize": "{dimension.size.600}", "extOption": { "gap": "{dimension.space.200}" }, "extOptionGroup": { "gap": "{dimension.space.200}" } }, @@ -1229,28 +1234,40 @@ "focusBackground": "{color.bg.neutral.medium.default}", "focusColor": "{color.fg.default}" }, + "extend": { + "option": { "background": "{color.bg.surface.default.default}" } + }, "dropdown": { "background": "{color.bg.surface.default.default}", "hoverBackground": "{color.bg.surface.default.default}", "activeBackground": "{color.bg.surface.default.default}", - "color": "{color.fg.active}", - "hoverColor": "{color.fg.active}", - "activeColor": "{color.fg.active}" + "color": "{color.fg.muted}", + "hoverColor": "{color.fg.muted}", + "activeColor": "{color.fg.muted}" } }, "dark": { "chip": { - "focusBackground": "{color.bg.neutral.strong.hover}", - "focusColor": "{color.fg.inverse.focus}" + "focusBackground": "{color.bg.neutral.medium.default}", + "focusColor": "{color.fg.default}" }, "dropdown": { "activeBackground": "{color.bg.surface.default.default}", - "activeColor": "{color.fg.inverse.active}", + "activeColor": "{color.fg.inverse.muted}", "background": "{color.bg.surface.default.default}", - "color": "{color.fg.inverse.active}", + "color": "{color.fg.inverse.muted}", "hoverBackground": "{color.bg.surface.default.default}", - "hoverColor": "{color.fg.inverse.active}" - } + "hoverColor": "{color.fg.inverse.muted}" + }, + "extend": { + "option": { "background": "{color.bg.surface.canvas.default}" } + }, + "option": { "focusBackground": "{color.bg.surface.canvas.hover}" }, + "optionGroup": { + "background": "{color.bg.surface.canvas.default}", + "color": "{color.fg.inverse.muted}" + }, + "overlay": { "background": "{color.bg.surface.canvas.default}" } } }, "root": { @@ -1262,7 +1279,7 @@ "borderColor": "{color.border.neutral.strong}", "hoverBorderColor": "{color.border.brand.strong}", "focusBorderColor": "{color.border.brand.strong}", - "invalidBorderColor": "{color.border.status.danger.subtle}", + "invalidBorderColor": "{color.border.status.danger.strong}", "color": "{color.fg.active}", "disabledColor": "{color.fg.muted}", "placeholderColor": "{color.fg.muted}", @@ -1283,9 +1300,9 @@ "overlay": { "background": "{color.bg.surface.default.default}", "borderColor": "{color.border.neutral.default}", - "borderRadius": "{dimension.borderRadius.400}", + "borderRadius": "{dimension.borderRadius.350}", "color": "{color.fg.default}", - "shadow": "{effects.elevation.200}" + "shadow": "{effects.elevation.400}" }, "list": { "padding": "{dimension.space.100}", @@ -1297,9 +1314,9 @@ "selectedFocusBackground": "{color.bg.neutral.strong.active}", "color": "{color.fg.default}", "focusColor": "{color.fg.default}", - "selectedColor": "{color.fg.on.fill.default}", + "selectedColor": "{color.fg.inverse.default}", "selectedFocusColor": "{color.fg.on.fill.active}", - "padding": "{dimension.space.200} {dimension.space.400}", + "padding": "{dimension.space.200} {dimension.space.350}", "borderRadius": "{dimension.borderRadius.200}" }, "optionGroup": { @@ -1331,7 +1348,7 @@ }, "avatar": { "extend": { - "borderColor": "{color.border.neutral.strong}", + "borderColor": "{color.border.neutral.inverse}", "circle": { "borderRadius": "{dimension.borderRadius.max}" } }, "root": { @@ -1529,20 +1546,21 @@ }, "extSm": { "borderRadius": "{dimension.borderRadius.400}", - "gap": "{dimension.space.200}" + "gap": "{dimension.space.200}", + "height": "{dimension.size.800}" }, "extLg": { "borderRadius": "{dimension.borderRadius.400}", "gap": "{dimension.space.400}", - "height": "{dimension.size.1300}" + "height": "{dimension.size.1200}" }, "extXlg": { "borderRadius": "{dimension.borderRadius.400}", "gap": "{dimension.space.400}", - "iconOnlyWidth": "{dimension.size.1400}", + "iconOnlyWidth": "{dimension.size.1300}", "paddingX": "{dimension.space.700}", "paddingY": "{dimension.space.600}", - "height": "{dimension.size.1400}" + "height": "{dimension.size.1300}" }, "borderWidth": "{dimension.borderWidth.100}", "iconSize": { @@ -1679,7 +1697,7 @@ "primary": { "hoverBackground": "{color.bg.brand.weak.hover}", "activeBackground": "{color.bg.brand.weak.active}", - "borderColor": "{color.border.focus}", + "borderColor": "{color.border.brand.subtle}", "color": "{color.fg.brand.default}" }, "success": { @@ -1799,7 +1817,7 @@ }, "danger": { "activeBackground": "{color.bg.status.danger.weak.active}", - "borderColor": "{color.border.status.danger.subtle}", + "borderColor": "{color.border.status.danger.strong}", "color": "{color.fg.status.danger.hover}", "hoverBackground": "{color.bg.status.danger.weak.hover}" }, @@ -1823,7 +1841,7 @@ }, "primary": { "activeBackground": "{color.bg.brand.weak.active}", - "borderColor": "{color.border.focus}", + "borderColor": "{color.border.brand.strong}", "color": "{color.fg.brand.muted}", "hoverBackground": "{color.bg.brand.weak.hover}" }, @@ -2023,7 +2041,8 @@ "gap": "{dimension.space.200}", "paddingX": "{dimension.space.400}", "paddingY": "{dimension.space.200}", - "iconOnlyWidth": "{dimension.size.1100}", + "height": "{dimension.size.1000}", + "iconOnlyWidth": "{dimension.size.1000}", "raisedShadow": "none", "badgeSize": "{dimension.size.450}", "transitionDuration": "{effects.transition.duration.200}", @@ -2034,13 +2053,13 @@ }, "sm": { "fontSize": "{fonts.fontSize.200}", - "iconOnlyWidth": "{dimension.size.900}", + "iconOnlyWidth": "{dimension.size.800}", "paddingX": "{dimension.space.400}", "paddingY": "{dimension.space.200}" }, "lg": { "fontSize": "{fonts.fontSize.500}", - "iconOnlyWidth": "{dimension.size.1300}", + "iconOnlyWidth": "{dimension.size.1200}", "paddingX": "{dimension.space.700}", "paddingY": "{dimension.space.400}" }, @@ -2067,7 +2086,8 @@ "fontSize": "{fonts.fontSize.400}", "fontWeight": "{fonts.fontWeight.demibold}" }, - "subtitle": { "color": "{color.fg.muted}" } + "subtitle": { "color": "{color.fg.muted}" }, + "overlay": { "shadow": "{effects.elevation.300}" } }, "carousel": { "colorScheme": { @@ -2080,8 +2100,8 @@ }, "dark": { "indicator": { - "activeBackground": "{color.bg.surface.canvas.default}", - "background": "{color.bg.neutral.strong.active}", + "activeBackground": "{color.bg.neutral.strong.default}", + "background": "{color.bg.neutral.weak.active}", "hoverBackground": "{color.bg.neutral.medium.active}" } } @@ -2123,7 +2143,7 @@ "checkedHoverBorderColor": "{color.border.neutral.bold.hover}", "checkedFocusBorderColor": "{color.border.brand.default}", "checkedDisabledBorderColor": "{color.border.neutral.strong}", - "invalidBorderColor": "{color.border.status.danger.subtle}", + "invalidBorderColor": "{color.border.status.danger.strong}", "shadow": "{effects.elevation.none}", "focusRing": { "focusRing": "{dimension.focusRing.width}", @@ -2146,7 +2166,7 @@ "icon": { "size": "{dimension.size.450}", "color": "{color.fg.active}", - "checkedColor": "{color.fg.on.fill.default}", + "checkedColor": "{color.fg.inverse.default}", "checkedHoverColor": "{color.fg.on.fill.hover}", "disabledColor": "{color.fg.muted}", "sm": { "size": "{dimension.size.350}" }, @@ -2682,7 +2702,7 @@ "background": "{color.bg.surface.raised.default}", "borderColor": "{color.border.neutral.default}", "color": "{color.fg.default}", - "borderRadius": "{dimension.borderRadius.max}", + "borderRadius": "{dimension.borderRadius.500}", "shadow": "{effects.elevation.400}" }, "header": { @@ -2697,6 +2717,11 @@ "footer": { "padding": "{dimension.space.none} {dimension.space.700} {dimension.space.700} {dimension.space.700}", "gap": "{dimension.space.200}" + }, + "colorScheme": { + "dark": { + "root": { "background": "{color.bg.surface.canvas.default}" } + } } }, "divider": { @@ -2716,7 +2741,7 @@ }, "content": { "background": "{color.bg.surface.default.default}", - "color": "{color.fg.default}" + "color": "{color.fg.muted}" }, "root": { "borderColor": "{color.border.neutral.default}" } }, @@ -2727,7 +2752,7 @@ "width": "{dimension.size.2000}", "extHeader": { "gap": "{dimension.space.200}", - "borderColor": "{color.border.neutral.default}" + "borderColor": "{color.border.neutral.strong}" }, "margin": "{dimension.space.200}", "scale": "{dimension.size.50}", @@ -3033,7 +3058,7 @@ "transitionDuration": "{effects.transition.duration.200}" }, "button": { - "width": "{dimension.size.1700}", + "width": "{dimension.size.1100}", "borderRadius": "{dimension.borderRadius.400}", "verticalPadding": "{dimension.space.400}" }, @@ -3046,9 +3071,11 @@ }, "root": { "gap": "{dimension.space.200}" }, "input": { - "width": "{dimension.size.1800}", - "lg": { "width": "{dimension.size.1900}" }, - "sm": { "width": "{dimension.size.1700}" } + "width": "{dimension.size.1100}", + "paddingTop": "{dimension.space.350}", + "paddingBottom": "{dimension.space.200}", + "lg": { "width": "{dimension.size.1200}" }, + "sm": { "width": "{dimension.size.800}" } }, "sm": { "width": "{dimension.size.none}" }, "lg": { "width": "{dimension.size.none}" } @@ -3060,9 +3087,10 @@ "borderWidth": "{dimension.borderWidth.100}", "extXlg": { "fontSize": "{fonts.fontSize.300}", - "paddingX": "{dimension.space.400}", - "paddingY": "{dimension.space.400}" - } + "paddingX": "{dimension.space.300}", + "paddingY": "{dimension.space.600}" + }, + "invalidFocusRing": { "color": "{color.border.status.danger.focus}" } }, "root": { "background": "{color.bg.surface.default.default}", @@ -3073,24 +3101,24 @@ "borderColor": "{color.border.neutral.strong}", "hoverBorderColor": "{color.border.brand.strong}", "focusBorderColor": "{color.border.brand.strong}", - "invalidBorderColor": "{color.border.status.danger.subtle}", + "invalidBorderColor": "{color.border.status.danger.strong}", "color": "{color.fg.default}", "disabledColor": "{color.fg.muted}", "placeholderColor": "{color.fg.muted}", "invalidPlaceholderColor": "{color.fg.status.danger.hover}", "shadow": "{effects.elevation.none}", - "paddingX": "{dimension.space.400}", - "paddingY": "{dimension.space.400}", + "paddingX": "{dimension.space.300}", + "paddingY": "{dimension.space.300}", "borderRadius": "{dimension.borderRadius.400}", "transitionDuration": "{effects.transition.duration.200}", "sm": { "fontSize": "{fonts.fontSize.300}", - "paddingX": "{dimension.space.400}", - "paddingY": "{dimension.space.400}" + "paddingX": "{dimension.space.300}", + "paddingY": "{dimension.space.200}" }, "lg": { "fontSize": "{fonts.fontSize.300}", - "paddingX": "{dimension.space.400}", + "paddingX": "{dimension.space.300}", "paddingY": "{dimension.space.400}" }, "focusRing": { @@ -3129,7 +3157,7 @@ "background": "{color.bg.surface.default.default}", "disabledBackground": "{color.bg.surface.canvas.hover}", "borderColor": "{color.border.neutral.strong}", - "invalidBorderColor": "{color.border.status.danger.subtle}", + "invalidBorderColor": "{color.border.status.danger.strong}", "color": "{color.fg.active}", "disabledColor": "{color.fg.muted}", "shadow": "{effects.elevation.none}", @@ -3157,7 +3185,7 @@ "optionGroup": { "background": "{color.bg.surface.raised.default}", "color": "{color.fg.muted}", - "fontWeight": "{fonts.fontWeight.demibold}", + "fontWeight": "{fonts.fontWeight.regular}", "padding": "{dimension.space.200} {dimension.space.400}" }, "checkmark": { @@ -3234,7 +3262,7 @@ "padding": "{dimension.space.200} {dimension.space.400}", "background": "transparent", "color": "{color.fg.muted}", - "fontWeight": "{fonts.fontWeight.demibold}" + "fontWeight": "{fonts.fontWeight.regular}" }, "submenuIcon": { "size": "{dimension.size.600}", @@ -3287,7 +3315,7 @@ }, "submenuLabel": { "padding": "{dimension.space.200} {dimension.space.400}", - "fontWeight": "{fonts.fontWeight.demibold}", + "fontWeight": "{fonts.fontWeight.regular}", "background": "transparent", "color": "{color.fg.muted}" }, @@ -3298,10 +3326,10 @@ "gap": "{dimension.space.200}", "color": "{color.fg.default}", "focusBackground": "{color.bg.surface.canvas.default}", - "focusColor": "{color.fg.hover}", + "focusColor": "{color.fg.default}", "icon": { "color": "{color.fg.default}", - "focusColor": "{color.fg.hover}" + "focusColor": "{color.fg.default}" } } }, @@ -3316,7 +3344,7 @@ }, "extSubmenuLabel": { "padding": "{dimension.space.200} {dimension.space.400}", - "fontWeight": "{fonts.fontWeight.demibold}", + "fontWeight": "{fonts.fontWeight.regular}", "background": "transparent", "color": "{color.fg.muted}" } @@ -3776,7 +3804,7 @@ "filledHoverBackground": "{color.bg.surface.default.default}", "focusBorderColor": "{color.border.brand.strong}", "hoverBorderColor": "{color.border.brand.strong}", - "invalidBorderColor": "{color.border.status.danger.subtle}", + "invalidBorderColor": "{color.border.status.danger.strong}", "invalidPlaceholderColor": "{color.fg.status.danger.hover}", "placeholderColor": "{color.fg.muted}" }, @@ -4017,7 +4045,7 @@ "checkedHoverBorderColor": "{color.border.neutral.bold.default}", "checkedFocusBorderColor": "{color.border.neutral.bold.default}", "checkedDisabledBorderColor": "{color.border.neutral.strong}", - "invalidBorderColor": "{color.border.status.danger.subtle}", + "invalidBorderColor": "{color.border.status.danger.strong}", "shadow": "{effects.elevation.none}", "transitionDuration": "{effects.transition.duration.200}", "focusRing": { @@ -4134,7 +4162,7 @@ "borderColor": "{color.border.neutral.strong}", "hoverBorderColor": "{color.border.brand.strong}", "focusBorderColor": "{color.border.brand.strong}", - "invalidBorderColor": "{color.border.status.danger.subtle}", + "invalidBorderColor": "{color.border.status.danger.strong}", "color": "{color.fg.default}", "disabledColor": "{color.fg.muted}", "placeholderColor": "{color.fg.muted}", @@ -4226,7 +4254,7 @@ "colorScheme": { "light": { "root": { - "invalidBorderColor": "{color.border.status.danger.subtle}" + "invalidBorderColor": "{color.border.status.danger.strong}" }, "extend": { "background": "{color.bg.neutral.medium.default}" } }, @@ -4237,7 +4265,7 @@ "extend": { "background": "{color.bg.neutral.strong.hover}" } } }, - "root": { "borderRadius": "{dimension.borderRadius.400}" } + "root": { "borderRadius": "{dimension.borderRadius.350}" } }, "skeleton": { "extend": { @@ -4276,7 +4304,7 @@ "root": { "transitionDuration": "{effects.transition.duration.200}" }, "track": { "background": "{color.bg.neutral.medium.default}", - "borderRadius": "{dimension.borderRadius.400}", + "borderRadius": "{dimension.borderRadius.350}", "size": "{dimension.size.100}" }, "range": { "background": "{color.bg.neutral.strong.default}" }, @@ -4299,6 +4327,9 @@ "width": "{dimension.size.350}", "height": "{dimension.size.350}", "shadow": "none" + }, + "extend": { + "hoverRing": "0 0 0 {dimension.focusRing.width} rgba(206, 207, 210, 0.5000)" } } }, @@ -4336,7 +4367,7 @@ "extStepNumber": { "invalidBackground": "{color.bg.status.danger.strong.hover}", "invalidColor": "{color.fg.on.status.danger.default}", - "invalidBorderColor": "{color.border.status.danger.subtle}", + "invalidBorderColor": "{color.border.status.danger.strong}", "borderWidth": "{dimension.borderWidth.100}", "iconSize": "{dimension.size.700}" } @@ -4378,7 +4409,7 @@ "activeColor": "{color.fg.default}", "size": "{dimension.size.700}", "fontSize": "{fonts.fontSize.300}", - "fontWeight": "{fonts.fontWeight.bold}", + "fontWeight": "{fonts.fontWeight.regular}", "borderRadius": "{dimension.borderRadius.max}", "shadow": "none" }, @@ -4444,7 +4475,7 @@ }, "root": { "transitionDuration": "{effects.transition.duration.200}" }, "tablist": { - "borderWidth": "{dimension.borderWidth.none} {dimension.borderWidth.none} {dimension.borderWidth.100} {dimension.borderWidth.none}", + "borderWidth": "{dimension.borderWidth.none} {dimension.borderWidth.none} {dimension.borderWidth.200} {dimension.borderWidth.none}", "background": "transparent", "borderColor": "{color.border.neutral.default}" }, @@ -4819,7 +4850,12 @@ "readonlyBackground": "{color.bg.neutral.weak.default}", "borderWidth": "{dimension.borderWidth.100}", "iconSize": "{dimension.size.450}", - "minHeight": "{dimension.size.1500}" + "minHeight": "{dimension.size.1500}", + "extXlg": { + "fontSize": "{fonts.fontSize.300}", + "paddingX": "{dimension.space.300}", + "paddingY": "{dimension.space.500}" + } }, "root": { "background": "{color.bg.surface.default.default}", @@ -4830,14 +4866,14 @@ "borderColor": "{color.border.neutral.strong}", "hoverBorderColor": "{color.border.brand.strong}", "focusBorderColor": "{color.border.brand.strong}", - "invalidBorderColor": "{color.border.status.danger.subtle}", + "invalidBorderColor": "{color.border.status.danger.strong}", "color": "{color.fg.active}", "disabledColor": "{color.fg.muted}", "placeholderColor": "{color.fg.muted}", "invalidPlaceholderColor": "{color.fg.status.danger.hover}", "shadow": "{effects.elevation.none}", - "paddingX": "{dimension.space.400}", - "paddingY": "{dimension.space.400}", + "paddingX": "{dimension.space.300}", + "paddingY": "{dimension.space.300}", "borderRadius": "{dimension.borderRadius.400}", "transitionDuration": "{effects.transition.duration.200}", "focusRing": { @@ -4849,24 +4885,14 @@ }, "lg": { "fontSize": "{fonts.fontSize.300}", - "paddingX": "{dimension.space.400}", + "paddingX": "{dimension.space.300}", "paddingY": "{dimension.space.400}" }, "sm": { "fontSize": "{fonts.fontSize.300}", - "paddingX": "{dimension.space.400}", - "paddingY": "{dimension.space.400}" + "paddingX": "{dimension.space.300}", + "paddingY": "{dimension.space.200}" } - }, - "sm": { - "fontSize": "{fonts.fontSize.300}", - "paddingX": "{dimension.space.200}", - "paddingY": "{dimension.space.200}" - }, - "lg": { - "fontSize": "{fonts.fontSize.300}", - "paddingX": "{dimension.space.400}", - "paddingY": "{dimension.space.400}" } }, "tieredmenu": { @@ -4913,9 +4939,9 @@ "submenu": { "mobileIndent": "{dimension.space.400}" }, "separator": { "borderColor": "{color.border.neutral.default}" }, "submenuIcon": { - "activeColor": "{color.fg.hover}", + "activeColor": "{color.fg.inverse.active}", "color": "{color.fg.default}", - "focusColor": "{color.fg.hover}", + "focusColor": "{color.fg.default}", "size": "{dimension.size.400}" } }, @@ -4960,11 +4986,11 @@ }, "iconOnlyWidth": "{dimension.size.1100}", "hoverBorderColor": "{color.border.neutral.strong}", - "checkedHoverColor": "{color.fg.on.fill.hover}", + "checkedHoverColor": "{color.fg.inverse.default}", "checkedHoverBackground": "{color.bg.neutral.strong.hover}", "checkedHoverBorderColor": "{color.border.neutral.bold.hover}", "extXlg": { - "padding": "{dimension.space.600} {dimension.space.600}", + "padding": "{dimension.space.600} {dimension.space.700}", "iconOnlyWidth": "{dimension.size.1400}" }, "extSm": { "iconOnlyWidth": "{dimension.size.900}" }, @@ -4984,7 +5010,7 @@ "disabledBackground": "{color.bg.surface.canvas.hover}", "disabledBorderColor": "{color.border.neutral.default}", "disabledColor": "{color.fg.muted}", - "invalidBorderColor": "{color.border.status.danger.subtle}" + "invalidBorderColor": "{color.border.status.danger.strong}" }, "icon": { "color": "{color.fg.default}", @@ -4996,23 +5022,23 @@ }, "dark": { "root": { - "hoverBackground": "{color.bg.neutral.strong.active}", - "background": "{color.bg.neutral.strong.hover}", - "borderColor": "{color.border.neutral.bold.hover}", - "color": "{color.fg.inverse.default}", - "hoverColor": "{color.fg.on.brand.hover}", - "checkedBackground": "{color.bg.surface.canvas.default}", - "checkedColor": "{color.fg.focus}", - "checkedBorderColor": "{color.border.neutral.inverse}", - "disabledBackground": "{color.bg.neutral.strong.hover}", - "disabledBorderColor": "{color.border.neutral.bold.hover}", + "hoverBackground": "{color.bg.surface.canvas.active}", + "background": "{color.bg.surface.canvas.hover}", + "borderColor": "{color.border.neutral.default}", + "color": "{color.fg.default}", + "hoverColor": "{color.fg.default}", + "checkedBackground": "{color.bg.neutral.strong.default}", + "checkedColor": "{color.fg.inverse.default}", + "checkedBorderColor": "{color.border.neutral.bold.default}", + "disabledBackground": "{color.bg.surface.canvas.hover}", + "disabledBorderColor": "{color.border.neutral.default}", "disabledColor": "{color.fg.inverse.muted}", "invalidBorderColor": "{color.border.status.danger.strong}" }, "icon": { - "color": "{color.fg.inverse.default}", - "hoverColor": "{color.fg.on.brand.hover}", - "checkedColor": "{color.fg.focus}", + "color": "{color.fg.default}", + "hoverColor": "{color.fg.default}", + "checkedColor": "{color.fg.inverse.default}", "disabledColor": "{color.fg.inverse.muted}" }, "content": { "checkedBackground": "transparent" } @@ -5049,7 +5075,7 @@ "disabledBorderColor": "{color.border.neutral.default}", "disabledColor": "{color.fg.muted}", "hoverColor": "{color.fg.hover}", - "invalidBorderColor": "{color.border.status.danger.subtle}" + "invalidBorderColor": "{color.border.status.danger.strong}" }, "content": { "checkedShadow": "none", @@ -5077,11 +5103,11 @@ "checkedHoverBackground": "{color.bg.neutral.strong.hover}" }, "handle": { - "background": "{color.bg.surface.raised.default}", - "hoverBackground": "{color.bg.surface.raised.default}", + "background": "{color.fg.on.fill.default}", + "hoverBackground": "{color.fg.on.fill.default}", "disabledBackground": "{color.bg.neutral.medium.strong}", - "checkedBackground": "{color.bg.surface.raised.default}", - "checkedHoverBackground": "{color.bg.surface.raised.default}", + "checkedBackground": "{color.fg.inverse.active}", + "checkedHoverBackground": "{color.fg.inverse.active}", "color": "{color.fg.default}", "hoverColor": "{color.fg.default}", "checkedColor": "{color.fg.default}", @@ -5090,22 +5116,22 @@ }, "dark": { "handle": { - "background": "{color.bg.surface.raised.default}", - "checkedBackground": "{color.bg.surface.raised.default}", + "background": "{color.fg.on.fill.default}", + "checkedBackground": "{color.fg.inverse.active}", "checkedColor": "{color.fg.inverse.focus}", - "checkedHoverBackground": "{color.bg.surface.raised.default}", + "checkedHoverBackground": "{color.fg.inverse.active}", "checkedHoverColor": "{color.fg.on.brand.hover}", "color": "{color.fg.inverse.default}", - "disabledBackground": "{color.bg.surface.raised.active}", - "hoverBackground": "{color.bg.surface.raised.default}", + "disabledBackground": "{color.bg.neutral.medium.strong}", + "hoverBackground": "{color.fg.on.fill.default}", "hoverColor": "{color.fg.on.brand.hover}" }, "root": { "background": "{color.bg.neutral.medium.active}", - "checkedBackground": "{color.bg.surface.canvas.default}", - "checkedHoverBackground": "{color.bg.surface.canvas.hover}", - "disabledBackground": "{color.bg.neutral.strong.hover}", - "hoverBackground": "{color.bg.surface.raised.active}" + "checkedBackground": "{color.bg.neutral.strong.default}", + "checkedHoverBackground": "{color.bg.neutral.strong.hover}", + "disabledBackground": "{color.bg.surface.canvas.hover}", + "hoverBackground": "{color.bg.neutral.medium.strong}" } } }, @@ -5127,7 +5153,7 @@ "hoverBorderColor": "transparent", "checkedBorderColor": "transparent", "checkedHoverBorderColor": "transparent", - "invalidBorderColor": "{color.border.status.danger.subtle}", + "invalidBorderColor": "{color.border.status.danger.strong}", "transitionDuration": "{effects.transition.duration.200}", "slideDuration": "{effects.transition.duration.200}" }, diff --git a/scripts/token-generator/__tests__/compiler.test.ts b/scripts/token-generator/__tests__/compiler.test.ts index 4805ff73..dce26924 100644 --- a/scripts/token-generator/__tests__/compiler.test.ts +++ b/scripts/token-generator/__tests__/compiler.test.ts @@ -582,7 +582,7 @@ describe('production input tokens', () => { { width: 4, style: 'none', - color: '#d4fedc', + color: 'rgba(68, 232, 88, 0.2)', offset: 0, shadow: 'inset 0 0 0 4px #d4fedc', } diff --git a/src/components/Input/InputOtp/InputOtpItem.tsx b/src/components/Input/InputOtp/InputOtpItem.tsx index 2ec53c8e..3694ed63 100644 --- a/src/components/Input/InputOtp/InputOtpItem.tsx +++ b/src/components/Input/InputOtp/InputOtpItem.tsx @@ -113,7 +113,8 @@ const styles = StyleSheet.create(({ components, semantic, fonts }) => ({ width: components.inputotp.input.width, height: components.inputotp.extend.height, paddingHorizontal: components.inputtext.root.paddingX, - paddingVertical: components.inputtext.root.paddingY, + paddingTop: components.inputotp.input.paddingTop, + paddingBottom: components.inputotp.input.paddingBottom, borderWidth: components.inputotp.extend.borderWidth, borderRadius: components.inputtext.root.borderRadius, borderColor: components.inputtext.root.borderColor, @@ -129,7 +130,7 @@ const styles = StyleSheet.create(({ components, semantic, fonts }) => ({ text: { fontSize: fonts.fontSize[200], lineHeight: fonts.fontSize[200], - fontFamily: fonts.fontFamily.base, + fontFamily: fonts.fontFamily.heading, fontWeight: fonts.fontWeight.regular, letterSpacing: fonts.letterSpacing[500], color: components.inputtext.root.color, diff --git a/src/theme/tokens/components/dark.json b/src/theme/tokens/components/dark.json index 0412aaae..4ae0db90 100644 --- a/src/theme/tokens/components/dark.json +++ b/src/theme/tokens/components/dark.json @@ -25,7 +25,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "inset 0 0 0 4px #d4fedc" }, @@ -61,17 +61,25 @@ } }, "autocomplete": { - "extend": { "extOption": { "gap": 8 }, "extOptionGroup": { "gap": 8 } }, + "extend": { + "checkmarkSize": 20, + "extOption": { "gap": 8 }, + "extOptionGroup": { "gap": 8 } + }, "colorScheme": { - "chip": { "focusBackground": "#e2e2e4", "focusColor": "#2b2e33" }, + "chip": { "focusBackground": "#404348", "focusColor": "#ffffff" }, "dropdown": { "activeBackground": "#404348", - "activeColor": "#181a1f", + "activeColor": "#85888e", "background": "#404348", - "color": "#181a1f", + "color": "#85888e", "hoverBackground": "#404348", - "hoverColor": "#181a1f" - } + "hoverColor": "#85888e" + }, + "extend": { "option": { "background": "#2b2e33" } }, + "option": { "focusBackground": "#404348" }, + "optionGroup": { "background": "#2b2e33", "color": "#85888e" }, + "overlay": { "background": "#2b2e33" } }, "root": { "background": "#404348", @@ -82,7 +90,7 @@ "borderColor": "#56595f", "hoverBorderColor": "#77f48a", "focusBorderColor": "#77f48a", - "invalidBorderColor": "#db3424", + "invalidBorderColor": "#f47f77", "color": "#f0f0f1", "disabledColor": "#a2a5a9", "placeholderColor": "#a2a5a9", @@ -95,7 +103,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "none" } @@ -103,9 +111,9 @@ "overlay": { "background": "#404348", "borderColor": "#404348", - "borderRadius": 14, + "borderRadius": 12, "color": "#ffffff", - "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" + "shadow": "0 4px 8px rgba(0, 0, 0, 0.2000)" }, "list": { "padding": 4, "gap": 4 }, "option": { @@ -114,12 +122,12 @@ "selectedFocusBackground": "#cecfd2", "color": "#ffffff", "focusColor": "#ffffff", - "selectedColor": "#ffffff", + "selectedColor": "#2b2e33", "selectedFocusColor": "#ffffff", "paddingTop": 8, - "paddingRight": 14, + "paddingRight": 12, "paddingBottom": 8, - "paddingLeft": 14, + "paddingLeft": 12, "borderRadius": 8 }, "optionGroup": { @@ -140,7 +148,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "none" }, @@ -156,7 +164,7 @@ } }, "avatar": { - "extend": { "borderColor": "#56595f", "circle": { "borderRadius": 1600 } }, + "extend": { "borderColor": "#2b2e33", "circle": { "borderRadius": 1600 } }, "root": { "width": 32, "height": 32, @@ -231,7 +239,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" }, @@ -242,7 +250,7 @@ "gap": 8, "icon": { "color": "#ffffff", "hoverColor": "#cecfd2" }, "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "none", "style": "none", @@ -279,15 +287,15 @@ "lg": { "iconOnlyWidth": 28 }, "xlg": { "iconOnlyWidth": 32 } }, - "extSm": { "borderRadius": 14, "gap": 8 }, - "extLg": { "borderRadius": 14, "gap": 14, "height": 56 }, + "extSm": { "borderRadius": 14, "gap": 8, "height": 28 }, + "extLg": { "borderRadius": 14, "gap": 14, "height": 48 }, "extXlg": { "borderRadius": 14, "gap": 14, - "iconOnlyWidth": 64, + "iconOnlyWidth": 56, "paddingX": 24, "paddingY": 20, - "height": 64 + "height": 56 }, "borderWidth": 1, "iconSize": { "sm": 16, "md": 20, "lg": 24 } @@ -307,7 +315,7 @@ }, "danger": { "activeBackground": "#611912", - "borderColor": "#db3424", + "borderColor": "#f47f77", "color": "#fbacaa", "hoverBackground": "#45120e" }, @@ -331,7 +339,7 @@ }, "primary": { "activeBackground": "#0e4514", - "borderColor": "#d4fedc", + "borderColor": "#77f48a", "color": "#44e858", "hoverBackground": "#0c3b11" }, @@ -363,7 +371,7 @@ "borderColor": "transparent", "color": "#2b2e33", "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" }, "hoverBackground": "#cecfd2", @@ -377,7 +385,7 @@ "background": "#8d2218", "borderColor": "transparent", "color": "#fff0f0", - "focusRing": { "color": "#d4fedc", "shadow": "none" }, + "focusRing": { "color": "rgba(68, 232, 88, 0.2)", "shadow": "none" }, "hoverBackground": "#db3424", "hoverBorderColor": "transparent", "hoverColor": "#fffafa" @@ -389,7 +397,7 @@ "background": "#7e22ce", "borderColor": "transparent", "color": "#f3e8ff", - "focusRing": { "color": "#d4fedc", "shadow": "none" }, + "focusRing": { "color": "rgba(68, 232, 88, 0.2)", "shadow": "none" }, "hoverBackground": "#9333ea", "hoverBorderColor": "transparent", "hoverColor": "#faf5ff" @@ -401,7 +409,7 @@ "background": "#18538d", "borderColor": "transparent", "color": "#f0f9ff", - "focusRing": { "color": "#d4fedc", "shadow": "none" }, + "focusRing": { "color": "rgba(68, 232, 88, 0.2)", "shadow": "none" }, "hoverBackground": "#1e76cd", "hoverBorderColor": "transparent", "hoverColor": "#fafdff" @@ -414,7 +422,7 @@ "borderColor": "transparent", "color": "#2b2e33", "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" }, "hoverBackground": "#1dc831", @@ -429,7 +437,7 @@ "borderColor": "transparent", "color": "#ffffff", "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" }, "hoverBackground": "#404348", @@ -443,7 +451,7 @@ "background": "#aafbb7", "borderColor": "transparent", "color": "#f0fff3", - "focusRing": { "color": "#d4fedc", "shadow": "none" }, + "focusRing": { "color": "rgba(68, 232, 88, 0.2)", "shadow": "none" }, "hoverBackground": "#77f48a", "hoverBorderColor": "transparent", "hoverColor": "#fafffb" @@ -455,7 +463,7 @@ "background": "#9d6d0e", "borderColor": "transparent", "color": "#fff9f0", - "focusRing": { "color": "#d4fedc", "shadow": "none" }, + "focusRing": { "color": "rgba(68, 232, 88, 0.2)", "shadow": "none" }, "hoverBackground": "#dc9710", "hoverBorderColor": "transparent", "hoverColor": "#fffdfa" @@ -515,20 +523,21 @@ "gap": 8, "paddingX": 14, "paddingY": 8, - "iconOnlyWidth": 40, + "height": 36, + "iconOnlyWidth": 36, "raisedShadow": "none", "badgeSize": 16, "transitionDuration": 180, "focusRing": { "width": 4, "style": "none", "offset": 0 }, "sm": { "fontSize": 14, - "iconOnlyWidth": 32, + "iconOnlyWidth": 28, "paddingX": 14, "paddingY": 8 }, "lg": { "fontSize": 20, - "iconOnlyWidth": 56, + "iconOnlyWidth": 48, "paddingX": 24, "paddingY": 14 }, @@ -546,13 +555,14 @@ "body": { "padding": 14, "gap": 14 }, "caption": { "gap": 4 }, "title": { "fontSize": 18, "fontWeight": 600 }, - "subtitle": { "color": "#a2a5a9" } + "subtitle": { "color": "#a2a5a9" }, + "overlay": { "shadow": "0 2px 4px rgba(0, 0, 0, 0.2000)" } }, "carousel": { "colorScheme": { "indicator": { - "activeBackground": "#2b2e33", - "background": "#cecfd2", + "activeBackground": "#f0f0f1", + "background": "#56595f", "hoverBackground": "#6d7076" } }, @@ -566,7 +576,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -590,12 +600,12 @@ "checkedHoverBorderColor": "#e2e2e4", "checkedFocusBorderColor": "#44e858", "checkedDisabledBorderColor": "#56595f", - "invalidBorderColor": "#db3424", + "invalidBorderColor": "#f47f77", "shadow": "none", "focusRing": { "focusRing": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)", "width": 4 @@ -607,7 +617,7 @@ "icon": { "size": 16, "color": "#f0f0f1", - "checkedColor": "#ffffff", + "checkedColor": "#2b2e33", "checkedHoverColor": "#ffffff", "disabledColor": "#a2a5a9", "sm": { "size": 12 }, @@ -635,7 +645,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -760,7 +770,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "inset 0 0 0 4px #d4fedc" }, @@ -778,7 +788,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "inset 0 0 0 4px #d4fedc" } @@ -822,7 +832,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -986,7 +996,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" }, @@ -1018,7 +1028,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -1047,12 +1057,12 @@ "root": { "transitionDuration": 180 } }, "dialog": { - "extend": { "borderWidth": 1, "backdrop": "rgba(0, 0, 0, 0.6000)" }, + "extend": { "borderWidth": 1, "backdrop": "rgba(0, 0, 0, 0.3000)" }, "root": { "background": "#56595f", "borderColor": "#404348", "color": "#ffffff", - "borderRadius": 1600, + "borderRadius": 20, "shadow": "0 4px 8px rgba(0, 0, 0, 0.2000)" }, "header": { @@ -1070,7 +1080,8 @@ "paddingBottom": 24, "paddingLeft": 24, "gap": 8 - } + }, + "colorScheme": { "root": { "background": "#2b2e33" } } }, "divider": { "extend": { "content": { "gap": 8 }, "iconSize": 16 }, @@ -1106,7 +1117,7 @@ "paddingLeft": 0 } }, - "content": { "background": "#404348", "color": "#ffffff" }, + "content": { "background": "#404348", "color": "#a2a5a9" }, "root": { "borderColor": "#404348" } }, "drawer": { @@ -1114,10 +1125,10 @@ "borderRadius": 8, "borderWidth": 1, "width": 400, - "extHeader": { "gap": 8, "borderColor": "#404348" }, + "extHeader": { "gap": 8, "borderColor": "#56595f" }, "margin": 8, "scale": 2, - "backdrop": "rgba(0, 0, 0, 0.6000)" + "backdrop": "rgba(0, 0, 0, 0.3000)" }, "root": { "background": "#404348", @@ -1210,7 +1221,7 @@ } }, "galleria": { - "extend": { "backdrop": "rgba(0, 0, 0, 0.6000)" }, + "extend": { "backdrop": "rgba(0, 0, 0, 0.3000)" }, "colorScheme": { "indicatorButton": { "background": "#cecfd2", @@ -1242,7 +1253,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -1256,7 +1267,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -1272,12 +1283,12 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } }, - "insetIndicatorList": { "background": "rgba(0, 0, 0, 0.6000)" }, + "insetIndicatorList": { "background": "rgba(0, 0, 0, 0.3000)" }, "insetIndicatorButton": { "background": "#404348", "hoverBackground": "#404348", @@ -1294,7 +1305,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -1331,13 +1342,19 @@ } }, "transitionDuration": { "transitionDuration": 180 }, - "button": { "width": 272, "borderRadius": 14, "verticalPadding": 14 }, + "button": { "width": 40, "borderRadius": 14, "verticalPadding": 14 }, "root": { "transitionDuration": 180 } }, "inputotp": { "extend": { "height": 40, "borderWidth": 1 }, "root": { "gap": 8 }, - "input": { "width": 304, "lg": { "width": 336 }, "sm": { "width": 272 } }, + "input": { + "width": 40, + "paddingTop": 12, + "paddingBottom": 8, + "lg": { "width": 48 }, + "sm": { "width": 28 } + }, "sm": { "width": 0 }, "lg": { "width": 0 } }, @@ -1346,7 +1363,8 @@ "readonlyBackground": "#2b2e33", "iconSize": 16, "borderWidth": 1, - "extXlg": { "fontSize": 16, "paddingX": 14, "paddingY": 14 } + "extXlg": { "fontSize": 16, "paddingX": 10, "paddingY": 20 }, + "invalidFocusRing": { "color": "rgba(232, 82, 68, 0.2)" } }, "root": { "background": "#404348", @@ -1357,22 +1375,22 @@ "borderColor": "#56595f", "hoverBorderColor": "#77f48a", "focusBorderColor": "#77f48a", - "invalidBorderColor": "#db3424", + "invalidBorderColor": "#f47f77", "color": "#ffffff", "disabledColor": "#a2a5a9", "placeholderColor": "#a2a5a9", "invalidPlaceholderColor": "#fbacaa", "shadow": "none", - "paddingX": 14, - "paddingY": 14, + "paddingX": 10, + "paddingY": 10, "borderRadius": 14, "transitionDuration": 180, - "sm": { "fontSize": 16, "paddingX": 14, "paddingY": 14 }, - "lg": { "fontSize": 16, "paddingX": 14, "paddingY": 14 }, + "sm": { "fontSize": 16, "paddingX": 10, "paddingY": 8 }, + "lg": { "fontSize": 16, "paddingX": 10, "paddingY": 14 }, "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "none" } @@ -1391,7 +1409,7 @@ "background": "#404348", "disabledBackground": "#404348", "borderColor": "#56595f", - "invalidBorderColor": "#db3424", + "invalidBorderColor": "#f47f77", "color": "#f0f0f1", "disabledColor": "#a2a5a9", "shadow": "none", @@ -1425,7 +1443,7 @@ "optionGroup": { "background": "#56595f", "color": "#a2a5a9", - "fontWeight": 600, + "fontWeight": 400, "paddingTop": 8, "paddingRight": 14, "paddingBottom": 8, @@ -1496,7 +1514,7 @@ "paddingLeft": 14, "background": "transparent", "color": "#a2a5a9", - "fontWeight": 600 + "fontWeight": 400 }, "submenuIcon": { "size": 20, @@ -1514,7 +1532,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -1546,7 +1564,7 @@ "paddingRight": 14, "paddingBottom": 8, "paddingLeft": 14, - "fontWeight": 600, + "fontWeight": 400, "background": "transparent", "color": "#a2a5a9" }, @@ -1560,8 +1578,8 @@ "gap": 8, "color": "#ffffff", "focusBackground": "#2b2e33", - "focusColor": "#cecfd2", - "icon": { "color": "#ffffff", "focusColor": "#cecfd2" } + "focusColor": "#ffffff", + "icon": { "color": "#ffffff", "focusColor": "#ffffff" } } }, "menubar": { @@ -1573,7 +1591,7 @@ "paddingRight": 14, "paddingBottom": 8, "paddingLeft": 14, - "fontWeight": 600, + "fontWeight": 400, "background": "transparent", "color": "#a2a5a9" } @@ -1637,7 +1655,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -1676,7 +1694,7 @@ "borderColor": "transparent", "closeButton": { "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" }, "hoverBackground": "transparent" @@ -1691,7 +1709,7 @@ "borderColor": "#e85244", "closeButton": { "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" }, "hoverBackground": "#611912" @@ -1706,7 +1724,7 @@ "borderColor": "#4496e8", "closeButton": { "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" }, "hoverBackground": "#123a61" @@ -1721,7 +1739,7 @@ "borderColor": "transparent", "closeButton": { "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" }, "hoverBackground": "transparent" @@ -1736,7 +1754,7 @@ "borderColor": "#44e858", "closeButton": { "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" }, "hoverBackground": "#12611b" @@ -1751,7 +1769,7 @@ "borderColor": "#f5b83d", "closeButton": { "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" }, "hoverBackground": "#6d4c0b" @@ -1766,7 +1784,7 @@ "closeButton": { "hoverBackground": "transparent", "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } }, @@ -1830,7 +1848,7 @@ "style": "none", "offset": 0, "shadow": "none", - "color": "#d4fedc" + "color": "rgba(68, 232, 88, 0.2)" }, "background": "#404348", "borderColor": "#56595f", @@ -1842,7 +1860,7 @@ "filledHoverBackground": "#404348", "focusBorderColor": "#77f48a", "hoverBorderColor": "#77f48a", - "invalidBorderColor": "#db3424", + "invalidBorderColor": "#f47f77", "invalidPlaceholderColor": "#fbacaa", "placeholderColor": "#a2a5a9" }, @@ -1923,7 +1941,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "focus": "0 0 4px rgba(0, 0, 0, 0.2000)", "shadow": "none" @@ -2049,11 +2067,11 @@ "checkedHoverBorderColor": "#f0f0f1", "checkedFocusBorderColor": "#f0f0f1", "checkedDisabledBorderColor": "#56595f", - "invalidBorderColor": "#db3424", + "invalidBorderColor": "#f47f77", "shadow": "none", "transitionDuration": 180, "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "none", "style": "none", @@ -2065,7 +2083,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" }, @@ -2085,7 +2103,7 @@ "gap": 8, "transitionDuration": 180, "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "none", "style": "none", @@ -2095,7 +2113,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" }, @@ -2116,7 +2134,7 @@ "focusRing": { "width": 0, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -2139,7 +2157,7 @@ "borderColor": "#56595f", "hoverBorderColor": "#77f48a", "focusBorderColor": "#77f48a", - "invalidBorderColor": "#db3424", + "invalidBorderColor": "#f47f77", "color": "#ffffff", "disabledColor": "#a2a5a9", "placeholderColor": "#a2a5a9", @@ -2154,7 +2172,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "none" } @@ -2224,7 +2242,7 @@ "root": { "invalidBorderColor": "#f47f77" }, "extend": { "background": "#e2e2e4" } }, - "root": { "borderRadius": 14 } + "root": { "borderRadius": 12 } }, "skeleton": { "extend": { "minWidth": 32, "height": 32 }, @@ -2236,7 +2254,7 @@ "slider": { "colorScheme": { "handle": { "content": { "background": "#2b2e33" } } }, "root": { "transitionDuration": 180 }, - "track": { "background": "#404348", "borderRadius": 14, "size": 4 }, + "track": { "background": "#404348", "borderRadius": 12, "size": 4 }, "range": { "background": "#f0f0f1" }, "handle": { "width": 20, @@ -2247,7 +2265,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" }, @@ -2257,7 +2275,8 @@ "width": 12, "height": 12, "shadow": "none" - } + }, + "extend": { "hoverRing": "0 0 0 4px rgba(206, 207, 210, 0.5000)" } } }, "splitter": { @@ -2275,7 +2294,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -2287,7 +2306,7 @@ "extStepNumber": { "invalidBackground": "#db3424", "invalidColor": "#fff0f0", - "invalidBorderColor": "#db3424", + "invalidBorderColor": "#f47f77", "borderWidth": 1, "iconSize": 24 } @@ -2310,7 +2329,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -2329,7 +2348,7 @@ "activeColor": "#ffffff", "size": 24, "fontSize": 16, - "fontWeight": 700, + "fontWeight": 400, "borderRadius": 1600, "shadow": "none" }, @@ -2346,7 +2365,7 @@ "gap": 8, "borderRadius": 14, "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "none", "style": "none", @@ -2387,7 +2406,7 @@ "tablist": { "borderTopWidth": 0, "borderRightWidth": 0, - "borderBottomWidth": 1, + "borderBottomWidth": 2, "borderLeftWidth": 0, "background": "transparent", "borderColor": "#404348" @@ -2407,7 +2426,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -2419,7 +2438,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -2432,7 +2451,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -2469,7 +2488,7 @@ "background": "#2b2e33", "borderColor": "#2b2e33", "closeButton": { - "focusRing": { "color": "#d4fedc", "shadow": "none" }, + "focusRing": { "color": "rgba(68, 232, 88, 0.2)", "shadow": "none" }, "hoverBackground": "#404348" }, "color": "#ffffff", @@ -2481,7 +2500,7 @@ "borderColor": "#e85244", "closeButton": { "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" }, "hoverBackground": "#611912" @@ -2495,7 +2514,7 @@ "borderColor": "#4496e8", "closeButton": { "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" }, "hoverBackground": "#123a61" @@ -2509,7 +2528,7 @@ "background": "#f0f0f1", "borderColor": "#56595f", "closeButton": { - "focusRing": { "color": "#d4fedc", "shadow": "none" }, + "focusRing": { "color": "rgba(68, 232, 88, 0.2)", "shadow": "none" }, "hoverBackground": "#f0f0f1" }, "color": "#2b2e33", @@ -2521,7 +2540,7 @@ "borderColor": "#44e858", "closeButton": { "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" }, "hoverBackground": "#12611b" @@ -2534,7 +2553,7 @@ "background": "#453008", "borderColor": "#f5b83d", "closeButton": { - "focusRing": { "color": "#d4fedc", "shadow": "none" }, + "focusRing": { "color": "rgba(68, 232, 88, 0.2)", "shadow": "none" }, "hoverBackground": "#6d4c0b" }, "color": "#2b2e33", @@ -2589,7 +2608,8 @@ "readonlyBackground": "#2b2e33", "borderWidth": 1, "iconSize": 16, - "minHeight": 80 + "minHeight": 80, + "extXlg": { "fontSize": 16, "paddingX": 10, "paddingY": 18 } }, "root": { "background": "#404348", @@ -2600,28 +2620,26 @@ "borderColor": "#56595f", "hoverBorderColor": "#77f48a", "focusBorderColor": "#77f48a", - "invalidBorderColor": "#db3424", + "invalidBorderColor": "#f47f77", "color": "#f0f0f1", "disabledColor": "#a2a5a9", "placeholderColor": "#a2a5a9", "invalidPlaceholderColor": "#fbacaa", "shadow": "none", - "paddingX": 14, - "paddingY": 14, + "paddingX": 10, + "paddingY": 10, "borderRadius": 14, "transitionDuration": 180, "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "none" }, - "lg": { "fontSize": 16, "paddingX": 14, "paddingY": 14 }, - "sm": { "fontSize": 16, "paddingX": 14, "paddingY": 14 } - }, - "sm": { "fontSize": 16, "paddingX": 8, "paddingY": 8 }, - "lg": { "fontSize": 16, "paddingX": 14, "paddingY": 14 } + "lg": { "fontSize": 16, "paddingX": 10, "paddingY": 14 }, + "sm": { "fontSize": 16, "paddingX": 10, "paddingY": 8 } + } }, "tieredmenu": { "extend": { @@ -2659,9 +2677,9 @@ "submenu": { "mobileIndent": 14 }, "separator": { "borderColor": "#404348" }, "submenuIcon": { - "activeColor": "#cecfd2", + "activeColor": "#181a1f", "color": "#ffffff", - "focusColor": "#cecfd2", + "focusColor": "#ffffff", "size": 14 } }, @@ -2705,14 +2723,14 @@ "iconSize": { "sm": 16, "md": 20, "lg": 24 }, "iconOnlyWidth": 40, "hoverBorderColor": "#56595f", - "checkedHoverColor": "#ffffff", + "checkedHoverColor": "#2b2e33", "checkedHoverBackground": "#e2e2e4", "checkedHoverBorderColor": "#e2e2e4", "extXlg": { "paddingTop": 20, - "paddingRight": 20, + "paddingRight": 24, "paddingBottom": 20, - "paddingLeft": 20, + "paddingLeft": 24, "iconOnlyWidth": 64 }, "extSm": { "iconOnlyWidth": 32 }, @@ -2720,23 +2738,23 @@ }, "colorScheme": { "root": { - "hoverBackground": "#cecfd2", - "background": "#e2e2e4", - "borderColor": "#e2e2e4", - "color": "#2b2e33", - "hoverColor": "#2b2e33", - "checkedBackground": "#2b2e33", - "checkedColor": "#ffffff", - "checkedBorderColor": "#2b2e33", - "disabledBackground": "#e2e2e4", - "disabledBorderColor": "#e2e2e4", + "hoverBackground": "#56595f", + "background": "#404348", + "borderColor": "#404348", + "color": "#ffffff", + "hoverColor": "#ffffff", + "checkedBackground": "#f0f0f1", + "checkedColor": "#2b2e33", + "checkedBorderColor": "#f0f0f1", + "disabledBackground": "#404348", + "disabledBorderColor": "#404348", "disabledColor": "#85888e", "invalidBorderColor": "#f47f77" }, "icon": { - "color": "#2b2e33", - "hoverColor": "#2b2e33", - "checkedColor": "#ffffff", + "color": "#ffffff", + "hoverColor": "#ffffff", + "checkedColor": "#2b2e33", "disabledColor": "#85888e" }, "content": { "checkedBackground": "transparent" } @@ -2752,7 +2770,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" }, @@ -2781,7 +2799,7 @@ "disabledBorderColor": "#404348", "disabledColor": "#a2a5a9", "hoverColor": "#cecfd2", - "invalidBorderColor": "#db3424" + "invalidBorderColor": "#f47f77" }, "content": { "checkedShadow": "none", @@ -2801,21 +2819,21 @@ "toggleswitch": { "colorScheme": { "handle": { - "background": "#56595f", - "checkedBackground": "#56595f", + "background": "#ffffff", + "checkedBackground": "#181a1f", "checkedColor": "#2b2e33", - "checkedHoverBackground": "#56595f", + "checkedHoverBackground": "#181a1f", "checkedHoverColor": "#2b2e33", "color": "#2b2e33", "disabledBackground": "#85888e", - "hoverBackground": "#56595f", + "hoverBackground": "#ffffff", "hoverColor": "#2b2e33" }, "root": { "background": "#6d7076", - "checkedBackground": "#2b2e33", - "checkedHoverBackground": "#404348", - "disabledBackground": "#e2e2e4", + "checkedBackground": "#f0f0f1", + "checkedHoverBackground": "#e2e2e4", + "disabledBackground": "#404348", "hoverBackground": "#85888e" } }, @@ -2829,7 +2847,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "none" }, @@ -2837,7 +2855,7 @@ "hoverBorderColor": "transparent", "checkedBorderColor": "transparent", "checkedHoverBorderColor": "transparent", - "invalidBorderColor": "#db3424", + "invalidBorderColor": "#f47f77", "transitionDuration": 180, "slideDuration": 180 }, @@ -2875,7 +2893,7 @@ "gap": 4, "borderRadius": 14, "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "none", "style": "none", @@ -2896,7 +2914,7 @@ "selectedHoverBackground": "#f0f0f1", "color": "#ffffff", "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "none", "style": "none", diff --git a/src/theme/tokens/components/light.json b/src/theme/tokens/components/light.json index be686424..7ea83ad2 100644 --- a/src/theme/tokens/components/light.json +++ b/src/theme/tokens/components/light.json @@ -25,7 +25,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "inset 0 0 0 4px #d4fedc" }, @@ -61,17 +61,25 @@ } }, "autocomplete": { - "extend": { "extOption": { "gap": 8 }, "extOptionGroup": { "gap": 8 } }, + "extend": { + "checkmarkSize": 20, + "extOption": { "gap": 8 }, + "extOptionGroup": { "gap": 8 } + }, "colorScheme": { "chip": { "focusBackground": "#e2e2e4", "focusColor": "#2b2e33" }, + "extend": { "option": { "background": "#ffffff" } }, "dropdown": { "background": "#ffffff", "hoverBackground": "#ffffff", "activeBackground": "#ffffff", - "color": "#181a1f", - "hoverColor": "#181a1f", - "activeColor": "#181a1f" - } + "color": "#85888e", + "hoverColor": "#85888e", + "activeColor": "#85888e" + }, + "option": { "focusBackground": "#f0f0f1" }, + "optionGroup": { "background": "#ffffff", "color": "#85888e" }, + "overlay": { "background": "#ffffff" } }, "root": { "background": "#ffffff", @@ -82,7 +90,7 @@ "borderColor": "#cecfd2", "hoverBorderColor": "#1dc831", "focusBorderColor": "#1dc831", - "invalidBorderColor": "#f47f77", + "invalidBorderColor": "#db3424", "color": "#181a1f", "disabledColor": "#85888e", "placeholderColor": "#85888e", @@ -95,7 +103,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "none" } @@ -103,9 +111,9 @@ "overlay": { "background": "#ffffff", "borderColor": "#e2e2e4", - "borderRadius": 14, + "borderRadius": 12, "color": "#2b2e33", - "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" + "shadow": "0 4px 8px rgba(0, 0, 0, 0.2000)" }, "list": { "padding": 4, "gap": 4 }, "option": { @@ -117,9 +125,9 @@ "selectedColor": "#ffffff", "selectedFocusColor": "#ffffff", "paddingTop": 8, - "paddingRight": 14, + "paddingRight": 12, "paddingBottom": 8, - "paddingLeft": 14, + "paddingLeft": 12, "borderRadius": 8 }, "optionGroup": { @@ -140,7 +148,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "none" }, @@ -156,7 +164,7 @@ } }, "avatar": { - "extend": { "borderColor": "#cecfd2", "circle": { "borderRadius": 1600 } }, + "extend": { "borderColor": "#ffffff", "circle": { "borderRadius": 1600 } }, "root": { "width": 32, "height": 32, @@ -231,7 +239,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" }, @@ -242,7 +250,7 @@ "gap": 8, "icon": { "color": "#2b2e33", "hoverColor": "#56595f" }, "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "none", "style": "none", @@ -279,15 +287,15 @@ "lg": { "iconOnlyWidth": 28 }, "xlg": { "iconOnlyWidth": 32 } }, - "extSm": { "borderRadius": 14, "gap": 8 }, - "extLg": { "borderRadius": 14, "gap": 14, "height": 56 }, + "extSm": { "borderRadius": 14, "gap": 8, "height": 28 }, + "extLg": { "borderRadius": 14, "gap": 14, "height": 48 }, "extXlg": { "borderRadius": 14, "gap": 14, - "iconOnlyWidth": 64, + "iconOnlyWidth": 56, "paddingX": 24, "paddingY": 20, - "height": 64 + "height": 56 }, "borderWidth": 1, "iconSize": { "sm": 16, "md": 20, "lg": 24 } @@ -305,7 +313,7 @@ "hoverColor": "#2b2e33", "activeColor": "#2b2e33", "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } }, @@ -320,7 +328,7 @@ "hoverColor": "#ffffff", "activeColor": "#ffffff", "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } }, @@ -335,7 +343,7 @@ "hoverColor": "#2b2e33", "activeColor": "#2b2e33", "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } }, @@ -349,7 +357,7 @@ "color": "#0e2a45", "hoverColor": "#0c243b", "activeColor": "#0e2a45", - "focusRing": { "color": "#d4fedc", "shadow": "none" } + "focusRing": { "color": "rgba(68, 232, 88, 0.2)", "shadow": "none" } }, "success": { "background": "#aafbb7", @@ -361,7 +369,7 @@ "color": "#0e4514", "hoverColor": "#0c3b11", "activeColor": "#0e4514", - "focusRing": { "color": "#d4fedc", "shadow": "none" } + "focusRing": { "color": "rgba(68, 232, 88, 0.2)", "shadow": "none" } }, "warn": { "background": "#fddeaa", @@ -373,7 +381,7 @@ "color": "#4f3709", "hoverColor": "#453008", "activeColor": "#4f3709", - "focusRing": { "color": "#d4fedc", "shadow": "none" } + "focusRing": { "color": "rgba(68, 232, 88, 0.2)", "shadow": "none" } }, "help": { "background": "#d8b4fe", @@ -385,7 +393,7 @@ "color": "#581c87", "hoverColor": "#3b0764", "activeColor": "#581c87", - "focusRing": { "color": "#d4fedc", "shadow": "none" } + "focusRing": { "color": "rgba(68, 232, 88, 0.2)", "shadow": "none" } }, "danger": { "background": "#fbacaa", @@ -397,7 +405,7 @@ "color": "#45120e", "hoverColor": "#3b100c", "activeColor": "#45120e", - "focusRing": { "color": "#d4fedc", "shadow": "none" } + "focusRing": { "color": "rgba(68, 232, 88, 0.2)", "shadow": "none" } } }, "outlined": { @@ -515,20 +523,21 @@ "gap": 8, "paddingX": 14, "paddingY": 8, - "iconOnlyWidth": 40, + "height": 36, + "iconOnlyWidth": 36, "raisedShadow": "none", "badgeSize": 16, "transitionDuration": 180, "focusRing": { "width": 4, "style": "none", "offset": 0 }, "sm": { "fontSize": 14, - "iconOnlyWidth": 32, + "iconOnlyWidth": 28, "paddingX": 14, "paddingY": 8 }, "lg": { "fontSize": 20, - "iconOnlyWidth": 56, + "iconOnlyWidth": 48, "paddingX": 24, "paddingY": 14 }, @@ -546,7 +555,8 @@ "body": { "padding": 14, "gap": 14 }, "caption": { "gap": 4 }, "title": { "fontSize": 18, "fontWeight": 600 }, - "subtitle": { "color": "#85888e" } + "subtitle": { "color": "#85888e" }, + "overlay": { "shadow": "0 2px 4px rgba(0, 0, 0, 0.2000)" } }, "carousel": { "colorScheme": { @@ -566,7 +576,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -590,12 +600,12 @@ "checkedHoverBorderColor": "#404348", "checkedFocusBorderColor": "#44e858", "checkedDisabledBorderColor": "#cecfd2", - "invalidBorderColor": "#f47f77", + "invalidBorderColor": "#db3424", "shadow": "none", "focusRing": { "focusRing": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)", "width": 4 @@ -635,7 +645,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -760,7 +770,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "inset 0 0 0 4px #d4fedc" }, @@ -778,7 +788,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "inset 0 0 0 4px #d4fedc" } @@ -822,7 +832,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -986,7 +996,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" }, @@ -1018,7 +1028,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -1047,12 +1057,12 @@ "root": { "transitionDuration": 180 } }, "dialog": { - "extend": { "borderWidth": 1, "backdrop": "rgba(0, 0, 0, 0.4000)" }, + "extend": { "borderWidth": 1, "backdrop": "rgba(0, 0, 0, 0.3000)" }, "root": { "background": "#ffffff", "borderColor": "#e2e2e4", "color": "#2b2e33", - "borderRadius": 1600, + "borderRadius": 20, "shadow": "0 4px 8px rgba(0, 0, 0, 0.2000)" }, "header": { @@ -1070,7 +1080,8 @@ "paddingBottom": 24, "paddingLeft": 24, "gap": 8 - } + }, + "colorScheme": { "root": { "background": "#ffffff" } } }, "divider": { "extend": { "content": { "gap": 8 }, "iconSize": 16 }, @@ -1106,7 +1117,7 @@ "paddingLeft": 0 } }, - "content": { "background": "#ffffff", "color": "#2b2e33" }, + "content": { "background": "#ffffff", "color": "#85888e" }, "root": { "borderColor": "#e2e2e4" } }, "drawer": { @@ -1114,10 +1125,10 @@ "borderRadius": 8, "borderWidth": 1, "width": 400, - "extHeader": { "gap": 8, "borderColor": "#e2e2e4" }, + "extHeader": { "gap": 8, "borderColor": "#cecfd2" }, "margin": 8, "scale": 2, - "backdrop": "rgba(0, 0, 0, 0.4000)" + "backdrop": "rgba(0, 0, 0, 0.3000)" }, "root": { "background": "#ffffff", @@ -1210,7 +1221,7 @@ } }, "galleria": { - "extend": { "backdrop": "rgba(0, 0, 0, 0.4000)" }, + "extend": { "backdrop": "rgba(0, 0, 0, 0.3000)" }, "colorScheme": { "thumbnailContent": { "background": "#f0f0f1" }, "thumbnailNavButton": { @@ -1242,7 +1253,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -1256,7 +1267,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -1272,12 +1283,12 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } }, - "insetIndicatorList": { "background": "rgba(0, 0, 0, 0.4000)" }, + "insetIndicatorList": { "background": "rgba(0, 0, 0, 0.3000)" }, "insetIndicatorButton": { "background": "#e2e2e4", "hoverBackground": "#e2e2e4", @@ -1294,7 +1305,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -1331,13 +1342,19 @@ } }, "transitionDuration": { "transitionDuration": 180 }, - "button": { "width": 272, "borderRadius": 14, "verticalPadding": 14 }, + "button": { "width": 40, "borderRadius": 14, "verticalPadding": 14 }, "root": { "transitionDuration": 180 } }, "inputotp": { "extend": { "height": 40, "borderWidth": 1 }, "root": { "gap": 8 }, - "input": { "width": 304, "lg": { "width": 336 }, "sm": { "width": 272 } }, + "input": { + "width": 40, + "paddingTop": 12, + "paddingBottom": 8, + "lg": { "width": 48 }, + "sm": { "width": 28 } + }, "sm": { "width": 0 }, "lg": { "width": 0 } }, @@ -1346,7 +1363,8 @@ "readonlyBackground": "#f0f0f1", "iconSize": 16, "borderWidth": 1, - "extXlg": { "fontSize": 16, "paddingX": 14, "paddingY": 14 } + "extXlg": { "fontSize": 16, "paddingX": 10, "paddingY": 20 }, + "invalidFocusRing": { "color": "rgba(232, 82, 68, 0.2)" } }, "root": { "background": "#ffffff", @@ -1357,22 +1375,22 @@ "borderColor": "#cecfd2", "hoverBorderColor": "#1dc831", "focusBorderColor": "#1dc831", - "invalidBorderColor": "#f47f77", + "invalidBorderColor": "#db3424", "color": "#2b2e33", "disabledColor": "#85888e", "placeholderColor": "#85888e", "invalidPlaceholderColor": "#db3424", "shadow": "none", - "paddingX": 14, - "paddingY": 14, + "paddingX": 10, + "paddingY": 10, "borderRadius": 14, "transitionDuration": 180, - "sm": { "fontSize": 16, "paddingX": 14, "paddingY": 14 }, - "lg": { "fontSize": 16, "paddingX": 14, "paddingY": 14 }, + "sm": { "fontSize": 16, "paddingX": 10, "paddingY": 8 }, + "lg": { "fontSize": 16, "paddingX": 10, "paddingY": 14 }, "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "none" } @@ -1391,7 +1409,7 @@ "background": "#ffffff", "disabledBackground": "#e2e2e4", "borderColor": "#cecfd2", - "invalidBorderColor": "#f47f77", + "invalidBorderColor": "#db3424", "color": "#181a1f", "disabledColor": "#85888e", "shadow": "none", @@ -1425,7 +1443,7 @@ "optionGroup": { "background": "#ffffff", "color": "#85888e", - "fontWeight": 600, + "fontWeight": 400, "paddingTop": 8, "paddingRight": 14, "paddingBottom": 8, @@ -1496,7 +1514,7 @@ "paddingLeft": 14, "background": "transparent", "color": "#85888e", - "fontWeight": 600 + "fontWeight": 400 }, "submenuIcon": { "size": 20, @@ -1514,7 +1532,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -1546,7 +1564,7 @@ "paddingRight": 14, "paddingBottom": 8, "paddingLeft": 14, - "fontWeight": 600, + "fontWeight": 400, "background": "transparent", "color": "#85888e" }, @@ -1560,8 +1578,8 @@ "gap": 8, "color": "#2b2e33", "focusBackground": "#f0f0f1", - "focusColor": "#56595f", - "icon": { "color": "#2b2e33", "focusColor": "#56595f" } + "focusColor": "#2b2e33", + "icon": { "color": "#2b2e33", "focusColor": "#2b2e33" } } }, "menubar": { @@ -1573,7 +1591,7 @@ "paddingRight": 14, "paddingBottom": 8, "paddingLeft": 14, - "fontWeight": 600, + "fontWeight": 400, "background": "transparent", "color": "#85888e" } @@ -1637,7 +1655,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -1680,7 +1698,7 @@ "closeButton": { "hoverBackground": "#d4fedc", "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } }, @@ -1691,7 +1709,7 @@ "closeButton": { "hoverBackground": "transparent", "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } }, @@ -1708,7 +1726,7 @@ "closeButton": { "hoverBackground": "#ffeed4", "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } }, @@ -1723,7 +1741,7 @@ "closeButton": { "hoverBackground": "#fed4d4", "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } }, @@ -1735,7 +1753,7 @@ "closeButton": { "hoverBackground": "transparent", "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } }, @@ -1750,7 +1768,7 @@ "closeButton": { "hoverBackground": "transparent", "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } }, @@ -1767,7 +1785,7 @@ "closeButton": { "hoverBackground": "#d4ecfe", "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } }, @@ -1830,7 +1848,7 @@ "style": "none", "offset": 0, "shadow": "none", - "color": "#d4fedc" + "color": "rgba(68, 232, 88, 0.2)" }, "background": "#ffffff", "borderColor": "#cecfd2", @@ -1842,7 +1860,7 @@ "filledHoverBackground": "#ffffff", "focusBorderColor": "#1dc831", "hoverBorderColor": "#1dc831", - "invalidBorderColor": "#f47f77", + "invalidBorderColor": "#db3424", "invalidPlaceholderColor": "#db3424", "placeholderColor": "#85888e" }, @@ -1923,7 +1941,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "focus": "0 0 4px rgba(0, 0, 0, 0.2000)", "shadow": "none" @@ -2049,11 +2067,11 @@ "checkedHoverBorderColor": "#2b2e33", "checkedFocusBorderColor": "#2b2e33", "checkedDisabledBorderColor": "#cecfd2", - "invalidBorderColor": "#f47f77", + "invalidBorderColor": "#db3424", "shadow": "none", "transitionDuration": 180, "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "none", "style": "none", @@ -2065,7 +2083,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" }, @@ -2085,7 +2103,7 @@ "gap": 8, "transitionDuration": 180, "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "none", "style": "none", @@ -2095,7 +2113,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" }, @@ -2116,7 +2134,7 @@ "focusRing": { "width": 0, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -2139,7 +2157,7 @@ "borderColor": "#cecfd2", "hoverBorderColor": "#1dc831", "focusBorderColor": "#1dc831", - "invalidBorderColor": "#f47f77", + "invalidBorderColor": "#db3424", "color": "#2b2e33", "disabledColor": "#85888e", "placeholderColor": "#85888e", @@ -2154,7 +2172,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "none" } @@ -2221,10 +2239,10 @@ "ext": { "borderRadius": 8 } }, "colorScheme": { - "root": { "invalidBorderColor": "#f47f77" }, + "root": { "invalidBorderColor": "#db3424" }, "extend": { "background": "#e2e2e4" } }, - "root": { "borderRadius": 14 } + "root": { "borderRadius": 12 } }, "skeleton": { "extend": { "minWidth": 32, "height": 32 }, @@ -2236,7 +2254,7 @@ "slider": { "colorScheme": { "handle": { "content": { "background": "#ffffff" } } }, "root": { "transitionDuration": 180 }, - "track": { "background": "#e2e2e4", "borderRadius": 14, "size": 4 }, + "track": { "background": "#e2e2e4", "borderRadius": 12, "size": 4 }, "range": { "background": "#2b2e33" }, "handle": { "width": 20, @@ -2247,7 +2265,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" }, @@ -2257,7 +2275,8 @@ "width": 12, "height": 12, "shadow": "none" - } + }, + "extend": { "hoverRing": "0 0 0 4px rgba(206, 207, 210, 0.5000)" } } }, "splitter": { @@ -2275,7 +2294,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -2287,7 +2306,7 @@ "extStepNumber": { "invalidBackground": "#f47f77", "invalidColor": "#45120e", - "invalidBorderColor": "#f47f77", + "invalidBorderColor": "#db3424", "borderWidth": 1, "iconSize": 24 } @@ -2310,7 +2329,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -2329,7 +2348,7 @@ "activeColor": "#2b2e33", "size": 24, "fontSize": 16, - "fontWeight": 700, + "fontWeight": 400, "borderRadius": 1600, "shadow": "none" }, @@ -2346,7 +2365,7 @@ "gap": 8, "borderRadius": 14, "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "none", "style": "none", @@ -2387,7 +2406,7 @@ "tablist": { "borderTopWidth": 0, "borderRightWidth": 0, - "borderBottomWidth": 1, + "borderBottomWidth": 2, "borderLeftWidth": 0, "background": "transparent", "borderColor": "#e2e2e4" @@ -2407,7 +2426,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -2419,7 +2438,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -2432,7 +2451,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } @@ -2474,7 +2493,7 @@ "closeButton": { "hoverBackground": "#d4ecfe", "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } } @@ -2488,7 +2507,7 @@ "closeButton": { "hoverBackground": "#d4fedc", "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } } @@ -2501,7 +2520,7 @@ "shadow": "0 4px 8px rgba(0, 0, 0, 0.2000)", "closeButton": { "hoverBackground": "#ffeed4", - "focusRing": { "color": "#d4fedc", "shadow": "none" } + "focusRing": { "color": "rgba(68, 232, 88, 0.2)", "shadow": "none" } } }, "error": { @@ -2513,7 +2532,7 @@ "closeButton": { "hoverBackground": "#fed4d4", "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" } } @@ -2523,7 +2542,7 @@ "background": "#f0f0f1", "borderColor": "#cecfd2", "closeButton": { - "focusRing": { "color": "#d4fedc", "shadow": "none" }, + "focusRing": { "color": "rgba(68, 232, 88, 0.2)", "shadow": "none" }, "hoverBackground": "#f0f0f1" }, "color": "#2b2e33", @@ -2534,7 +2553,7 @@ "background": "#2b2e33", "borderColor": "#2b2e33", "closeButton": { - "focusRing": { "color": "#d4fedc", "shadow": "none" }, + "focusRing": { "color": "rgba(68, 232, 88, 0.2)", "shadow": "none" }, "hoverBackground": "#404348" }, "color": "#ffffff", @@ -2589,7 +2608,8 @@ "readonlyBackground": "#f0f0f1", "borderWidth": 1, "iconSize": 16, - "minHeight": 80 + "minHeight": 80, + "extXlg": { "fontSize": 16, "paddingX": 10, "paddingY": 18 } }, "root": { "background": "#ffffff", @@ -2600,28 +2620,26 @@ "borderColor": "#cecfd2", "hoverBorderColor": "#1dc831", "focusBorderColor": "#1dc831", - "invalidBorderColor": "#f47f77", + "invalidBorderColor": "#db3424", "color": "#181a1f", "disabledColor": "#85888e", "placeholderColor": "#85888e", "invalidPlaceholderColor": "#db3424", "shadow": "none", - "paddingX": 14, - "paddingY": 14, + "paddingX": 10, + "paddingY": 10, "borderRadius": 14, "transitionDuration": 180, "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "none" }, - "lg": { "fontSize": 16, "paddingX": 14, "paddingY": 14 }, - "sm": { "fontSize": 16, "paddingX": 14, "paddingY": 14 } - }, - "sm": { "fontSize": 16, "paddingX": 8, "paddingY": 8 }, - "lg": { "fontSize": 16, "paddingX": 14, "paddingY": 14 } + "lg": { "fontSize": 16, "paddingX": 10, "paddingY": 14 }, + "sm": { "fontSize": 16, "paddingX": 10, "paddingY": 8 } + } }, "tieredmenu": { "extend": { @@ -2659,9 +2677,9 @@ "submenu": { "mobileIndent": 14 }, "separator": { "borderColor": "#e2e2e4" }, "submenuIcon": { - "activeColor": "#56595f", + "activeColor": "#ffffff", "color": "#2b2e33", - "focusColor": "#56595f", + "focusColor": "#2b2e33", "size": 14 } }, @@ -2710,9 +2728,9 @@ "checkedHoverBorderColor": "#404348", "extXlg": { "paddingTop": 20, - "paddingRight": 20, + "paddingRight": 24, "paddingBottom": 20, - "paddingLeft": 20, + "paddingLeft": 24, "iconOnlyWidth": 64 }, "extSm": { "iconOnlyWidth": 32 }, @@ -2731,7 +2749,7 @@ "disabledBackground": "#e2e2e4", "disabledBorderColor": "#e2e2e4", "disabledColor": "#85888e", - "invalidBorderColor": "#f47f77" + "invalidBorderColor": "#db3424" }, "icon": { "color": "#2b2e33", @@ -2752,7 +2770,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "0 0 4px rgba(0, 0, 0, 0.2000)" }, @@ -2781,7 +2799,7 @@ "disabledBorderColor": "#e2e2e4", "disabledColor": "#85888e", "hoverColor": "#56595f", - "invalidBorderColor": "#f47f77" + "invalidBorderColor": "#db3424" }, "content": { "checkedShadow": "none", @@ -2829,7 +2847,7 @@ "focusRing": { "width": 4, "style": "none", - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "none" }, @@ -2837,7 +2855,7 @@ "hoverBorderColor": "transparent", "checkedBorderColor": "transparent", "checkedHoverBorderColor": "transparent", - "invalidBorderColor": "#f47f77", + "invalidBorderColor": "#db3424", "transitionDuration": 180, "slideDuration": 180 }, @@ -2875,7 +2893,7 @@ "gap": 4, "borderRadius": 14, "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "none", "style": "none", @@ -2896,7 +2914,7 @@ "selectedHoverBackground": "#2b2e33", "color": "#2b2e33", "focusRing": { - "color": "#d4fedc", + "color": "rgba(68, 232, 88, 0.2)", "offset": 0, "shadow": "none", "style": "none", diff --git a/src/theme/tokens/semantic/colorScheme/dark.json b/src/theme/tokens/semantic/colorScheme/dark.json index 4ae11e18..079ce235 100644 --- a/src/theme/tokens/semantic/colorScheme/dark.json +++ b/src/theme/tokens/semantic/colorScheme/dark.json @@ -176,7 +176,7 @@ "selected": "#6d7076" }, "overlay": { "default": "#404348", "hover": "#56595f" }, - "backdrop": "rgba(0, 0, 0, 0.6000)" + "backdrop": "rgba(0, 0, 0, 0.3000)" }, "neutral": { "weak": { @@ -200,7 +200,7 @@ } }, "border": { - "focus": "#d4fedc", + "focus": "rgba(68, 232, 88, 0.2)", "neutral": { "default": "#404348", "strong": "#56595f", @@ -216,7 +216,8 @@ "danger": { "subtle": "#db3424", "default": "#e85244", - "strong": "#f47f77" + "strong": "#f47f77", + "focus": "rgba(232, 82, 68, 0.2)" }, "info": { "default": "#4496e8", "strong": "#77baf4" }, "warning": { "default": "#f5b83d", "strong": "#facb75" }, diff --git a/src/theme/tokens/semantic/colorScheme/light.json b/src/theme/tokens/semantic/colorScheme/light.json index 695bff01..bf8696b1 100644 --- a/src/theme/tokens/semantic/colorScheme/light.json +++ b/src/theme/tokens/semantic/colorScheme/light.json @@ -176,7 +176,7 @@ "selected": "#fafafa" }, "overlay": { "default": "#ffffff", "hover": "#fafafa" }, - "backdrop": "rgba(0, 0, 0, 0.4000)" + "backdrop": "rgba(0, 0, 0, 0.3000)" }, "neutral": { "weak": { @@ -200,7 +200,7 @@ } }, "border": { - "focus": "#d4fedc", + "focus": "rgba(68, 232, 88, 0.2)", "neutral": { "default": "#e2e2e4", "strong": "#cecfd2", @@ -216,7 +216,8 @@ "danger": { "subtle": "#f47f77", "default": "#e85244", - "strong": "#db3424" + "strong": "#db3424", + "focus": "rgba(232, 82, 68, 0.2)" }, "info": { "default": "#4496e8", "strong": "#1e76cd" }, "warning": { "default": "#f5b83d", "strong": "#dc9710" }, diff --git a/src/theme/tokens/semantic/dimensions.json b/src/theme/tokens/semantic/dimensions.json index 28c7802d..5397a89f 100644 --- a/src/theme/tokens/semantic/dimensions.json +++ b/src/theme/tokens/semantic/dimensions.json @@ -4,6 +4,7 @@ "150": 6, "200": 8, "300": 10, + "350": 12, "400": 14, "500": 18, "600": 20, @@ -45,6 +46,7 @@ "100": 4, "200": 8, "300": 10, + "350": 12, "400": 14, "500": 20, "none": 0, From dcceb3ac66aeba1c0441aed8e03bc52146c497c2 Mon Sep 17 00:00:00 2001 From: Alesya Volosach Date: Wed, 2 Sep 2026 14:29:10 +0300 Subject: [PATCH 7/7] =?UTF-8?q?fix(input-otp):=20=D1=81=D0=B8=D0=BD=D1=85?= =?UTF-8?q?=D1=80=D0=BE=D0=BD=D0=B8=D0=B7=D0=B8=D1=80=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D1=8B=20=D1=81=D0=BE=D1=81=D1=82=D0=BE=D1=8F=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Input/InputOtp/InputOtpItem.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/Input/InputOtp/InputOtpItem.tsx b/src/components/Input/InputOtp/InputOtpItem.tsx index 3694ed63..e3abd584 100644 --- a/src/components/Input/InputOtp/InputOtpItem.tsx +++ b/src/components/Input/InputOtp/InputOtpItem.tsx @@ -145,12 +145,10 @@ const styles = StyleSheet.create(({ components, semantic, fonts }) => ({ boxShadow: `0 0 0 ${components.inputtext.root.focusRing.width}px ${components.inputtext.root.focusRing.color}`, }, - error: { - borderColor: semantic.colorScheme.color.border.status.danger.strong, - }, + error: { borderColor: components.inputtext.root.invalidBorderColor }, errorFocused: { - boxShadow: `0 0 0 3.5px ${semantic.colorScheme.color.bg.status.danger.weak.hover}`, + boxShadow: `0 0 0 ${components.inputtext.root.focusRing.width}px ${semantic.colorScheme.color.border.status.danger.focus}`, }, disabled: {