From 250fbc650955c33b52b5224dc4153c802dfc75aa Mon Sep 17 00:00:00 2001 From: Adrien Castagliola Date: Tue, 28 Nov 2023 16:20:04 +0100 Subject: [PATCH 1/2] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20testID=20-=20extends?= =?UTF-8?q?=20props=20with=20wrapper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Screen.tsx | 14 +++++++++----- src/components/keyboard/DeleteButton.tsx | 18 +++++++++++++----- src/components/keyboard/SubmitButton.tsx | 18 +++++++++++++----- src/components/topAppBar/TopAppBar.tsx | 24 ++++++++++++++++++------ 4 files changed, 53 insertions(+), 21 deletions(-) diff --git a/src/components/Screen.tsx b/src/components/Screen.tsx index c4d828b8..b7fc60f8 100644 --- a/src/components/Screen.tsx +++ b/src/components/Screen.tsx @@ -2,13 +2,13 @@ import React from 'react'; import { SafeAreaView, ViewStyle, StatusBar, StyleSheet } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useTheme } from '../styles/themes'; +import type { WithTestID } from 'src/shared/type'; -type Props = { +type Props = WithTestID<{ children?: React.ReactNode; style?: ViewStyle; - testID?: string; statusBarColor?: string; -}; +}>; export const Screen = ({ children, style, testID, statusBarColor }: Props) => { const theme = useTheme(); @@ -29,8 +29,12 @@ export const Screen = ({ children, style, testID, statusBarColor }: Props) => { {children} diff --git a/src/components/keyboard/DeleteButton.tsx b/src/components/keyboard/DeleteButton.tsx index 1b4abb9d..8f1bded4 100644 --- a/src/components/keyboard/DeleteButton.tsx +++ b/src/components/keyboard/DeleteButton.tsx @@ -3,11 +3,11 @@ import { Pressable, StyleSheet, View } from 'react-native'; import { useTheme } from '../../styles/themes'; import { Icon } from '../icons/Icon'; import type { KeyboardActions } from './Keyboard'; +import type { WithTestID } from 'src/shared/type'; -interface Props { +type Props = WithTestID<{ onPress: (action: KeyboardActions) => void; - testID?: string; -} +}>; export const DeleteButton = ({ onPress, testID }: Props) => { const theme = useTheme(); @@ -25,9 +25,17 @@ export const DeleteButton = ({ onPress, testID }: Props) => { }); return ( - onPress('delete')}> + onPress('delete')} + > - + ); diff --git a/src/components/keyboard/SubmitButton.tsx b/src/components/keyboard/SubmitButton.tsx index 13180eb7..8d23ff76 100644 --- a/src/components/keyboard/SubmitButton.tsx +++ b/src/components/keyboard/SubmitButton.tsx @@ -3,11 +3,11 @@ import { Pressable, StyleSheet, View } from 'react-native'; import { useTheme } from '../../styles/themes'; import { Icon } from '../icons/Icon'; import type { KeyboardActions } from './Keyboard'; +import type { WithTestID } from 'src/shared/type'; -interface Props { +type Props = WithTestID<{ onPress: (action: KeyboardActions) => void; - testID?: string; -} +}>; export const SubmitButton = ({ onPress, testID }: Props) => { const theme = useTheme(); @@ -26,9 +26,17 @@ export const SubmitButton = ({ onPress, testID }: Props) => { }); return ( - onPress('submit')}> + onPress('submit')} + > - + ); diff --git a/src/components/topAppBar/TopAppBar.tsx b/src/components/topAppBar/TopAppBar.tsx index 91fb5f6e..c7dd028b 100644 --- a/src/components/topAppBar/TopAppBar.tsx +++ b/src/components/topAppBar/TopAppBar.tsx @@ -4,6 +4,7 @@ import { useTheme } from '../../styles/themes'; import { StyleSheet, type ViewStyle } from 'react-native'; import { Headline } from '../typography/Headline'; import type { IconSource } from 'react-native-paper/lib/typescript/components/Icon'; +import type { WithTestID } from 'src/shared/type'; interface Icon { name: IconSource; @@ -15,16 +16,22 @@ export interface Title { onPress?: () => void; } -export interface Props { +export type Props = WithTestID<{ size?: 'small' | 'medium' | 'large' | 'center-aligned'; title: Title; icon?: Icon; onBack?: () => void; style?: ViewStyle; - testID?: string; -} +}>; -export const TopAppBar = ({ size = 'small', title, icon, onBack, style, testID }: Props) => { +export const TopAppBar = ({ + size = 'small', + title, + icon, + onBack, + style, + testID, +}: Props) => { const theme = useTheme(); const styles = StyleSheet.create({ button: { @@ -47,7 +54,12 @@ export const TopAppBar = ({ size = 'small', title, icon, onBack, style, testID } return theme.sw.colors.neutral[600]; }; return ( - + {onBack !== undefined && ( {title.value} + {title.value} ) : ( title.value ) From c6a9310a009ae79c753cf994f20b5dc991b39e29 Mon Sep 17 00:00:00 2001 From: thomas IMPERY Date: Tue, 28 Nov 2023 14:11:33 +0100 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9C=A8=20Add=20badge=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Storybook/.ondevice/storybook.requires.js | 1 + Storybook/components/Badge/Badge.stories.tsx | 31 +++++++++++++ src/__tests__/components/Badge.test.tsx | 41 +++++++++++++++++ src/components/badge/Badge.tsx | 48 ++++++++++++++++++++ src/components/index.tsx | 2 + src/index.tsx | 2 + src/shared/type.ts | 1 + 7 files changed, 126 insertions(+) create mode 100644 Storybook/components/Badge/Badge.stories.tsx create mode 100644 src/__tests__/components/Badge.test.tsx create mode 100644 src/components/badge/Badge.tsx create mode 100644 src/shared/type.ts diff --git a/Storybook/.ondevice/storybook.requires.js b/Storybook/.ondevice/storybook.requires.js index 58fe1c8e..cc9c8612 100644 --- a/Storybook/.ondevice/storybook.requires.js +++ b/Storybook/.ondevice/storybook.requires.js @@ -50,6 +50,7 @@ try { const getStories = () => { return { "./components/ActionCard/ActionCard.stories.tsx": require("../components/ActionCard/ActionCard.stories.tsx"), + "./components/Badge/Badge.stories.tsx": require("../components/Badge/Badge.stories.tsx"), "./components/BottomSheet/BottomSheet.stories.tsx": require("../components/BottomSheet/BottomSheet.stories.tsx"), "./components/Button/Button.stories.tsx": require("../components/Button/Button.stories.tsx"), "./components/Card/Card.stories.tsx": require("../components/Card/Card.stories.tsx"), diff --git a/Storybook/components/Badge/Badge.stories.tsx b/Storybook/components/Badge/Badge.stories.tsx new file mode 100644 index 00000000..ccd25f53 --- /dev/null +++ b/Storybook/components/Badge/Badge.stories.tsx @@ -0,0 +1,31 @@ +import { Badge } from 'smartway-react-native-ui'; +import type { ComponentMeta, ComponentStory } from '@storybook/react-native'; +import React from 'react'; +import { StyleSheet, View } from 'react-native'; + +export default { + title: 'components/Badge', + component: Badge, + decorators: [ + (Story) => { + const styles = StyleSheet.create({ + container: { paddingTop: 16 }, + }); + return ( + + + + ); + }, + ], +} as ComponentMeta; + +export const Base: ComponentStory = () => ; + +export const NotTruncated: ComponentStory = () => ( + +); + +export const Truncated: ComponentStory = () => ( + +); diff --git a/src/__tests__/components/Badge.test.tsx b/src/__tests__/components/Badge.test.tsx new file mode 100644 index 00000000..2bb1d021 --- /dev/null +++ b/src/__tests__/components/Badge.test.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react-native'; + +import { ThemeProvider } from '../../styles/themes'; +import { Badge } from '../../components/badge/Badge'; + +describe('MODULE | Badge', () => { + describe('Given a `number`', () => { + it('should display it', () => { + render( + + + , + ); + + expect(screen.getByText('100')).toBeDefined(); + }); + }); + describe("Given a `number` and a `maxDigits` greater than number's total digit", () => { + it('should display a truncated `number`', () => { + render( + + + , + ); + + expect(screen.getByText('99+')).toBeDefined(); + }); + }); + describe("Given a `number` and a `maxDigits` smaller or equal than number's total digit'", () => { + it('should display the entire `number`', () => { + render( + + + , + ); + + expect(screen.getByText('88')).toBeDefined(); + }); + }); +}); diff --git a/src/components/badge/Badge.tsx b/src/components/badge/Badge.tsx new file mode 100644 index 00000000..89443d74 --- /dev/null +++ b/src/components/badge/Badge.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { StyleSheet, ViewStyle } from 'react-native'; + +import { Body } from '../typography/Body'; +import { ThemType, useTheme } from '../../styles/themes'; +import type { WithTestID } from 'src/shared/type'; + +type BadgeProps = WithTestID<{ + number: number; + maxDigits?: number; + style?: ViewStyle; +}>; + +const createStyle = (theme: ThemType, style?: ViewStyle) => { + return StyleSheet.create({ + badge: { + backgroundColor: theme.sw.colors.neutral[700], + color: theme.sw.colors.neutral[50], + paddingHorizontal: theme.sw.spacing.xs, + paddingVertical: theme.sw.spacing.xxs, + alignSelf: 'flex-start', + borderRadius: 8, + ...style, + }, + }); +}; + +export const Badge = ({ number, maxDigits, testID, style }: BadgeProps) => { + const theme = useTheme(); + + const displayText = maxDigits + ? getTruncatedNumber(number, maxDigits) + : number; + + const badgeStyle = createStyle(theme, style).badge; + + return ( + + {displayText} + + ); +}; + +function getTruncatedNumber(number: number, maxDigits: number): string { + const maxNumber = Math.pow(10, maxDigits) - 1; + const isTruncated = number > maxNumber; + return isTruncated ? `${maxNumber}+` : `${number}`; +} diff --git a/src/components/index.tsx b/src/components/index.tsx index 272d3287..f4218281 100644 --- a/src/components/index.tsx +++ b/src/components/index.tsx @@ -26,6 +26,7 @@ import { Label } from './label/Label'; import { Product } from './product/Product'; import { Divider } from './divider/Divider'; import { LineChart } from './charts/LineChart'; +import { Badge } from './badge/Badge'; export { Body, @@ -56,4 +57,5 @@ export { Product, Divider, LineChart, + Badge, }; diff --git a/src/index.tsx b/src/index.tsx index ec890556..3e098c40 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -27,6 +27,7 @@ import { Product, Divider, LineChart, + Badge, } from './components'; import { ThemeProvider, useTheme } from './styles/themes'; @@ -61,4 +62,5 @@ export { Product, Divider, LineChart, + Badge }; diff --git a/src/shared/type.ts b/src/shared/type.ts new file mode 100644 index 00000000..30f74338 --- /dev/null +++ b/src/shared/type.ts @@ -0,0 +1 @@ +export type WithTestID = Props & { testID?: string };