Skip to content

Commit

Permalink
pass along variant name to restyled component
Browse files Browse the repository at this point in the history
  • Loading branch information
dominic authored and dominic committed Jun 5, 2024
1 parent 5fbb3f4 commit 406a8ef
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 81 deletions.
27 changes: 15 additions & 12 deletions src/hooks/useRestyle.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import {useMemo} from 'react';
import {StyleProp, useWindowDimensions} from 'react-native';
import { useMemo } from 'react';

Check failure on line 1 in src/hooks/useRestyle.ts

View workflow job for this annotation

GitHub Actions / Lint Typescript

Replace `·useMemo·` with `useMemo`
import { StyleProp, useWindowDimensions } from 'react-native';

Check failure on line 2 in src/hooks/useRestyle.ts

View workflow job for this annotation

GitHub Actions / Lint Typescript

Replace `·StyleProp,·useWindowDimensions·` with `StyleProp,·useWindowDimensions`

import {BaseTheme, RNStyle, Dimensions} from '../types';
import { BaseTheme, RNStyle, Dimensions } from '../types';

Check failure on line 4 in src/hooks/useRestyle.ts

View workflow job for this annotation

GitHub Actions / Lint Typescript

Replace `·BaseTheme,·RNStyle,·Dimensions·` with `BaseTheme,·RNStyle,·Dimensions`

import useTheme from './useTheme';

const filterRestyleProps = <
TRestyleProps,
TProps extends {[key: string]: unknown} & TRestyleProps,
TProps extends { [key: string]: unknown } & TRestyleProps,

Check failure on line 10 in src/hooks/useRestyle.ts

View workflow job for this annotation

GitHub Actions / Lint Typescript

Replace `·[key:·string]:·unknown·` with `[key:·string]:·unknown`
>(
componentProps: TProps,
omitPropertiesMap: {[key in keyof TProps]: boolean},
omitPropertiesMap: { [key in keyof TProps]: boolean },

Check failure on line 13 in src/hooks/useRestyle.ts

View workflow job for this annotation

GitHub Actions / Lint Typescript

Replace `·[key·in·keyof·TProps]:·boolean·` with `[key·in·keyof·TProps]:·boolean`
) => {
const cleanProps: TProps = {} as TProps;
const restyleProps: TProps & {variant?: unknown} = {} as TProps;
const restyleProps: TProps & { variant?: unknown } = {} as TProps;

Check failure on line 16 in src/hooks/useRestyle.ts

View workflow job for this annotation

GitHub Actions / Lint Typescript

Replace `·variant?:·unknown·` with `variant?:·unknown`
let serializedRestyleProps = '';
if (omitPropertiesMap.variant) {
restyleProps.variant = componentProps.variant ?? 'defaults';
Expand All @@ -27,14 +27,14 @@ const filterRestyleProps = <
}
}

const keys = {cleanProps, restyleProps, serializedRestyleProps};
const keys = { cleanProps, restyleProps, serializedRestyleProps };

Check failure on line 30 in src/hooks/useRestyle.ts

View workflow job for this annotation

GitHub Actions / Lint Typescript

Replace `·cleanProps,·restyleProps,·serializedRestyleProps·` with `cleanProps,·restyleProps,·serializedRestyleProps`
return keys;
};

const useRestyle = <
Theme extends BaseTheme,
TRestyleProps extends {[key: string]: any},
TProps extends TRestyleProps & {style?: StyleProp<RNStyle>},
TRestyleProps extends { [key: string]: any },

Check failure on line 36 in src/hooks/useRestyle.ts

View workflow job for this annotation

GitHub Actions / Lint Typescript

Replace `·[key:·string]:·any·` with `[key:·string]:·any`
TProps extends TRestyleProps & { style?: StyleProp<RNStyle>, variant?: string },

Check failure on line 37 in src/hooks/useRestyle.ts

View workflow job for this annotation

GitHub Actions / Lint Typescript

Replace `·style?:·StyleProp<RNStyle>,·variant?:·string·` with `style?:·StyleProp<RNStyle>;·variant?:·string`
>(
composedRestyleFunction: {
buildStyle: <TInputProps extends TProps>(
Expand All @@ -48,7 +48,7 @@ const useRestyle = <
},
) => RNStyle;
properties: (keyof TProps)[];
propertiesMap: {[key in keyof TProps]: boolean};
propertiesMap: { [key in keyof TProps]: boolean };

Check failure on line 51 in src/hooks/useRestyle.ts

View workflow job for this annotation

GitHub Actions / Lint Typescript

Replace `·[key·in·keyof·TProps]:·boolean·` with `[key·in·keyof·TProps]:·boolean`
},
props: TProps,
) => {
Expand All @@ -59,10 +59,10 @@ const useRestyle = <
// as this hook is called extremely often and incurs some performance hit.
const dimensions = theme.breakpoints
? // eslint-disable-next-line react-hooks/rules-of-hooks
useWindowDimensions()
useWindowDimensions()
: null;

const {cleanProps, restyleProps, serializedRestyleProps} = filterRestyleProps(
const { cleanProps, restyleProps, serializedRestyleProps } = filterRestyleProps(
props,
composedRestyleFunction.propertiesMap,
);
Expand Down Expand Up @@ -93,6 +93,9 @@ const useRestyle = <
]);

