From fb46544e8b8214e0b6040673101c542f7563020f Mon Sep 17 00:00:00 2001 From: Alesya Volosach Date: Thu, 13 Aug 2026 15:08:01 +0300 Subject: [PATCH 1/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=D1=82=D0=BE=D0=BA=D0=B5=D0=BD?= =?UTF-8?q?=D1=8B=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design-tokens/input/tokens.json | 42 +++++++++++++------------- src/theme/tokens/components/dark.json | 30 +++++++++--------- src/theme/tokens/components/light.json | 8 ++--- 3 files changed, 40 insertions(+), 40 deletions(-) diff --git a/design-tokens/input/tokens.json b/design-tokens/input/tokens.json index 4f26c240..0b157a6d 100644 --- a/design-tokens/input/tokens.json +++ b/design-tokens/input/tokens.json @@ -1331,14 +1331,14 @@ }, "avatar": { "extend": { - "borderColor": "{color.border.neutral.inverse}", + "borderColor": "{color.border.neutral.strong}", "circle": { "borderRadius": "{dimension.borderRadius.max}" } }, "root": { "width": "{dimension.size.900}", "height": "{dimension.size.900}", "fontSize": "{fonts.fontSize.300}", - "color": "{color.fg.default}", + "color": "{color.fg.on.brand.default}", "background": "{color.bg.brand.strong.default}", "borderRadius": "{dimension.borderRadius.400}" }, @@ -1427,7 +1427,7 @@ "color": "{color.fg.default}" }, "success": { - "background": "{color.bg.brand.strong.active}", + "background": "{color.bg.status.success.strong.default}", "color": "{color.fg.on.status.success.default}" }, "warn": { @@ -2146,7 +2146,7 @@ "icon": { "size": "{dimension.size.450}", "color": "{color.fg.active}", - "checkedColor": "{color.fg.inverse.default}", + "checkedColor": "{color.fg.on.fill.default}", "checkedHoverColor": "{color.fg.on.fill.hover}", "disabledColor": "{color.fg.muted}", "sm": { "size": "{dimension.size.350}" }, @@ -2679,10 +2679,10 @@ "backdrop": "{color.bg.surface.backdrop}" }, "root": { - "background": "{color.bg.surface.default.default}", + "background": "{color.bg.surface.raised.default}", "borderColor": "{color.border.neutral.default}", "color": "{color.fg.default}", - "borderRadius": "{dimension.borderRadius.500}", + "borderRadius": "{dimension.borderRadius.max}", "shadow": "{effects.elevation.400}" }, "header": { @@ -2716,7 +2716,7 @@ }, "content": { "background": "{color.bg.surface.default.default}", - "color": "{color.fg.muted}" + "color": "{color.fg.default}" }, "root": { "borderColor": "{color.border.neutral.default}" } }, @@ -4444,7 +4444,7 @@ }, "root": { "transitionDuration": "{effects.transition.duration.200}" }, "tablist": { - "borderWidth": "{dimension.borderWidth.none} {dimension.borderWidth.none} {dimension.borderWidth.200} {dimension.borderWidth.none}", + "borderWidth": "{dimension.borderWidth.none} {dimension.borderWidth.none} {dimension.borderWidth.100} {dimension.borderWidth.none}", "background": "transparent", "borderColor": "{color.border.neutral.default}" }, @@ -5077,11 +5077,11 @@ "checkedHoverBackground": "{color.bg.neutral.strong.hover}" }, "handle": { - "background": "{color.fg.on.fill.default}", - "hoverBackground": "{color.fg.on.fill.default}", + "background": "{color.bg.surface.raised.default}", + "hoverBackground": "{color.bg.surface.raised.default}", "disabledBackground": "{color.bg.neutral.medium.strong}", - "checkedBackground": "{color.fg.inverse.active}", - "checkedHoverBackground": "{color.fg.inverse.active}", + "checkedBackground": "{color.bg.surface.raised.default}", + "checkedHoverBackground": "{color.bg.surface.raised.default}", "color": "{color.fg.default}", "hoverColor": "{color.fg.default}", "checkedColor": "{color.fg.default}", @@ -5090,22 +5090,22 @@ }, "dark": { "handle": { - "background": "{color.fg.on.fill.default}", - "checkedBackground": "{color.fg.inverse.active}", + "background": "{color.bg.surface.raised.default}", + "checkedBackground": "{color.bg.surface.raised.default}", "checkedColor": "{color.fg.inverse.focus}", - "checkedHoverBackground": "{color.fg.inverse.active}", + "checkedHoverBackground": "{color.bg.surface.raised.default}", "checkedHoverColor": "{color.fg.on.brand.hover}", "color": "{color.fg.inverse.default}", - "disabledBackground": "{color.bg.neutral.medium.strong}", - "hoverBackground": "{color.fg.on.fill.default}", + "disabledBackground": "{color.bg.surface.raised.active}", + "hoverBackground": "{color.bg.surface.raised.default}", "hoverColor": "{color.fg.on.brand.hover}" }, "root": { "background": "{color.bg.neutral.medium.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}" + "checkedBackground": "{color.bg.surface.canvas.default}", + "checkedHoverBackground": "{color.bg.surface.canvas.hover}", + "disabledBackground": "{color.bg.neutral.strong.hover}", + "hoverBackground": "{color.bg.surface.raised.active}" } } }, diff --git a/src/theme/tokens/components/dark.json b/src/theme/tokens/components/dark.json index 9b6b1109..0412aaae 100644 --- a/src/theme/tokens/components/dark.json +++ b/src/theme/tokens/components/dark.json @@ -156,12 +156,12 @@ } }, "avatar": { - "extend": { "borderColor": "#2b2e33", "circle": { "borderRadius": 1600 } }, + "extend": { "borderColor": "#56595f", "circle": { "borderRadius": 1600 } }, "root": { "width": 32, "height": 32, "fontSize": 16, - "color": "#ffffff", + "color": "#2b2e33", "background": "#44e858", "borderRadius": 14 }, @@ -200,7 +200,7 @@ "info": { "background": "#18538d", "color": "#f0f9ff" }, "primary": { "background": "#44e858", "color": "#2b2e33" }, "secondary": { "background": "#2b2e33", "color": "#ffffff" }, - "success": { "background": "#aafbb7", "color": "#f0fff3" }, + "success": { "background": "#168322", "color": "#f0fff3" }, "warn": { "background": "#9d6d0e", "color": "#fff9f0" } }, "root": { @@ -607,7 +607,7 @@ "icon": { "size": 16, "color": "#f0f0f1", - "checkedColor": "#2b2e33", + "checkedColor": "#ffffff", "checkedHoverColor": "#ffffff", "disabledColor": "#a2a5a9", "sm": { "size": 12 }, @@ -1049,10 +1049,10 @@ "dialog": { "extend": { "borderWidth": 1, "backdrop": "rgba(0, 0, 0, 0.6000)" }, "root": { - "background": "#404348", + "background": "#56595f", "borderColor": "#404348", "color": "#ffffff", - "borderRadius": 20, + "borderRadius": 1600, "shadow": "0 4px 8px rgba(0, 0, 0, 0.2000)" }, "header": { @@ -1106,7 +1106,7 @@ "paddingLeft": 0 } }, - "content": { "background": "#404348", "color": "#a2a5a9" }, + "content": { "background": "#404348", "color": "#ffffff" }, "root": { "borderColor": "#404348" } }, "drawer": { @@ -2387,7 +2387,7 @@ "tablist": { "borderTopWidth": 0, "borderRightWidth": 0, - "borderBottomWidth": 2, + "borderBottomWidth": 1, "borderLeftWidth": 0, "background": "transparent", "borderColor": "#404348" @@ -2801,21 +2801,21 @@ "toggleswitch": { "colorScheme": { "handle": { - "background": "#ffffff", - "checkedBackground": "#181a1f", + "background": "#56595f", + "checkedBackground": "#56595f", "checkedColor": "#2b2e33", - "checkedHoverBackground": "#181a1f", + "checkedHoverBackground": "#56595f", "checkedHoverColor": "#2b2e33", "color": "#2b2e33", "disabledBackground": "#85888e", - "hoverBackground": "#ffffff", + "hoverBackground": "#56595f", "hoverColor": "#2b2e33" }, "root": { "background": "#6d7076", - "checkedBackground": "#f0f0f1", - "checkedHoverBackground": "#e2e2e4", - "disabledBackground": "#404348", + "checkedBackground": "#2b2e33", + "checkedHoverBackground": "#404348", + "disabledBackground": "#e2e2e4", "hoverBackground": "#85888e" } }, diff --git a/src/theme/tokens/components/light.json b/src/theme/tokens/components/light.json index 2d6869f0..be686424 100644 --- a/src/theme/tokens/components/light.json +++ b/src/theme/tokens/components/light.json @@ -156,7 +156,7 @@ } }, "avatar": { - "extend": { "borderColor": "#ffffff", "circle": { "borderRadius": 1600 } }, + "extend": { "borderColor": "#cecfd2", "circle": { "borderRadius": 1600 } }, "root": { "width": 32, "height": 32, @@ -1052,7 +1052,7 @@ "background": "#ffffff", "borderColor": "#e2e2e4", "color": "#2b2e33", - "borderRadius": 20, + "borderRadius": 1600, "shadow": "0 4px 8px rgba(0, 0, 0, 0.2000)" }, "header": { @@ -1106,7 +1106,7 @@ "paddingLeft": 0 } }, - "content": { "background": "#ffffff", "color": "#85888e" }, + "content": { "background": "#ffffff", "color": "#2b2e33" }, "root": { "borderColor": "#e2e2e4" } }, "drawer": { @@ -2387,7 +2387,7 @@ "tablist": { "borderTopWidth": 0, "borderRightWidth": 0, - "borderBottomWidth": 2, + "borderBottomWidth": 1, "borderLeftWidth": 0, "background": "transparent", "borderColor": "#e2e2e4" From 991d8b5007f001bd5b729ff0c5c292ef07c8b3bb Mon Sep 17 00:00:00 2001 From: Alesya Volosach Date: Thu, 13 Aug 2026 15:40:53 +0300 Subject: [PATCH 2/7] =?UTF-8?q?feat(badge):=20=D0=B4=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D1=80=D0=B0=D0=B7=D0=BC=D0=B5?= =?UTF-8?q?=D1=80=D1=8B=20Badge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Badge/Badge.stories.ts | 38 +- src/components/Badge/Badge.tsx | 94 ++- src/components/Badge/__tests__/Badge.test.tsx | 68 +- .../__snapshots__/Badge.test.tsx.snap | 657 ------------------ 4 files changed, 165 insertions(+), 692 deletions(-) delete mode 100644 src/components/Badge/__tests__/__snapshots__/Badge.test.tsx.snap diff --git a/src/components/Badge/Badge.stories.ts b/src/components/Badge/Badge.stories.ts index c5b7857c..953b6c64 100644 --- a/src/components/Badge/Badge.stories.ts +++ b/src/components/Badge/Badge.stories.ts @@ -1,16 +1,48 @@ import type { Meta, StoryObj } from '@storybook/react' +import { createElement } from 'react' +import { StyleSheet, View } from 'react-native' -import { Badge } from './Badge' +import { Body } from '../Typography' + +import { Badge, type BadgeProps } from './Badge' + +const styles = StyleSheet.create({ + container: { gap: 16 }, + example: { gap: 8 }, +}) const meta: Meta = { title: 'Misc/Badge', component: Badge, - args: { severity: 'basic', dot: false, children: 'Test' }, + args: { children: 'Badge', severity: 'basic', size: 'base' }, argTypes: { + children: { control: 'text' }, severity: { control: 'radio', options: ['basic', 'info', 'success', 'warning', 'danger'], }, + size: { control: 'radio', options: ['base', 'large', 'xlarge'] }, + }, + parameters: { controls: { exclude: ['dot'] } }, + render: ({ children = 'Badge', severity, size }) => { + const textBadgeProps: BadgeProps = { children, dot: false, severity, size } + + return createElement( + View, + { style: styles.container }, + createElement( + View, + { style: styles.example }, + createElement(Body, null, 'Severity'), + createElement(Badge, textBadgeProps) + ), + createElement( + View, + { style: styles.example }, + createElement(Body, null, 'Dot'), + createElement(Badge, { dot: true, severity, size }) + ) + ) }, } @@ -18,6 +50,6 @@ export default meta type Story = StoryObj -const BadgeStory: Story = { args: { children: 'Test' }, argTypes: {} } +const BadgeStory: Story = {} export { BadgeStory as Badge } diff --git a/src/components/Badge/Badge.tsx b/src/components/Badge/Badge.tsx index 2cdf0052..eac73773 100644 --- a/src/components/Badge/Badge.tsx +++ b/src/components/Badge/Badge.tsx @@ -22,6 +22,11 @@ export interface BadgeBase * @default 'basic' */ severity?: BadgeSeverity + /** + * Выбор размера компонента + * @default 'base' + */ + size?: 'base' | 'large' | 'xlarge' /** Дополнительная стилизация для контейнера компонента */ style?: StyleProp } @@ -46,13 +51,22 @@ export type BadgeProps = BadgeText | BadgeDot * Компонент Badge * @param children - Текст внутри бейджа * @param dot - Отображать бейдж в форме точки - * @param severiy - Выбор варианта стиля компонента + * @param severity - Выбор варианта стиля компонента + * @param size - Выбор размера компонента * @param style - Дополнительная стилизация для контейнера компонента - * @link https://www.figma.com/design/4TYeki0MDLhfPGJstbIicf/UI-kit-PrimeFace-(DS)?node-id=484-4871&m=dev + * @link https://www.figma.com/design/Q1BWgZ7zoV5UzlBOnjW0cM/UI-Kit--DS--v2.1?node-id=24043-13668 */ export const Badge = memo( - ({ children, dot, severity = 'basic', style, testID, ...rest }) => { - badgeStyles.useVariants({ severity }) + ({ + children, + dot, + severity = 'basic', + size = 'base', + style, + testID, + ...rest + }) => { + badgeStyles.useVariants({ severity, size }) const [textLayout, setTextLayout] = useState() const onTextLayout = useCallback((e: LayoutChangeEvent) => { @@ -68,10 +82,7 @@ export const Badge = memo( /> ) : ( <> - + ({ dotShape: { width: components.badge.dot.size, height: components.badge.dot.size, - borderRadius: components.badge.root.borderRadius, + borderRadius: semantic.dimension.borderRadius.max, + borderWidth: components.overlaybadge.root.outline.width, + borderColor: components.overlaybadge.root.outline.color, + variants: { + size: { + base: {}, + large: { + width: components.badge.extend.extDot.lg.size, + height: components.badge.extend.extDot.lg.size, + }, + xlarge: { + width: components.badge.extend.extDot.xlg.size, + height: components.badge.extend.extDot.xlg.size, + }, + }, + }, }, textBadgeContainer: { height: components.badge.root.height, + minWidth: components.badge.root.minWidth, paddingHorizontal: components.badge.root.padding, justifyContent: 'center', - borderRadius: semantic.dimension.borderRadius.max, + borderRadius: components.badge.root.borderRadius, + borderWidth: components.overlaybadge.root.outline.width, + borderColor: components.overlaybadge.root.outline.color, + variants: { + severity: { + basic: { + backgroundColor: components.badge.colorScheme.primary.background, + }, + info: { backgroundColor: components.badge.colorScheme.info.background }, + success: { + backgroundColor: components.badge.colorScheme.success.background, + }, + warning: { + backgroundColor: components.badge.colorScheme.warn.background, + }, + danger: { + backgroundColor: components.badge.colorScheme.danger.background, + }, + }, + size: { + base: {}, + large: { + height: components.badge.lg.height, + minWidth: components.badge.lg.minWidth, + }, + xlarge: { + height: components.badge.xl.height, + minWidth: components.badge.xl.minWidth, + }, + }, + }, }, textBadge: { color: components.badge.colorScheme.primary.color, fontSize: components.badge.root.fontSize, + fontWeight: fonts.fontWeight.regular, + lineHeight: fonts.lineHeight[100], + letterSpacing: fonts.letterSpacing[500], includeFontPadding: false, verticalAlign: 'middle', fontFamily: fonts.fontFamily.heading, + variants: { + severity: { + basic: { color: components.badge.colorScheme.primary.color }, + info: { color: components.badge.colorScheme.info.color }, + success: { color: components.badge.colorScheme.success.color }, + warning: { color: components.badge.colorScheme.warn.color }, + danger: { color: components.badge.colorScheme.danger.color }, + }, + size: { + base: {}, + large: {}, + xlarge: { lineHeight: fonts.lineHeight[350] }, + }, + }, }, hiddenContainer: { width: Dimensions.get('window').width, diff --git a/src/components/Badge/__tests__/Badge.test.tsx b/src/components/Badge/__tests__/Badge.test.tsx index 0fcac425..8f3e605a 100644 --- a/src/components/Badge/__tests__/Badge.test.tsx +++ b/src/components/Badge/__tests__/Badge.test.tsx @@ -1,29 +1,53 @@ import { render } from '@testing-library/react-native' -import { Badge, type BadgeProps } from '../Badge' - -describe('Badge component tests', () => { - const snapshotCases: Array<[string, BadgeProps]> = [ - ['dot, severity: basic', { dot: true, severity: 'basic' }], - ['dot, severity: info', { dot: true, severity: 'info' }], - ['dot, severity: success', { dot: true, severity: 'success' }], - ['dot, severity: warning', { dot: true, severity: 'warning' }], - ['dot, severity: danger', { dot: true, severity: 'danger' }], - ['severity: basic', { children: '12', severity: 'basic' }], - ['severity: basic', { children: '12', severity: 'info' }], - ['severity: basic', { children: '12', severity: 'success' }], - ['severity: basic', { children: '12', severity: 'warning' }], - ['severity: basic', { children: '12', severity: 'danger' }], - ['severity: default', { children: '12' }], - [ - 'with custom style', - { dot: true, severity: 'basic', style: { margin: 10 } }, - ], +import { Badge, type BadgeSeverity } from '../Badge' + +describe('Badge', () => { + const severities: BadgeSeverity[] = [ + 'basic', + 'info', + 'success', + 'warning', + 'danger', ] - test.each(snapshotCases)('%s', (_, props) => { - const renderedBadge = render() + test.each(severities)( + 'отображает переданный текст для severity %s', + (severity) => { + const { getByText } = render(12) + + expect(getByText('12')).toBeOnTheScreen() + } + ) - expect(renderedBadge.toJSON()).toMatchSnapshot() + test.each(severities)('отображает dot для severity %s', (severity) => { + const { getByTestId, queryByText } = render( + + ) + + expect(getByTestId('Badge')).toBeOnTheScreen() + expect(queryByText('12')).not.toBeOnTheScreen() }) + + test.each(['large', 'xlarge'] as const)( + 'отображает переданный текст для размера %s', + (size) => { + const { getByText } = render( + + 12 + + ) + + expect(getByText('12')).toBeOnTheScreen() + } + ) + + test.each(['large', 'xlarge'] as const)( + 'отображает dot-вариант для размера %s', + (size) => { + const { getByTestId } = render() + + expect(getByTestId('Badge')).toBeOnTheScreen() + } + ) }) diff --git a/src/components/Badge/__tests__/__snapshots__/Badge.test.tsx.snap b/src/components/Badge/__tests__/__snapshots__/Badge.test.tsx.snap deleted file mode 100644 index c728269d..00000000 --- a/src/components/Badge/__tests__/__snapshots__/Badge.test.tsx.snap +++ /dev/null @@ -1,657 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Badge component tests dot, severity: basic 1`] = ` - - - -`; - -exports[`Badge component tests dot, severity: danger 1`] = ` - - - -`; - -exports[`Badge component tests dot, severity: info 1`] = ` - - - -`; - -exports[`Badge component tests dot, severity: success 1`] = ` - - - -`; - -exports[`Badge component tests dot, severity: warning 1`] = ` - - - -`; - -exports[`Badge component tests severity: basic 1`] = ` - - - - 12 - - - - - - 12 - - - - -`; - -exports[`Badge component tests severity: basic 2`] = ` - - - - 12 - - - - - - 12 - - - - -`; - -exports[`Badge component tests severity: basic 3`] = ` - - - - 12 - - - - - - 12 - - - - -`; - -exports[`Badge component tests severity: basic 4`] = ` - - - - 12 - - - - - - 12 - - - - -`; - -exports[`Badge component tests severity: basic 5`] = ` - - - - 12 - - - - - - 12 - - - - -`; - -exports[`Badge component tests severity: default 1`] = ` - - - - 12 - - - - - - 12 - - - - -`; - -exports[`Badge component tests with custom style 1`] = ` - - - -`; From 82b8e526d4c6a745adb49400216460637b03d19a Mon Sep 17 00:00:00 2001 From: Alesya Volosach Date: Thu, 13 Aug 2026 16:28:09 +0300 Subject: [PATCH 3/7] =?UTF-8?q?fix(button):=20=D0=B8=D1=81=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=BE=20=D1=86=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D1=80=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=B8?= =?UTF-8?q?=20=D0=BE=D1=82=D0=BE=D0=B1=D1=80=D0=B0=D0=B6=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=B2=D0=B0=D1=80=D0=B8=D0=B0=D0=BD=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Button/Button.stories.ts | 51 +- src/components/Button/Button.tsx | 1 + src/components/Button/ButtonBadge.stories.ts | 52 +- src/components/Button/ButtonBadge.tsx | 36 +- .../Button/ButtonSeverity.stories.ts | 52 +- .../Button/__tests__/Button.test.tsx | 126 +- .../Button/__tests__/ButtonBadge.test.tsx | 18 +- .../Button/__tests__/ButtonSeverity.test.tsx | 20 +- .../__snapshots__/Button.test.tsx.snap | 139262 --------------- .../__snapshots__/ButtonBadge.test.tsx.snap | 298 - .../ButtonSeverity.test.tsx.snap | 277 - .../Button/utils/ButtonActivityIndicator.tsx | 8 +- .../Button/utils/ButtonContainer.tsx | 58 +- src/components/Button/utils/ButtonLabel.tsx | 21 +- 14 files changed, 288 insertions(+), 139992 deletions(-) delete mode 100644 src/components/Button/__tests__/__snapshots__/Button.test.tsx.snap delete mode 100644 src/components/Button/__tests__/__snapshots__/ButtonBadge.test.tsx.snap delete mode 100644 src/components/Button/__tests__/__snapshots__/ButtonSeverity.test.tsx.snap diff --git a/src/components/Button/Button.stories.ts b/src/components/Button/Button.stories.ts index 7c3d9298..8bb2a380 100644 --- a/src/components/Button/Button.stories.ts +++ b/src/components/Button/Button.stories.ts @@ -3,10 +3,19 @@ import { IconArrowDownRight, IconArrowDownLeft, } from '@tabler/icons-react-native' +import { createElement } from 'react' +import { StyleSheet, View } from 'react-native' + +import { Body } from '../Typography' import { Button } from './Button' +import type { ButtonBaseVariant, ButtonProps } from './types' const Icons = { IconArrowDownRight, IconArrowDownLeft, undefined } +const styles = StyleSheet.create({ + container: { gap: 16 }, + example: { gap: 8 }, +}) const meta: Meta = { title: 'Button', @@ -31,13 +40,45 @@ const meta: Meta = { disabled: { control: 'boolean' }, iconPosition: { control: 'radio', options: ['prefix', 'postfix'] }, onPress: { action: 'OnPress' }, - iconOnly: { - control: 'radio', - options: ['IconOnly', 'Not IconOnly'], - mapping: { IconOnly: true, 'Not IconOnly': undefined }, - }, Icon: { control: 'select', options: Object.keys(Icons), mapping: Icons }, }, + parameters: { controls: { exclude: ['iconOnly'] } }, + render: ({ + iconOnly: _iconOnly, + Icon, + iconPosition, + label = 'Button', + ...args + }) => { + const buttonProps: ButtonProps = { + ...args, + Icon, + iconPosition, + label, + } + const iconOnlyButtonProps: ButtonProps = { + ...args, + iconOnly: true, + Icon: Icon ?? IconArrowDownRight, + } + + return createElement( + View, + { style: styles.container }, + createElement( + View, + { style: styles.example }, + createElement(Body, null, 'С текстом'), + createElement(Button, buttonProps) + ), + createElement( + View, + { style: styles.example }, + createElement(Body, null, 'Только иконка'), + createElement(Button, iconOnlyButtonProps) + ) + ) + }, } export default meta diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx index 5e2ca5e7..bb3320ac 100644 --- a/src/components/Button/Button.tsx +++ b/src/components/Button/Button.tsx @@ -17,6 +17,7 @@ import { ButtonVariantContext } from './utils/ButtonVariantContext' * @param label - button label * @param style - external style control for component * @see BaseButton + * @link https://www.figma.com/design/Q1BWgZ7zoV5UzlBOnjW0cM/UI-Kit--DS--v2.1?node-id=160-5223 */ export const Button = memo>( ({ variant = 'primary', ...props }) => { diff --git a/src/components/Button/ButtonBadge.stories.ts b/src/components/Button/ButtonBadge.stories.ts index e84a14b1..675ce47f 100644 --- a/src/components/Button/ButtonBadge.stories.ts +++ b/src/components/Button/ButtonBadge.stories.ts @@ -3,10 +3,19 @@ import { IconArrowDownRight, IconArrowDownLeft, } from '@tabler/icons-react-native' +import { createElement } from 'react' +import { StyleSheet, View } from 'react-native' + +import { Body } from '../Typography' import { ButtonBadge } from './ButtonBadge' +import type { ButtonBadgeProps, ButtonBaseVariant, ButtonProps } from './types' const Icons = { IconArrowDownRight, IconArrowDownLeft, undefined } +const styles = StyleSheet.create({ + container: { gap: 16 }, + example: { gap: 8 }, +}) const meta: Meta = { title: 'Button/Badge', @@ -37,13 +46,46 @@ const meta: Meta = { control: 'radio', options: ['basic', 'info', 'success', 'warning', 'danger'], }, - iconOnly: { - control: 'radio', - options: ['IconOnly', 'Not IconOnly'], - mapping: { IconOnly: true, 'Not IconOnly': undefined }, - }, Icon: { control: 'select', options: Object.keys(Icons), mapping: Icons }, }, + parameters: { controls: { exclude: ['iconOnly'] } }, + render: ({ + iconOnly: _iconOnly, + Icon, + iconPosition, + label = 'Button', + ...args + }) => { + const buttonProps: ButtonProps & ButtonBadgeProps = { + ...args, + Icon, + iconPosition, + label, + } + const iconOnlyButtonProps: ButtonProps & + ButtonBadgeProps = { + ...args, + iconOnly: true, + Icon: Icon ?? IconArrowDownRight, + } + + return createElement( + View, + { style: styles.container }, + createElement( + View, + { style: styles.example }, + createElement(Body, null, 'С текстом'), + createElement(ButtonBadge, buttonProps) + ), + createElement( + View, + { style: styles.example }, + createElement(Body, null, 'Только иконка'), + createElement(ButtonBadge, iconOnlyButtonProps) + ) + ) + }, } export default meta diff --git a/src/components/Button/ButtonBadge.tsx b/src/components/Button/ButtonBadge.tsx index 9436f6f3..5519b079 100644 --- a/src/components/Button/ButtonBadge.tsx +++ b/src/components/Button/ButtonBadge.tsx @@ -3,7 +3,7 @@ import { View } from 'react-native' import { StyleSheet } from 'react-native-unistyles' -import { Badge } from '../Badge' +import { Badge, type BadgeProps } from '../Badge' import { BaseButton } from './BaseButton' import type { ButtonBadgeProps, ButtonBaseVariant, ButtonProps } from './types' @@ -30,39 +30,27 @@ export const ButtonBadge = memo< >(({ badgeLabel, badgeSeverity, variant = 'primary', ...props }) => { const variantContextValue = useMemo(() => ({ variant }), [variant]) - const badgeCommonProps = useMemo( - () => ({ severity: badgeSeverity, testID: ButtonBadgeTestId.badge }), - [badgeSeverity] - ) + const badgeCommonProps = { + severity: badgeSeverity, + testID: ButtonBadgeTestId.badge, + } + const badgeProps: BadgeProps = badgeLabel + ? { ...badgeCommonProps, children: badgeLabel, style: styles.badge } + : { ...badgeCommonProps, dot: true, style: styles.badgeDot } return ( - - - + + - {badgeLabel ? ( - - {badgeLabel} - - ) : ( - - )} - + ) }) const styles = StyleSheet.create(() => ({ - root: { flexDirection: 'row' }, - contentContainer: { flex: 1 }, - iconOnlyContainer: { flex: 0 }, + container: { alignSelf: 'flex-start' }, badgeDot: { position: 'absolute', top: 0, right: -0.5 }, badge: { position: 'absolute', diff --git a/src/components/Button/ButtonSeverity.stories.ts b/src/components/Button/ButtonSeverity.stories.ts index fe7b83f8..3d71b9ae 100644 --- a/src/components/Button/ButtonSeverity.stories.ts +++ b/src/components/Button/ButtonSeverity.stories.ts @@ -3,10 +3,23 @@ import { IconArrowDownRight, IconArrowDownLeft, } from '@tabler/icons-react-native' +import { createElement } from 'react' +import { StyleSheet, View } from 'react-native' + +import { Body } from '../Typography' import { ButtonSeverity } from './ButtonSeverity' +import type { + ButtonProps, + ButtonSeverityProps, + ButtonSeverityVariant, +} from './types' const Icons = { IconArrowDownRight, IconArrowDownLeft, undefined } +const styles = StyleSheet.create({ + container: { gap: 16 }, + example: { gap: 8 }, +}) const meta: Meta = { title: 'Button/Severity', @@ -33,13 +46,42 @@ const meta: Meta = { control: 'radio', options: ['info', 'success', 'warning', 'danger'], }, - iconOnly: { - control: 'radio', - options: ['IconOnly', 'Not IconOnly'], - mapping: { IconOnly: true, 'Not IconOnly': undefined }, - }, Icon: { control: 'select', options: Object.keys(Icons), mapping: Icons }, }, + parameters: { controls: { exclude: ['iconOnly'] } }, + render: ({ + iconOnly: _iconOnly, + Icon, + iconPosition, + label = 'Button', + ...args + }) => { + const buttonProps: ButtonProps & + ButtonSeverityProps = { ...args, Icon, iconPosition, label } + const iconOnlyButtonProps: ButtonProps & + ButtonSeverityProps = { + ...args, + iconOnly: true, + Icon: Icon ?? IconArrowDownRight, + } + + return createElement( + View, + { style: styles.container }, + createElement( + View, + { style: styles.example }, + createElement(Body, null, 'С текстом'), + createElement(ButtonSeverity, buttonProps) + ), + createElement( + View, + { style: styles.example }, + createElement(Body, null, 'Только иконка'), + createElement(ButtonSeverity, iconOnlyButtonProps) + ) + ) + }, } export default meta diff --git a/src/components/Button/__tests__/Button.test.tsx b/src/components/Button/__tests__/Button.test.tsx index c2abe708..024a869a 100644 --- a/src/components/Button/__tests__/Button.test.tsx +++ b/src/components/Button/__tests__/Button.test.tsx @@ -1,101 +1,73 @@ import { IconArrowDownRight } from '@tabler/icons-react-native' -import { render } from '@testing-library/react-native' +import { render, userEvent } from '@testing-library/react-native' import { Button } from '../Button' -import type { - ButtonBaseVariant, - IconOnlyButtonProps, - IconTextButton, -} from '../types' -describe('Button component tests', () => { - const buttonSnapshotCases = generatePropsCombinations< - IconTextButton - >({ - size: ['base', 'small', 'large', 'xlarge'], - shape: ['square', 'circle'], - loading: [true, false], - variant: ['primary', 'secondary', 'tertiary', 'text', 'link'], - disabled: [true, false], - label: ['Button'], - testOnly_pressed: [true, false], - }) - - test.each(buttonSnapshotCases)( - 'Button with text, size - $size, shape - $shape, variant - $variant, loading - $loading, disabled - $disabled', - (props) => { - const renderedButton = render(