Skip to content

Commit

Permalink
feat(ListItemOptionMedia): support ListItemOptionMedia (#169)
Browse files Browse the repository at this point in the history
  • Loading branch information
judastheo authored Mar 19, 2024
1 parent 0027d14 commit 9d98219
Show file tree
Hide file tree
Showing 14 changed files with 331 additions and 0 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions sandbox/e2e/visual_regressions_flow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ appId: ${APPID}
file: subflow/takeScreenshot.yaml
env:
COMPONENT: ListItemOption
- runFlow:
file: subflow/takeScreenshot.yaml
env:
COMPONENT: ListItemOptionMedia
- runFlow:
file: subflow/takeScreenshot.yaml
env:
Expand Down
53 changes: 53 additions & 0 deletions sandbox/src/app/sandbox/docs/list-item-option-media.doc.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { ListItemOptionMedia } from '@getluko/streamline';
import React, { useState } from 'react';

import { DocList } from '../components/DocList';
import { sleep } from '../sandbox.utils';

export const ListItemOptionMediaSandbox = () => {
const [selected0, setSelected0] = useState(false);
const [selected1, setSelected1] = useState(true);
const [selected2, setSelected2] = useState(false);

const onAsyncPress = async () => {
await sleep(2000);
setSelected2((prev) => !prev);
};

const LIST_ITEMS: JSX.Element[] = [
<ListItemOptionMedia
iconName="Area"
title="Option"
onPress={() => setSelected0((prev) => !prev)}
isSelected={selected0}
/>,
<ListItemOptionMedia
iconName="Area"
title="Sync change"
onPress={() => setSelected1((prev) => !prev)}
isSelected={selected1}
/>,
<ListItemOptionMedia
iconName="Area"
title="Async change"
onPress={onAsyncPress}
isSelected={selected2}
/>,
<ListItemOptionMedia
iconName="Area"
isDisabled
title="Disabled"
onPress={onAsyncPress}
isSelected={false}
/>,
<ListItemOptionMedia
iconName="Area"
isSkeleton
title="Skeleton"
onPress={onAsyncPress}
isSelected={selected2}
/>,
];

return <DocList docs={LIST_ITEMS} margin="md" />;
};
2 changes: 2 additions & 0 deletions sandbox/src/app/sandbox/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { InputTextSandbox } from './docs/input-text.doc';
import { ListItemArticleSandbox } from './docs/list-item-article.doc';
import { ListItemCountrySandbox } from './docs/list-item-country.doc';
import { ListItemGroupSandbox } from './docs/list-item-group.doc';
import { ListItemOptionMediaSandbox } from './docs/list-item-option-media.doc';
import { ListItemOptionSandbox } from './docs/list-item-option.doc';
import { ListItemSelectableSandbox } from './docs/list-item-selectable.doc';
import { ListItemSwitchSandbox } from './docs/list-item-switch.doc';
Expand Down Expand Up @@ -115,6 +116,7 @@ export const sandboxItems: SandBoxSectionType[] = [
{ title: 'ListItemArticle', SandBox: ListItemArticleSandbox },
{ title: 'ListItemCountry', SandBox: ListItemCountrySandbox },
{ title: 'ListItemOption', SandBox: ListItemOptionSandbox },
{ title: 'ListItemOptionMedia', SandBox: ListItemOptionMediaSandbox },
{ title: 'ListItemValue', SandBox: ListItemValueSandbox },
{ title: 'ListItemGroup', SandBox: ListItemGroupSandbox },
{ title: 'ListItemSelectable', SandBox: ListItemSelectableSandbox },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { act, fireEvent } from '@testing-library/react-native';
import React from 'react';

import { renderWithProvider } from '../../../../testing/render-with-provider';
import { ListItemOptionMedia } from '../list-item-option-media';

describe('ListItemOptionMedia', () => {
afterEach(() => {
jest.clearAllMocks();
});

it('should call onPress when pressed', async () => {
// Given
const onPress = jest.fn();
const { getByText } = renderWithProvider(
<ListItemOptionMedia iconName="Area" title="Title" onPress={onPress} />
);

// When
await act(async () => {
fireEvent.press(getByText('Title'));
});

// Then
expect(onPress).toHaveBeenCalledTimes(1);
});

it('should not call onPress when disabled', async () => {
// Given
const onPress = jest.fn();
const { getByText } = renderWithProvider(
<ListItemOptionMedia
iconName="Area"
title="Title"
onPress={onPress}
isDisabled
/>
);

// When
await act(async () => {
fireEvent.press(getByText('Title'));
});

// Then
expect(onPress).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Text } from '../../../../primitives/text/text';

interface Props {
isSelected?: boolean;
isDisabled?: boolean;
title: string;
}

export const BottomContent = ({ isSelected, isDisabled, title }: Props) => {
const color = isDisabled ? 'GREY_500' : 'GREY_1000';
return (
<Text
marginTop="md"
color={isSelected ? 'BLUKO_500' : color}
variant="bodyBold"
>
{title}
</Text>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Icon } from '../../../../primitives/icon/icon';
import { IconsName } from '../../../../primitives/icon/icon.types';
import Spinner from '../../../spinner/spinner';

interface Props {
isLoading: boolean;
isDisabled?: boolean;
isSelected?: boolean;
iconName: IconsName;
}

export const TopContent = ({
iconName,
isLoading,
isSelected,
isDisabled,
}: Props) => {
if (isLoading) {
return <Spinner color="BLUKO_500" size="xlarge" />;
}
return (
<Icon
iconName={iconName}
color={isDisabled && !isSelected ? 'GREY_500' : 'BLUKO_500'}
size="xlarge"
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Box } from '../../../primitives/box/box';
import { Skeleton } from '../../../primitives/skeleton/skeleton';

import { CONTAINER_WIDTH } from './list-item-option-media.constants';

export const ListItemOptionMediaSkeleton = () => (
<Box
backgroundColor="GREY_100"
paddingVertical="xl"
paddingHorizontal="md"
alignItems="center"
justifyContent="center"
width={CONTAINER_WIDTH}
borderRadius="lg"
>
<Skeleton shape="square" size="xl" />
<Box paddingBottom="md" />
<Skeleton percentage={100} />
</Box>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const CONTAINER_WIDTH = 160;
export const HIGHLIGHT_BORDER_WIDTH = 2;
export const HIGHLIGHT_WIDTH = CONTAINER_WIDTH + 2 * HIGHLIGHT_BORDER_WIDTH;
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { Pressable } from 'react-native-ama';
import {
interpolateColor,
useAnimatedStyle,
useDerivedValue,
useSharedValue,
withTiming,
} from 'react-native-reanimated';

import { usePress } from '../../../hooks/use-press.hook';
import { AnimatedBox } from '../../../primitives/animated-box/animated-box';
import { useStreamlineTheme } from '../../../theme';

import { BottomContent } from './components/bottom-content';
import { TopContent } from './components/top-content';
import { ListItemOptionMediaSkeleton } from './list-item-option-media-skeleton';
import {
CONTAINER_WIDTH,
HIGHLIGHT_BORDER_WIDTH,
HIGHLIGHT_WIDTH,
} from './list-item-option-media.constants';
import { ListItemOptionMediaProps } from './list-item-option-media.types';

export const ListItemOptionMedia = ({
accessibilityLabel,
iconName,
isDisabled,
isSelected,
isSkeleton,
onLongPress,
onPress,
title,
}: ListItemOptionMediaProps) => {
const [handlePress, isResolving] = usePress({ onPress });
const { colors } = useStreamlineTheme();

const isPressed = useSharedValue(0);
const isHighlighted = useDerivedValue(
() =>
(isSelected && isPressed.value) || (isSelected && isResolving ? 1 : 0),
[isSelected, isResolving]
);

const highlightStyle = useAnimatedStyle(() => {
return {
opacity: isHighlighted.value,
};
});

const containerStyle = useAnimatedStyle(() => {
const defaultBackgroundColor = isSelected
? colors.BLUKO_50
: colors.PURE_WHITE_1000;
const pressedBackgroundColor = isSelected
? colors.BLUKO_100
: colors.PURE_WHITE_1000;
const backgroundColor = interpolateColor(
isPressed.value,
[0, 1],
[defaultBackgroundColor, pressedBackgroundColor]
);

const defaultBorderColor = isSelected ? colors.BLUKO_500 : colors.GREY_100;
const pressedBorderColor = isSelected ? colors.BLUKO_600 : colors.GREY_300;
const borderColor = interpolateColor(
isPressed.value,
[0, 1],
[defaultBorderColor, pressedBorderColor]
);

return {
backgroundColor,
borderColor,
};
});

if (isSkeleton) {
return <ListItemOptionMediaSkeleton />;
}

const onPressIn = () => {
isPressed.value = withTiming(1);
};

const onPressOut = () => {
isPressed.value = withTiming(0);
};

return (
<Pressable
accessibilityRole="radio"
accessibilityLabel={accessibilityLabel ?? title}
disabled={isDisabled}
onPress={handlePress}
onPressIn={onPressIn}
onPressOut={onPressOut}
onLongPress={onLongPress}
selected={isSelected}
>
<>
<AnimatedBox
position="absolute"
top={-HIGHLIGHT_BORDER_WIDTH}
left={-HIGHLIGHT_BORDER_WIDTH}
right={-HIGHLIGHT_BORDER_WIDTH}
bottom={-HIGHLIGHT_BORDER_WIDTH}
backgroundColor="BLUKO_100"
style={highlightStyle}
borderRadius="lg"
width={HIGHLIGHT_WIDTH}
/>
<AnimatedBox
alignItems="center"
paddingHorizontal="md"
paddingVertical="xl"
borderRadius="lg"
borderWidth={2}
style={containerStyle}
width={CONTAINER_WIDTH}
>
<TopContent
iconName={iconName}
isDisabled={isDisabled}
isLoading={isResolving}
isSelected={isSelected}
/>
<BottomContent
title={title}
isDisabled={isDisabled}
isSelected={isSelected}
/>
</AnimatedBox>
</>
</Pressable>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { IconsName } from '../../../types';
import { ListItemOptionProps } from '../list-item-option/list-item-option.types';

export type ListItemOptionMediaProps = Pick<
ListItemOptionProps,
| 'accessibilityLabel'
| 'onLongPress'
| 'onPress'
| 'title'
| 'isDisabled'
| 'isSelected'
| 'isSkeleton'
> & {
iconName: IconsName;
};
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export * from './components/inputs/input-slider-range/input-slider-range';
export * from './components/list-items/list-item-group/list-item-group';
export * from './components/list-items/list-item-country/list-item-country';
export * from './components/list-items/list-item-option/list-item-option';
export * from './components/list-items/list-item-option-media/list-item-option-media';
export * from './components/list-items/list-item-switch/list-item-switch';
export * from './components/list-items/list-item-value/list-item-value';
export * from './components/list-items/list-item/list-item';
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export * from './components/list-items/list-item/list-item.types';
export * from './components/list-items/list-item-article/list-item-article.types';
export * from './components/list-items/list-item-country/list-item-country.types';
export * from './components/list-items/list-item-group/list-item-group.types';
export * from './components/list-items/list-item-option-media/list-item-option-media.types';
export * from './components/list-items/list-item-selectable/list-item-selectable.type';
export * from './components/list-items/list-item-switch/list-item-switch.types';
export * from './components/list-items/list-item-value/list-item-value.types';
Expand Down

0 comments on commit 9d98219

Please sign in to comment.