cleanProps.style = calculatedStyle;
if (restyleProps.variant) {
cleanProps.variant = restyleProps.variant
}
return cleanProps;
};

Expand Down
157 changes: 88 additions & 69 deletions src/test/createRestyleComponent.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import {create as render} from 'react-test-renderer';
import {View, ViewProps} from 'react-native';
import { create as render } from 'react-test-renderer';
import { View, ViewProps } from 'react-native';

import createRestyleComponent from '../createRestyleComponent';
import {
Expand All @@ -13,8 +13,8 @@ import {
OpacityProps,
opacity,
} from '../restyleFunctions';
import {ThemeProvider} from '../context';
import createVariant, {VariantProps} from '../createVariant';
import { ThemeProvider } from '../context';
import createVariant, { VariantProps } from '../createVariant';

const theme = {
colors: {
Expand Down Expand Up @@ -48,7 +48,7 @@ const themeWithVariant = {
},
};

const {breakpoints, ...themeWithoutBreakpoints} = theme;
const { breakpoints, ...themeWithoutBreakpoints } = theme;

type Theme = typeof theme;
type ThemeWithVariant = typeof themeWithVariant;
Expand All @@ -61,105 +61,105 @@ jest.mock('react-native/Libraries/Utilities/useWindowDimensions', () => ({

const Component = createRestyleComponent<
BackgroundColorProps<Theme> &
SpacingProps<Theme> &
SpacingShorthandProps<Theme> &
OpacityProps<Theme> &
ViewProps,
SpacingProps<Theme> &
SpacingShorthandProps<Theme> &
OpacityProps<Theme> &
ViewProps,
Theme
>([backgroundColor, spacing, spacingShorthand, opacity]);
const cardVariant = createVariant<ThemeWithVariant, 'cardVariants'>({
themeKey: 'cardVariants',
});
const ComponentWithVariant = createRestyleComponent<
BackgroundColorProps<ThemeWithVariant> &
SpacingProps<ThemeWithVariant> &
SpacingShorthandProps<Theme> &
OpacityProps<ThemeWithVariant> &
VariantProps<ThemeWithVariant, 'cardVariants'> &
ViewProps,
SpacingProps<ThemeWithVariant> &
SpacingShorthandProps<Theme> &
OpacityProps<ThemeWithVariant> &
VariantProps<ThemeWithVariant, 'cardVariants'> &
ViewProps,
ThemeWithVariant
>([backgroundColor, spacing, spacingShorthand, opacity, cardVariant]);

describe('createRestyleComponent', () => {
describe('creates a component that', () => {
beforeEach(() => {
mockUseWindowDimensions.mockReturnValue({width: 375, height: 667});
mockUseWindowDimensions.mockReturnValue({ width: 375, height: 667 });
});

it('passes styles based on the given props', () => {
const {root} = render(
const { root } = render(
<ThemeProvider theme={theme}>
<Component opacity={0.5} />
</ThemeProvider>,
);
expect(root.findByType(View).props.style).toStrictEqual([{opacity: 0.5}]);
expect(root.findByType(View).props.style).toStrictEqual([{ opacity: 0.5 }]);
});

it('passes styles based on the given props for theme without breakpoints', () => {
const {root} = render(
const { root } = render(
<ThemeProvider theme={themeWithoutBreakpoints}>
<Component opacity={0.5} />
</ThemeProvider>,
);
expect(root.findByType(View).props.style).toStrictEqual([{opacity: 0.5}]);
expect(root.findByType(View).props.style).toStrictEqual([{ opacity: 0.5 }]);
});

it('appends style prop to the end', () => {
const {root} = render(
const { root } = render(
<ThemeProvider theme={theme}>
<Component opacity={0.5} style={{width: 100}} />
<Component opacity={0.5} style={{ width: 100 }} />
</ThemeProvider>,
);
expect(root.findByType(View).props.style).toStrictEqual([
{opacity: 0.5},
{width: 100},
{ opacity: 0.5 },
{ width: 100 },
]);
});

it('does not pass styling properties to the child', () => {
const {root} = render(
const { root } = render(
<ThemeProvider theme={theme}>
<Component opacity={0.5} pointerEvents="auto" />
</ThemeProvider>,
);
expect(root.findByType(View).props).toStrictEqual({
style: [{opacity: 0.5}],
style: [{ opacity: 0.5 }],
pointerEvents: 'auto',
});
});

it('picks up values from the theme provided with ThemeProvider', () => {
const {root} = render(
const { root } = render(
<ThemeProvider theme={theme}>
<Component backgroundColor="coral" />
</ThemeProvider>,
);
expect(root.findByType(View).props).toStrictEqual({
style: [{backgroundColor: '#FFE6E4'}],
style: [{ backgroundColor: '#FFE6E4' }],
});
});

it('renders with phone-specific style', async () => {
const {root} = render(
const { root } = render(
<ThemeProvider theme={theme}>
<Component opacity={{phone: 0.5, tablet: 0.8}} />
<Component opacity={{ phone: 0.5, tablet: 0.8 }} />
</ThemeProvider>,
);
expect(root.findByType(View).props).toStrictEqual({
style: [{opacity: 0.5}],
style: [{ opacity: 0.5 }],
});
await new Promise(resolve => setTimeout(resolve, 0));
});

it('renders with tablet-specific style when dimensions are bigger', async () => {
mockUseWindowDimensions.mockReturnValue({width: 768, height: 1024});
const {root} = render(
mockUseWindowDimensions.mockReturnValue({ width: 768, height: 1024 });
const { root } = render(
<ThemeProvider theme={theme}>
<Component opacity={{phone: 0.5, tablet: 0.8}} />
<Component opacity={{ phone: 0.5, tablet: 0.8 }} />
</ThemeProvider>,
);
expect(root.findByType(View).props).toStrictEqual({
style: [{opacity: 0.8}],
style: [{ opacity: 0.8 }],
});
await new Promise(resolve => setTimeout(resolve, 0));
});
Expand All @@ -174,61 +174,80 @@ describe('createRestyleComponent', () => {

expect(spy).toHaveBeenCalledWith(
expect.objectContaining({
props: expect.objectContaining({testID: 'RENDERED_COMPONENT'}),
props: expect.objectContaining({ testID: 'RENDERED_COMPONENT' }),
}),
);
});

it('passes styles from default variant when no variant prop is defined', () => {
const {root} = render(
<ThemeProvider theme={themeWithVariant}>
<ComponentWithVariant margin="s" />
</ThemeProvider>,
);
expect(root.findByType(View).props.style).toStrictEqual([
{
alignItems: 'flex-start',
backgroundColor: '#FFB6C1',
margin: 8,
},
]);
});

it('passes styles from the defined variant', () => {
const {root} = render(
<ThemeProvider theme={themeWithVariant}>
<ComponentWithVariant variant="regular" margin="s" />
</ThemeProvider>,
);
expect(root.findByType(View).props.style).toStrictEqual([
{
alignItems: 'center',
backgroundColor: '#E0FFFF',
margin: 8,
},
]);
});

it('uses gap values from the theme', () => {
const {root} = render(
const { root } = render(
<ThemeProvider theme={theme}>
<Component gap="s" columnGap="s" rowGap="s" />
</ThemeProvider>,
);
expect(root.findByType(View).props).toStrictEqual({
style: [{gap: 8, columnGap: 8, rowGap: 8}],
style: [{ gap: 8, columnGap: 8, rowGap: 8 }],
});
});

it('passes gap shorthands as gap values', () => {
const {root} = render(
const { root } = render(
<ThemeProvider theme={theme}>
<Component g="s" cg="s" rg="s" />
</ThemeProvider>,
);
expect(root.findByType(View).props).toStrictEqual({
style: [{gap: 8, columnGap: 8, rowGap: 8}],
style: [{ gap: 8, columnGap: 8, rowGap: 8 }],
});
});

describe('variant', () => {
it('does not pass variant prop if no variant is created', () => {
const { root } = render(
<ThemeProvider theme={theme}>
<Component opacity={0.5} />
</ThemeProvider>,
);
expect(root.findByType(View).props).toStrictEqual({
style: [{ opacity: 0.5 }]
});
});

it('passes styles from default variant when no variant prop is defined', () => {
const { root } = render(
<ThemeProvider theme={themeWithVariant}>
<ComponentWithVariant margin="s" />
</ThemeProvider>,
);
expect(root.findByType(View).props).toStrictEqual({
variant: 'defaults',
style: [
{
alignItems: 'flex-start',
backgroundColor: '#FFB6C1',
margin: 8,
},
]
})
});

it('passes styles from the defined variant', () => {
const { root } = render(
<ThemeProvider theme={themeWithVariant}>
<ComponentWithVariant variant="regular" margin="s" />
</ThemeProvider>,
);
expect(root.findByType(View).props).toStrictEqual({
variant: 'regular',
style: [
{
alignItems: 'center',
backgroundColor: '#E0FFFF',
margin: 8,
},
]
})
});
})
});
});

0 comments on commit 406a8ef

Please sign in to comment.