diff --git a/src/components/ui/card.stories.tsx b/src/components/ui/card/docs.stories.tsx similarity index 98% rename from src/components/ui/card.stories.tsx rename to src/components/ui/card/docs.stories.tsx index 3375905..d41237d 100644 --- a/src/components/ui/card.stories.tsx +++ b/src/components/ui/card/docs.stories.tsx @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from '@storybook/react-native'; import { Box, Button, HStack, Text, VStack } from 'react-native-ficus-ui'; -import { Card, CardBody, CardHeader, CardTitle } from './card'; +import { Card, CardBody, CardHeader, CardTitle } from '.'; const meta: Meta = { title: 'UI/Card', diff --git a/src/components/ui/card.tsx b/src/components/ui/card/index.tsx similarity index 100% rename from src/components/ui/card.tsx rename to src/components/ui/card/index.tsx diff --git a/src/components/ui/skeleton/docs.stories.tsx b/src/components/ui/skeleton/docs.stories.tsx new file mode 100644 index 0000000..8ff8a9b --- /dev/null +++ b/src/components/ui/skeleton/docs.stories.tsx @@ -0,0 +1,33 @@ +import type { Meta, StoryObj } from '@storybook/react-native'; +import { Box } from 'react-native-ficus-ui'; + +import { Skeleton } from '.'; + +const meta: Meta = { + title: 'UI/Skeleton', + component: Skeleton, + parameters: { + notes: 'Basic skeleton component using react-native-ficus-ui.', + }, + decorators: [ + (Story) => ( + + + + ), + ], +}; +export default meta; + +type Story = StoryObj; + +export const Basic: Story = { + name: 'Basic', + render: (args) => , +}; diff --git a/src/components/ui/skeleton/index.tsx b/src/components/ui/skeleton/index.tsx new file mode 100644 index 0000000..15a8ebd --- /dev/null +++ b/src/components/ui/skeleton/index.tsx @@ -0,0 +1,43 @@ +import { useEffect } from 'react'; +import { StyleProp, ViewProps, ViewStyle } from 'react-native'; +import Animated, { + useAnimatedStyle, + useSharedValue, + withRepeat, + withTiming, +} from 'react-native-reanimated'; + +import theme from '@/lib/ficus-ui/theme'; + +export type SkeletonProps = Omit & { + style?: StyleProp; +}; + +export const Skeleton = (props: SkeletonProps) => { + const opacity = useSharedValue(0.6); + + useEffect(() => { + opacity.value = withRepeat(withTiming(0.3, { duration: 800 }), -1, true); + }, [opacity]); + + const animatedStyles = useAnimatedStyle(() => ({ + opacity: opacity.value, + })); + + return ( + + ); +}; diff --git a/src/features/books/book-cover.tsx b/src/features/books/book-cover.tsx index 6146aa3..6f8ae33 100644 --- a/src/features/books/book-cover.tsx +++ b/src/features/books/book-cover.tsx @@ -1,33 +1,26 @@ -import { Box, BoxProps, Text } from 'react-native-ficus-ui'; +import { Box, BoxProps, Flex, Text } from 'react-native-ficus-ui'; import { BookGetByIdResponse } from '@/lib/hey-api/generated'; export type BookCoverProps = BoxProps & { book: BookGetByIdResponse }; -const COVER_HEIGHT = 240; - -export const BookCover = ({ - book, - h = COVER_HEIGHT, - ...props -}: BookCoverProps) => { +export const BookCover = ({ book, ...props }: BookCoverProps) => { return ( - - - {book.title} - - - {book.author} - + + + + {book.title} + + + {book.author} + + ); }; diff --git a/src/features/books/view-book.tsx b/src/features/books/view-book.tsx index 59252ff..b6f7e24 100644 --- a/src/features/books/view-book.tsx +++ b/src/features/books/view-book.tsx @@ -6,7 +6,7 @@ import { Center, Divider, HStack, Stack, Text } from 'react-native-ficus-ui'; import { api } from '@/lib/hey-api/api'; import { Card, CardBody } from '@/components/ui/card'; -import { FullLoader } from '@/components/ui/full-loader'; +import { Skeleton } from '@/components/ui/skeleton'; import { BookCover } from '@/features/books/book-cover'; import { ViewTabContent } from '@/layout/view-tab-content'; @@ -26,13 +26,13 @@ export const ViewBook = (props: { bookId: string }) => { }); return ( - + {ui - .match('pending', () => ) + .match('pending', () => ) .match('error', () => <>) .match('default', ({ data }) => ( - + { -
- +
+
)) diff --git a/src/features/books/view-books.tsx b/src/features/books/view-books.tsx index 6ced673..c1d0d8b 100644 --- a/src/features/books/view-books.tsx +++ b/src/features/books/view-books.tsx @@ -2,11 +2,17 @@ import { getUiState } from '@bearstudio/ui-state'; import { useInfiniteQuery } from '@tanstack/react-query'; import { Link } from 'expo-router'; import { ActivityIndicator } from 'react-native'; -import { FlashList, Text } from 'react-native-ficus-ui'; +import { + Box, + FlashList, + HStack, + Text, + useMediaQuery, +} from 'react-native-ficus-ui'; import { api } from '@/lib/hey-api/api'; -import { FullLoader } from '@/components/ui/full-loader'; +import { Skeleton } from '@/components/ui/skeleton'; import { BookCover } from '@/features/books/book-cover'; import { ViewTabContent } from '@/layout/view-tab-content'; @@ -29,17 +35,40 @@ export const ViewBooks = () => { }); }); + const [isTablet] = useMediaQuery({ + minWidth: 480, + }); + return ( {ui - .match('pending', () => ) + .match('pending', () => ( + + + + + + + + {isTablet && ( + <> + + + + + + + + )} + + )) .match('error', () => <>) .match('empty', () => There is no books) .match('default', ({ data }) => ( item.id} - numColumns={2} + numColumns={isTablet ? 4 : 2} horizontal={false} renderItem={({ item }) => ( { pathname: '/books/[id]', params: { id: item.id, title: item.title }, }} - style={{ padding: 8, flex: 1 }} + style={{ aspectRatio: 2 / 3 }} > - + diff --git a/src/layout/view-tab-content.tsx b/src/layout/view-tab-content.tsx index 23e8ede..0bdd4b5 100644 --- a/src/layout/view-tab-content.tsx +++ b/src/layout/view-tab-content.tsx @@ -7,10 +7,20 @@ import { isApple } from '@/constants/device'; export const ViewTabContent = ({ withHeader = isApple && WITH_NATIVE_TABS, children, + fixed, ...props -}: BoxProps & { withHeader?: boolean }) => { +}: BoxProps & { withHeader?: boolean; fixed?: boolean }) => { const insets = useSafeAreaInsets(); + if (fixed) { + return ( + + {children} + + + ); + } + return (