Skip to content

Commit

Permalink
Merge pull request #247 from ZeroGachis/SORD-564-Update-banner
Browse files Browse the repository at this point in the history
💄 Banner - update and refactoring
  • Loading branch information
clementdejoie authored Jul 17, 2024
2 parents f55632e + 30a08f6 commit 8a92c94
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 45 deletions.
66 changes: 41 additions & 25 deletions Storybook/components/Alert/Banner.stories.tsx
Original file line number Diff line number Diff line change
@@ -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<typeof Banner>;
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 (
<View style={styles.container}>
<View style={{ paddingTop: 16 }}>
<Story />
</View>
);
},
],
} as Meta<ComponentProps>;
} as ComponentMeta<BannerType>;

type Story = StoryObj<ComponentProps>;

export const Default: Story = {
args: {
title: 'This is a title',
description: 'This is a description',
export const Base: ComponentStory<BannerType> = (args) => {
return <Banner {...args} />;
};
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 (
<View style={{ gap: 8 }}>
<Banner {...args} status='info' />
<Banner {...args} status='success' />
<Banner {...args} status='warning' />
<Banner {...args} status='error' />
</View>
);
onDismiss: {
control: { type: 'boolean' },
mapping: { true: action('Dismissed'), false: undefined },
},
};

export const Catalog: ComponentStory<BannerType> = (args) => {
return (
<View style={{ gap: 8 }}>
<Banner {...args} status="info" />
<Banner {...args} status="success" />
<Banner {...args} status="warning" />
<Banner {...args} status="error" />
</View>
);
};
20 changes: 19 additions & 1 deletion src/__tests__/components/Banner.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<Banner
status='info'
onDismiss={mockOnDismiss}
title='This is a title'
description='This is a decription'
buttonText='Button'
onDismiss={mockOnDismiss}
onButtonPress={mockOnButtonPress}
/>,
);
});
Expand All @@ -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', () => {
Expand Down
64 changes: 45 additions & 19 deletions src/components/alert/Alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<keyof Theme['sw']['color'], 'primary' | 'secondary' | 'neutral'>;

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);
Expand All @@ -26,24 +37,32 @@ const Alert = ({ title, description, onDismiss, status, style }: AlertProps) =>
<View style={styles.body}>
<Icon name={iconName} size={24} color={theme.sw.color[status][500]} />
<View style={styles.texts}>
<Body typography="n1" style={styles.title} accessibilityRole="alert">
{title}
</Body>
{description && (
<Body typography="n1" style={styles.description} accessibilityRole="alert">
{description}
{title && (
<Body typography="n1" style={styles.title} accessibilityRole="alert">
{title}
</Body>
)}
<Body typography="n2" style={styles.description} accessibilityRole="alert">
{description}
</Body>
</View>
</View>
<Pressable
onPress={onDismiss}
accessibilityLabel="Dismiss"
accessibilityHint="Dismiss the alert"
accessible
>
<Icon name="close" size={20} color={theme.sw.color[status][700]} />
</Pressable>
<View style={styles.actionsContainer}>
{onButtonPress && buttonText && (
<Button onPress={onButtonPress}>{buttonText}</Button>
)}
{onDismiss && (
<Pressable
onPress={onDismiss}
style={styles.dismiss}
accessibilityLabel="Dismiss"
accessibilityHint="Dismiss the alert"
accessible
>
<Icon name="close" size={20} color={theme.sw.color[status][700]} />
</Pressable>
)}
</View>
</View>
);
};
Expand Down Expand Up @@ -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',
Expand All @@ -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,
},
});
}

Expand Down
1 change: 1 addition & 0 deletions src/components/alert/Snackbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useTheme } from '../../../src/styles/themes';
export interface SnackbarProps extends Omit<AlertProps, 'style'> {
visible: boolean;
duration?: number;
onDismiss: () => void;
}

const Snackbar = ({
Expand Down

0 comments on commit 8a92c94

Please sign in to comment.