Skip to content

Commit

Permalink
fix(polymorphic): inherit original props (#46)
Browse files Browse the repository at this point in the history
  • Loading branch information
danilowoz committed May 5, 2022
1 parent cc74ea8 commit 8898fee
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 73 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: 12
node-version: 14.17

- name: Install dependencies
run: npm ci

- name: Build
run: npm run build

Expand Down
33 changes: 30 additions & 3 deletions examples/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import React, {useRef} from 'react';
import {SafeAreaView, ScrollView, Text, TouchableOpacity} from 'react-native';
import {
SafeAreaView,
ScrollView,
View,
Text,
TouchableOpacity,
} from 'react-native';

import Highlighter from './SyntaxHighlight';
import {Provider, styl} from '../styl';
Expand All @@ -14,6 +20,27 @@ const Title = styl(Text)({
paddingHorizontal: 16,
});

const Slider = styl(ScrollView)({});

const Foo = () => {
const setCurrentIndexByScroll = (foo: number) => {};

return (
<>
<Slider onPress={() => {}} />
<Slider
onScroll={({
nativeEvent: {
contentOffset: {x},
},
}) => {
setCurrentIndexByScroll(x);
}}
/>
</>
);
};

const DynamicText = styl(Text)<{color: string}>(({props}) => ({
padding: 16,
color: props.color,
Expand Down Expand Up @@ -46,7 +73,7 @@ const PresetComp = styl((props) => (
))({padding: 16});

const App = () => {
const ref = useRef();
const ref = useRef<Text>();

return (
<SafeAreaView>
Expand Down Expand Up @@ -127,7 +154,7 @@ const ExtendedText = styl(BaseText)({

{/* */}
<Title>`as` prop</Title>
<AsComp as={TouchableOpacity}>
<AsComp as={TouchableOpacity} onPress={() => {}}>
<Text>TouchableOpacity</Text>
</AsComp>

Expand Down
135 changes: 76 additions & 59 deletions examples/styl/styl.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable react/display-name */
import React, {
ComponentProps,
ComponentType,
createContext,
createElement,
Expand All @@ -8,8 +9,8 @@ import React, {
ReactElement,
ReactNode,
useContext,
} from 'react'
import { ViewStyle, TextStyle, ImageStyle, StyleProp } from 'react-native'
} from 'react';
import {ViewStyle, TextStyle, ImageStyle, StyleProp} from 'react-native';

/**
* Types definition
Expand All @@ -34,32 +35,48 @@ import { ViewStyle, TextStyle, ImageStyle, StyleProp } from 'react-native'
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface DefaultTheme {}

// Theme
type StyleProperties = ViewStyle | TextStyle | ImageStyle
/**
* Theme
*/
type StyleProperties = ViewStyle | TextStyle | ImageStyle;

type StylesWithTheme<P> = (args: {
props: P
theme: DefaultTheme
}) => StyleProperties
props: P;
theme: DefaultTheme;
}) => StyleProperties;

type Styles<P> = StylesWithTheme<P> | StyleProperties
type Styles<P> = StylesWithTheme<P> | StyleProperties;

// Props
/**
* Props
*/
type DefaultProps = object & {
as?: ComponentType<any>
style?: StyleProp<StyleProperties>
children?: ReactNode
}
as?: ComponentType<any>;
style?: StyleProp<StyleProperties>;
children?: ReactNode;
};

type Merge<P1 = {}, P2 = {}> = Omit<P1, keyof P2> & P2;

// Polymorphic
interface Polymorphic<IntrinsicElement, OwnProps = {}> {
<As extends ComponentType<any>>(
props: As extends JSXElementConstructor<infer P>
? OwnProps & { ref?: As } & { as?: As } & P
: IntrinsicElement extends JSXElementConstructor<infer E>
? OwnProps & { ref?: IntrinsicElement } & { as?: IntrinsicElement } & E
: never
): ReactElement | null
type ForwardRefExoticComponent<E, OwnProps> = React.ForwardRefExoticComponent<
Merge<
E extends React.ElementType ? React.ComponentPropsWithRef<E> : never,
OwnProps & {as?: E}
>
>;

/**
* Polymorphic
*/
interface Polymorphic<
IntrinsicElement extends JSXElementConstructor<any>,
OwnProps = {},
> extends ForwardRefExoticComponent<IntrinsicElement, OwnProps> {
<As extends JSXElementConstructor<any> | undefined>(
props: As extends JSXElementConstructor<infer E>
? Merge<E, OwnProps & {as?: As; ref?: As}>
: Merge<ComponentProps<IntrinsicElement>, OwnProps>,
): ReactElement | null;
}

/**
Expand All @@ -70,7 +87,7 @@ interface Polymorphic<IntrinsicElement, OwnProps = {}> {
*
* @internal
*/
const Context = createContext({ theme: {} })
const Context = createContext({theme: {}});

/**
* Provider
Expand All @@ -87,19 +104,19 @@ const Context = createContext({ theme: {} })
* )
* ```
*/
const Provider: React.FC<{ theme: DefaultTheme }> = ({ children, theme }) =>
createElement(Context.Provider, { value: { theme }, children })
const Provider: React.FC<{theme: DefaultTheme}> = ({children, theme}) =>
createElement(Context.Provider, {value: {theme}, children});

/**
* useTheme
*
* Expose the `theme` as a React hook
*/
const useTheme = (): DefaultTheme => {
const { theme } = useContext(Context)
const {theme} = useContext(Context);

return theme
}
return theme;
};

/**
* styl
Expand All @@ -119,34 +136,34 @@ const useTheme = (): DefaultTheme => {
* const BigTitle = styl(Title)({ fontSize: 40 })
* ```
*/
const styl = <Comp extends ComponentType<any>>(Component: Comp) => <
Props extends DefaultProps = DefaultProps
>(
stylesProp: Styles<Props>
): Polymorphic<Comp, Props> => {
return forwardRef(function ForwardedComponent(props: Props, ref) {
// Get theme from context
const { theme } = useContext(Context)

// Spread props and inline styles
const { style: inlineStyles = {}, as, ...restProps } = props

// Check type of argument
const styles =
typeof stylesProp === 'function'
? stylesProp({ props, theme })
: stylesProp

// Create component
return createElement<DefaultProps>(as || Component, {
...restProps,
ref,
style: [
styles,
...(Array.isArray(inlineStyles) ? inlineStyles : [inlineStyles]),
],
})
})
}

export { styl, Provider, useTheme }
const styl =
<Comp extends ComponentType<any>>(Component: Comp) =>
<Props extends DefaultProps = DefaultProps>(
stylesProp: Styles<Props>,
): Polymorphic<Comp, Props> => {
return forwardRef(function ForwardedComponent(props: Props, ref) {
// Get theme from context
const {theme} = useContext(Context);

// Spread props and inline styles
const {style: inlineStyles = {}, as, ...restProps} = props;

// Check type of argument
const styles =
typeof stylesProp === 'function'
? stylesProp({props, theme})
: stylesProp;

// Create component
return createElement<DefaultProps>(as || Component, {
...restProps,
ref,
style: [
styles,
...(Array.isArray(inlineStyles) ? inlineStyles : [inlineStyles]),
],
});
}) as any;
};

export {styl, Provider, useTheme};
25 changes: 17 additions & 8 deletions lib/styl.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable react/display-name */
import React, {
ComponentProps,
ComponentType,
createContext,
createElement,
Expand Down Expand Up @@ -55,18 +56,26 @@ type DefaultProps = object & {
children?: ReactNode
}

type Merge<P1 = {}, P2 = {}> = Omit<P1, keyof P2> & P2

type ForwardRefExoticComponent<E, OwnProps> = React.ForwardRefExoticComponent<
Merge<
E extends React.ElementType ? React.ComponentPropsWithRef<E> : never,
OwnProps & { as?: E }
>
>

/**
* Polymorphic
*/
interface Polymorphic<
IntrinsicElement extends ComponentType<any>,
IntrinsicElement extends JSXElementConstructor<any>,
OwnProps = {}
> {
<As extends ComponentType<any> = IntrinsicElement>(
props: As extends JSXElementConstructor<infer AsProps>
? Omit<DefaultProps, 'style'> &
Omit<OwnProps, 'style'> & { ref?: As } & { as?: As } & AsProps
: never
> extends ForwardRefExoticComponent<IntrinsicElement, OwnProps> {
<As extends JSXElementConstructor<any> | undefined>(
props: As extends JSXElementConstructor<infer E>
? Merge<E, OwnProps & { as?: As; ref?: As }>
: Merge<ComponentProps<IntrinsicElement>, OwnProps>
): ReactElement | null
}

Expand Down Expand Up @@ -154,7 +163,7 @@ const styl = <Comp extends ComponentType<any>>(Component: Comp) => <
...(Array.isArray(inlineStyles) ? inlineStyles : [inlineStyles]),
],
})
})
}) as any
}

export { styl, Provider, useTheme }

0 comments on commit 8898fee

Please sign in to comment.