diff --git a/Storybook/components/Alert/Banner.stories.tsx b/Storybook/components/Alert/Banner.stories.tsx index 665e6c93..58c74c77 100644 --- a/Storybook/components/Alert/Banner.stories.tsx +++ b/Storybook/components/Alert/Banner.stories.tsx @@ -1,45 +1,61 @@ -import type { Meta, StoryObj } from '@storybook/react-native'; +import type { ComponentMeta, ComponentStory } from '@storybook/react-native'; import React from 'react'; -import { StyleSheet, View } from 'react-native'; +import { View } from 'react-native'; import Banner from '../../../src/components/alert/Banner'; +import { action } from '@storybook/addon-actions'; -type ComponentProps = React.ComponentProps; +type BannerType = typeof Banner; export default { title: 'components/Banner', component: Banner, - argTypes: { - onDismiss: { action: 'onDismiss' }, + args: { + status: 'info', + title: 'This is a title', + description: 'This is a description', + buttonText: 'Click me', + onButtonPress: () => action('onButtonPress')('Button pressed'), + onDismiss: () => action('onDismiss')('Dismissed'), }, decorators: [ (Story) => { - const styles = StyleSheet.create({ - container: { flex: 1 }, - }); return ( - + ); }, ], -} as Meta; +} as ComponentMeta; -type Story = StoryObj; - -export const Default: Story = { - args: { - title: 'This is a title', - description: 'This is a description', +export const Base: ComponentStory = (args) => { + return ; +}; +Base.argTypes = { + status: { + control: { type: 'radio' }, + options: ['info', 'success', 'warning', 'error'], + }, + title: { control: { type: 'text' } }, + description: { control: { type: 'text' } }, + buttonText: { control: { type: 'text' } }, + onButtonPress: { + control: { type: 'boolean' }, + mapping: { true: action('Button pressed'), false: undefined }, }, - render(args) { - return ( - - - - - - - ); + onDismiss: { + control: { type: 'boolean' }, + mapping: { true: action('Dismissed'), false: undefined }, }, }; + +export const Catalog: ComponentStory = (args) => { + return ( + + + + + + + ); +}; diff --git a/src/__tests__/components/Banner.test.tsx b/src/__tests__/components/Banner.test.tsx index 999a0767..57541e67 100644 --- a/src/__tests__/components/Banner.test.tsx +++ b/src/__tests__/components/Banner.test.tsx @@ -10,15 +10,19 @@ import Banner, { useBanner } from '../../../src/components/alert/Banner'; describe('Uncontrolled Banner', () => { let mockOnDismiss: jest.Mock; + let mockOnButtonPress: jest.Mock; beforeEach(() => { mockOnDismiss = jest.fn(); + mockOnButtonPress = jest.fn(); render( , ); }); @@ -40,6 +44,20 @@ describe('Uncontrolled Banner', () => { cleanUpFakeTimer(); }); + + it('triggers `onButtonPress` event when user press button', async () => { + setupFakeTimer(); + + const user = userEvent.setup(); + + expect(mockOnButtonPress).not.toHaveBeenCalled(); + + await user.press(screen.getByText(/button/i)); + + expect(mockOnButtonPress).toHaveBeenCalledTimes(1); + + cleanUpFakeTimer(); + }); }); describe('Controlled Banner', () => { diff --git a/src/components/alert/Alert.tsx b/src/components/alert/Alert.tsx index 6d4365f6..6da0ed35 100644 --- a/src/components/alert/Alert.tsx +++ b/src/components/alert/Alert.tsx @@ -4,18 +4,29 @@ import { Theme, useTheme } from '../../styles/themes'; import { Icon } from '../icons/Icon'; import type { IconName } from '../icons/IconProps'; import { Body } from '../typography/Body'; +import { Button } from '../buttons/Button'; type Status = Exclude; export interface AlertProps { status: Status; - title: string; - description?: string; + title?: string; + description: string; + buttonText?: string; + onButtonPress?: () => void; + onDismiss?: () => void; style?: ViewStyle; - onDismiss: () => void; } -const Alert = ({ title, description, onDismiss, status, style }: AlertProps) => { +const Alert = ({ + status, + title, + description, + buttonText, + onButtonPress, + onDismiss, + style, +}: AlertProps) => { const theme = useTheme(); const iconName = getStatusIcon(status); @@ -26,24 +37,32 @@ const Alert = ({ title, description, onDismiss, status, style }: AlertProps) => - - {title} - - {description && ( - - {description} + {title && ( + + {title} )} + + {description} + - - - + + {onButtonPress && buttonText && ( + + )} + {onDismiss && ( + + + + )} + ); }; @@ -91,7 +110,6 @@ function useStyles(theme: Theme, variantTheme: Status) { alignItems: 'center', justifyContent: 'space-between', padding: theme.sw.spacing.s, - gap: theme.sw.spacing.s, }, body: { flexDirection: 'row', @@ -110,6 +128,14 @@ function useStyles(theme: Theme, variantTheme: Status) { description: { color: theme.sw.color[variantTheme][700], }, + dismiss: { + padding: 9, + }, + actionsContainer: { + flexDirection: 'row', + alignItems: 'center', + gap: theme.sw.spacing.s, + }, }); } diff --git a/src/components/alert/Snackbar.tsx b/src/components/alert/Snackbar.tsx index a8e74210..db8cbfba 100644 --- a/src/components/alert/Snackbar.tsx +++ b/src/components/alert/Snackbar.tsx @@ -7,6 +7,7 @@ import { useTheme } from '../../../src/styles/themes'; export interface SnackbarProps extends Omit { visible: boolean; duration?: number; + onDismiss: () => void; } const Snackbar = ({