Skip to content

Commit

Permalink
feat: Add column tile layout
Browse files Browse the repository at this point in the history
  • Loading branch information
bruce-glazier committed Apr 16, 2024
1 parent f8b81a3 commit 20ecd1c
Show file tree
Hide file tree
Showing 10 changed files with 317 additions and 136 deletions.
3 changes: 2 additions & 1 deletion example/storybook/stories/CustomAppTileScreen.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
AppTileScreen,
CustomAppTileScreen,
DeveloperConfigProvider,
Tile,
} from '../../../src';
import { TilesList } from '../../../src/components/tiles/TilesList';
import {
Expand All @@ -16,6 +15,7 @@ import {
} from '../../../src/navigators/types';
import { DataProviderDecorator } from '../helpers/DataProviderDecorator';
import { ScreenSurface } from '../../../src/components/ScreenSurface';
import { Tile } from '../../../src/components/tiles/Tile';

storiesOf('Custom App Tile Screen', module)
.addDecorator(
Expand Down Expand Up @@ -78,6 +78,7 @@ function HomeScreen({ navigation, route }: HomeStackScreenProps<'Home'>) {
}}
>
<Tile
tileListMode="list"
id="app-tile-invalid"
title="Error 2"
onPress={() => {
Expand Down
1 change: 1 addition & 0 deletions src/common/DeveloperConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ export type DeveloperConfig = {
) => { id: string; title: string; action: () => void }[];
navigationLinking?: LinkingOptions<any>;
skipInviteParams?: boolean;
tileListMode?: 'list' | 'column';
};

export type LogoHeaderConfig = { [key in Route]?: LogoHeaderOptions };
Expand Down
1 change: 1 addition & 0 deletions src/components/MessagesTile/MessagesTile.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const directMessageScreen = (
<ActiveAccountProvider account="mockaccount">
<GraphQLClientContextProvider baseURL={baseURL}>
<MessagesTile
tileListMode="list"
Icon={() => null}
navigation={navigateMock as any}
id="some-messages-tile"
Expand Down
10 changes: 9 additions & 1 deletion src/components/MessagesTile/MessagesTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,16 @@ interface Props extends Pick<HomeStackScreenProps<'Home'>, 'navigation'> {
id: string;
title: string;
Icon: React.FC<SvgProps>;
tileListMode: 'list' | 'column';
}

export function MessagesTile({ navigation, title, id, Icon }: Props) {
export function MessagesTile({
navigation,
title,
id,
Icon,
tileListMode,
}: Props) {
const hasUnread = useHasUnread(id);

return (
Expand All @@ -27,6 +34,7 @@ export function MessagesTile({ navigation, title, id, Icon }: Props) {
tileId: id,
});
}}
tileListMode={tileListMode}
/>
);
}
93 changes: 93 additions & 0 deletions src/components/tiles/BoxTile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import React from 'react';
import { Dimensions, Text, View } from 'react-native';
import { useStyles } from '../../hooks/useStyles';
import { createStyles } from '../BrandConfigProvider';
import { tID } from '../../common/testID';
import { Card } from 'react-native-paper';
import { BoxTileProps } from './Tile';

export function BoxTile({
onPress,
id,
title,
tileMode = 'halfLength',
style: instanceStyles,
Icon,
showBadge,
badge,
}: BoxTileProps) {
const { styles } = useStyles(defaultStyles, instanceStyles);
const backgroundColor =
showBadge || !!badge
? styles.tileNotificationView?.backgroundColor
: styles.tileSpacingView?.backgroundColor;

return (
<Card
testID={tID(`tile-button-${id}`)}
onPress={onPress}
disabled={!onPress}
style={[
styles.tileSpacingView,
tileMode === 'fullLength'
? { width: tileWidth * 2 + spaceBetweenTiles }
: { width: tileWidth },
{ backgroundColor: backgroundColor },
]}
>
<Card.Content
style={styles.tileContentView}
testID={tID(`tile-view-${id}`)}
>
<View style={styles.iconView}>{Icon && <Icon />}</View>
<Text numberOfLines={2} style={styles.titleText}>
{title}
</Text>
</Card.Content>
</Card>
);
}

export const spaceBetweenTiles = 16;
export const tileWidth =
Math.floor(Dimensions.get('window').width - spaceBetweenTiles * 3) / 2;

const defaultStyles = createStyles('BoxTile', (theme) => ({
tileColor: {
backgroundColor: theme.colors.surface,
},
tileSpacingView: {
marginHorizontal: theme.spacing.small,
marginBottom: theme.spacing.medium,
backgroundColor: theme.colors.surface,
justifyContent: 'center',
alignItems: 'center',
},
tileNotificationView: {
backgroundColor: theme.colors.surfaceVariant,
},
tileContentView: {
height: 130,
flexDirection: 'row',
justifyContent: 'center',
},
titleText: {
position: 'absolute',
paddingTop: 100,
textAlign: 'center',
...theme.fonts.bodySmall,
fontWeight: 'bold',
},
iconView: {
alignItems: 'center',
alignContent: 'center',
alignSelf: 'center',
},
}));

declare module '@styles' {
interface ComponentStyles
extends ComponentNamedStyles<typeof defaultStyles> {}
}

export type BoxTileStyles = NamedStylesProp<typeof defaultStyles>;
114 changes: 114 additions & 0 deletions src/components/tiles/ListTile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import React from 'react';
import { TouchableOpacity, View, ViewStyle } from 'react-native';
import { Badge, Text, shadow } from 'react-native-paper';
import { useStyles } from '../../hooks/useStyles';
import { createStyles, useIcons } from '../BrandConfigProvider';
import { tID } from '../../common/testID';
import { ListTileProps } from './Tile';

export const ListTile = ({
Icon,
title,
id,
testID,
children,
onPress,
showBadge,
badge,
style: instanceStyles,
}: ListTileProps) => {
const { ChevronRight } = useIcons();
const { styles } = useStyles(defaultStyles, instanceStyles);

return (
<TouchableOpacity
onPress={onPress}
disabled={!onPress}
testID={tID(`tile-button-${id}`)}
>
<View style={styles.container} id={id} testID={testID}>
<View style={styles.contentsWrapper}>
<View style={styles.contentsView}>
<View style={styles.iconContainer}>
{Icon && <Icon style={styles.icon} />}
</View>
<Text numberOfLines={2} style={styles.titleText}>
{title}
</Text>
<View style={{ paddingRight: 24 }}>
{showBadge && <Badge size={12} testID={tID('tile-badge')} />}
{badge?.()}
</View>
{onPress ? (
<View
style={styles.arrowIconView}
testID={tID('tile-chevron-icon-container')}
>
<ChevronRight
height={styles.arrowImage?.height}
stroke={styles.arrowImage?.overlayColor}
/>
</View>
) : (
<View style={styles.arrowIconView} />
)}
</View>
</View>
</View>
{children}
</TouchableOpacity>
);
};

const defaultStyles = createStyles('ListTile', (theme) => ({
contentsView: {
flexDirection: 'row',
alignItems: 'center',
paddingLeft: theme.spacing.large,
},
contentsWrapper: {
flex: 1,
borderRadius: 10,
overflow: 'hidden',
},
container: {
height: 55,
borderRadius: 10,
backgroundColor: theme.colors.surface,
marginBottom: theme.spacing.large,
...shadow(3),
} as ViewStyle,
titleText: {
...theme.fonts.titleMedium,
color: theme.colors.text,
paddingLeft: theme.spacing.small,
flex: 1,
},
iconContainer: {
width: 38,
paddingRight: theme.spacing.small,
},
icon: {
resizeMode: 'cover',
marginLeft: 0,
marginRight: theme.spacing.small,
},
arrowIconView: {
height: '100%',
aspectRatio: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: theme.colors.primarySource,
},
arrowImage: {
height: '100%',
overlayColor: theme.colors.onPrimary,
},
}));

declare module '@styles' {
interface ComponentStyles
extends ComponentNamedStyles<typeof defaultStyles> {}
}

export type ListTileStyles = NamedStylesProp<typeof defaultStyles>;
23 changes: 19 additions & 4 deletions src/components/tiles/Tile.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ const exampleTileConfig = {
title: 'My Tile',
};

test('can render tile with onPress', () => {
test('can render tile with onPress in list mode', () => {
const onPress = jest.fn();
const tile = render(<Tile {...exampleTileConfig} onPress={onPress} />);
const tile = render(
<Tile tileListMode="list" {...exampleTileConfig} onPress={onPress} />,
);

expect(tile.getByText('My Tile')).toBeDefined();
expect(tile.getByTestId('tile-button-tile-id')).toBeDefined();
Expand All @@ -19,10 +21,23 @@ test('can render tile with onPress', () => {
expect(onPress).toHaveBeenCalled();
});

test('can render tile without onPress', () => {
const tile = render(<Tile {...exampleTileConfig} />);
test('can render tile without onPress in list mode', () => {
const tile = render(<Tile tileListMode="list" {...exampleTileConfig} />);

expect(tile.getByTestId('tile-button-tile-id')).toBeDefined();
expect(tile.getByText('My Tile')).toBeDefined();
expect(tile.queryAllByTestId('tile-chevron-icon-container').length).toBe(0);
});

test('can render tile in column mode', () => {
const onPress = jest.fn();
const tile = render(
<Tile tileListMode="column" {...exampleTileConfig} onPress={onPress} />,
);

expect(tile.getByText('My Tile')).toBeDefined();
expect(tile.getByTestId('tile-button-tile-id')).toBeDefined();

fireEvent.press(tile.getByTestId('tile-button-tile-id'));
expect(onPress).toHaveBeenCalled();
});
Loading

0 comments on commit 20ecd1c

Please sign in to comment.