Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions design-tokens/input/tokens.json
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,7 @@
"neutral": {
"default": "{colors.solid.zinc.200}",
"strong": "{colors.solid.zinc.300}",
"focus": "{colors.alpha.black.100}",
"bold": {
"default": "{colors.solid.zinc.900}",
"hover": "{colors.solid.zinc.800}"
Expand Down Expand Up @@ -966,6 +967,7 @@
"neutral": {
"default": "{colors.solid.zinc.800}",
"strong": "{colors.solid.zinc.700}",
"focus": "{colors.alpha.white.100}",
"bold": {
"default": "{colors.solid.zinc.100}",
"hover": "{colors.solid.zinc.200}"
Expand Down Expand Up @@ -5117,14 +5119,14 @@
},
"dark": {
"handle": {
"background": "{color.fg.on.fill.default}",
"checkedBackground": "{color.fg.inverse.active}",
"background": "{colors.solid.zinc.700}",
"checkedBackground": "{colors.solid.zinc.700}",
"checkedColor": "{color.fg.inverse.focus}",
"checkedHoverBackground": "{color.fg.inverse.active}",
"checkedHoverBackground": "{colors.solid.zinc.700}",
"checkedHoverColor": "{color.fg.on.brand.hover}",
"color": "{color.fg.inverse.default}",
"disabledBackground": "{color.bg.neutral.medium.strong}",
"hoverBackground": "{color.fg.on.fill.default}",
"hoverBackground": "{colors.solid.zinc.700}",
"hoverColor": "{color.fg.on.brand.hover}"
},
"root": {
Expand Down
2 changes: 1 addition & 1 deletion src/components/Input/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@
InputLabelInfo,
RenderTextInputArgs,
} from './core/types'
export { InputSwitch } from '../InputSwitch'
export { InputSwitch, type InputSwitchProps } from '../InputSwitch'

Check warning on line 18 in src/components/Input/index.ts

View workflow job for this annotation

GitHub Actions / lint

`InputSwitchProps` is deprecated. Используйте ToggleSwitchProps

Check warning on line 18 in src/components/Input/index.ts

View workflow job for this annotation

GitHub Actions / lint

`InputSwitch` is deprecated. Используйте ToggleSwitch
export { InputOtp, InputOtpTestId, type InputOtpProps } from '../InputOtp'
39 changes: 0 additions & 39 deletions src/components/InputSwitch/InputSwitch.stories.tsx

This file was deleted.

2 changes: 2 additions & 0 deletions src/components/InputSwitch/InputSwitch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@

import { useHandleStyles, useSliderStyles } from './styles'

/** @deprecated Используйте ToggleSwitchProps. */
export interface InputSwitchProps extends PressableProps {
checked: boolean
onCheckedChange?: (checked: boolean) => void
disabled?: boolean
danger?: boolean
}

/** @deprecated Используйте ToggleSwitch. */
export const InputSwitch = memo<InputSwitchProps>(

Check warning on line 16 in src/components/InputSwitch/InputSwitch.tsx

View workflow job for this annotation

GitHub Actions / lint

`InputSwitchProps` is deprecated. Используйте ToggleSwitchProps
({
checked,
onCheckedChange,
Expand Down
2 changes: 1 addition & 1 deletion src/components/InputSwitch/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { InputSwitch } from './InputSwitch'
export { InputSwitch, type InputSwitchProps } from './InputSwitch'
54 changes: 54 additions & 0 deletions src/components/ToggleSwitch/ToggleSwitch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# ToggleSwitch

Управляемый переключатель с необязательными `label` и `caption`.

## Использование

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

export function Notifications() {
const [checked, setChecked] = useState(false)

return (
<ToggleSwitch
checked={checked}
onCheckedChange={setChecked}
label='Уведомления'
caption='Сообщать об изменении статуса заказа'
/>
)
}
```

## Props

- `checked` — обязательное boolean-значение. Состояние меняется после обновления
`checked` потребителем.
- `onCheckedChange` — необязательный callback, передающий следующее значение
после нажатия.
- `label` — строка справа от переключателя. Без `label` отображается только
переключатель.
- `caption` — строка под `label`. Без `label` не отображается.
- `disabled` — блокирует нажатия. По умолчанию `false`.
- `danger` — выделяет переключатель как ошибочный. По умолчанию `false`.

Пользовательский `style` объединяется с внутренним, включая callback-форму.
Нативные обработчики нажатия вызываются вместе с `onCheckedChange`.

Полный контракт — [ToggleSwitchProps](./types.ts).

## Доступность

Вся строка — один элемент с ролью `switch` и состояниями `checked` и `disabled`.
`label` задаёт доступное имя, `caption` — подсказку. Их можно переопределить
нативными атрибутами доступности. Без `label` рекомендуется передать
`accessibilityLabel` или связать компонент с внешним текстом через
`accessibilityLabelledBy` на Android. Атрибуты доступного имени необязательны.
Ожидание запроса можно обозначить через
`accessibilityState={{ busy: loading }}`.

## Макет

[ToggleSwitch в Figma](https://www.figma.com/design/Q1BWgZ7zoV5UzlBOnjW0cM/UI-Kit--DS--v2.1?node-id=19-13673&m=dev)
54 changes: 54 additions & 0 deletions src/components/ToggleSwitch/ToggleSwitch.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { useArgs } from '@storybook/preview-api'
import type { Meta, StoryObj } from '@storybook/react'

import { ToggleSwitch } from './ToggleSwitch'
import type { ToggleSwitchProps } from './types'

const meta: Meta<ToggleSwitchProps> = {
title: 'Form/ToggleSwitch',
component: ToggleSwitch,
args: {
checked: false,
disabled: false,
danger: false,
label: 'Уведомления',
caption: 'Сообщать об изменении статуса заказа',
},
argTypes: {
checked: { control: 'boolean' },
disabled: { control: 'boolean' },
danger: { control: 'boolean' },
label: { control: 'text' },
caption: { control: 'text' },
onCheckedChange: { action: 'onCheckedChange' },
},
parameters: {
controls: {
include: ['theme', 'checked', 'disabled', 'danger', 'label', 'caption'],
},
},
render: ({ label, caption, ...args }) => {
const [, updateArgs] = useArgs<ToggleSwitchProps>()

return (
<ToggleSwitch
{...args}
accessibilityLabel={label ? undefined : 'Уведомления'}
caption={caption || undefined}
label={label || undefined}
onCheckedChange={(checked) => {
args.onCheckedChange?.(checked)
updateArgs({ checked })
}}
/>
)
},
}

export default meta

type Story = StoryObj<ToggleSwitchProps>

const ToggleSwitchStory: Story = {}

export { ToggleSwitchStory as ToggleSwitch }
87 changes: 87 additions & 0 deletions src/components/ToggleSwitch/ToggleSwitch.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { StyleSheet } from 'react-native-unistyles'

export const styles = StyleSheet.create(
({ components: { toggleswitch }, semantic, fonts }) => ({
root: {
flexDirection: 'row',
alignItems: 'center',
alignSelf: 'flex-start',
flexShrink: 1,
gap: semantic.dimension.space[350],
minHeight: 44,
minWidth: 44,
},
track: (
checked: boolean,
active: boolean,
disabled: boolean,
focused: boolean
) => ({
width: toggleswitch.root.width,
height: toggleswitch.root.height,
flexShrink: 0,
borderRadius: toggleswitch.root.borderRadius,
borderWidth: toggleswitch.root.borderWidth,
borderColor:
focused && !disabled
? semantic.colorScheme.color.border.neutral.strong
: checked
? active && !disabled
? toggleswitch.root.checkedHoverBorderColor
: toggleswitch.root.checkedBorderColor
: active && !disabled
? toggleswitch.root.hoverBorderColor
: toggleswitch.root.borderColor,
backgroundColor: disabled
? toggleswitch.colorScheme.root.disabledBackground
: checked
? active
? toggleswitch.colorScheme.root.checkedHoverBackground
: toggleswitch.colorScheme.root.checkedBackground
: active
? toggleswitch.colorScheme.root.hoverBackground
: toggleswitch.colorScheme.root.background,
}),
handle: (checked: boolean, active: boolean, disabled: boolean) => ({
position: 'absolute',
top: toggleswitch.root.gap - toggleswitch.root.borderWidth,
left: toggleswitch.root.gap - toggleswitch.root.borderWidth,
width: toggleswitch.handle.size,
height: toggleswitch.handle.size,
borderRadius: toggleswitch.handle.borderRadius,
backgroundColor: disabled
? toggleswitch.colorScheme.handle.disabledBackground
: checked
? active
? toggleswitch.colorScheme.handle.checkedHoverBackground
: toggleswitch.colorScheme.handle.checkedBackground
: active
? toggleswitch.colorScheme.handle.hoverBackground
: toggleswitch.colorScheme.handle.background,
}),
ring: (danger: boolean) => ({
position: 'absolute',
inset:
-semantic.dimension.focusRing.width - toggleswitch.root.borderWidth,
borderRadius:
toggleswitch.root.borderRadius + semantic.dimension.focusRing.width,
borderWidth: semantic.dimension.focusRing.width,
borderColor: danger
? semantic.colorScheme.color.border.status.danger.focus
: semantic.colorScheme.color.border.neutral.focus,
}),
content: { flexShrink: 1 },
label: {
fontSize: fonts.fontSize[200],
lineHeight: fonts.lineHeight[400],
letterSpacing: fonts.letterSpacing[500],
color: semantic.colorScheme.color.fg.default,
},
caption: {
fontSize: fonts.fontSize[200],
lineHeight: fonts.lineHeight[300],
letterSpacing: fonts.letterSpacing[200],
color: semantic.colorScheme.color.fg.muted,
},
})
)
Loading
Loading