diff --git a/.storybook/storybook.requires.ts b/.storybook/storybook.requires.ts
index fa27ce80..d4f273b4 100644
--- a/.storybook/storybook.requires.ts
+++ b/.storybook/storybook.requires.ts
@@ -1,56 +1,48 @@
/* do not change this file, it is auto generated by storybook. */
///
-import { start, updateView, View, type Features } from '@storybook/react-native';
+import { start, updateView, View, type Features } from '@storybook/react-native'
-import "@storybook/addon-ondevice-notes/register";
-import "@storybook/addon-ondevice-controls/register";
-import "@storybook/addon-ondevice-actions/register";
+import '@storybook/addon-ondevice-notes/register'
+import '@storybook/addon-ondevice-controls/register'
+import '@storybook/addon-ondevice-actions/register'
const normalizedStories = [
{
- titlePrefix: "",
- directory: "./src",
- files: "**/*.stories.?(ts|tsx|js|jsx)",
- importPathMatcher: /^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.stories\.(?:ts|tsx|js|jsx)?)$/,
+ titlePrefix: '',
+ directory: './src',
+ files: '**/*.stories.?(ts|tsx|js|jsx)',
+ importPathMatcher:
+ /^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.stories\.(?:ts|tsx|js|jsx)?)$/,
req: require.context(
'../src',
true,
/^\.(?:(?:^|\/|(?:(?:(?!(?:^|\/)\.).)*?)\/)(?!\.)(?=.)[^/]*?\.stories\.(?:ts|tsx|js|jsx)?)$/
),
- }
-];
-
+ },
+]
declare global {
- var view: View;
- var STORIES: typeof normalizedStories;
+ var view: View
+ var STORIES: typeof normalizedStories
var STORYBOOK_WEBSOCKET:
| { host?: string; port?: number; secured?: boolean }
- | undefined;
- var FEATURES: Features;
+ | undefined
+ var FEATURES: Features
}
-
const annotations = [
require('./preview'),
- require("@storybook/react-native/preview")
-];
-
-globalThis.STORIES = normalizedStories;
-
-
-module?.hot?.accept?.();
+ require('@storybook/react-native/preview'),
+]
+globalThis.STORIES = normalizedStories
+module?.hot?.accept?.()
if (!globalThis.view) {
- globalThis.view = start({
- annotations,
- storyEntries: normalizedStories,
-
- });
+ globalThis.view = start({ annotations, storyEntries: normalizedStories })
} else {
- updateView(globalThis.view, annotations, normalizedStories);
+ updateView(globalThis.view, annotations, normalizedStories)
}
-export const view: View = globalThis.view;
+export const view: View = globalThis.view
diff --git a/design-tokens/input/tokens.json b/design-tokens/input/tokens.json
index f18cadbc..e447545d 100644
--- a/design-tokens/input/tokens.json
+++ b/design-tokens/input/tokens.json
@@ -1547,19 +1547,19 @@
"extSm": {
"borderRadius": "{dimension.borderRadius.400}",
"gap": "{dimension.space.200}",
- "height": "{dimension.size.800}"
+ "height": "{dimension.size.1000}"
},
"extLg": {
"borderRadius": "{dimension.borderRadius.400}",
- "gap": "{dimension.space.400}",
- "height": "{dimension.size.1200}"
+ "gap": "{dimension.space.300}",
+ "height": "3.125rem"
},
"extXlg": {
"borderRadius": "{dimension.borderRadius.400}",
- "gap": "{dimension.space.400}",
+ "gap": "{dimension.space.300}",
"iconOnlyWidth": "{dimension.size.1300}",
"paddingX": "{dimension.space.700}",
- "paddingY": "{dimension.space.600}",
+ "paddingY": "{dimension.space.400}",
"height": "{dimension.size.1300}"
},
"borderWidth": "{dimension.borderWidth.100}",
@@ -2036,13 +2036,13 @@
}
},
"root": {
- "borderRadius": "{dimension.borderRadius.400}",
+ "borderRadius": "{dimension.borderRadius.300}",
"roundedBorderRadius": "{dimension.borderRadius.max}",
"gap": "{dimension.space.200}",
"paddingX": "{dimension.space.400}",
"paddingY": "{dimension.space.200}",
- "height": "{dimension.size.1000}",
- "iconOnlyWidth": "{dimension.size.1000}",
+ "height": "{sizing.21x}",
+ "iconOnlyWidth": "{sizing.21x}",
"raisedShadow": "none",
"badgeSize": "{dimension.size.450}",
"transitionDuration": "{effects.transition.duration.200}",
@@ -2052,16 +2052,16 @@
"offset": "{dimension.focusRing.offset}"
},
"sm": {
- "fontSize": "{fonts.fontSize.200}",
- "iconOnlyWidth": "{dimension.size.800}",
+ "fontSize": "{fonts.fontSize.100}",
+ "iconOnlyWidth": "{dimension.size.1000}",
"paddingX": "{dimension.space.400}",
"paddingY": "{dimension.space.200}"
},
"lg": {
- "fontSize": "{fonts.fontSize.500}",
- "iconOnlyWidth": "{dimension.size.1200}",
+ "fontSize": "{fonts.fontSize.400}",
+ "iconOnlyWidth": "3.125rem",
"paddingX": "{dimension.space.700}",
- "paddingY": "{dimension.space.400}"
+ "paddingY": "{dimension.space.350}"
},
"label": { "fontWeight": "{fonts.fontWeight.demibold}" }
}
diff --git a/src/components/Button/BaseButton.tsx b/src/components/Button/BaseButton.tsx
deleted file mode 100644
index 5e752b27..00000000
--- a/src/components/Button/BaseButton.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import { useCallback, useState } from 'react'
-import type { GestureResponderEvent } from 'react-native'
-
-import { genericMemo } from '../../utils/genericMemo'
-
-import type { ButtonProps, ButtonVariant } from './types'
-import {
- ButtonLeftArea,
- ButtonRightArea,
- ButtonLabel,
- ButtonContainer,
-} from './utils'
-import { ButtonPressedContext } from './utils/ButtonPressedContext'
-
-export type BaseButtonComponentProps = Omit<
- ButtonProps,
- 'variant'
-> & { readonly variant: Variant }
-
-const BaseButtonComponent = ({
- size = 'base',
- shape = 'square',
- loading = false,
- variant,
- disabled = false,
- iconOnly,
- iconPosition = 'prefix',
- Icon,
- label,
- style,
- onPressIn: onPressInProp,
- onPressOut: onPressOutProp,
- ...props
-}: BaseButtonComponentProps) => {
- const isDisabled = !!disabled
- const [pressed, setPressed] = useState(false)
-
- const onPressIn = useCallback(
- (event: GestureResponderEvent) => {
- onPressInProp?.(event)
- setPressed(true)
- },
- [onPressInProp]
- )
-
- const onPressOut = useCallback(
- (event: GestureResponderEvent) => {
- onPressOutProp?.(event)
- setPressed(false)
- },
- [onPressOutProp]
- )
-
- return (
-
-
-
-
-
-
-
- )
-}
-
-export const BaseButton = genericMemo(BaseButtonComponent)
diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx
index bb3320ac..353b2b23 100644
--- a/src/components/Button/Button.tsx
+++ b/src/components/Button/Button.tsx
@@ -1,13 +1,14 @@
-import { memo, useMemo } from 'react'
+import { memo } from 'react'
-import { BaseButton } from './BaseButton'
+import { BaseButton } from './internal/BaseButton'
+import { adaptButtonProps } from './internal/deprecated/adaptButtonProps'
+import { getButtonStyles } from './internal/getButtonStyles'
import type { ButtonBaseVariant, ButtonProps } from './types'
-import { ButtonVariantContext } from './utils/ButtonVariantContext'
/**
* Button component
* @param size - button size
- * @param shape - button shape
+ * @param rounded - rounded corners
* @param loading - button loading state
* @param variant - button variant
* @param disabled - button disabled state
@@ -20,13 +21,10 @@ import { ButtonVariantContext } from './utils/ButtonVariantContext'
* @link https://www.figma.com/design/Q1BWgZ7zoV5UzlBOnjW0cM/UI-Kit--DS--v2.1?node-id=160-5223
*/
export const Button = memo>(
- ({ variant = 'primary', ...props }) => {
- const variantContextValue = useMemo(() => ({ variant }), [variant])
-
- return (
-
-
-
- )
- }
+ ({ variant = 'primary', ...props }) => (
+
+ )
)
diff --git a/src/components/Button/ButtonBadge.tsx b/src/components/Button/ButtonBadge.tsx
index 5519b079..1689b725 100644
--- a/src/components/Button/ButtonBadge.tsx
+++ b/src/components/Button/ButtonBadge.tsx
@@ -1,18 +1,19 @@
-import { memo, useMemo } from 'react'
+import { memo } from 'react'
import { View } from 'react-native'
import { StyleSheet } from 'react-native-unistyles'
import { Badge, type BadgeProps } from '../Badge'
-import { BaseButton } from './BaseButton'
+import { BaseButton } from './internal/BaseButton'
+import { adaptButtonProps } from './internal/deprecated/adaptButtonProps'
+import { getButtonStyles } from './internal/getButtonStyles'
import type { ButtonBadgeProps, ButtonBaseVariant, ButtonProps } from './types'
-import { ButtonVariantContext } from './utils/ButtonVariantContext'
/**
* Button component with badge
* @param size - button size
- * @param shape - button shape
+ * @param rounded - rounded corners
* @param loading - button loading state
* @param variant - button variant
* @param disabled - button disabled state
@@ -28,8 +29,6 @@ import { ButtonVariantContext } from './utils/ButtonVariantContext'
export const ButtonBadge = memo<
ButtonProps & ButtonBadgeProps
>(({ badgeLabel, badgeSeverity, variant = 'primary', ...props }) => {
- const variantContextValue = useMemo(() => ({ variant }), [variant])
-
const badgeCommonProps = {
severity: badgeSeverity,
testID: ButtonBadgeTestId.badge,
@@ -39,13 +38,14 @@ export const ButtonBadge = memo<
: { ...badgeCommonProps, dot: true, style: styles.badgeDot }
return (
-
-
-
-
-
-
-
+
+
+
+
+
)
})
diff --git a/src/components/Button/ButtonSeverity.tsx b/src/components/Button/ButtonSeverity.tsx
index 978588ce..831dfb94 100644
--- a/src/components/Button/ButtonSeverity.tsx
+++ b/src/components/Button/ButtonSeverity.tsx
@@ -1,19 +1,20 @@
-import { memo, useMemo } from 'react'
+import { memo } from 'react'
-import { BaseButton } from './BaseButton'
+import { BaseButton } from './internal/BaseButton'
+import { adaptButtonSeverityProps } from './internal/deprecated/adaptButtonSeverityProps'
+import { getSeverityButtonStyles } from './internal/getSeverityButtonStyles'
import type {
ButtonProps,
ButtonSeverityProps,
ButtonSeverityVariant,
} from './types'
-import { ButtonVariantContext } from './utils/ButtonVariantContext'
/**
* Button component
* @param size - button size
- * @param shape - button shape
+ * @param rounded - rounded corners
* @param loading - button loading state
- * @param variant - button variant
+ * @param appearance - button presentation
* @param disabled - button disabled state
* @param iconOnly - button with only Icon
* @param iconPosition - icon position
@@ -25,15 +26,14 @@ import { ButtonVariantContext } from './utils/ButtonVariantContext'
*/
export const ButtonSeverity = memo<
ButtonProps & ButtonSeverityProps
->(({ severity, variant = 'basic', ...props }) => {
- const variantContextValue = useMemo(
- () => ({ variant, severity }),
- [severity, variant]
- )
+>((props) => {
+ const { appearance, severity, ...buttonProps } =
+ adaptButtonSeverityProps(props)
return (
-
-
-
+
)
})
diff --git a/src/components/Button/__tests__/Button.test.tsx b/src/components/Button/__tests__/Button.test.tsx
index 024a869a..2b633160 100644
--- a/src/components/Button/__tests__/Button.test.tsx
+++ b/src/components/Button/__tests__/Button.test.tsx
@@ -4,33 +4,23 @@ import { render, userEvent } from '@testing-library/react-native'
import { Button } from '../Button'
describe('Button', () => {
- test.each(['small', 'base', 'large', 'xlarge'] as const)(
- 'отображает текст для размера %s',
- (size) => {
- const { getByText } = render()
+ test('отображает переданный текст', () => {
+ const { getByText } = render()
- expect(getByText('Button')).toBeOnTheScreen()
- }
- )
+ expect(getByText('Button')).toBeOnTheScreen()
+ })
- test.each(['prefix', 'postfix'] as const)(
- 'отображает иконку в позиции %s',
- (iconPosition) => {
- const { getAllByTestId } = render(
-
- )
+ test('отображает переданную иконку', () => {
+ const { getAllByTestId } = render(
+
+ )
- expect(getAllByTestId('Button_Icon')).not.toHaveLength(0)
- }
- )
+ expect(getAllByTestId('Button_Icon')).not.toHaveLength(0)
+ })
test('скрывает текст в режиме iconOnly', () => {
const { getAllByTestId, queryByTestId } = render(
-
+
)
expect(getAllByTestId('Button_Icon')).not.toHaveLength(0)
@@ -42,7 +32,9 @@ describe('Button', () => {
)
- expect(getByTestId('Button_ActivityIndicator')).toBeOnTheScreen()
+ expect(
+ getByTestId('Button_ActivityIndicator', { includeHiddenElements: true })
+ ).toBeOnTheScreen()
expect(queryByTestId('Button_Icon')).not.toBeOnTheScreen()
})
diff --git a/src/components/Button/__tests__/ButtonBadge.test.tsx b/src/components/Button/__tests__/ButtonBadge.test.tsx
index 9bf829c2..b433a1b7 100644
--- a/src/components/Button/__tests__/ButtonBadge.test.tsx
+++ b/src/components/Button/__tests__/ButtonBadge.test.tsx
@@ -20,4 +20,22 @@ describe('ButtonBadge', () => {
expect(getByTestId(ButtonBadgeTestId.badge)).toBeOnTheScreen()
expect(queryByText('Badge')).not.toBeOnTheScreen()
})
+
+ test.each([
+ { loading: true },
+ { disabled: true },
+ { loading: true, disabled: true },
+ ])('сохраняет бейдж при %j', (state) => {
+ const { getByText, getAllByRole } = render(
+
+ )
+
+ expect(getByText('3')).toBeOnTheScreen()
+ expect(getAllByRole('button')).toHaveLength(1)
+ })
})
diff --git a/src/components/Button/__tests__/ButtonInteraction.test.tsx b/src/components/Button/__tests__/ButtonInteraction.test.tsx
new file mode 100644
index 00000000..81af5546
--- /dev/null
+++ b/src/components/Button/__tests__/ButtonInteraction.test.tsx
@@ -0,0 +1,220 @@
+import { IconArrowDownRight } from '@tabler/icons-react-native'
+import { fireEvent, render, userEvent } from '@testing-library/react-native'
+import type { PressableProps } from 'react-native'
+
+import { Button } from '../Button'
+import { ButtonBadge } from '../ButtonBadge'
+import { ButtonSeverity } from '../ButtonSeverity'
+import type { ButtonIconPosition } from '../types'
+
+type InteractionProps = PressableProps & {
+ loading?: boolean
+ iconPosition?: ButtonIconPosition
+}
+
+describe.each([
+ {
+ name: 'Button',
+ createButton: (props: InteractionProps) => (
+
+ ),
+ },
+ {
+ name: 'ButtonBadge',
+ createButton: (props: InteractionProps) => (
+
+ ),
+ },
+ {
+ name: 'ButtonSeverity',
+ createButton: (props: InteractionProps) => (
+
+ ),
+ },
+])('$name interactions', ({ createButton }) => {
+ test.each([
+ { name: 'default', disabled: false, loading: false },
+ { name: 'disabled', disabled: true, loading: false },
+ { name: 'loading', disabled: false, loading: true },
+ { name: 'loading + disabled', disabled: true, loading: true },
+ ])(
+ 'сохраняет accessibilityState и доступность нажатия: $name',
+ async ({ disabled, loading }) => {
+ const onPress = jest.fn()
+ const { getByRole } = render(
+ createButton({
+ disabled,
+ loading,
+ onPress,
+ accessibilityState: {
+ selected: true,
+ expanded: false,
+ checked: 'mixed',
+ disabled: false,
+ busy: false,
+ },
+ })
+ )
+ const button = getByRole('button', { name: 'Сохранить' })
+
+ expect(button.props.accessibilityState).toStrictEqual({
+ selected: true,
+ expanded: false,
+ checked: 'mixed',
+ disabled: disabled || loading,
+ busy: loading,
+ })
+
+ await userEvent.setup().press(button)
+
+ expect(onPress).toHaveBeenCalledTimes(disabled || loading ? 0 : 1)
+ }
+ )
+
+ test.each(['prefix', 'postfix'] as const)(
+ 'показывает один нефокусируемый индикатор при loading + disabled в позиции %s',
+ (iconPosition) => {
+ const { getByTestId, queryByTestId, getByText } = render(
+ createButton({ loading: true, disabled: true, iconPosition })
+ )
+ const indicator = getByTestId('Button_ActivityIndicator', {
+ includeHiddenElements: true,
+ })
+
+ expect(indicator.props.accessible).toBeFalse()
+ expect(indicator.props.accessibilityElementsHidden).toBeTrue()
+ expect(indicator.props.importantForAccessibility).toBe(
+ 'no-hide-descendants'
+ )
+ expect(queryByTestId('Button_Icon')).not.toBeOnTheScreen()
+ expect(getByText('Сохранить')).toBeOnTheScreen()
+ }
+ )
+
+ test('использует переданное доступное имя и роль', () => {
+ const { getByRole } = render(
+ createButton({
+ accessibilityLabel: 'Отправить изменения',
+ accessibilityRole: 'link',
+ })
+ )
+
+ expect(getByRole('link', { name: 'Отправить изменения' })).toBeOnTheScreen()
+ })
+
+ test('передаёт события onPressIn и onPressOut', async () => {
+ const onPressIn = jest.fn()
+ const onPressOut = jest.fn()
+ const { getByRole } = render(createButton({ onPressIn, onPressOut }))
+ await userEvent.setup().press(getByRole('button'))
+
+ expect(onPressIn).toHaveBeenCalledOnce()
+ expect(onPressOut).toHaveBeenCalledOnce()
+ expect(onPressIn).toHaveBeenCalledWith(
+ expect.objectContaining({ nativeEvent: expect.any(Object) })
+ )
+ expect(onPressOut).toHaveBeenCalledWith(
+ expect.objectContaining({ nativeEvent: expect.any(Object) })
+ )
+ })
+
+ test('изменяет состояние загрузки только после обновления пропсов', async () => {
+ const onPress = jest.fn()
+ const user = userEvent.setup()
+ const { getByRole, queryByTestId, rerender } = render(
+ createButton({ onPress })
+ )
+ await user.press(getByRole('button'))
+
+ expect(
+ queryByTestId('Button_ActivityIndicator', { includeHiddenElements: true })
+ ).not.toBeOnTheScreen()
+
+ rerender(createButton({ onPress, loading: true, disabled: true }))
+
+ expect(getByRole('button').props.accessibilityState).toMatchObject({
+ disabled: true,
+ busy: true,
+ })
+
+ await user.press(getByRole('button'))
+
+ expect(onPress).toHaveBeenCalledOnce()
+
+ rerender(createButton({ onPress }))
+
+ expect(
+ queryByTestId('Button_ActivityIndicator', { includeHiddenElements: true })
+ ).not.toBeOnTheScreen()
+
+ await user.press(getByRole('button'))
+
+ expect(onPress).toHaveBeenCalledTimes(2)
+ })
+
+ test.each([
+ { name: 'не задан', hitSlop: undefined },
+ { name: 'число', hitSlop: 12 },
+ { name: 'отступы по сторонам', hitSlop: { top: 20, right: 4 } },
+ ])('передаёт hitSlop без изменений: $name', ({ hitSlop }) => {
+ const { getByRole } = render(createButton({ hitSlop }))
+
+ expect(getByRole('button').props.hitSlop).toBe(hitSlop)
+ })
+
+ test('передаёт исходное событие onLayout', () => {
+ const onLayout = jest.fn()
+ const { getByRole } = render(createButton({ onLayout }))
+ const event = {
+ nativeEvent: { layout: { x: 0, y: 0, width: 28, height: 28 } },
+ }
+
+ fireEvent(getByRole('button'), 'layout', event)
+
+ expect(onLayout).toHaveBeenCalledOnce()
+ expect(onLayout).toHaveBeenCalledWith(event)
+ })
+
+ test('передаёт состояние нажатия в пользовательскую функцию style', async () => {
+ const style = jest.fn(() => ({ opacity: 1 }))
+ const { getByRole } = render(createButton({ style }))
+ await userEvent.setup().press(getByRole('button'))
+
+ expect(style).toHaveBeenCalledWith({ pressed: true })
+ expect(style).toHaveBeenLastCalledWith({ pressed: false })
+ })
+
+ test('блокирует нажатие при включении загрузки во время pressIn', async () => {
+ const onPress = jest.fn()
+ const onPressOut = jest.fn()
+ const onPressIn = jest.fn(() => {
+ rerender(createButton({ onPress, onPressOut, loading: true }))
+ })
+ const { getByRole, getByTestId, rerender } = render(
+ createButton({ onPress, onPressIn, onPressOut })
+ )
+ await userEvent.setup().press(getByRole('button'))
+
+ expect(onPressIn).toHaveBeenCalledOnce()
+ expect(onPress).not.toHaveBeenCalled()
+ expect(
+ getByTestId('Button_ActivityIndicator', { includeHiddenElements: true })
+ ).toBeOnTheScreen()
+
+ rerender(createButton({ onPress, onPressIn: undefined, onPressOut }))
+ await userEvent.setup().press(getByRole('button'))
+
+ expect(onPress).toHaveBeenCalledOnce()
+ })
+})
diff --git a/src/components/Button/__tests__/ButtonSeverity.test.tsx b/src/components/Button/__tests__/ButtonSeverity.test.tsx
index 06c77242..657c6c12 100644
--- a/src/components/Button/__tests__/ButtonSeverity.test.tsx
+++ b/src/components/Button/__tests__/ButtonSeverity.test.tsx
@@ -3,14 +3,11 @@ import { render } from '@testing-library/react-native'
import { ButtonSeverity } from '../ButtonSeverity'
describe('ButtonSeverity', () => {
- test.each(['info', 'warning', 'danger', 'success'] as const)(
- 'отображает текст для severity=%s',
- (severity) => {
- const { getByText } = render(
-
- )
+ test('отображает переданный текст', () => {
+ const { getByText } = render(
+
+ )
- expect(getByText('Button')).toBeOnTheScreen()
- }
- )
+ expect(getByText('Button')).toBeOnTheScreen()
+ })
})
diff --git a/src/components/Button/index.ts b/src/components/Button/index.ts
index d0e5330e..2163e80b 100644
--- a/src/components/Button/index.ts
+++ b/src/components/Button/index.ts
@@ -5,4 +5,5 @@ export type {
ButtonProps,
ButtonBadgeProps,
ButtonSeverityProps,
+ ButtonSeverityAppearance,
} from './types'
diff --git a/src/components/Button/internal/BaseButton.tsx b/src/components/Button/internal/BaseButton.tsx
new file mode 100644
index 00000000..a63498c5
--- /dev/null
+++ b/src/components/Button/internal/BaseButton.tsx
@@ -0,0 +1,93 @@
+import { memo } from 'react'
+import { Pressable, type PressableStateCallbackType } from 'react-native'
+
+import type { BaseButtonProps } from '../types'
+
+import { ButtonActivityIndicator } from './components/ButtonActivityIndicator'
+import { ButtonIcon } from './components/ButtonIcon'
+import { ButtonLabel } from './components/ButtonLabel'
+import type { ButtonStyleResolver, ButtonVisualState } from './types'
+
+type BaseButtonComponentProps = Omit<
+ BaseButtonProps,
+ 'variant' | 'shape'
+> & { readonly resolveStyles: ButtonStyleResolver }
+
+const getButtonState = ({
+ loading,
+ disabled,
+ pressed,
+}: Pick, 'loading' | 'disabled'> &
+ PressableStateCallbackType): ButtonVisualState => {
+ if (loading) return 'loading'
+
+ if (disabled) return 'disabled'
+
+ return pressed ? 'pressed' : 'default'
+}
+
+export const BaseButton = memo(
+ ({
+ size = 'base',
+ rounded = false,
+ loading = false,
+ resolveStyles,
+ disabled = false,
+ iconOnly,
+ iconPosition = 'prefix',
+ Icon,
+ label,
+ style,
+ pressableRef,
+ accessibilityLabel,
+ accessibilityState,
+ onPress,
+ onLongPress,
+ ...props
+ }) => {
+ const interactionDisabled = disabled || loading
+ const getStyles = ({ pressed }: PressableStateCallbackType) => {
+ const state = getButtonState({ loading, disabled, pressed })
+
+ return resolveStyles({ size, rounded, iconOnly: !!iconOnly, state })
+ }
+
+ return (
+ [
+ getStyles(state).container,
+ typeof style === 'function' ? style(state) : style,
+ ]}
+ onLongPress={interactionDisabled ? undefined : onLongPress}
+ onPress={interactionDisabled ? undefined : onPress}
+ >
+ {(state) => {
+ const styles = getStyles(state)
+ const icon = loading ? (
+
+ ) : Icon ? (
+
+ ) : null
+
+ return (
+ <>
+ {iconPosition === 'prefix' && icon}
+ {!iconOnly && }
+ {iconPosition === 'postfix' && icon}
+ >
+ )
+ }}
+
+ )
+ }
+)
diff --git a/src/components/Button/internal/buttonStyles.ts b/src/components/Button/internal/buttonStyles.ts
new file mode 100644
index 00000000..93110e4d
--- /dev/null
+++ b/src/components/Button/internal/buttonStyles.ts
@@ -0,0 +1,149 @@
+import { StyleSheet } from 'react-native-unistyles'
+
+import type { ThemeType } from '../../../theme'
+
+import type { ButtonResolvedStyles, ButtonStyleProps } from './types'
+
+type ButtonTokens = ThemeType['components']['button']
+
+const getButtonLineHeight = (
+ fonts: ThemeType['fonts'],
+ size: ButtonStyleProps['size']
+) =>
+ ({
+ small: fonts.lineHeight[300],
+ base: fonts.lineHeight[500],
+ large: fonts.lineHeight[500],
+ xlarge: fonts.lineHeight[550],
+ })[size]
+
+type ButtonColorProps = Pick
+
+export type ButtonColorResolver<
+ Props extends ButtonColorProps = ButtonColorProps,
+> = (
+ button: ButtonTokens,
+ props: Props
+) => { backgroundColor: string; borderColor: string; color: string }
+
+const createButtonStyles = (
+ getButtonColors: ButtonColorResolver
+) =>
+ StyleSheet.create(({ components: { button }, fonts, semantic }) => ({
+ container: (props: Props) => {
+ const sizes = {
+ xlarge: {
+ paddingX: button.extend.extXlg.paddingX,
+ height: button.extend.extXlg.height,
+ gap: button.extend.extXlg.gap,
+ borderRadius: button.extend.extXlg.borderRadius,
+ },
+ large: {
+ paddingX: button.root.lg.paddingX,
+ height: button.extend.extLg.height,
+ gap: button.extend.extLg.gap,
+ borderRadius: button.extend.extLg.borderRadius,
+ },
+ base: {
+ paddingX: button.root.paddingX,
+ height: button.root.height,
+ gap: button.root.gap,
+ borderRadius: button.root.borderRadius,
+ },
+ small: {
+ paddingX: button.root.sm.paddingX,
+ height: button.extend.extSm.height,
+ gap: button.root.gap,
+ borderRadius: button.root.borderRadius,
+ },
+ }
+ const iconOnlyHeights = {
+ xlarge: button.extend.extXlg.iconOnlyWidth,
+ large: button.root.lg.iconOnlyWidth,
+ base: button.root.iconOnlyWidth,
+ small: button.root.sm.iconOnlyWidth,
+ }
+ const linkIconOnlyHeights = {
+ xlarge: button.extend.extLink.xlg.iconOnlyWidth,
+ large: button.extend.extLink.lg.iconOnlyWidth,
+ base: button.extend.extLink.base.iconOnlyWidth,
+ small: button.extend.extLink.sm.iconOnlyWidth,
+ }
+ const { size, rounded, iconOnly, variant } = props
+ const sizing = sizes[size]
+ const link = variant === 'link'
+ const height = iconOnly
+ ? link
+ ? linkIconOnlyHeights[size]
+ : iconOnlyHeights[size]
+ : undefined
+ const minHeight = link
+ ? getButtonLineHeight(fonts, size) + button.extend.extLink.paddingY * 2
+ : sizing.height
+ const colors = getButtonColors(button, props)
+
+ return {
+ flexDirection: 'row' as const,
+ justifyContent: 'center' as const,
+ alignItems: 'center' as const,
+ backgroundColor: colors.backgroundColor,
+ borderColor: colors.borderColor,
+ borderWidth: link
+ ? semantic.dimension.space.none
+ : button.extend.borderWidth,
+ borderRadius: rounded
+ ? button.root.roundedBorderRadius
+ : sizing.borderRadius,
+ paddingHorizontal:
+ iconOnly || link ? semantic.dimension.space.none : sizing.paddingX,
+ paddingVertical: semantic.dimension.space.none,
+ gap: sizing.gap,
+ height,
+ minHeight: iconOnly ? height : minHeight,
+ maxHeight: height,
+ aspectRatio: iconOnly ? 1 : undefined,
+ }
+ },
+ label: (props: Props) => {
+ const sizes = {
+ xlarge: button.root.lg.fontSize,
+ large: button.root.lg.fontSize,
+ base: fonts.fontSize[200],
+ small: button.root.sm.fontSize,
+ }
+
+ return {
+ fontWeight: fonts.fontWeight.demibold,
+ includeFontPadding: false,
+ verticalAlign: 'middle' as const,
+ fontFamily: fonts.fontFamily.heading,
+ letterSpacing: fonts.letterSpacing[500],
+ color: getButtonColors(button, props).color,
+ fontSize: sizes[props.size],
+ lineHeight: getButtonLineHeight(fonts, props.size),
+ }
+ },
+ }))
+
+export const createButtonStyleResolver = (
+ getColors: ButtonColorResolver
+) => {
+ const styles = createButtonStyles(getColors)
+
+ return (props: Props): ButtonResolvedStyles => ({
+ container: styles.container(props),
+ label: styles.label(props),
+ icon: ({ components: { button } }) => {
+ const dimension =
+ button.extend.iconSize[
+ props.size === 'small' ? 'sm' : props.size === 'base' ? 'md' : 'lg'
+ ]
+
+ return {
+ color: getColors(button, props).color,
+ width: dimension,
+ height: dimension,
+ }
+ },
+ })
+}
diff --git a/src/components/Button/utils/ButtonActivityIndicator.tsx b/src/components/Button/internal/components/ButtonActivityIndicator.tsx
similarity index 82%
rename from src/components/Button/utils/ButtonActivityIndicator.tsx
rename to src/components/Button/internal/components/ButtonActivityIndicator.tsx
index 71d241fd..dc01077c 100644
--- a/src/components/Button/utils/ButtonActivityIndicator.tsx
+++ b/src/components/Button/internal/components/ButtonActivityIndicator.tsx
@@ -2,8 +2,8 @@ import { ActivityIndicator } from 'react-native'
import { StyleSheet } from 'react-native-unistyles'
-import { genericMemo } from '../../../utils/genericMemo'
-import type { BaseButtonProps } from '../types'
+import { genericMemo } from '../../../../utils/genericMemo'
+import type { BaseButtonProps } from '../../types'
export type ButtonActivityIndicatorProps = Pick<
Required>,
@@ -17,7 +17,10 @@ const ButtonActivityIndicatorComponent = ({
return (
diff --git a/src/components/Button/internal/components/ButtonIcon.tsx b/src/components/Button/internal/components/ButtonIcon.tsx
new file mode 100644
index 00000000..f89df334
--- /dev/null
+++ b/src/components/Button/internal/components/ButtonIcon.tsx
@@ -0,0 +1,14 @@
+import { memo } from 'react'
+
+import { SvgUniversal, type SvgSource } from '../../../../utils/SvgUniversal'
+
+import type { ButtonResolvedStyles } from '../types'
+
+interface ButtonIconProps {
+ readonly Icon: SvgSource
+ readonly uniProps: ButtonResolvedStyles['icon']
+}
+
+export const ButtonIcon = memo(({ Icon, uniProps }) => (
+
+))
diff --git a/src/components/Button/internal/components/ButtonLabel.tsx b/src/components/Button/internal/components/ButtonLabel.tsx
new file mode 100644
index 00000000..b954b99e
--- /dev/null
+++ b/src/components/Button/internal/components/ButtonLabel.tsx
@@ -0,0 +1,13 @@
+import { memo } from 'react'
+import { Text, type StyleProp, type TextStyle } from 'react-native'
+
+interface ButtonLabelProps {
+ readonly label?: string
+ readonly style: StyleProp
+}
+
+export const ButtonLabel = memo(({ label, style }) => (
+
+ {label}
+
+))
diff --git a/src/components/Button/internal/deprecated/adaptButtonProps.ts b/src/components/Button/internal/deprecated/adaptButtonProps.ts
new file mode 100644
index 00000000..67cd53c6
--- /dev/null
+++ b/src/components/Button/internal/deprecated/adaptButtonProps.ts
@@ -0,0 +1,9 @@
+import type { ButtonShape } from '../../types'
+
+export const adaptButtonProps = <
+ Props extends { rounded?: boolean; shape?: ButtonShape },
+>({
+ shape,
+ rounded,
+ ...props
+}: Props) => ({ ...props, rounded: rounded ?? shape === 'circle' })
diff --git a/src/components/Button/internal/deprecated/adaptButtonSeverityProps.ts b/src/components/Button/internal/deprecated/adaptButtonSeverityProps.ts
new file mode 100644
index 00000000..a671fd6e
--- /dev/null
+++ b/src/components/Button/internal/deprecated/adaptButtonSeverityProps.ts
@@ -0,0 +1,24 @@
+import type {
+ ButtonSeverityAppearance,
+ ButtonSeverityVariant,
+ ButtonShape,
+} from '../../types'
+
+import { adaptButtonProps } from './adaptButtonProps'
+
+export const adaptButtonSeverityProps = <
+ Props extends {
+ appearance?: ButtonSeverityAppearance
+ variant?: ButtonSeverityVariant
+ rounded?: boolean
+ shape?: ButtonShape
+ },
+>({
+ appearance,
+ variant,
+ ...props
+}: Props) => ({
+ ...adaptButtonProps(props),
+ appearance:
+ appearance ?? (variant === 'basic' ? 'filled' : variant) ?? 'filled',
+})
diff --git a/src/components/Button/internal/getButtonStyles.ts b/src/components/Button/internal/getButtonStyles.ts
new file mode 100644
index 00000000..df2d5491
--- /dev/null
+++ b/src/components/Button/internal/getButtonStyles.ts
@@ -0,0 +1,62 @@
+import type { ButtonBaseVariant } from '../types'
+
+import {
+ createButtonStyleResolver,
+ type ButtonColorResolver,
+} from './buttonStyles'
+import type { ButtonStyleResolver, ButtonAppearanceProps } from './types'
+
+const getButtonColors: ButtonColorResolver = (
+ button,
+ props
+) => {
+ if (props.state === 'disabled' || props.state === 'loading') {
+ return {
+ backgroundColor:
+ props.variant === 'link'
+ ? 'transparent'
+ : button.extend.disabledBackground,
+ borderColor: 'transparent',
+ color: button.extend.disabledColor,
+ }
+ }
+
+ const { variant, state } = props
+ const pressed = state === 'pressed'
+
+ if (variant === 'link') {
+ return {
+ backgroundColor: button.extend.extLink.background,
+ borderColor: 'transparent',
+ color: pressed
+ ? button.colorScheme.link.activeColor
+ : button.colorScheme.link.color,
+ }
+ }
+
+ if (variant === 'text') {
+ const tokens = button.colorScheme.text.primary
+
+ return {
+ backgroundColor: pressed ? tokens.activeBackground : 'transparent',
+ borderColor: 'transparent',
+ color: tokens.color,
+ }
+ }
+
+ const key = variant === 'tertiary' ? 'contrast' : variant
+ const tokens = button.colorScheme.root[key]
+
+ return {
+ backgroundColor: pressed ? tokens.activeBackground : tokens.background,
+ borderColor: pressed ? tokens.activeBorderColor : tokens.borderColor,
+ color: pressed ? tokens.activeColor : tokens.color,
+ }
+}
+
+const resolveStyles = createButtonStyleResolver(getButtonColors)
+
+export const getButtonStyles =
+ (variant: ButtonBaseVariant): ButtonStyleResolver =>
+ (props) =>
+ resolveStyles({ ...props, variant })
diff --git a/src/components/Button/internal/getSeverityButtonStyles.ts b/src/components/Button/internal/getSeverityButtonStyles.ts
new file mode 100644
index 00000000..cf170672
--- /dev/null
+++ b/src/components/Button/internal/getSeverityButtonStyles.ts
@@ -0,0 +1,57 @@
+import type { ButtonSeverityAppearance, ButtonSeverity } from '../types'
+
+import {
+ createButtonStyleResolver,
+ type ButtonColorResolver,
+} from './buttonStyles'
+import type {
+ ButtonStyleResolver,
+ SeverityButtonAppearanceProps,
+} from './types'
+
+const getSeverityButtonColors: ButtonColorResolver<
+ SeverityButtonAppearanceProps
+> = (button, props) => {
+ if (props.state === 'disabled' || props.state === 'loading') {
+ return {
+ backgroundColor: button.extend.disabledBackground,
+ borderColor: 'transparent',
+ color: button.extend.disabledColor,
+ }
+ }
+
+ const { variant, severity, state } = props
+ const key = severity === 'warning' ? 'warn' : severity
+ const pressed = state === 'pressed'
+
+ if (variant === 'outlined' || variant === 'text') {
+ const tokens = button.colorScheme[variant][key]
+
+ return {
+ backgroundColor: pressed ? tokens.activeBackground : 'transparent',
+ borderColor:
+ variant === 'outlined'
+ ? button.colorScheme.outlined[key].borderColor
+ : 'transparent',
+ color: tokens.color,
+ }
+ }
+
+ const tokens = button.colorScheme.root[key]
+
+ return {
+ backgroundColor: pressed ? tokens.activeBackground : tokens.background,
+ borderColor: pressed ? tokens.activeBorderColor : tokens.borderColor,
+ color: pressed ? tokens.activeColor : tokens.color,
+ }
+}
+
+const resolveStyles = createButtonStyleResolver(getSeverityButtonColors)
+
+export const getSeverityButtonStyles =
+ (
+ appearance: ButtonSeverityAppearance,
+ severity: ButtonSeverity
+ ): ButtonStyleResolver =>
+ (props) =>
+ resolveStyles({ ...props, variant: appearance, severity })
diff --git a/src/components/Button/internal/types.ts b/src/components/Button/internal/types.ts
new file mode 100644
index 00000000..397b632e
--- /dev/null
+++ b/src/components/Button/internal/types.ts
@@ -0,0 +1,47 @@
+import type { TextStyle, ViewStyle } from 'react-native'
+
+import type { ThemeType } from '../../../theme'
+
+import type {
+ ButtonBaseVariant,
+ ButtonSeverity,
+ ButtonSeverityAppearance,
+ ButtonSize,
+} from '../types'
+
+export type ButtonVisualVariant = ButtonBaseVariant | ButtonSeverityAppearance
+export type ButtonVisualState = 'default' | 'pressed' | 'disabled' | 'loading'
+
+export interface ButtonStyleProps {
+ variant: ButtonVisualVariant
+ severity?: ButtonSeverity
+ state: ButtonVisualState
+ size: ButtonSize
+ rounded: boolean
+ iconOnly: boolean
+}
+
+export type ButtonLayoutProps = Pick<
+ ButtonStyleProps,
+ 'size' | 'rounded' | 'iconOnly' | 'state'
+>
+
+export interface ButtonResolvedStyles {
+ container: ViewStyle
+ label: TextStyle
+ icon: (theme: ThemeType) => { color: string; width: number; height: number }
+}
+
+export type ButtonStyleResolver = (
+ props: ButtonLayoutProps
+) => ButtonResolvedStyles
+
+export type ButtonAppearanceProps = Omit<
+ ButtonStyleProps,
+ 'variant' | 'severity'
+> & { variant: ButtonBaseVariant }
+
+export type SeverityButtonAppearanceProps = Omit<
+ ButtonStyleProps,
+ 'variant' | 'severity'
+> & { variant: ButtonSeverityAppearance; severity: ButtonSeverity }
diff --git a/src/components/Button/Button.stories.tsx b/src/components/Button/stories/Button.stories.tsx
similarity index 84%
rename from src/components/Button/Button.stories.tsx
rename to src/components/Button/stories/Button.stories.tsx
index 24dac296..8cffca5f 100644
--- a/src/components/Button/Button.stories.tsx
+++ b/src/components/Button/stories/Button.stories.tsx
@@ -5,10 +5,10 @@ import {
} from '@tabler/icons-react-native'
import { StyleSheet, View } from 'react-native'
-import { Body } from '../Typography'
+import { Body } from '../../Typography'
-import { Button } from './Button'
-import type { ButtonBaseVariant, ButtonProps } from './types'
+import { Button } from '../Button'
+import type { ButtonBaseVariant, ButtonProps } from '../types'
const Icons = { IconArrowDownRight, IconArrowDownLeft, undefined }
const styles = StyleSheet.create({
@@ -17,11 +17,11 @@ const styles = StyleSheet.create({
})
const meta: Meta = {
- title: 'Button',
+ title: 'Button/Button',
component: Button,
args: {
size: 'base',
- shape: 'square',
+ rounded: false,
variant: 'primary',
label: 'Button',
loading: false,
@@ -30,7 +30,7 @@ const meta: Meta = {
},
argTypes: {
size: { control: 'radio', options: ['small', 'base', 'large', 'xlarge'] },
- shape: { control: 'radio', options: ['square', 'circle'] },
+ rounded: { control: 'boolean' },
variant: {
control: 'radio',
options: ['primary', 'secondary', 'tertiary', 'text', 'link'],
@@ -41,7 +41,7 @@ const meta: Meta = {
onPress: { action: 'OnPress' },
Icon: { control: 'select', options: Object.keys(Icons), mapping: Icons },
},
- parameters: { controls: { exclude: ['iconOnly'] } },
+ parameters: { controls: { exclude: ['iconOnly', 'shape'] } },
render: ({
iconOnly: _iconOnly,
Icon,
@@ -58,6 +58,7 @@ const meta: Meta = {
const iconOnlyButtonProps: ButtonProps = {
...args,
iconOnly: true,
+ accessibilityLabel: args.accessibilityLabel ?? label,
Icon: Icon ?? IconArrowDownRight,
}
diff --git a/src/components/Button/ButtonBadge.stories.tsx b/src/components/Button/stories/ButtonBadge.stories.tsx
similarity index 86%
rename from src/components/Button/ButtonBadge.stories.tsx
rename to src/components/Button/stories/ButtonBadge.stories.tsx
index 1f776b2f..302ae5ba 100644
--- a/src/components/Button/ButtonBadge.stories.tsx
+++ b/src/components/Button/stories/ButtonBadge.stories.tsx
@@ -5,10 +5,10 @@ import {
} from '@tabler/icons-react-native'
import { StyleSheet, View } from 'react-native'
-import { Body } from '../Typography'
+import { Body } from '../../Typography'
-import { ButtonBadge } from './ButtonBadge'
-import type { ButtonBadgeProps, ButtonBaseVariant, ButtonProps } from './types'
+import { ButtonBadge } from '../ButtonBadge'
+import type { ButtonBadgeProps, ButtonBaseVariant, ButtonProps } from '../types'
const Icons = { IconArrowDownRight, IconArrowDownLeft, undefined }
const styles = StyleSheet.create({
@@ -17,11 +17,11 @@ const styles = StyleSheet.create({
})
const meta: Meta = {
- title: 'Button/Badge',
+ title: 'Button/ButtonBadge',
component: ButtonBadge,
args: {
size: 'base',
- shape: 'square',
+ rounded: false,
variant: 'primary',
label: 'Button',
loading: false,
@@ -32,7 +32,7 @@ const meta: Meta = {
},
argTypes: {
size: { control: 'radio', options: ['small', 'base', 'large', 'xlarge'] },
- shape: { control: 'radio', options: ['square', 'circle'] },
+ rounded: { control: 'boolean' },
variant: {
control: 'radio',
options: ['primary', 'secondary', 'tertiary', 'text', 'link'],
@@ -47,7 +47,7 @@ const meta: Meta = {
},
Icon: { control: 'select', options: Object.keys(Icons), mapping: Icons },
},
- parameters: { controls: { exclude: ['iconOnly'] } },
+ parameters: { controls: { exclude: ['iconOnly', 'shape'] } },
render: ({
iconOnly: _iconOnly,
Icon,
@@ -65,6 +65,7 @@ const meta: Meta = {
ButtonBadgeProps = {
...args,
iconOnly: true,
+ accessibilityLabel: args.accessibilityLabel ?? label,
Icon: Icon ?? IconArrowDownRight,
}
@@ -89,4 +90,4 @@ type Story = StoryObj
const ButtonStory: Story = { args: {}, argTypes: {} }
-export { ButtonStory as Badge }
+export { ButtonStory as ButtonBadge }
diff --git a/src/components/Button/ButtonSeverity.stories.tsx b/src/components/Button/stories/ButtonSeverity.stories.tsx
similarity index 80%
rename from src/components/Button/ButtonSeverity.stories.tsx
rename to src/components/Button/stories/ButtonSeverity.stories.tsx
index 9396cd0f..ff0f1ebe 100644
--- a/src/components/Button/ButtonSeverity.stories.tsx
+++ b/src/components/Button/stories/ButtonSeverity.stories.tsx
@@ -5,14 +5,14 @@ import {
} from '@tabler/icons-react-native'
import { StyleSheet, View } from 'react-native'
-import { Body } from '../Typography'
+import { Body } from '../../Typography'
-import { ButtonSeverity } from './ButtonSeverity'
+import { ButtonSeverity } from '../ButtonSeverity'
import type {
ButtonProps,
ButtonSeverityProps,
ButtonSeverityVariant,
-} from './types'
+} from '../types'
const Icons = { IconArrowDownRight, IconArrowDownLeft, undefined }
const styles = StyleSheet.create({
@@ -21,12 +21,12 @@ const styles = StyleSheet.create({
})
const meta: Meta = {
- title: 'Button/Severity',
+ title: 'Button/ButtonSeverity',
component: ButtonSeverity,
args: {
size: 'base',
- shape: 'square',
- variant: 'basic',
+ rounded: false,
+ appearance: 'filled',
label: 'Button',
loading: false,
disabled: false,
@@ -35,8 +35,8 @@ const meta: Meta = {
},
argTypes: {
size: { control: 'radio', options: ['small', 'base', 'large', 'xlarge'] },
- shape: { control: 'radio', options: ['square', 'circle'] },
- variant: { control: 'radio', options: ['basic', 'outlined', 'text'] },
+ rounded: { control: 'boolean' },
+ appearance: { control: 'radio', options: ['filled', 'outlined', 'text'] },
loading: { control: 'boolean' },
disabled: { control: 'boolean' },
iconPosition: { control: 'radio', options: ['prefix', 'postfix'] },
@@ -47,7 +47,7 @@ const meta: Meta = {
},
Icon: { control: 'select', options: Object.keys(Icons), mapping: Icons },
},
- parameters: { controls: { exclude: ['iconOnly'] } },
+ parameters: { controls: { exclude: ['iconOnly', 'shape', 'variant'] } },
render: ({
iconOnly: _iconOnly,
Icon,
@@ -61,6 +61,7 @@ const meta: Meta = {
ButtonSeverityProps = {
...args,
iconOnly: true,
+ accessibilityLabel: args.accessibilityLabel ?? label,
Icon: Icon ?? IconArrowDownRight,
}
@@ -85,4 +86,4 @@ type Story = StoryObj
const ButtonStory: Story = { args: {}, argTypes: {} }
-export { ButtonStory as Severity }
+export { ButtonStory as ButtonSeverity }
diff --git a/src/components/Button/types.ts b/src/components/Button/types.ts
index 66b874c9..9d9b8f43 100644
--- a/src/components/Button/types.ts
+++ b/src/components/Button/types.ts
@@ -11,6 +11,7 @@ export type ButtonBaseVariant =
| 'text'
| 'link'
export type ButtonSeverityVariant = 'basic' | 'outlined' | 'text'
+export type ButtonSeverityAppearance = 'filled' | 'outlined' | 'text'
export type ButtonVariant = ButtonBaseVariant | ButtonSeverityVariant
export type ButtonSize = 'xlarge' | 'large' | 'base' | 'small'
export type ButtonShape = 'square' | 'circle'
@@ -20,44 +21,49 @@ export interface BaseButtonProps<
Variant extends ButtonVariant,
> extends PressableProps {
/**
- * Controls button size
+ * Размер кнопки
* @default 'base'
*/
size?: ButtonSize
/**
- * Controls button shape
- * @default 'square'
+ * Форма кнопки.
+ * @deprecated Используйте rounded. Удаляется в версии 2.0
*/
shape?: ButtonShape
/**
- * Controls button loading state
+ * Скругление углов кнопки
+ * @default false
+ */
+ rounded?: boolean
+ /**
+ * Состояние загрузки кнопки
* @default false
*/
loading?: boolean
/**
- * Button visual presentation type
+ * Вариант оформления кнопки
*/
variant?: Variant
/**
- * Icon position
+ * Положение иконки относительно текста
* @default 'prefix'
*/
iconPosition?: ButtonIconPosition
/**
- * Controls icon only button variant
+ * Кнопка только с иконкой, без текста
*/
iconOnly?: unknown
/**
- * SVG icon
+ * SVG-иконка
* @default undefined
*/
Icon?: SvgSource
/**
- * Label in button
+ * Текст кнопки
*/
label?: string
/**
- * Ref for the pressable component
+ * Ссылка на компонент Pressable
*/
pressableRef?: Ref
}
@@ -72,6 +78,10 @@ export interface IconTextButton<
export interface IconOnlyButtonProps<
Variant extends ButtonVariant,
> extends BaseButtonProps {
+ /**
+ * До версии 2.0 необязательное для совместимости; в 2.0 станет обязательным
+ */
+ accessibilityLabel?: string
Icon: SvgSource
iconOnly: true
iconPosition?: never
@@ -86,21 +96,28 @@ export type ButtonSeverity = 'info' | 'success' | 'warning' | 'danger'
export interface ButtonSeverityProps {
/**
- * Controls severity button styling variant
+ * Семантический цвет кнопки
*/
severity: ButtonSeverity
+ /**
+ * @default 'filled'
+ */
+ appearance?: ButtonSeverityAppearance
+ /**
+ * @deprecated Используйте appearance. Удаляется в версии 2.0
+ */
variant?: ButtonSeverityVariant
}
export interface ButtonBadgeProps {
/**
- * Controls color of Badge component
+ * Цвет бейджа.
*
* @type {BadgeSeverity}
*/
badgeSeverity: BadgeSeverity
/**
- * Текст внутри бейджа. Если не указан, то бейдж будет в форме точки.
+ * Текст внутри бейджа. Если не указан, то бейдж будет в форме точки
*/
badgeLabel?: string
}
diff --git a/src/components/Button/utils/ButtonContainer.tsx b/src/components/Button/utils/ButtonContainer.tsx
deleted file mode 100644
index a7eae9f5..00000000
--- a/src/components/Button/utils/ButtonContainer.tsx
+++ /dev/null
@@ -1,510 +0,0 @@
-/* eslint-disable max-lines, max-lines-per-function */
-import { type ReactNode, useContext } from 'react'
-import { Pressable, type PressableStateCallbackType } from 'react-native'
-
-import { StyleSheet } from 'react-native-unistyles'
-
-import { genericMemo } from '../../../utils/genericMemo'
-import type { BaseButtonProps, ButtonShape, ButtonSize } from '../types'
-
-import { ButtonPressedContext } from './ButtonPressedContext'
-import { ButtonVariantContext } from './ButtonVariantContext'
-
-export interface ButtonContainerComponentProps {
- readonly size: ButtonSize
- readonly shape: ButtonShape
- readonly disabled: boolean
- readonly loading: boolean
- readonly isIconOnly: boolean
- readonly style?: BaseButtonProps['style']
- readonly pressableRef?: BaseButtonProps['pressableRef']
- readonly children: ReactNode
- readonly onPressIn?: BaseButtonProps['onPressIn']
- readonly onPressOut?: BaseButtonProps['onPressOut']
- [key: string]: unknown
-}
-
-const ButtonContainerComponent = ({
- style,
- size,
- disabled,
- loading,
- shape,
- isIconOnly,
- children,
- pressableRef,
- onPressIn,
- onPressOut,
- ...props
-}: ButtonContainerComponentProps) => {
- const pressed = useContext(ButtonPressedContext)
- const { variant, severity } = useContext(ButtonVariantContext)
-
- buttonContainerStyles.useVariants({
- size,
- shape,
- variant,
- severity,
- pressed: pressed ? 'true' : 'false',
- disabled: disabled || loading ? 'true' : 'false',
- iconOnly: isIconOnly ? 'true' : 'false',
- })
-
- const resolvedStyle =
- typeof style === 'function'
- ? (state: PressableStateCallbackType) => [
- buttonContainerStyles.container,
- isIconOnly && buttonContainerStyles.iconOnly,
- style(state),
- ]
- : [
- buttonContainerStyles.container,
- isIconOnly && buttonContainerStyles.iconOnly,
- ...(style ? [style] : []),
- ]
-
- return (
-
- {children}
-
- )
-}
-
-export const ButtonContainer = genericMemo(ButtonContainerComponent)
-
-// `transparent` здесь — отсутствие фона или границы, а не цвет дизайн-системы:
-// прозрачность не выражена ни semantic-токеном, ни свойствами button.extend.extText.
-const buttonContainerStyles = StyleSheet.create(
- ({ components: { button }, semantic }) => ({
- container: {
- flexDirection: 'row',
- justifyContent: 'center',
- alignItems: 'center',
- borderWidth: button.extend.borderWidth,
- variants: {
- size: {
- xlarge: {
- paddingHorizontal: button.extend.extXlg.paddingX,
- paddingVertical: button.extend.extXlg.paddingY,
- height: button.extend.extXlg.height,
- minHeight: button.extend.extXlg.height,
- maxHeight: button.extend.extXlg.height,
- gap: button.extend.extXlg.gap,
- borderRadius: button.extend.extXlg.borderRadius,
- },
- large: {
- paddingHorizontal: button.root.lg.paddingX,
- paddingVertical: button.root.lg.paddingY,
- height: button.extend.extLg.height,
- minHeight: button.extend.extLg.height,
- maxHeight: button.extend.extLg.height,
- gap: button.extend.extLg.gap,
- borderRadius: button.extend.extLg.borderRadius,
- },
- base: {
- paddingHorizontal: button.root.paddingX,
- paddingVertical: button.root.paddingY,
- height: 'auto',
- gap: button.root.gap,
- borderRadius: button.extend.extSm.borderRadius,
- },
- small: {
- paddingHorizontal: button.root.sm.paddingX,
- paddingVertical: button.root.sm.paddingY,
- height: 'auto',
- gap: button.root.gap,
- borderRadius: button.root.borderRadius,
- },
- },
- shape: {
- square: {},
- circle: { borderRadius: button.root.roundedBorderRadius },
- },
- variant: {
- primary: {
- borderColor: button.colorScheme.root.primary.borderColor,
- backgroundColor: button.colorScheme.root.primary.background,
- },
- secondary: {
- borderColor: button.colorScheme.root.secondary.borderColor,
- backgroundColor: button.colorScheme.root.secondary.background,
- },
- tertiary: {
- borderColor: button.colorScheme.root.contrast.borderColor,
- backgroundColor: button.colorScheme.root.contrast.background,
- },
- text: { borderColor: 'transparent', backgroundColor: 'transparent' },
- link: {
- paddingHorizontal: semantic.dimension.space.none,
- paddingVertical: semantic.dimension.space[100],
- height: 'auto',
- minHeight: 'auto',
- borderColor: 'transparent',
- backgroundColor: button.extend.extLink.background,
- },
- basic: {
- borderColor: button.colorScheme.root.primary.borderColor,
- backgroundColor: button.colorScheme.root.primary.background,
- },
- outlined: {
- borderColor: button.colorScheme.outlined.primary.borderColor,
- backgroundColor: 'transparent',
- },
- },
- severity: { info: {}, success: {}, warning: {}, danger: {} },
- pressed: { true: {}, false: {} },
- disabled: {
- true: {
- backgroundColor: button.extend.disabledBackground,
- borderColor: 'transparent',
- },
- false: {},
- },
- iconOnly: { true: {}, false: {} },
- },
- compoundVariants: [
- // basic button pressed
- {
- variant: 'primary',
- pressed: 'true',
- styles: {
- borderColor: button.colorScheme.root.primary.hoverBorderColor,
- backgroundColor: button.colorScheme.root.primary.hoverBackground,
- },
- },
- {
- variant: 'secondary',
- pressed: 'true',
- styles: {
- borderColor: button.colorScheme.root.secondary.hoverBorderColor,
- backgroundColor: button.colorScheme.root.secondary.hoverBackground,
- },
- },
- {
- variant: 'tertiary',
- pressed: 'true',
- styles: {
- borderColor: button.colorScheme.root.contrast.hoverBorderColor,
- backgroundColor: button.colorScheme.root.contrast.hoverBackground,
- },
- },
- {
- variant: 'text',
- pressed: 'true',
- styles: {
- borderColor: 'transparent',
- backgroundColor: button.colorScheme.text.primary.hoverBackground,
- },
- },
- {
- variant: 'link',
- disabled: 'true',
- styles: {
- borderColor: 'transparent',
- backgroundColor: 'transparent',
- },
- },
-
- // severity container (override placeholder variant styles)
- {
- variant: 'basic',
- severity: 'info',
- styles: {
- borderColor: button.colorScheme.root.info.borderColor,
- backgroundColor: button.colorScheme.root.info.background,
- },
- },
- {
- variant: 'outlined',
- severity: 'info',
- styles: {
- borderColor: button.colorScheme.outlined.info.borderColor,
- backgroundColor: 'transparent',
- },
- },
- {
- variant: 'text',
- severity: 'info',
- styles: {
- borderColor: 'transparent',
- backgroundColor: 'transparent',
- },
- },
- {
- variant: 'basic',
- severity: 'success',
- styles: {
- borderColor: button.colorScheme.root.success.borderColor,
- backgroundColor: button.colorScheme.root.success.background,
- },
- },
- {
- variant: 'outlined',
- severity: 'success',
- styles: {
- borderColor: button.colorScheme.outlined.success.borderColor,
- backgroundColor: 'transparent',
- },
- },
- {
- variant: 'text',
- severity: 'success',
- styles: {
- borderColor: 'transparent',
- backgroundColor: 'transparent',
- },
- },
- {
- variant: 'basic',
- severity: 'warning',
- styles: {
- borderColor: button.colorScheme.root.warn.borderColor,
- backgroundColor: button.colorScheme.root.warn.background,
- },
- },
- {
- variant: 'outlined',
- severity: 'warning',
- styles: {
- borderColor: button.colorScheme.outlined.warn.borderColor,
- backgroundColor: 'transparent',
- },
- },
- {
- variant: 'text',
- severity: 'warning',
- styles: {
- borderColor: 'transparent',
- backgroundColor: 'transparent',
- },
- },
- {
- variant: 'basic',
- severity: 'danger',
- styles: {
- borderColor: button.colorScheme.root.danger.borderColor,
- backgroundColor: button.colorScheme.root.danger.background,
- },
- },
- {
- variant: 'outlined',
- severity: 'danger',
- styles: {
- borderColor: button.colorScheme.outlined.danger.borderColor,
- backgroundColor: 'transparent',
- },
- },
- {
- variant: 'text',
- severity: 'danger',
- styles: {
- borderColor: 'transparent',
- backgroundColor: 'transparent',
- },
- },
-
- // severity pressed
- {
- variant: 'basic',
- severity: 'info',
- pressed: 'true',
- styles: {
- borderColor: button.colorScheme.root.info.hoverBorderColor,
- backgroundColor: button.colorScheme.root.info.hoverBackground,
- },
- },
- {
- variant: 'outlined',
- severity: 'info',
- pressed: 'true',
- styles: {
- borderColor: button.colorScheme.outlined.info.borderColor,
- backgroundColor: button.colorScheme.outlined.info.hoverBackground,
- },
- },
- {
- variant: 'text',
- severity: 'info',
- pressed: 'true',
- styles: {
- borderColor: 'transparent',
- backgroundColor: button.colorScheme.text.info.hoverBackground,
- },
- },
- {
- variant: 'basic',
- severity: 'success',
- pressed: 'true',
- styles: {
- borderColor: button.colorScheme.root.success.hoverBorderColor,
- backgroundColor: button.colorScheme.root.success.hoverBackground,
- },
- },
- {
- variant: 'outlined',
- severity: 'success',
- pressed: 'true',
- styles: {
- borderColor: button.colorScheme.outlined.success.borderColor,
- backgroundColor:
- button.colorScheme.outlined.success.hoverBackground,
- },
- },
- {
- variant: 'text',
- severity: 'success',
- pressed: 'true',
- styles: {
- borderColor: 'transparent',
- backgroundColor: button.colorScheme.text.success.hoverBackground,
- },
- },
- {
- variant: 'basic',
- severity: 'warning',
- pressed: 'true',
- styles: {
- borderColor: button.colorScheme.root.warn.hoverBorderColor,
- backgroundColor: button.colorScheme.root.warn.hoverBackground,
- },
- },
- {
- variant: 'outlined',
- severity: 'warning',
- pressed: 'true',
- styles: {
- borderColor: button.colorScheme.outlined.warn.borderColor,
- backgroundColor: button.colorScheme.outlined.warn.hoverBackground,
- },
- },
- {
- variant: 'text',
- severity: 'warning',
- pressed: 'true',
- styles: {
- borderColor: 'transparent',
- backgroundColor: button.colorScheme.text.warn.hoverBackground,
- },
- },
- {
- variant: 'basic',
- severity: 'danger',
- pressed: 'true',
- styles: {
- borderColor: button.colorScheme.root.danger.hoverBorderColor,
- backgroundColor: button.colorScheme.root.danger.hoverBackground,
- },
- },
- {
- variant: 'outlined',
- severity: 'danger',
- pressed: 'true',
- styles: {
- borderColor: button.colorScheme.outlined.danger.borderColor,
- backgroundColor: button.colorScheme.outlined.danger.hoverBackground,
- },
- },
- {
- variant: 'text',
- severity: 'danger',
- pressed: 'true',
- styles: {
- borderColor: 'transparent',
- backgroundColor: button.colorScheme.text.danger.hoverBackground,
- },
- },
-
- {
- iconOnly: 'true',
- size: 'xlarge',
- styles: {
- height: button.extend.extXlg.iconOnlyWidth,
- minHeight: button.extend.extXlg.iconOnlyWidth,
- maxHeight: button.extend.extXlg.iconOnlyWidth,
- },
- },
- {
- iconOnly: 'true',
- size: 'large',
- styles: {
- height: button.root.lg.iconOnlyWidth,
- minHeight: button.root.lg.iconOnlyWidth,
- maxHeight: button.root.lg.iconOnlyWidth,
- },
- },
- {
- iconOnly: 'true',
- size: 'base',
- styles: {
- height: button.root.iconOnlyWidth,
- minHeight: button.root.iconOnlyWidth,
- maxHeight: button.root.iconOnlyWidth,
- },
- },
- {
- iconOnly: 'true',
- size: 'small',
- styles: {
- height: button.root.sm.iconOnlyWidth,
- minHeight: button.root.sm.iconOnlyWidth,
- maxHeight: button.root.sm.iconOnlyWidth,
- },
- },
-
- {
- variant: 'link',
- iconOnly: 'true',
- size: 'xlarge',
- styles: {
- height: button.extend.extLink.xlg.iconOnlyWidth,
- minHeight: button.extend.extLink.xlg.iconOnlyWidth,
- maxHeight: button.extend.extLink.xlg.iconOnlyWidth,
- },
- },
- {
- variant: 'link',
- iconOnly: 'true',
- size: 'large',
- styles: {
- height: button.extend.extLink.lg.iconOnlyWidth,
- minHeight: button.extend.extLink.lg.iconOnlyWidth,
- maxHeight: button.extend.extLink.lg.iconOnlyWidth,
- },
- },
- {
- variant: 'link',
- iconOnly: 'true',
- size: 'base',
- styles: {
- height: button.extend.extLink.base.iconOnlyWidth,
- minHeight: button.extend.extLink.base.iconOnlyWidth,
- maxHeight: button.extend.extLink.base.iconOnlyWidth,
- },
- },
- {
- variant: 'link',
- iconOnly: 'true',
- size: 'small',
- styles: {
- height: button.extend.extLink.sm.iconOnlyWidth,
- minHeight: button.extend.extLink.sm.iconOnlyWidth,
- maxHeight: button.extend.extLink.sm.iconOnlyWidth,
- },
- },
- ],
- },
- iconOnly: {
- aspectRatio: 1,
- paddingHorizontal: semantic.dimension.space.none,
- paddingVertical: semantic.dimension.space.none,
- },
- })
-)
diff --git a/src/components/Button/utils/ButtonIcon.tsx b/src/components/Button/utils/ButtonIcon.tsx
deleted file mode 100644
index 6be2a77f..00000000
--- a/src/components/Button/utils/ButtonIcon.tsx
+++ /dev/null
@@ -1,158 +0,0 @@
-import { useContext } from 'react'
-
-import { StyleSheet } from 'react-native-unistyles'
-
-import { SvgUniversal } from '../../../utils/SvgUniversal'
-import { genericMemo } from '../../../utils/genericMemo'
-import type { BaseButtonProps, ButtonSize } from '../types'
-
-import { ButtonPressedContext } from './ButtonPressedContext'
-import { ButtonVariantContext } from './ButtonVariantContext'
-
-export interface ButtonIconProps {
- readonly size: ButtonSize
- readonly disabled: boolean
- readonly loading: boolean
- readonly Icon?: BaseButtonProps['Icon']
-}
-
-const ButtonIconComponent = ({
- size,
- disabled,
- loading,
- Icon,
-}: ButtonIconProps) => {
- const pressed = useContext(ButtonPressedContext)
- const { variant, severity } = useContext(ButtonVariantContext)
-
- buttonIconStyles.useVariants({
- size,
- variant,
- severity,
- pressed: pressed ? 'true' : 'false',
- disabled: disabled || loading ? 'true' : 'false',
- })
-
- if (!Icon) {
- return null
- }
-
- return (
-
- )
-}
-
-export const ButtonIcon = genericMemo(ButtonIconComponent)
-
-const buttonIconStyles = StyleSheet.create(
- ({ components: { button }, semantic }) => ({
- icon: {
- variants: {
- size: {
- xlarge: {
- height: button.extend.iconSize.lg,
- width: button.extend.iconSize.lg,
- },
- large: {
- height: button.extend.iconSize.lg,
- width: button.extend.iconSize.lg,
- },
- base: {
- height: button.extend.iconSize.md,
- width: button.extend.iconSize.md,
- },
- small: {
- height: button.extend.iconSize.sm,
- width: button.extend.iconSize.sm,
- },
- },
- variant: {
- primary: { color: button.colorScheme.root.primary.color },
- secondary: { color: button.colorScheme.root.secondary.color },
- tertiary: { color: button.colorScheme.root.contrast.color },
- text: { color: button.colorScheme.text.primary.color },
- link: { color: button.colorScheme.link.color },
- basic: { color: button.colorScheme.root.primary.color },
- outlined: { color: button.colorScheme.outlined.primary.color },
- },
- severity: { info: {}, success: {}, warning: {}, danger: {} },
- pressed: { true: {}, false: {} },
- disabled: { true: { color: button.extend.disabledColor }, false: {} },
- },
- compoundVariants: [
- // link pressed
- {
- variant: 'link',
- pressed: 'true',
- styles: { color: semantic.colorScheme.color.fg.muted },
- },
-
- // severity icon colors
- {
- variant: 'basic',
- severity: 'info',
- styles: { color: button.colorScheme.root.info.color },
- },
- {
- variant: 'outlined',
- severity: 'info',
- styles: { color: button.colorScheme.outlined.info.color },
- },
- {
- variant: 'text',
- severity: 'info',
- styles: { color: button.colorScheme.text.info.color },
- },
- {
- variant: 'basic',
- severity: 'success',
- styles: { color: button.colorScheme.root.success.color },
- },
- {
- variant: 'outlined',
- severity: 'success',
- styles: { color: button.colorScheme.outlined.success.color },
- },
- {
- variant: 'text',
- severity: 'success',
- styles: { color: button.colorScheme.text.success.color },
- },
- {
- variant: 'basic',
- severity: 'warning',
- styles: { color: button.colorScheme.root.warn.color },
- },
- {
- variant: 'outlined',
- severity: 'warning',
- styles: { color: button.colorScheme.outlined.warn.color },
- },
- {
- variant: 'text',
- severity: 'warning',
- styles: { color: button.colorScheme.text.warn.color },
- },
- {
- variant: 'basic',
- severity: 'danger',
- styles: { color: button.colorScheme.root.danger.color },
- },
- {
- variant: 'outlined',
- severity: 'danger',
- styles: { color: button.colorScheme.outlined.danger.color },
- },
- {
- variant: 'text',
- severity: 'danger',
- styles: { color: button.colorScheme.text.danger.color },
- },
- ],
- },
- })
-)
diff --git a/src/components/Button/utils/ButtonLabel.tsx b/src/components/Button/utils/ButtonLabel.tsx
deleted file mode 100644
index b10bab8c..00000000
--- a/src/components/Button/utils/ButtonLabel.tsx
+++ /dev/null
@@ -1,163 +0,0 @@
-import { useContext } from 'react'
-import { Text } from 'react-native'
-
-import { StyleSheet } from 'react-native-unistyles'
-
-import { genericMemo } from '../../../utils/genericMemo'
-import type { BaseButtonProps, ButtonSize } from '../types'
-
-import { ButtonPressedContext } from './ButtonPressedContext'
-import { ButtonVariantContext } from './ButtonVariantContext'
-
-export interface ButtonLabelProps {
- readonly iconOnly?: BaseButtonProps['iconOnly']
- readonly label?: string
- readonly size: ButtonSize
- readonly disabled: boolean
- readonly loading: boolean
-}
-
-const ButtonLabelComponent = ({
- label,
- iconOnly,
- size,
- disabled,
- loading,
-}: ButtonLabelProps) => {
- const pressed = useContext(ButtonPressedContext)
- const { variant, severity } = useContext(ButtonVariantContext)
-
- buttonLabelStyles.useVariants({
- size,
- variant,
- severity,
- pressed: pressed ? 'true' : 'false',
- disabled: disabled || loading ? 'true' : 'false',
- })
-
- if (iconOnly) {
- return null
- }
-
- return (
-
- {label}
-
- )
-}
-
-export const ButtonLabel = genericMemo(ButtonLabelComponent)
-
-const buttonLabelStyles = StyleSheet.create(
- ({ components: { button }, semantic, fonts }) => ({
- label: {
- fontWeight: fonts.fontWeight.demibold,
- includeFontPadding: false,
- verticalAlign: 'middle',
- fontFamily: fonts.fontFamily.heading,
- letterSpacing: fonts.letterSpacing[500],
- variants: {
- size: {
- xlarge: {
- fontSize: fonts.fontSize[500],
- lineHeight: fonts.lineHeight[550],
- },
- large: {
- fontSize: fonts.fontSize[500],
- lineHeight: fonts.lineHeight[550],
- },
- base: {
- fontSize: fonts.fontSize[300],
- lineHeight: fonts.lineHeight[500],
- },
- small: {
- fontSize: fonts.fontSize[100],
- lineHeight: fonts.lineHeight[250],
- },
- },
- variant: {
- primary: { color: button.colorScheme.root.primary.color },
- secondary: { color: button.colorScheme.root.secondary.color },
- tertiary: { color: button.colorScheme.root.contrast.color },
- text: { color: button.colorScheme.text.primary.color },
- link: { color: button.colorScheme.link.color },
- basic: { color: button.colorScheme.root.primary.color },
- outlined: { color: button.colorScheme.outlined.primary.color },
- },
- severity: { info: {}, success: {}, warning: {}, danger: {} },
- pressed: { true: {}, false: {} },
- disabled: { true: { color: button.extend.disabledColor }, false: {} },
- },
- compoundVariants: [
- // link pressed color change
- {
- variant: 'link',
- pressed: 'true',
- styles: { color: semantic.colorScheme.color.fg.muted },
- },
-
- // severity label colors
- {
- variant: 'basic',
- severity: 'info',
- styles: { color: button.colorScheme.root.info.color },
- },
- {
- variant: 'outlined',
- severity: 'info',
- styles: { color: button.colorScheme.outlined.info.color },
- },
- {
- variant: 'text',
- severity: 'info',
- styles: { color: button.colorScheme.text.info.color },
- },
- {
- variant: 'basic',
- severity: 'success',
- styles: { color: button.colorScheme.root.success.color },
- },
- {
- variant: 'outlined',
- severity: 'success',
- styles: { color: button.colorScheme.outlined.success.color },
- },
- {
- variant: 'text',
- severity: 'success',
- styles: { color: button.colorScheme.text.success.color },
- },
- {
- variant: 'basic',
- severity: 'warning',
- styles: { color: button.colorScheme.root.warn.color },
- },
- {
- variant: 'outlined',
- severity: 'warning',
- styles: { color: button.colorScheme.outlined.warn.color },
- },
- {
- variant: 'text',
- severity: 'warning',
- styles: { color: button.colorScheme.text.warn.color },
- },
- {
- variant: 'basic',
- severity: 'danger',
- styles: { color: button.colorScheme.root.danger.color },
- },
- {
- variant: 'outlined',
- severity: 'danger',
- styles: { color: button.colorScheme.outlined.danger.color },
- },
- {
- variant: 'text',
- severity: 'danger',
- styles: { color: button.colorScheme.text.danger.color },
- },
- ],
- },
- })
-)
diff --git a/src/components/Button/utils/ButtonLeftArea.tsx b/src/components/Button/utils/ButtonLeftArea.tsx
deleted file mode 100644
index 459a1a17..00000000
--- a/src/components/Button/utils/ButtonLeftArea.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { genericMemo } from '../../../utils/genericMemo'
-import type { BaseButtonProps, ButtonSize } from '../types'
-
-import { ButtonActivityIndicator } from './ButtonActivityIndicator'
-import { ButtonIcon } from './ButtonIcon'
-
-export interface ButtonLeftAreaProps {
- readonly size: ButtonSize
- readonly iconPosition?: BaseButtonProps['iconPosition']
- readonly loading: boolean
- readonly disabled: boolean
- readonly Icon?: BaseButtonProps['Icon']
-}
-
-const ButtonLeftAreaComponent = ({
- size,
- iconPosition,
- Icon,
- loading,
- disabled,
-}: ButtonLeftAreaProps) => {
- if (iconPosition === 'prefix') {
- if (loading && !disabled) {
- return
- }
-
- return
- }
-
- return null
-}
-
-export const ButtonLeftArea = genericMemo(ButtonLeftAreaComponent)
diff --git a/src/components/Button/utils/ButtonPressedContext.ts b/src/components/Button/utils/ButtonPressedContext.ts
deleted file mode 100644
index 0a8bfcc6..00000000
--- a/src/components/Button/utils/ButtonPressedContext.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { createContext } from 'react'
-
-export const ButtonPressedContext = createContext(false)
diff --git a/src/components/Button/utils/ButtonRightArea.tsx b/src/components/Button/utils/ButtonRightArea.tsx
deleted file mode 100644
index 48eadac0..00000000
--- a/src/components/Button/utils/ButtonRightArea.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { genericMemo } from '../../../utils/genericMemo'
-import type { BaseButtonProps, ButtonSize } from '../types'
-
-import { ButtonActivityIndicator } from './ButtonActivityIndicator'
-import { ButtonIcon } from './ButtonIcon'
-
-export interface ButtonRightAreaProps {
- readonly size: ButtonSize
- readonly iconPosition?: BaseButtonProps['iconPosition']
- readonly loading: boolean
- readonly disabled: boolean
- readonly Icon?: BaseButtonProps['Icon']
-}
-
-const ButtonRightAreaComponent = ({
- size,
- iconPosition,
- Icon,
- loading,
- disabled,
-}: ButtonRightAreaProps) => {
- if (iconPosition === 'postfix') {
- if (loading && !disabled) {
- return
- }
-
- return
- }
-
- return null
-}
-
-export const ButtonRightArea = genericMemo(ButtonRightAreaComponent)
diff --git a/src/components/Button/utils/ButtonVariantContext.ts b/src/components/Button/utils/ButtonVariantContext.ts
deleted file mode 100644
index 82cafe05..00000000
--- a/src/components/Button/utils/ButtonVariantContext.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { createContext } from 'react'
-
-import type { ButtonSeverity, ButtonVariant } from '../types'
-
-export interface ButtonVariantContextValue {
- variant: ButtonVariant
- severity?: ButtonSeverity
-}
-
-export const ButtonVariantContext = createContext({
- variant: 'primary',
-})
diff --git a/src/components/Button/utils/index.ts b/src/components/Button/utils/index.ts
deleted file mode 100644
index 2d07a795..00000000
--- a/src/components/Button/utils/index.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export { ButtonLeftArea } from './ButtonLeftArea'
-export { ButtonRightArea } from './ButtonRightArea'
-export { ButtonLabel } from './ButtonLabel'
-export { ButtonContainer } from './ButtonContainer'
diff --git a/src/theme/tokens/components/dark.json b/src/theme/tokens/components/dark.json
index 10241ac3..ca2eb5b1 100644
--- a/src/theme/tokens/components/dark.json
+++ b/src/theme/tokens/components/dark.json
@@ -287,14 +287,14 @@
"lg": { "iconOnlyWidth": 28 },
"xlg": { "iconOnlyWidth": 32 }
},
- "extSm": { "borderRadius": 14, "gap": 8, "height": 28 },
- "extLg": { "borderRadius": 14, "gap": 14, "height": 48 },
+ "extSm": { "borderRadius": 14, "gap": 8, "height": 36 },
+ "extLg": { "borderRadius": 14, "gap": 10, "height": 50 },
"extXlg": {
"borderRadius": 14,
- "gap": 14,
+ "gap": 10,
"iconOnlyWidth": 56,
"paddingX": 24,
- "paddingY": 20,
+ "paddingY": 14,
"height": 56
},
"borderWidth": 1,
@@ -518,28 +518,28 @@
}
},
"root": {
- "borderRadius": 14,
+ "borderRadius": 10,
"roundedBorderRadius": 1600,
"gap": 8,
"paddingX": 14,
"paddingY": 8,
- "height": 36,
- "iconOnlyWidth": 36,
+ "height": 42,
+ "iconOnlyWidth": 42,
"raisedShadow": "none",
"badgeSize": 16,
"transitionDuration": 180,
"focusRing": { "width": 4, "style": "none", "offset": 0 },
"sm": {
- "fontSize": 14,
- "iconOnlyWidth": 28,
+ "fontSize": 12,
+ "iconOnlyWidth": 36,
"paddingX": 14,
"paddingY": 8
},
"lg": {
- "fontSize": 20,
- "iconOnlyWidth": 48,
+ "fontSize": 18,
+ "iconOnlyWidth": 50,
"paddingX": 24,
- "paddingY": 14
+ "paddingY": 12
},
"label": { "fontWeight": 600 }
}
diff --git a/src/theme/tokens/components/light.json b/src/theme/tokens/components/light.json
index 99e90cc5..42dcee33 100644
--- a/src/theme/tokens/components/light.json
+++ b/src/theme/tokens/components/light.json
@@ -287,14 +287,14 @@
"lg": { "iconOnlyWidth": 28 },
"xlg": { "iconOnlyWidth": 32 }
},
- "extSm": { "borderRadius": 14, "gap": 8, "height": 28 },
- "extLg": { "borderRadius": 14, "gap": 14, "height": 48 },
+ "extSm": { "borderRadius": 14, "gap": 8, "height": 36 },
+ "extLg": { "borderRadius": 14, "gap": 10, "height": 50 },
"extXlg": {
"borderRadius": 14,
- "gap": 14,
+ "gap": 10,
"iconOnlyWidth": 56,
"paddingX": 24,
- "paddingY": 20,
+ "paddingY": 14,
"height": 56
},
"borderWidth": 1,
@@ -518,28 +518,28 @@
}
},
"root": {
- "borderRadius": 14,
+ "borderRadius": 10,
"roundedBorderRadius": 1600,
"gap": 8,
"paddingX": 14,
"paddingY": 8,
- "height": 36,
- "iconOnlyWidth": 36,
+ "height": 42,
+ "iconOnlyWidth": 42,
"raisedShadow": "none",
"badgeSize": 16,
"transitionDuration": 180,
"focusRing": { "width": 4, "style": "none", "offset": 0 },
"sm": {
- "fontSize": 14,
- "iconOnlyWidth": 28,
+ "fontSize": 12,
+ "iconOnlyWidth": 36,
"paddingX": 14,
"paddingY": 8
},
"lg": {
- "fontSize": 20,
- "iconOnlyWidth": 48,
+ "fontSize": 18,
+ "iconOnlyWidth": 50,
"paddingX": 24,
- "paddingY": 14
+ "paddingY": 12
},
"label": { "fontWeight": 600 }
}