diff --git a/apps/docs/pages/docs/v2/Components/Lists/list.en-US.mdx b/apps/docs/pages/docs/v2/Components/Lists/list.en-US.mdx new file mode 100644 index 00000000..c9ba5f27 --- /dev/null +++ b/apps/docs/pages/docs/v2/Components/Lists/list.en-US.mdx @@ -0,0 +1,47 @@ +--- +searchable: true +--- + +import { CodeEditor } from '@components/code-editor'; + +# List + +Wrapper around `FlatList` component from `react-native`. + +`List` component accepts every props from react native `FlatList` component. + +## Import + +```js +import { List } from "@ficus-ui/native"; +``` + +## Usage + + ( + + {item.title} + + )} +/>`} /> + +## Props + +Extends every `Box` and `FlatList` props. \ No newline at end of file diff --git a/apps/examples/app/components-v2/List.tsx b/apps/examples/app/components-v2/List.tsx new file mode 100644 index 00000000..73631e40 --- /dev/null +++ b/apps/examples/app/components-v2/List.tsx @@ -0,0 +1,39 @@ +import { Box, Flex, List, SafeAreaBox, Text } from '@ficus-ui/native'; + +const ListComponent = () => { + const DATA = [ + { + id: "bd7acbea-c1b1-46c2-aed5-3ad53abb28ba", + title: "First Item", + }, + { + id: "3ac68afc-c605-48d3-a4f8-fbd91aa97f63", + title: "Second Item", + }, + { + id: "58694a0f-3da1-471f-bd96-145571e29d72", + title: "Third Item", + }, + ]; + return ( + + + List component + + + ( + + {item?.title} + + )} + /> + + + ); +}; + +export default ListComponent; diff --git a/apps/examples/app/items-v2.ts b/apps/examples/app/items-v2.ts index 6d4eae6d..d5e2598b 100644 --- a/apps/examples/app/items-v2.ts +++ b/apps/examples/app/items-v2.ts @@ -16,6 +16,7 @@ import DividerComponent from '@/app/components-v2/Divider'; import SpinnerComponent from '@/app/components-v2/Spinner'; import SliderComponent from '@/app/components-v2/Slider'; import IconComponent from '@/app/components-v2/Icon'; +import ListComponent from '@/app/components-v2/List'; type ExampleComponentType = { onScreenName: string; @@ -41,4 +42,5 @@ export const components: ExampleComponentType[] = [ { navigationPath: 'Pressable', onScreenName: 'Pressable', component: PressableComponent }, { navigationPath: 'Slider', onScreenName: 'Slider', component: SliderComponent }, { navigationPath: 'Icon', onScreenName: 'Icon', component: IconComponent }, + { navigationPath: 'List', onScreenName: 'List', component: ListComponent }, ]; diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index c9a1a19e..c372b181 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -13,5 +13,6 @@ export * from './divider'; export * from './spinner'; export * from './slider'; export * from './icon'; +export * from './list'; export { ThemeProvider } from '@ficus-ui/theme'; diff --git a/packages/components/src/list/index.tsx b/packages/components/src/list/index.tsx new file mode 100644 index 00000000..d969b89a --- /dev/null +++ b/packages/components/src/list/index.tsx @@ -0,0 +1,9 @@ +import { type NativeFicusProps, ficus } from '../system'; + +export interface ListProps extends NativeFicusProps<'Flatlist'> {} + +export const List = ficus('Flatlist', { + baseStyle: { + flex: 1, + }, +}); diff --git a/packages/components/src/list/list.spec.tsx b/packages/components/src/list/list.spec.tsx new file mode 100644 index 00000000..b02cfd95 --- /dev/null +++ b/packages/components/src/list/list.spec.tsx @@ -0,0 +1,83 @@ +import React from 'react'; + +import { render } from '@testing-library/react-native'; + +import { List } from '.'; +import { Text } from '../text'; + +jest.mock('react-native-toast-message', () => 'Toast'); + +describe('List component', () => { + const mockData = [ + { id: '1', title: 'First Item' }, + { id: '2', title: 'Second Item' }, + { id: '3', title: 'Third Item' }, + ]; + + it('should render without crashing', () => { + const { getByTestId } = render( + {item?.title}} + /> + ); + + expect(getByTestId('list-container')).toBeTruthy(); + }); + + it('should render the correct number of list items', () => { + const { getAllByTestId } = render( + ( + {item.title} + )} + /> + ); + + const items = getAllByTestId('list-item'); + expect(items.length).toBe(mockData.length); + }); + + it('should display correct item text', () => { + const { getByText } = render( + {item.title}} + /> + ); + + expect(getByText('First Item')).toBeTruthy(); + expect(getByText('Second Item')).toBeTruthy(); + expect(getByText('Third Item')).toBeTruthy(); + }); + + it('should use the correct keyExtractor', () => { + const keyExtractor = jest.fn((item) => item.id); + render( + {item.title}} + keyExtractor={keyExtractor} + /> + ); + + expect(keyExtractor).toHaveBeenCalledWith(mockData[0], 0); + expect(keyExtractor).toHaveBeenCalledWith(mockData[1], 1); + expect(keyExtractor).toHaveBeenCalledWith(mockData[2], 2); + }); + + it('should render empty list when no data is passed', () => { + const { queryAllByTestId } = render( + ( + {item.title} + )} + /> + ); + + expect(queryAllByTestId('list-item').length).toBe(0); + }); +}); diff --git a/packages/components/src/system/base-elements.ts b/packages/components/src/system/base-elements.ts index b7859b6d..d52e49c2 100644 --- a/packages/components/src/system/base-elements.ts +++ b/packages/components/src/system/base-elements.ts @@ -1,6 +1,7 @@ import RNSlider from '@react-native-community/slider'; import { ActivityIndicator as RNActivityIndicator, + FlatList as RNFlatList, Image as RNImage, Pressable as RNPressable, SafeAreaView as RNSafeAreaView, @@ -27,6 +28,7 @@ export const baseRNElements = { Pressable: RNPressable, ActivityIndicator: RNActivityIndicator, Slider: RNSlider, + Flatlist: RNFlatList, } as const; export type BaseRNElements = keyof typeof baseRNElements;