feat(suite-native): storybook atoms

This commit is contained in:
Petr Knetl
2026-01-08 14:50:14 +01:00
committed by Petr Knetl
parent 1121d1132a
commit e6215c6854
75 changed files with 1684 additions and 149 deletions

View File

@@ -26,4 +26,12 @@ export default [
],
},
},
{
// TARGET: Storybook files anywhere in the project
files: ['**/*.stories.@(ts|tsx|js|jsx)'],
rules: {
'import/no-default-export': 'off', // Storybook stories need default exports by design.
'react-hooks/rules-of-hooks': 'off', // It is possible to use hooks in Storybook stories outside of the component (e.g in the render method).
},
},
];

View File

@@ -363,3 +363,5 @@ export const colorVariants: Record<ThemeColorVariant, Colors> = {
...colorsV2.dark,
} as Colors,
} as const;
export const COLOR_TOKENS = Object.keys(light);

View File

@@ -41,6 +41,7 @@
"react-native-safe-area-context": "5.6.1",
"react-native-svg": "15.14.0",
"react-redux": "9.2.0",
"storybook": "^10.1.0",
"type-fest": "4.24.0"
},
"devDependencies": {

View File

@@ -3,7 +3,7 @@ import { useSharedValue } from 'react-native-reanimated';
import { AccordionItem, AccordionItemProps } from './AccordionItem';
import { VStack } from '../Stack';
type AccordionListProps = {
export type AccordionListProps = {
items: Omit<AccordionItemProps, 'currentIndexOpened' | 'index' | 'isDividerDisplayed'>[];
};

View File

@@ -12,10 +12,22 @@ import { HStack } from './Stack';
import { Text } from './Text';
import { SurfaceElevation } from './types';
export type BadgeVariant = 'neutral' | 'green' | 'greenSubtle' | 'yellow' | 'red' | 'blue' | 'bold';
export type BadgeSize = 'small' | 'medium';
export const BADGE_VARIANTS = [
'neutral',
'green',
'greenSubtle',
'yellow',
'red',
'blue',
'bold',
] as const;
export type BadgeVariant = (typeof BADGE_VARIANTS)[number];
export const BADGE_SIZES = ['small', 'medium'] as const;
export type BadgeSize = (typeof BADGE_SIZES)[number];
type IconType = IconName | NetworkSymbol;
type BadgeProps = {
export type BadgeProps = {
label: ReactNode;
variant?: BadgeVariant;
size?: BadgeSize;

View File

@@ -7,7 +7,7 @@ import { Text } from './Text';
const INDENTED_BULLET_POINT_SYMBOL = ' \u2022 ';
type BulletListItemProps = {
export type BulletListItemProps = {
children: ReactNode;
variant?: NativeTypographyStyle;
color?: Color;

View File

@@ -20,7 +20,11 @@ import {
import { useButtonPressAnimatedStyle } from './useButtonPressAnimatedStyle';
import { Loader } from '../Loader';
import { AnimatedPressable } from '../Pressable';
type IconButtonProps = Omit<PressableProps, 'style' | 'onPressIn' | 'onPressOut'> & {
export type IconButtonProps = Omit<
PressableProps,
'style' | 'onPressIn' | 'onPressOut' | 'children'
> & {
iconName: IconName;
colorScheme?: ButtonColorScheme;
size?: ButtonSize;

View File

@@ -15,7 +15,7 @@ import { ButtonAccessoryView, ButtonProps, ButtonSize, buttonToTextSizeMap } fro
export type TextButtonVariant = 'primary' | 'tertiary' | 'blue';
type TextButtonProps = Omit<ButtonProps, 'colorScheme'> & {
export type TextButtonProps = Omit<ButtonProps, 'colorScheme'> & {
isUnderlined?: boolean;
variant?: TextButtonVariant;
isBold?: boolean;

View File

@@ -14,7 +14,7 @@ const contentStyle = prepareNativeStyle(() => ({
flexShrink: 1,
}));
type CardWithIconLayoutProps = {
export type CardWithIconLayoutProps = {
icon: IconName;
title: ReactNode;
children: ReactNode;

View File

@@ -14,7 +14,8 @@ import { HStack, VStack } from '../Stack';
import { Text } from '../Text';
import { Card } from './Card';
type CardVariant = 'normal' | 'danger' | 'primary';
export const COMPACT_CARD_VARIANTS = ['normal', 'danger', 'primary'] as const;
type CompactCardVariant = (typeof COMPACT_CARD_VARIANTS)[number];
export type CompactCardWithIconLayoutProps = {
icon: IconName;
@@ -23,7 +24,7 @@ export type CompactCardWithIconLayoutProps = {
isDisabled?: boolean;
alertBoxProps?: Omit<InlineAlertBoxProps, 'borderRadius'>;
onPress?: () => void;
variant?: CardVariant;
variant?: CompactCardVariant;
noShadow?: boolean;
borderColor?: Color | null;
} & PressableProps;
@@ -58,7 +59,7 @@ export const cardVariantToColorsMap = {
subtitleColor: 'textSecondaryHighlight',
caretColor: 'iconPrimaryDefault',
},
} as const satisfies Record<CardVariant, CardColorScheme>;
} as const satisfies Record<CompactCardVariant, CardColorScheme>;
const contentStyle = prepareNativeStyle(() => ({
flexGrow: 1,

View File

@@ -10,7 +10,7 @@ import { Headered } from '../Headered';
import { HStack } from '../Stack';
import { Text } from '../Text';
type HeaderedCardProps = CardProps & CardHeaderProps;
export type HeaderedCardProps = CardProps & CardHeaderProps;
type CardHeaderProps = RequireAllOrNone<
{

View File

@@ -15,7 +15,7 @@ export type CardStepperMap<ContentIdType = undefined> = Record<
}
>;
type CardStepperProps<ContentIdType = undefined> = {
export type CardStepperProps<ContentIdType = undefined> = {
onFinish: () => void;
primaryButtonText: ReactNode;
secondaryButtonText: ReactNode;

View File

@@ -4,7 +4,7 @@ import { NativeStyleObject, prepareNativeStyle, useNativeStyles } from '@trezor/
import { PressableOpacity } from './Pressable';
import { ACCESSIBILITY_FONTSIZE_MULTIPLIER } from './Text';
type CheckBoxProps = {
export type CheckBoxProps = {
isChecked: boolean;
isDisabled?: boolean;
onChange: (value: boolean) => void;

View File

@@ -15,7 +15,7 @@ import { Color } from '@trezor/theme';
import { ENDLESS_ANIMATION_VALUE } from './constants';
type CircularSpinnerProps = {
export type CircularSpinnerProps = {
size: number;
color: Color;
width: number;

View File

@@ -9,7 +9,7 @@ import {
import { Box, BoxProps } from './Box';
type DividerProps = Omit<BoxProps, 'style'> & {
export type DividerProps = Omit<BoxProps, 'style'> & {
style?: NativeStyleObject;
};

View File

@@ -2,7 +2,15 @@ import { IconName } from '@suite-native/icons';
import { Color } from '@trezor/theme';
import { ButtonColorScheme } from '../Button/Button';
export type AlertVariant = 'info' | 'critical' | 'neutral' | 'success' | 'warning';
export const FULL_ALERT_BOX_VARIANTS = [
'info',
'critical',
'neutral',
'success',
'warning',
] as const;
export type AlertVariant = (typeof FULL_ALERT_BOX_VARIANTS)[number];
export type FullAlertStyles = {
backgroundColor: Color;

View File

@@ -7,9 +7,10 @@ import { Color } from '@trezor/theme';
import { HStack } from './Stack';
import { Text } from './Text';
type HintVariant = 'hint' | 'error' | 'info';
export const HINT_VARIANTS = ['hint', 'error', 'info'] as const;
export type HintVariant = (typeof HINT_VARIANTS)[number];
type HintProps = {
export type HintProps = {
variant?: HintVariant;
style?: NativeStyleObject;
children?: ReactNode;

View File

@@ -35,7 +35,7 @@ const LOADER_ARC_OVAL_CONFIG = {
height: CANVAS_SIZE - BORDER_WIDTH * 2,
};
type HoldToConfirmButtonProps = {
export type HoldToConfirmButtonProps = {
onSuccess: () => void;
isDisplayed?: SharedValue<boolean>;
buttonLabelId?: TxKeyPath;

View File

@@ -9,7 +9,10 @@ import { OrderedListIcon } from './OrderedListIcon';
import { HStack } from './Stack';
import { Text } from './Text';
type Variant = 'default' | 'blue' | 'red' | 'yellow' | 'primary';
export const ICON_LIST_ITEM_VARIANTS = ['default', 'blue', 'red', 'yellow', 'primary'] as const;
export type IconListItemVariant = (typeof ICON_LIST_ITEM_VARIANTS)[number];
type IconColors = {
iconColor: Color;
iconBorderColor: Color;
@@ -42,18 +45,18 @@ const iconColorsMap = {
iconBorderColor: 'backgroundPrimaryDefault',
iconBackgroundColor: 'backgroundPrimaryDefault',
},
} as const satisfies Record<Variant, IconColors>;
} as const satisfies Record<IconListItemVariant, IconColors>;
type IconListItemProps = {
export type IconListItemProps = {
children: ReactNode;
icon: IconName;
iconSize?: IconSize;
variant?: Variant;
variant?: IconListItemVariant;
verticalAlign?: FlexAlignType;
spacing?: NativeSpacing | number;
};
type IconListTextItemProps = IconListItemProps & {
export type IconListTextItemProps = IconListItemProps & {
textVariant?: NativeTypographyStyle;
textColor?: Color;
};

View File

@@ -3,7 +3,14 @@ import { Color } from '@trezor/theme';
import { ButtonColorScheme } from '../Button/Button';
export type InlineAlertBoxVariant = 'info' | 'critical' | 'neutral' | 'success' | 'warning';
export const INLINE_ALERT_BOX_VARIANTS = [
'info',
'critical',
'neutral',
'success',
'warning',
] as const;
export type InlineAlertBoxVariant = (typeof INLINE_ALERT_BOX_VARIANTS)[number];
export type InlineAlertBoxStyles = {
backgroundColor: Color;

View File

@@ -6,10 +6,11 @@ import { Color } from '@trezor/theme';
import { HStack } from './Stack';
import { Text } from './Text';
type Variant = 'info' | 'success' | 'critical';
export const INLINE_ALERT_TEXT_VARIANTS = ['info', 'success', 'critical'] as const;
export type InlineAlertTextVariant = (typeof INLINE_ALERT_TEXT_VARIANTS)[number];
export type InlineAlertTextProps = {
variant: Variant;
variant: InlineAlertTextVariant;
children: ReactNode;
};
@@ -31,7 +32,7 @@ const variants = {
icon: 'warningCircle',
color: 'textAlertRed',
},
} as const satisfies Record<Variant, VariantConfig>;
} as const satisfies Record<InlineAlertTextVariant, VariantConfig>;
export const InlineAlertText = ({ variant, children }: InlineAlertTextProps) => {
const { color, icon } = variants[variant];

View File

@@ -10,7 +10,7 @@ import { SearchInputMagnifyingGlass } from './SearchInputMagnifyingGlass';
import { inputStyle, inputWrapperStyle } from './searchInputStyles';
import { useSearchInputCallbacks } from './useSearchInputCallbacks';
export type SearchInputProps = {
export type BaseSearchInputProps = {
onChange: (value: string) => void;
placeholder?: string;
isDisabled?: boolean;
@@ -19,7 +19,7 @@ export type SearchInputProps = {
onFocus?: () => void;
onBlur?: () => void;
};
export const BaseSearchInput = forwardRef<TextInput, SearchInputProps>(
export const BaseSearchInput = forwardRef<TextInput, BaseSearchInputProps>(
(
{ onChange, placeholder, maxLength, isDisabled = false, elevation = '0', onFocus, onBlur },
ref,

View File

@@ -4,7 +4,7 @@ import { TextInput } from 'react-native';
import { SurfaceElevation } from '../types';
import { BaseSearchInput } from './BaseSearchInput';
type InputProps = {
export type SearchInputProps = {
onChange: (value: string) => void;
placeholder?: string;
isDisabled?: boolean;
@@ -22,7 +22,7 @@ export const SearchInput = ({
elevation = '0',
onFocus,
onBlur,
}: InputProps) => {
}: SearchInputProps) => {
const searchInputRef = useRef<TextInput>(null);
return (

View File

@@ -7,7 +7,7 @@ import { Color } from '@trezor/theme';
import { Box } from './Box';
import { Text } from './Text';
type LoaderProps = {
export type LoaderProps = {
size?: ActivityIndicatorProps['size'];
title?: ReactNode;
color?: Color;

View File

@@ -6,7 +6,7 @@ import { Box } from './Box';
import { HStack } from './Stack';
import { Text } from './Text';
type NumberedListItemProps = {
export type NumberedListItemProps = {
children: ReactNode;
variant?: NativeTypographyStyle;
color?: Color;

View File

@@ -15,9 +15,10 @@ import { WarningIconSvg } from './WarningIconSvg';
import { WarningShapeSvg } from './WarningShapeSvg';
import { PictogramIconSvgProps } from './types';
export type PictogramVariant = 'success' | 'info' | 'warning' | 'critical';
export const PICTOGRAM_VARIANTS = ['success', 'info', 'warning', 'critical'] as const;
export type PictogramVariant = (typeof PICTOGRAM_VARIANTS)[number];
type PictogramProps = {
export type PictogramProps = {
variant: PictogramVariant;
icon?: IconName;
};

View File

@@ -1,34 +0,0 @@
import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
import { CSSColor } from '@trezor/theme';
import { Box } from './Box';
type ProgressBarProps = {
value: number; // Percentage value
color: CSSColor;
};
const PROGRESS_BAR_WIDTH = 82;
const progressBarStyle = prepareNativeStyle(utils => ({
height: 3,
width: PROGRESS_BAR_WIDTH,
backgroundColor: utils.colors.backgroundSurfaceElevationNegative,
}));
const progressFillStyle = prepareNativeStyle<{ width: number; color: CSSColor }>(
(_, { width, color }) => ({
width: (width / 100) * PROGRESS_BAR_WIDTH,
height: 3,
backgroundColor: color,
}),
);
export const ProgressBar = ({ value, color }: ProgressBarProps) => {
const { applyStyle } = useNativeStyles();
return (
<Box style={applyStyle(progressBarStyle)}>
<Box style={applyStyle(progressFillStyle, { width: value, color })} />
</Box>
);
};

View File

@@ -6,8 +6,9 @@ import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
const ANIMATION_SPEED = 1.5;
export type SpinnerLoadingState = 'success' | 'error' | 'idle';
type SpinnerProps = {
export const SPINNER_LOADING_STATES = ['success', 'error', 'idle'] as const;
export type SpinnerLoadingState = (typeof SPINNER_LOADING_STATES)[number];
export type SpinnerProps = {
loadingState: SpinnerLoadingState;
onComplete?: () => void;
endFrame?: number;

View File

@@ -13,7 +13,7 @@ import { paletteV1 } from '@trezor/theme';
import { ACCESSIBILITY_FONTSIZE_MULTIPLIER } from './Text';
type SwitchProps = {
export type SwitchProps = {
isChecked: boolean;
onChange: (value: boolean) => void;
isDisabled?: boolean; // Functionality of disabled works but styles are not implemented yet (waiting for design)

View File

@@ -4,7 +4,7 @@ import { Box } from './Box';
import { HStack } from './Stack';
import { Text } from './Text';
type TableProps = {
export type TableProps = {
children: ReactNode;
};
type TdProps = {

View File

@@ -6,7 +6,7 @@ import { Box } from './Box';
import { HStack } from './Stack';
import { Text } from './Text';
type TextDividerProps = {
export type TextDividerProps = {
title?: TxKeyPath;
horizontalMargin?: number;
lineColor?: Color;

View File

@@ -1,53 +0,0 @@
import { ReactNode } from 'react';
import { prepareNativeStyle, useNativeStyles } from '@trezor/styles';
import { NativeTypographyStyle } from '@trezor/theme';
import { VStack } from './Stack';
import { Text } from './Text';
type TitleHeaderProps = {
title?: ReactNode;
titleVariant?: NativeTypographyStyle;
subtitle?: ReactNode;
textAlign?: 'left' | 'center';
};
const textStyle = prepareNativeStyle<{ textAlign: TitleHeaderProps['textAlign'] }>(
(_, { textAlign }) => ({
textAlign,
width: '100%',
}),
);
export const TitleHeader = ({
title,
subtitle,
titleVariant = 'titleSmall',
textAlign = 'left',
}: TitleHeaderProps) => {
const { applyStyle } = useNativeStyles();
return (
<VStack alignItems="center">
{title && (
<Text variant={titleVariant} style={applyStyle(textStyle, { textAlign })}>
{title}
</Text>
)}
{subtitle && (
<Text color="textSubdued" style={applyStyle(textStyle, { textAlign })}>
{subtitle}
</Text>
)}
</VStack>
);
};
export const CenteredTitleHeader = ({
title,
subtitle,
titleVariant = 'titleSmall',
}: TitleHeaderProps) => (
<TitleHeader titleVariant={titleVariant} title={title} subtitle={subtitle} textAlign="center" />
);

View File

@@ -7,7 +7,7 @@ import { Pictogram, PictogramVariant } from '../Pictogram/Pictogram';
import { VStack } from '../Stack';
import { CenteredTitleHeader } from './CenteredTitleHeader';
type PictogramTitleHeaderProps = {
export type PictogramTitleHeaderProps = {
variant: PictogramVariant;
icon?: IconName;
title?: ReactNode;

View File

@@ -9,7 +9,7 @@ import { HStack, VStack } from './Stack';
import { Switch } from './Switch';
import { Text } from './Text';
type TouchableSwitchRowProps = {
export type TouchableSwitchRowProps = {
icon: IconName;
accessibilityLabel: string;
text: ReactNode;

View File

@@ -37,7 +37,6 @@ export * from './Badge';
export * from './Divider';
export * from './TextDivider';
export * from './TitledSection';
export * from './ProgressBar';
export * from './Card/Card';
export * from './Card/HeaderedCard';
export * from './Card/CardDivider';

View File

@@ -0,0 +1,53 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import { ICON_NAMES } from '@suite-native/icons';
import {
FullAlertBox as FullAlertBoxComponent,
FullAlertBoxProps,
} from '../../FullAlertBox/FullAlertBox';
import { FULL_ALERT_BOX_VARIANTS } from '../../FullAlertBox/presets';
type FullAlertBoxStory = StoryObj<FullAlertBoxProps>;
const meta: Meta<FullAlertBoxProps> = {
title: 'Atoms/AlertBoxes',
component: FullAlertBoxComponent,
};
export default meta;
export const FullAlertBox: FullAlertBoxStory = {
name: 'FullAlertBox',
args: {
title: 'Title',
description:
'Very descriptive description. Read carefully or you will miss something important.',
primaryButtonLabel: 'Primary',
secondaryButtonLabel: 'Secondary',
variant: 'info',
iconName: undefined,
},
argTypes: {
title: {
control: { type: 'text' },
},
description: {
control: { type: 'text' },
},
primaryButtonLabel: {
control: { type: 'text' },
},
secondaryButtonLabel: {
control: { type: 'text' },
},
variant: {
control: { type: 'select' },
options: FULL_ALERT_BOX_VARIANTS,
},
iconName: {
control: { type: 'select' },
options: ICON_NAMES,
},
},
};

View File

@@ -0,0 +1,48 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import { ICON_NAMES } from '@suite-native/icons';
import { FULL_ALERT_BOX_VARIANTS } from '../../FullAlertBox/presets';
import {
InlineAlertBox as InlineAlertBoxComponent,
InlineAlertBoxProps,
} from '../../InlineAlertBox/InlineAlertBox';
type InlineAlertBoxStory = StoryObj<InlineAlertBoxProps>;
const meta: Meta<InlineAlertBoxProps> = {
title: 'Atoms/AlertBoxes',
component: InlineAlertBoxComponent,
};
export default meta;
export const InlineAlertBox: InlineAlertBoxStory = {
name: 'InlineAlertBox',
args: {
title: 'Something very important to communicate.',
buttonLabel: 'Button',
variant: 'info',
iconName: undefined,
buttonProps: undefined,
},
argTypes: {
title: {
control: { type: 'text' },
},
buttonLabel: {
control: { type: 'text' },
},
viewLeft: {
control: false,
},
variant: {
control: { type: 'select' },
options: FULL_ALERT_BOX_VARIANTS,
},
iconName: {
control: { type: 'select' },
options: ICON_NAMES,
},
},
};

View File

@@ -0,0 +1,57 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import { ICON_NAMES, ICON_SIZES } from '@suite-native/icons';
import { BADGE_SIZES, BADGE_VARIANTS, Badge as BadgeComponent, BadgeProps } from '../Badge';
import { SURFACE_ELEVATIONS } from '../types';
type BadgeStory = StoryObj<BadgeProps>;
const meta: Meta<BadgeProps> = {
title: 'Atoms',
component: BadgeComponent,
render: args => <BadgeComponent {...args} style={{ alignSelf: 'center' }} />,
};
export default meta;
export const Badge: BadgeStory = {
name: 'Badge',
args: {
label: 'badge',
variant: 'green',
size: 'medium',
icon: undefined,
iconSize: 'small',
elevation: '0',
isDisabled: false,
},
argTypes: {
label: {
control: { type: 'text' },
},
variant: {
control: { type: 'select' },
options: BADGE_VARIANTS,
},
icon: {
control: { type: 'select' },
options: ICON_NAMES,
},
size: {
control: { type: 'select' },
options: BADGE_SIZES,
},
iconSize: {
control: { type: 'select' },
options: ICON_SIZES,
},
elevation: {
control: { type: 'select' },
options: SURFACE_ELEVATIONS,
},
isDisabled: {
control: { type: 'boolean' },
},
},
};

View File

@@ -1,4 +1,3 @@
/* eslint-disable import/no-default-export */
import type { Meta, StoryObj } from '@storybook/react-native';
import { ICON_NAMES } from '@suite-native/icons';
@@ -8,19 +7,24 @@ import {
BUTTON_SIZES,
Button as ButtonComponent,
ButtonProps,
} from '../Button/Button';
} from '../../Button/Button';
type ButtonStory = StoryObj<ButtonProps>;
const meta: Meta<ButtonProps> = {
title: 'Atoms/buttons',
title: 'Atoms/Buttons',
component: ButtonComponent,
};
export default meta;
export const Button: ButtonStory = {
args: { children: 'Button', colorScheme: 'primary', size: 'medium' },
args: {
children: 'Press me',
viewLeft: 'magnifyingGlass',
colorScheme: 'primary',
size: 'medium',
},
argTypes: {
children: {
type: 'string',

View File

@@ -0,0 +1,39 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import { ICON_NAMES } from '@suite-native/icons';
import { BUTTON_COLOR_SCHEMES, BUTTON_SIZES } from '../../Button/Button';
import { IconButton as IconButtonComponent, IconButtonProps } from '../../Button/IconButton';
type IconButtonStory = StoryObj<IconButtonProps>;
const meta: Meta<IconButtonProps> = {
title: 'Atoms/Buttons',
component: IconButtonComponent,
};
export default meta;
export const IconButton: IconButtonStory = {
args: { colorScheme: 'primary', size: 'medium', iconName: 'magnifyingGlass' },
argTypes: {
iconName: {
control: { type: 'select' },
options: ICON_NAMES,
},
colorScheme: {
control: { type: 'select' },
options: BUTTON_COLOR_SCHEMES,
},
size: {
control: { type: 'select' },
options: BUTTON_SIZES,
},
isDisabled: {
control: { type: 'boolean' },
},
isLoading: {
control: { type: 'boolean' },
},
},
};

View File

@@ -0,0 +1,63 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import { ICON_NAMES } from '@suite-native/icons';
import { BUTTON_COLOR_SCHEMES, BUTTON_SIZES } from '../../Button/Button';
import { TextButton as TextButtonComponent, TextButtonProps } from '../../Button/TextButton';
type TextButtonStory = StoryObj<TextButtonProps>;
const meta: Meta<TextButtonProps> = {
title: 'Atoms/Buttons',
component: TextButtonComponent,
};
export default meta;
export const TextButton: TextButtonStory = {
args: { children: 'Press me', viewLeft: 'magnifyingGlass', variant: 'primary', size: 'medium' },
argTypes: {
children: {
type: 'string',
},
variant: {
control: { type: 'select' },
options: BUTTON_COLOR_SCHEMES,
},
size: {
control: { type: 'select' },
options: BUTTON_SIZES,
},
isDisabled: {
control: { type: 'boolean' },
},
isLoading: {
control: { type: 'boolean' },
},
viewLeft: {
control: { type: 'select' },
options: ICON_NAMES,
},
viewRight: {
control: { type: 'select' },
options: ICON_NAMES,
},
isUnderlined: {
control: { type: 'boolean' },
},
isBold: {
control: { type: 'boolean' },
},
justifyContent: {
control: { type: 'select' },
options: [
'flex-start',
'center',
'flex-end',
'space-between',
'space-around',
'space-evenly',
],
},
},
};

View File

@@ -0,0 +1,65 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import { COLOR_TOKENS } from '@trezor/theme';
import { Card as CardComponent, CardProps } from '../../Card/Card';
import { InlineAlertBoxProps } from '../../InlineAlertBox/InlineAlertBox';
import { INLINE_ALERT_BOX_VARIANTS, InlineAlertBoxVariant } from '../../InlineAlertBox/presets';
import { Text } from '../../Text';
type CardStory = StoryObj<CardProps>;
const meta: Meta<CardProps> = {
title: 'Atoms/Cards',
component: CardComponent,
render: args => (
<CardComponent {...args}>
<Text>{args.children}</Text>
</CardComponent>
),
};
export default meta;
export const Card: CardStory = {
args: {
children:
'This is content inside the Card. It can be some very long text or even some other components. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
},
argTypes: {
children: {
control: false,
},
noPadding: {
control: { type: 'boolean' },
},
noShadow: {
control: { type: 'boolean' },
},
borderColor: {
control: { type: 'select' },
options: COLOR_TOKENS,
},
alertPosition: {
name: 'Alert position',
control: { type: 'select' },
options: ['top', 'bottom'],
},
alertProps: {
name: 'Alert variant',
control: { type: 'select' },
options: INLINE_ALERT_BOX_VARIANTS,
mapping: INLINE_ALERT_BOX_VARIANTS.reduce(
(acc, variant) => {
acc[variant] = {
title: `This is some very important ${variant} alert than needs to be highlighted`,
variant,
};
return acc;
},
{} as Record<InlineAlertBoxVariant, InlineAlertBoxProps>,
),
},
},
};

View File

@@ -0,0 +1,69 @@
import { View } from 'react-native';
import type { Meta, StoryObj } from '@storybook/react-native';
import {
CardStepper as CardStepperComponent,
CardStepperProps,
} from '../../CardStepper/CardStepper';
type CardStepperStory = StoryObj<CardStepperProps>;
const meta: Meta<CardStepperProps> = {
title: 'Atoms/Cards',
component: CardStepperComponent,
decorators: [
Story => (
<View style={{ width: '100%' }}>
<Story />
</View>
),
],
};
export default meta;
export const CardStepper: CardStepperStory = {
name: 'CardStepper',
args: {
primaryButtonText: 'Continue',
secondaryButtonText: 'Cancel',
buttonsActionType: 'primary',
onFinish: () => {},
stepToContentMap: {
1: {
header: 'Read this',
description: 'Step 1 description read this properly',
icon: 'star',
},
2: {
header: 'Then another one',
description: 'This is step number two ;)',
icon: 'timer',
},
3: {
header: 'Still reading?',
description: 'Step 3 description',
icon: 'stack',
},
4: {
header: 'Final step',
description: 'Step 4 description',
icon: 'flag',
},
},
},
argTypes: {
primaryButtonText: {
control: { type: 'text' },
},
secondaryButtonText: {
control: { type: 'text' },
},
buttonsActionType: {
control: { type: 'select' },
options: ['primary', 'destructive'],
},
},
};

View File

@@ -0,0 +1,46 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import { ICON_NAMES } from '@suite-native/icons';
import { Card as CardStory } from './Card.stories';
import {
CardWithIconLayout as CardWithIconLayoutComponent,
CardWithIconLayoutProps,
} from '../../Card/CardWithIconLayout';
import { Text } from '../../Text';
type CardWithIconLayoutStory = StoryObj<CardWithIconLayoutProps>;
const meta: Meta<CardWithIconLayoutProps> = {
title: 'Atoms/Cards',
component: CardWithIconLayoutComponent,
render: args => (
<CardWithIconLayoutComponent {...args}>
<Text>{args.children}</Text>
</CardWithIconLayoutComponent>
),
};
export default meta;
export const CardWithIconLayout: CardWithIconLayoutStory = {
name: 'CardWithIconLayout',
args: {
children: CardStory.args?.children,
icon: 'flag',
title: 'Card title',
},
argTypes: {
children: {
control: false,
},
icon: {
control: { type: 'select' },
options: ICON_NAMES,
},
title: {
control: { type: 'text' },
},
alertBoxProps: CardStory.argTypes?.alertProps,
},
};

View File

@@ -0,0 +1,57 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import { ICON_NAMES } from '@suite-native/icons';
import { COLOR_TOKENS } from '@trezor/theme';
import { Card as CardStory } from './Card.stories';
import {
COMPACT_CARD_VARIANTS,
CompactCardWithIconLayout as CompactCardWithIconLayoutComponent,
CompactCardWithIconLayoutProps,
} from '../../Card/CompactCardWithIconLayout';
type CompactCardWithIconLayoutStory = StoryObj<CompactCardWithIconLayoutProps>;
const meta: Meta<CompactCardWithIconLayoutProps> = {
title: 'Atoms/Cards',
component: CompactCardWithIconLayoutComponent,
};
export default meta;
export const CompactCardWithIconLayout: CompactCardWithIconLayoutStory = {
name: 'CompactCardWithIconLayout',
args: {
icon: 'flag',
title: 'Card title that you can press to interact with',
variant: 'primary',
subtitle: 'This is a subtitle that you can use to describe the card',
},
argTypes: {
variant: {
control: { type: 'select' },
options: COMPACT_CARD_VARIANTS,
},
icon: {
control: { type: 'select' },
options: ICON_NAMES,
},
title: {
control: { type: 'text' },
},
subtitle: {
control: { type: 'text' },
},
isDisabled: {
control: { type: 'boolean' },
},
noShadow: {
control: { type: 'boolean' },
},
alertBoxProps: CardStory.argTypes?.alertProps,
borderColor: {
control: { type: 'select' },
options: COLOR_TOKENS,
},
},
};

View File

@@ -0,0 +1,46 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import { ICON_NAMES } from '@suite-native/icons';
import { Card } from './Card.stories';
import { HeaderedCard as HeaderedCardComponent, HeaderedCardProps } from '../../Card/HeaderedCard';
import { Text } from '../../Text';
type HeaderedCardStory = StoryObj<HeaderedCardProps>;
const meta: Meta<HeaderedCardProps> = {
title: 'Atoms/Cards',
component: HeaderedCardComponent,
render: args => (
<HeaderedCardComponent {...args}>
<Text>{args.children}</Text>
</HeaderedCardComponent>
),
};
export default meta;
export const HeaderedCard: HeaderedCardStory = {
name: 'HeaderedCard',
args: {
buttonIcon: 'flag',
buttonTitle: 'Button title',
title: 'Card title',
children: Card.args?.children,
},
argTypes: {
children: {
control: { type: 'text' },
},
title: {
control: { type: 'text' },
},
buttonTitle: {
control: { type: 'text' },
},
buttonIcon: {
control: { type: 'select' },
options: ICON_NAMES,
},
},
};

View File

@@ -0,0 +1,38 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import { COLOR_TOKENS } from '@trezor/theme';
import {
CircularSpinner as CircularSpinnerComponent,
CircularSpinnerProps,
} from '../CircularSpinner';
type CircularSpinnerStory = StoryObj<CircularSpinnerProps>;
const meta: Meta<CircularSpinnerProps> = {
title: 'Atoms',
component: CircularSpinnerComponent,
};
export default meta;
export const CircularSpinner: CircularSpinnerStory = {
name: 'CircularSpinner',
args: {
size: 50,
color: 'textDefault',
width: 5,
},
argTypes: {
size: {
control: { type: 'number' },
},
color: {
control: { type: 'select' },
options: COLOR_TOKENS,
},
width: {
control: { type: 'number' },
},
},
};

View File

@@ -0,0 +1,25 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import { Divider as DividerComponent, DividerProps } from '../../Divider';
import { VStack } from '../../Stack';
import { Text } from '../../Text';
type DividerStory = StoryObj<DividerProps>;
const meta: Meta<DividerProps> = {
title: 'Atoms/Dividers',
component: DividerComponent,
render: args => (
<VStack alignItems="center">
<Text>Text above the divider</Text>
<DividerComponent {...args} style={{ backgroundColor: '#afb3b9', width: '100%' }} />
<Text>Text below the divider</Text>
</VStack>
),
};
export default meta;
export const Divider: DividerStory = {
name: 'Divider',
};

View File

@@ -0,0 +1,51 @@
import { View } from 'react-native';
import { type Meta, type StoryObj } from '@storybook/react-native';
import { COLOR_TOKENS } from '@trezor/theme';
import { VStack } from '../../Stack';
import { Text } from '../../Text';
import { TextDivider as TextDividerComponent, TextDividerProps } from '../../TextDivider';
type TextDividerStory = StoryObj<TextDividerProps>;
const meta: Meta<TextDividerProps> = {
title: 'Atoms/Dividers',
component: TextDividerComponent,
render: args => (
<VStack alignItems="center">
<Text>Text above the divider</Text>
<View style={{ width: '100%' }}>
<TextDividerComponent {...args} />
</View>
<Text>Text below the divider</Text>
</VStack>
),
};
export default meta;
export const TextDivider: TextDividerStory = {
name: 'Text Divider',
args: {
lineColor: 'textDefault',
textColor: 'textDefault',
},
argTypes: {
title: {
control: { type: 'text' },
},
horizontalMargin: {
control: { type: 'number' },
},
lineColor: {
control: { type: 'select' },
options: COLOR_TOKENS,
},
textColor: {
control: { type: 'select' },
options: COLOR_TOKENS,
},
},
};

View File

@@ -0,0 +1,29 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import { HINT_VARIANTS, Hint as HintComponent, HintProps } from '../Hint';
type HintStory = StoryObj<HintProps>;
const meta: Meta<HintProps> = {
title: 'Atoms',
component: HintComponent,
};
export default meta;
export const Hint: HintStory = {
name: 'Hint',
args: {
variant: 'hint',
children: 'Hint message',
},
argTypes: {
variant: {
control: { type: 'select' },
options: HINT_VARIANTS,
},
children: {
control: { type: 'text' },
},
},
};

View File

@@ -0,0 +1,33 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import {
INLINE_ALERT_TEXT_VARIANTS,
InlineAlertText as InlineAlertTextComponent,
InlineAlertTextProps,
} from '../InlineAlertText';
type InlineAlertTextStory = StoryObj<InlineAlertTextProps>;
const meta: Meta<InlineAlertTextProps> = {
title: 'Atoms',
component: InlineAlertTextComponent,
};
export default meta;
export const InlineAlertText: InlineAlertTextStory = {
name: 'InlineAlertText',
args: {
variant: 'info',
children: 'Text message',
},
argTypes: {
variant: {
control: { type: 'select' },
options: INLINE_ALERT_TEXT_VARIANTS,
},
children: {
control: { type: 'text' },
},
},
};

View File

@@ -0,0 +1,41 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import { useArgs } from 'storybook/preview-api';
import { CheckBox as CheckBoxComponent, CheckBoxProps } from '../../CheckBox';
type CheckBoxStory = StoryObj<CheckBoxProps>;
const meta: Meta<CheckBoxProps> = {
title: 'Atoms/Inputs',
component: CheckBoxComponent,
render: args => {
const [{ isChecked }, updateArgs] = useArgs();
return (
<CheckBoxComponent
{...args}
isChecked={isChecked}
onChange={() => updateArgs({ isChecked: !isChecked })}
/>
);
},
};
export default meta;
export const CheckBox: CheckBoxStory = {
name: 'CheckBox',
args: {
isChecked: true,
isDisabled: false,
onChange: () => {},
},
argTypes: {
isChecked: {
control: { type: 'boolean' },
},
isDisabled: {
control: { type: 'boolean' },
},
},
};

View File

@@ -0,0 +1,48 @@
import { type Meta, type StoryObj } from '@storybook/react-native';
import { useArgs } from 'storybook/preview-api';
import { Radio as RadioComponent, RadioProps } from '../../Radio';
type RadioStory = StoryObj<RadioProps<number>>;
const meta: Meta<RadioProps<number>> = {
title: 'Atoms/Inputs',
component: RadioComponent<number>,
render: args => {
const [{ isChecked }, updateArgs] = useArgs();
return (
<RadioComponent
{...args}
isChecked={isChecked}
onPress={() => updateArgs({ isChecked: !isChecked })}
/>
);
},
};
export default meta;
export const Radio: RadioStory = {
name: 'Radio',
args: {
value: 1,
isChecked: true,
isDisabled: false,
activeColor: 'backgroundPrimaryDefault',
},
argTypes: {
value: {
control: false,
},
isChecked: {
control: { type: 'boolean' },
},
isDisabled: {
control: { type: 'boolean' },
},
activeColor: {
control: { type: 'color' },
},
},
};

View File

@@ -0,0 +1,38 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import { SearchInput as SearchInputComponent, SearchInputProps } from '../../Input/SearchInput';
type SearchInputStory = StoryObj<SearchInputProps>;
const meta: Meta<SearchInputProps> = {
title: 'Atoms/Inputs',
component: SearchInputComponent,
};
export default meta;
export const SearchInput: SearchInputStory = {
name: 'SearchInput',
args: {
placeholder: 'Search for something',
isDisabled: false,
maxLength: 100,
elevation: '0',
onChange: () => {},
},
argTypes: {
placeholder: {
control: { type: 'text' },
},
isDisabled: {
control: { type: 'boolean' },
},
maxLength: {
control: { type: 'number' },
},
elevation: {
control: { type: 'select' },
options: ['0', '1'],
},
},
};

View File

@@ -0,0 +1,40 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import { useArgs } from 'storybook/preview-api';
import { Switch as SwitchComponent, SwitchProps } from '../../Switch';
type SwitchStory = StoryObj<SwitchProps>;
const meta: Meta<SwitchProps> = {
title: 'Atoms/Inputs',
component: SwitchComponent,
render: args => {
const [{ isChecked }, updateArgs] = useArgs();
return (
<SwitchComponent
{...args}
isChecked={isChecked}
onChange={() => updateArgs({ isChecked: !isChecked })}
/>
);
},
};
export default meta;
export const Switch: SwitchStory = {
name: 'Switch',
args: {
isChecked: true,
isDisabled: false,
},
argTypes: {
isChecked: {
control: { type: 'boolean' },
},
isDisabled: {
control: { type: 'boolean' },
},
},
};

View File

@@ -0,0 +1,62 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import { useArgs } from 'storybook/preview-api';
import { Input as InputComponent, InputProps } from '../../Input/Input';
import { InputWrapper } from '../../Input/InputWrapper';
type TextInputArgs = InputProps & { wrapperLabel: string; hint: string; error: string };
type TextInputStory = StoryObj<TextInputArgs>;
const meta: Meta<TextInputArgs> = {
title: 'Atoms/Inputs',
component: InputComponent,
decorators: [
(Story, { context: { args } }) => {
const [{ value }, updateArgs] = useArgs();
return (
<InputWrapper hint={args.hint} label={args.wrapperLabel} error={args.error}>
<Story
args={{
...args,
label: undefined,
hasError: !!args.error,
value,
onChangeText: (text: string) => updateArgs({ value: text }),
}}
/>
</InputWrapper>
);
},
],
};
export default meta;
export const TextInput: TextInputStory = {
name: 'TextInput',
args: {
value: '',
wrapperLabel: 'Label',
hint: 'This is a hint',
placeholder: 'placeholder',
},
argTypes: {
label: {
control: { type: 'text' },
},
hint: {
control: { type: 'text' },
},
value: {
control: { type: 'text' },
},
placeholder: {
control: { type: 'text' },
},
error: {
control: { type: 'text' },
},
},
};

View File

@@ -0,0 +1,58 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import { useArgs } from 'storybook/preview-api';
import { ICON_NAMES } from '@suite-native/icons';
import {
TouchableSwitchRow as TouchableSwitchRowComponent,
TouchableSwitchRowProps,
} from '../../TouchableSwitchRow';
type TouchableSwitchRowStory = StoryObj<TouchableSwitchRowProps>;
const meta: Meta<TouchableSwitchRowProps> = {
title: 'Atoms/Inputs',
component: TouchableSwitchRowComponent,
render: args => {
const [{ isChecked }, updateArgs] = useArgs();
return (
<TouchableSwitchRowComponent
{...args}
isChecked={isChecked}
onChange={() => updateArgs({ isChecked: !isChecked })}
/>
);
},
};
export default meta;
export const TouchableSwitchRow: TouchableSwitchRowStory = {
name: 'TouchableSwitchRow',
args: {
isChecked: true,
icon: 'info',
text: 'Toggle label',
description: 'Toggle description that is more detailed.',
accessibilityLabel: 'Info',
},
argTypes: {
isChecked: {
control: { type: 'boolean' },
},
icon: {
control: { type: 'select' },
options: ICON_NAMES,
},
text: {
control: { type: 'text' },
},
description: {
control: { type: 'text' },
},
accessibilityLabel: {
control: false,
},
},
};

View File

@@ -0,0 +1,66 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import {
AccordionList as AccordionListComponent,
AccordionListProps,
} from '../../Accordion/AccordionList';
import { Text } from '../../Text';
type AccordionListStory = StoryObj<AccordionListProps>;
const meta: Meta<AccordionListProps> = {
title: 'Atoms/Lists',
component: AccordionListComponent,
decorators: [
(_Story, context) => {
const { args } = context;
const textWrappedArgs = {
...args,
items: args.items.map(item => ({
...item,
content: <Text variant="label">{item.content}</Text>,
})),
};
return <_Story args={textWrappedArgs} />;
},
],
};
export default meta;
const defaultAccordionItems: AccordionListProps['items'] = [
{
title: 'Can I connect my Trezor to Trezor Suite on Mobile?',
content: `Yes, you can connect your Trezor Safe 7 and use the app to manage your crypto with ease and confidence. For all Trezor devices the app is designed to work as a companion to the desktop/web version of Trezor Suite. As we add more features, itll become a standalone mobile application to manage your crypto funds on the go.`,
iconName: 'trezorSafe7',
},
{
title: 'What is the difference between Portfolio Tracker and Connected Trezor functionality?',
content:
'Portfolio Tracker helps you monitor your account balances without having to physically connect your Trezor device. Simply sync your coin addresses and keep track of your crypto on the go. You can also combine coin addresses from multiple wallets or Trezor devices to track your whole portfolio in one place. Connected Trezor allows you to manage your funds protected by your Trezor device. You can verify receive addresses and check your balances and transactions. However, if you disconnect the Trezor, youll no longer see the data from the Trezor device.',
iconName: 'wallet',
},
{
title: 'What are public keys (XPUB) and receive addresses?',
content:
'An XPUB is a master public key for hierarchical deterministic wallets like bitcoin, generating multiple child keys and receive addresses for improved privacy. Ethereum uses a single, unchanging address for all transactions. For Ethereum, share only your address, while keeping your private key secure.',
iconName: 'qrCode',
},
{
title: 'My Trezor device cant connect',
content:
'Check the devices are in close proximityMake sure bluetooth is enabled on both devicesRemove old Trezor device Bluetooth connectionsRestart your device(s)Turn Bluetooth on/off again on your mobile deviceForget and re-pair the devicesUpdate Trezor firmware and your mobile device OS',
iconName: 'cableUsbC',
},
];
export const AccordionList: AccordionListStory = {
args: { items: defaultAccordionItems },
argTypes: {
items: {
control: { type: 'object' },
},
},
};

View File

@@ -0,0 +1,39 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import { COLOR_TOKENS, nativeTypographyStyles } from '@trezor/theme';
import {
BulletListItem as BulletListItemComponent,
BulletListItemProps,
} from '../../BulletListItem';
type BulletListItemStory = StoryObj<BulletListItemProps>;
const meta: Meta<BulletListItemProps> = {
title: 'Atoms/Lists',
component: BulletListItemComponent,
};
export default meta;
export const BulletListItem: BulletListItemStory = {
name: 'BulletListItem',
args: {
children: 'textual bullet point',
variant: 'body',
color: 'textDefault',
},
argTypes: {
children: {
control: { type: 'text' },
},
variant: {
control: { type: 'select' },
options: nativeTypographyStyles,
},
color: {
control: { type: 'select' },
options: COLOR_TOKENS,
},
},
};

View File

@@ -0,0 +1,59 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import { ICON_NAMES, ICON_SIZES } from '@suite-native/icons';
import { nativeSpacings, nativeTypographyStyles } from '@trezor/theme';
import {
ICON_LIST_ITEM_VARIANTS,
IconListTextItem as IconListTextItemComponent,
IconListTextItemProps,
} from '../../IconListItem';
type IconListTextItemStory = StoryObj<IconListTextItemProps>;
const meta: Meta<IconListTextItemProps> = {
title: 'Atoms/Lists',
component: IconListTextItemComponent,
};
export default meta;
export const IconListTextItem: IconListTextItemStory = {
name: 'IconListTextItem',
args: {
children: 'Text value',
icon: 'discover',
iconSize: 'medium',
variant: 'default',
verticalAlign: 'center',
spacing: 'sp12',
},
argTypes: {
children: {
control: { type: 'text' },
},
icon: {
control: { type: 'select' },
options: ICON_NAMES,
},
iconSize: {
control: { type: 'select' },
options: Object.values(ICON_SIZES),
},
variant: {
control: { type: 'select' },
options: Object.values(ICON_LIST_ITEM_VARIANTS),
},
textVariant: {
control: { type: 'select' },
options: nativeTypographyStyles,
},
spacing: {
control: { type: 'select' },
options: Object.keys(nativeSpacings),
},
verticalAlign: {
control: false,
},
},
};

View File

@@ -0,0 +1,43 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import { COLOR_TOKENS, nativeTypographyStyles } from '@trezor/theme';
import {
NumberedListItem as NumberedListItemComponent,
NumberedListItemProps,
} from '../../NumberedListItem';
type NumberedListItemStory = StoryObj<NumberedListItemProps>;
const meta: Meta<NumberedListItemProps> = {
title: 'Atoms/Lists',
component: NumberedListItemComponent,
};
export default meta;
export const NumberedListItem: NumberedListItemStory = {
name: 'NumberedListItem',
args: {
children: 'value',
variant: 'body',
color: 'textDefault',
number: 1,
},
argTypes: {
children: {
control: { type: 'text' },
},
variant: {
control: { type: 'select' },
options: nativeTypographyStyles,
},
color: {
control: { type: 'select' },
options: COLOR_TOKENS,
},
number: {
control: { type: 'number' },
},
},
};

View File

@@ -0,0 +1,65 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import { ICON_SIZES } from '@suite-native/icons';
import { COLOR_TOKENS, nativeBorders } from '@trezor/theme';
import {
OrderedListIcon as OrderedListIconComponent,
OrderedListIconProps,
} from '../../OrderedListIcon';
import { HStack } from '../../Stack';
import { Text } from '../../Text';
type OrderedListIconStory = StoryObj<OrderedListIconProps>;
const meta: Meta<OrderedListIconProps> = {
title: 'Atoms/Lists',
component: OrderedListIconComponent,
decorators: [
Story => (
<HStack spacing="sp12" alignItems="center">
<Story />
<Text variant="body">Text value</Text>
</HStack>
),
],
};
export default meta;
export const OrderedListIcon: OrderedListIconStory = {
name: 'OrderedListIcon',
args: {
iconNumber: 1,
iconBackgroundColor: 'backgroundTertiaryDefaultOnElevation1',
iconBorderColor: 'borderElevation0',
iconBorderRadius: 'r12',
iconColor: 'textDefault',
iconSize: 'mediumLarge',
},
argTypes: {
iconNumber: {
control: { type: 'number' },
},
iconBackgroundColor: {
control: { type: 'select' },
options: COLOR_TOKENS,
},
iconBorderColor: {
control: { type: 'select' },
options: COLOR_TOKENS,
},
iconBorderRadius: {
control: { type: 'select' },
options: Object.keys(nativeBorders.radii),
},
iconColor: {
control: { type: 'select' },
options: COLOR_TOKENS,
},
iconSize: {
control: { type: 'select' },
options: Object.values(ICON_SIZES),
},
},
};

View File

@@ -0,0 +1,36 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import { COLOR_TOKENS } from '@trezor/theme';
import { Loader as LoaderComponent, LoaderProps } from '../Loader';
type LoaderStory = StoryObj<LoaderProps>;
const meta: Meta<LoaderProps> = {
title: 'Atoms',
component: LoaderComponent,
};
export default meta;
export const Loader: LoaderStory = {
name: 'Loader',
args: {
size: 'large',
title: 'Loading...',
color: 'backgroundPrimaryDefault',
},
argTypes: {
size: {
control: { type: 'select' },
options: ['small', 'large'],
},
title: {
control: { type: 'text' },
},
color: {
control: { type: 'select' },
options: COLOR_TOKENS,
},
},
};

View File

@@ -0,0 +1,36 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import { ICON_NAMES } from '@suite-native/icons';
import {
PICTOGRAM_VARIANTS,
Pictogram as PictogramComponent,
PictogramProps,
} from '../Pictogram/Pictogram';
type PictogramStory = StoryObj<PictogramProps>;
const meta: Meta<PictogramProps> = {
title: 'Atoms',
component: PictogramComponent,
};
export default meta;
export const Pictogram: PictogramStory = {
name: 'Pictogram',
args: {
variant: 'success',
icon: undefined,
},
argTypes: {
variant: {
control: { type: 'select' },
options: PICTOGRAM_VARIANTS,
},
icon: {
control: { type: 'select' },
options: ICON_NAMES,
},
},
};

View File

@@ -0,0 +1,34 @@
import { View } from 'react-native';
import type { Meta, StoryObj } from '@storybook/react-native';
import {
PriceChangeBadge as PriceChangeBadgeComponent,
PriceChangeBadgeProps,
} from '../PriceChangeBadge';
type PriceChangeBadgeStory = StoryObj<PriceChangeBadgeProps>;
const meta: Meta<PriceChangeBadgeProps> = {
title: 'Atoms',
component: PriceChangeBadgeComponent,
render: args => (
<View style={{ alignSelf: 'center' }}>
<PriceChangeBadgeComponent {...args} />
</View>
),
};
export default meta;
export const PriceChangeBadge: PriceChangeBadgeStory = {
name: 'PriceChangeBadge',
args: {
valuePercentageChange: 0.123,
},
argTypes: {
valuePercentageChange: {
control: { type: 'number' },
},
},
};

View File

@@ -0,0 +1,49 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import { ICON_SIZES, icons } from '@suite-native/icons';
import { COLOR_TOKENS } from '@trezor/theme';
import { RoundedIcon as RoundedIconComponent, RoundedIconProps } from '../RoundedIcon';
type RoundedIconStory = StoryObj<RoundedIconProps>;
const meta: Meta<RoundedIconProps> = {
title: 'Atoms',
component: RoundedIconComponent,
};
export default meta;
export const RoundedIcon: RoundedIconStory = {
name: 'RoundedIcon',
args: {
name: 'flag',
color: 'textPrimaryDefault',
iconSize: 'mediumLarge',
backgroundColor: 'backgroundTertiaryDefaultOnElevation1',
containerSize: 48,
},
argTypes: {
name: {
control: { type: 'select' },
options: Object.keys(icons),
},
iconSize: {
control: { type: 'select' },
options: Object.values(ICON_SIZES),
},
color: {
control: { type: 'select' },
options: COLOR_TOKENS,
},
backgroundColor: {
control: { type: 'select' },
options: COLOR_TOKENS,
},
symbol: {
control: false,
},
},
};

View File

@@ -0,0 +1,41 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import { COLOR_TOKENS, nativeTypographyStyles } from '@trezor/theme';
import { Text as TextComponent, TextProps } from '../Text';
type TextStory = StoryObj<TextProps>;
const meta: Meta<TextProps> = {
title: 'Atoms',
component: TextComponent,
};
export default meta;
export const Text: TextStory = {
name: 'Text',
args: {
children: 'Text value',
variant: 'body',
color: 'textDefault',
textAlign: 'left',
},
argTypes: {
children: {
control: { type: 'text' },
},
variant: {
control: { type: 'select' },
options: nativeTypographyStyles,
},
textAlign: {
control: { type: 'select' },
options: ['left', 'center', 'right'],
},
color: {
control: { type: 'select' },
options: COLOR_TOKENS,
},
},
};

View File

@@ -0,0 +1,50 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import { ICON_NAMES } from '@suite-native/icons';
import { nativeTypographyStyles } from '@trezor/theme';
import { PICTOGRAM_VARIANTS } from '../../Pictogram/Pictogram';
import {
PictogramTitleHeader as PictogramTitleHeaderComponent,
PictogramTitleHeaderProps,
} from '../../TitleHeader/PictogramTitleHeader';
type PictogramTitleHeaderStory = StoryObj<PictogramTitleHeaderProps>;
const meta: Meta<PictogramTitleHeaderProps> = {
title: 'Atoms/TitleHeaders',
component: PictogramTitleHeaderComponent,
};
export default meta;
export const PictogramTitleHeader: PictogramTitleHeaderStory = {
name: 'PictogramTitleHeader',
args: {
variant: 'success',
icon: undefined,
title: 'Title message',
subtitle: 'Something longer to say that has secondary informative value.',
titleVariant: 'titleSmall',
},
argTypes: {
variant: {
control: { type: 'select' },
options: PICTOGRAM_VARIANTS,
},
icon: {
control: { type: 'select' },
options: ICON_NAMES,
},
title: {
control: { type: 'text' },
},
subtitle: {
control: { type: 'text' },
},
titleVariant: {
control: { type: 'select' },
options: nativeTypographyStyles,
},
},
};

View File

@@ -0,0 +1,48 @@
import type { Meta, StoryObj } from '@storybook/react-native';
import { nativeSpacings, nativeTypographyStyles } from '@trezor/theme';
import {
TitleHeader as TitleHeaderComponent,
TitleHeaderProps,
} from '../../TitleHeader/TitleHeader';
type TitleHeaderStory = StoryObj<TitleHeaderProps>;
const meta: Meta<TitleHeaderProps> = {
title: 'Atoms/TitleHeaders',
component: TitleHeaderComponent,
};
export default meta;
export const TitleHeader: TitleHeaderStory = {
name: 'TitleHeader',
args: {
title: 'Title message',
subtitle: 'Something longer to say that has secondary informative value.',
titleVariant: 'titleSmall',
textAlign: 'left',
titleSpacing: 'sp8',
},
argTypes: {
title: {
control: { type: 'text' },
},
subtitle: {
control: { type: 'text' },
},
titleVariant: {
control: { type: 'select' },
options: nativeTypographyStyles,
},
textAlign: {
control: { type: 'select' },
options: ['left', 'center'],
},
titleSpacing: {
control: { type: 'select' },
options: Object.keys(nativeSpacings),
},
},
};

View File

@@ -7,4 +7,5 @@ export type TestProps = {
['testID']?: string;
};
export type SurfaceElevation = '0' | '1';
export const SURFACE_ELEVATIONS = ['0', '1'] as const;
export type SurfaceElevation = (typeof SURFACE_ELEVATIONS)[number];

View File

@@ -32,6 +32,7 @@ export const iconSizes = {
extraLarge: 32,
} as const;
export const ICON_SIZES = Object.keys(iconSizes) as IconSize[];
export type IconSize = keyof typeof iconSizes;
export const getIconSize = (size: IconSize | number) =>

View File

@@ -3,7 +3,6 @@ import { INITIAL_VIEWPORTS } from 'storybook/viewport';
import { SHARED_DECORATORS } from '../decorators/decorators';
import './fonts.css';
const preview: Preview = {

View File

@@ -1,7 +1,4 @@
import { intlDecorator } from './intlDecorator';
import { themeDecorator } from './themeDecorator';
export const SHARED_DECORATORS = [
intlDecorator,
themeDecorator,
] as const;
export const SHARED_DECORATORS = [intlDecorator, themeDecorator] as const;

View File

@@ -1,11 +1,9 @@
import React from 'react';
import { IntlProvider as ReactIntlProvider } from 'react-intl';
import { StoryContext } from '@storybook/react';
import enMessages from '@suite-native/intl/translations/en-US.json';
export const intlDecorator = (Story: React.FC, context: StoryContext) => (
export const intlDecorator = (Story: React.FC) => (
<ReactIntlProvider locale="en-US" messages={enMessages}>
<Story />
</ReactIntlProvider>

View File

@@ -11689,6 +11689,7 @@ __metadata:
react-native-safe-area-context: "npm:5.6.1"
react-native-svg: "npm:15.14.0"
react-redux: "npm:9.2.0"
storybook: "npm:^10.1.0"
type-fest: "npm:4.24.0"
languageName: unknown
linkType: soft