From fe53111a45d7beaafd25685f64dd8d67bc232502 Mon Sep 17 00:00:00 2001 From: Jan W Date: Wed, 23 Oct 2024 13:34:46 +0200 Subject: [PATCH] fix: type support for props supplied in `.attrs()` (#25) * feat: support .attrs * fix: attrs used in factory config * test: update * refactor: simplify * refactor: DP => A2 --- package.json | 1 + src/index.ts | 32 ++++++++++++-------------------- test/index.type-test.tsx | 32 +++++++++++++++++++++++++++----- 3 files changed, 40 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index 15c6f47..904bb83 100755 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "microtime": "^3.1.1", "react": "18.2.0", "react-native": "^0.73.6", + "react-native-linear-gradient": "^2.8.3", "react-native-reanimated": "^3.8.1", "react-test-renderer": "18.2.0", "rollup": "^4.13.2", diff --git a/src/index.ts b/src/index.ts index 34a6f95..c72aebd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,12 @@ import React from 'react'; import type {StyleProp} from "react-native"; -export interface Config

{ +export interface Config

{ name?: string; props?: Partial

; style?: any; fixedStyle?: any; - attrs?: (props: P) => any; + attrs?: Attrs; comp?: React.ComponentType; child?: React.ComponentType; childProps?: ((props: any) => any) | any; @@ -18,6 +18,9 @@ export type StyledProps = { style?: StyleProp; }; +type WithOptional = Omit & { [P in K]?: P extends keyof T ? T[P] : never } + +type Attrs = ((props: Partial) => Partial) | Partial type ComponentStyle

= ((props: Partial

& SP) => S) | S export type StyledComponent

= React.ForwardRefExoticComponent< @@ -25,26 +28,15 @@ export type StyledComponent

= React.ForwardR React.RefAttributes > & { extend: (more: ComponentStyle) => StyledComponent

; - attrs: (attrs: any) => StyledComponent; + attrs: (attrs: Attrs) => StyledComponent, S>; withComponent: (comp: React.ComponentType) => StyledComponent; withChild: (child: React.ComponentType, childProps?: any) => StyledComponent; }; -type ComponentFactory

= ( - componentStyle: ComponentStyle -) => StyledComponent

| (() => StyledComponent); - -export interface StyledFunction{ -

( - Comp: React.ComponentType

, - config?: Config

- ): ComponentFactory; -} - -const styled =

( +const styled =

= {}>( Comp: React.ComponentType

, - config: Config

= {} -) => (componentStyle?: ComponentStyle): StyledComponent

=> { + config: Config = {} +) => (componentStyle?: ComponentStyle): StyledComponent, S> => { const { name, props: factoryProps = {}, @@ -67,7 +59,7 @@ const styled =

( let style = { ...factoryStyle, - ...attrsResult.style, + ...(attrsResult as any).style, ...(typeof componentStyle === 'function' ? componentStyle(restProps as P & SP) : componentStyle), @@ -127,14 +119,14 @@ const styled =

( // Extend the Styled component with custom methods const StyledComponent = Object.assign(Styled, { extend: (more: ComponentStyle) => styled(StyledComponent, { name })(more), - attrs: (attrs: any) => styled(StyledComponent, { attrs })() as StyledComponent

, + attrs: (attrs: Attrs) => styled(StyledComponent, { attrs })() as StyledComponent, S>, withComponent: (comp: React.ComponentType) => styled(StyledComponent, { comp })(componentStyle) as StyledComponent

, withChild: (child: React.ComponentType, childProps: any) => styled(StyledComponent, { child, childProps })() as StyledComponent

, }); - return StyledComponent as StyledComponent

; + return StyledComponent as StyledComponent, S>; }; export default styled; diff --git a/test/index.type-test.tsx b/test/index.type-test.tsx index bde61fb..a16dd8c 100644 --- a/test/index.type-test.tsx +++ b/test/index.type-test.tsx @@ -1,18 +1,19 @@ -import React, { Text } from 'react-native' +import React, {Text, ViewStyle} from 'react-native' import styled from '../src/index' import extendedStyled from '../src/rn' import {useRef} from "react"; +import LinearGradient, { LinearGradientProps } from 'react-native-linear-gradient' const StyledText = styled(Text)(({ transparent }: { transparent?: boolean; color: string }) => ({ flex: 1, opacity: transparent ? 0.5 : 1 })) +const StyledTextWithAttrs = StyledText.attrs({ color: 'black' }) +const StyledTextWithAttrsFromConfig = styled(StyledText, { attrs: { color: 'black' } })() const StyledScalableText = StyledText.extend(({ big }: { big?: boolean }) => ({ fontSize: big ? 20 : 10 })) const StyledTextWithObjectProps = styled(Text)({ flex: 1, opacity: 1 }) const ExtendedStyledText = extendedStyled.Text(({ transparent }: { transparent?: boolean; }) => ({ flex: 1, opacity: transparent ? 0.5 : 1 })) - -const ExtenedStyledTextWithAttrs = extendedStyled.Text(({ transparent }: { transparent?: boolean; big?:boolean }) => ({ flex: 1, opacity: transparent ? 0.5 : 1 })).attrs(({big}: {big?: boolean}) => ({ +const ExtendedStyledTextWithStyle = extendedStyled(Text)((props: {specificColor: string}) => ({color: props.specificColor})) +const ExtenedStyledTextWithAttrs = extendedStyled.Text(({ transparent }: { transparent?: boolean; big?:boolean }) => ({ flex: 1, opacity: transparent ? 0.5 : 1 })).attrs(({ big}) => ({ ellipsizeMode: big ? 'middle' : 'head', })) -const ExtendedStyledTextWithStyle = extendedStyled(Text)((props: {specificColor: string}) => ({color: props.specificColor})) - const StyledView = extendedStyled.View({width: 100}) const StyledViewWithDynamicProps = extendedStyled.View((props: {active: boolean}) => ({width: props.active ? 100 : 50})) @@ -31,12 +32,25 @@ const StyledTouchable = extendedStyled.Touchable({width: 100}) const StyledTouchableWithDynamicProps = extendedStyled.Image((props: {active: boolean}) => ({width: props.active ? 100 : 50})) const ViewWithText = extendedStyled.View({}).withChild(StyledText) +const extendedWithLinear = Object.assign(extendedStyled, { + LinearGradient: styled(LinearGradient, { + name: 'styled(LinearGradient)', + }) +}) + +const DangerGradient = styled(LinearGradient)().attrs({ colors: ['orange', 'red']}) +const DefaultGradient = extendedWithLinear.LinearGradient({ }).attrs({ + colors: ['blue', 'green'], +}) + const MyScreen = () => { const ref = useRef() return ( <> Hello Transparent World Hello Transparent World with ref + Color set through .attrs() and therefore not required + Color is set through config.attrs and therefore not required {/* should also work without transparent prop */} Hello World {/* @ts-expect-error -- should not work without required color prop */} @@ -66,6 +80,14 @@ const MyScreen = () => { Touchable with dynamic style + + {/* @ts-expect-error -- missing "colors" */} + + {/* doesn't require optional props */} + + + {/* but they can be provided */} + ) }