, childProps?: any) => StyledComponent
+}
- if (styleFromProps) {
- if (
- typeof styleFromProps === 'number' ||
- (styleFromProps as any).hasOwnProperty('viewDescriptors')
- ) {
- style = [style, styleFromProps, fixedStyle];
- } else if (Array.isArray(styleFromProps)) {
- style = [style, ...styleFromProps, fixedStyle];
- } else {
- style = {
- ...style,
- ...styleFromProps,
- ...fixedStyle,
- };
+const styled =
+
= {}>( // eslint-disable-line @typescript-eslint/no-empty-object-type
+ Comp: React.ComponentType
,
+ config: Config
= {}
+ ) =>
+ (
+ componentStyle?: ComponentStyle
+ ): StyledComponent, S> => {
+ const {
+ name,
+ props: factoryProps = {},
+ style: factoryStyle = {},
+ fixedStyle = {},
+ ...opts
+ } = config
+
+ const Styled = React.forwardRef & { children?: React.ReactNode }>(
+ (props, ref) => {
+ const { childRef, children, ...restProps } = props
+ const { comp, child, childProps = {} } = opts
+
+ const attrs = opts.attrs
+ const attrsResult = attrs
+ ? typeof attrs === 'function'
+ ? attrs(restProps as P)
+ : attrs
+ : {}
+
+ let style = {
+ ...factoryStyle,
+ ...(attrsResult as any).style,
+ ...(typeof componentStyle === 'function'
+ ? componentStyle(restProps as P & SP)
+ : componentStyle),
}
- }
- const parentProps = {
- ...factoryProps,
- ...attrsResult,
- ...restProps,
- style,
- };
-
- return React.createElement(
- comp || Comp,
- { ref, ...parentProps },
- child
- ? React.createElement(
- child,
- {
- ref: childRef,
- ...(typeof childProps === 'function'
- ? childProps(parentProps)
- : childProps),
- },
- children
- )
- : children
- );
- }
- );
-
- Styled.displayName = name || `styled(${Comp.displayName || Comp.name})`;
-
- // Extend the Styled component with custom methods
- const StyledComponent = Object.assign(Styled, {
- extend: (more: ComponentStyle) => styled(StyledComponent, { name })(more),
- 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, S>;
-};
-
-export default styled;
+ let styleFromProps = props.style
+
+ if (Array.isArray(styleFromProps)) {
+ styleFromProps = styleFromProps.filter((style) => !!style)
+ }
+
+ if (styleFromProps) {
+ if (
+ typeof styleFromProps === 'number' ||
+ (styleFromProps as any).hasOwnProperty('viewDescriptors')
+ ) {
+ style = [style, styleFromProps, fixedStyle]
+ } else if (Array.isArray(styleFromProps)) {
+ style = [style, ...styleFromProps, fixedStyle]
+ } else {
+ style = {
+ ...style,
+ ...styleFromProps,
+ ...fixedStyle,
+ }
+ }
+ }
+
+ const parentProps = {
+ ...factoryProps,
+ ...attrsResult,
+ ...restProps,
+ style,
+ }
+
+ return React.createElement(
+ comp || Comp,
+ { ref, ...parentProps },
+ child
+ ? React.createElement(
+ child,
+ {
+ ref: childRef,
+ ...(typeof childProps === 'function' ? childProps(parentProps) : childProps),
+ },
+ children
+ )
+ : children
+ )
+ }
+ )
+
+ Styled.displayName = name || `styled(${Comp.displayName || Comp.name})`
+
+ // Extend the Styled component with custom methods
+ const StyledComponent = Object.assign(Styled, {
+ extend: (more: ComponentStyle) =>
+ styled(StyledComponent, { name })(more),
+ 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, S>
+ }
+
+export default styled
diff --git a/src/rn.ts b/src/rn.ts
index 7880ffe..7118f86 100644
--- a/src/rn.ts
+++ b/src/rn.ts
@@ -1,27 +1,24 @@
import styled from './index'
-import {
- Image,
+import type {
ImageProps,
ImageStyle,
- Text,
TextInputProps,
- TextInput,
TextProps,
TextStyle,
- TouchableOpacity,
TouchableOpacityProps,
- View,
ViewProps,
ViewStyle,
} from 'react-native'
+import { Image, Text, TextInput, TouchableOpacity, View } from 'react-native'
-
-const extended = Object.assign(styled,{
+const extended = Object.assign(styled, {
View: styled(View, { name: 'styled(View)' }),
Text: styled(Text, { name: 'styled(Text)' }),
- Image: styled(Image, { name: "styled(Image)" }),
- Touchable: styled(TouchableOpacity, { name: 'styled(Touchable)' }),
+ Image: styled(Image, { name: 'styled(Image)' }),
+ Touchable: styled(TouchableOpacity, {
+ name: 'styled(Touchable)',
+ }),
TextInput: styled(TextInput, { name: 'styled(TextInput)' }),
})
-export default extended;
+export default extended
diff --git a/test/Example.js b/test/Example.js
index 993a340..0b67a24 100644
--- a/test/Example.js
+++ b/test/Example.js
@@ -44,6 +44,12 @@ const ButtonText = s.Text({ color: 'green' })
const AnotherButton = s.Touchable({ flex: 1 }).withChild(ButtonText)
+const Container = s(ScrollView, { name: 'Container' })(({ backgroundColor }) => ({
+ backgroundColor,
+}))
+
+Container.defaultProps = { backgroundColor: '#000' }
+
export default class Example extends React.PureComponent {
container = React.createRef()
@@ -76,9 +82,3 @@ export default class Example extends React.PureComponent {
)
}
}
-
-const Container = s(ScrollView, { name: 'Container' })(({ backgroundColor }) => ({
- backgroundColor,
-}))
-
-Container.defaultProps = { backgroundColor: '#000' }
diff --git a/test/index.type-test.tsx b/test/index.type-test.tsx
index a16dd8c..bc570d8 100644
--- a/test/index.type-test.tsx
+++ b/test/index.type-test.tsx
@@ -1,75 +1,112 @@
-import React, {Text, ViewStyle} from 'react-native'
+import type { ViewStyle } from 'react-native'
+import React, { Text } 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'
+import { useRef } from 'react'
+import type { LinearGradientProps } from 'react-native-linear-gradient'
+import LinearGradient from 'react-native-linear-gradient'
-const StyledText = styled(Text)(({ transparent }: { transparent?: boolean; color: string }) => ({ flex: 1, opacity: transparent ? 0.5 : 1 }))
+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 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 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 ExtendedStyledText = extendedStyled.Text(({ transparent }: { transparent?: boolean }) => ({
+ flex: 1,
+ opacity: transparent ? 0.5 : 1,
}))
+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 StyledView = extendedStyled.View({width: 100})
-const StyledViewWithDynamicProps = extendedStyled.View((props: {active: boolean}) => ({width: props.active ? 100 : 50}))
+const StyledView = extendedStyled.View({ width: 100 })
+const StyledViewWithDynamicProps = extendedStyled.View((props: { active: boolean }) => ({
+ width: props.active ? 100 : 50,
+}))
const Row = extendedStyled.View((props: { spaced?: boolean; full?: boolean }) => ({
- alignItems: 'center',
- flexDirection: 'row',
- justifyContent: props.spaced ? 'space-between' : undefined,
- width: props.full ? '100%' : undefined,
+ alignItems: 'center',
+ flexDirection: 'row',
+ justifyContent: props.spaced ? 'space-between' : undefined,
+ width: props.full ? '100%' : undefined,
}))
+const StyledImage = extendedStyled.Image({ width: 100 })
+const StyledImageWithDynamicProps = extendedStyled.Image((props: { active: boolean }) => ({
+ width: props.active ? 100 : 50,
+}))
-const StyledImage = extendedStyled.Image({width: 100})
-const StyledImageWithDynamicProps = extendedStyled.Image((props: {active: boolean}) => ({width: props.active ? 100 : 50}))
-
-const StyledTouchable = extendedStyled.Touchable({width: 100})
-const StyledTouchableWithDynamicProps = extendedStyled.Image((props: {active: boolean}) => ({width: props.active ? 100 : 50}))
+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)',
- })
+ LinearGradient: styled(LinearGradient, {
+ name: 'styled(LinearGradient)',
+ }),
})
-const DangerGradient = styled(LinearGradient)().attrs({ colors: ['orange', 'red']})
-const DefaultGradient = extendedWithLinear.LinearGradient({ }).attrs({
- colors: ['blue', 'green'],
+const DangerGradient = styled(LinearGradient)().attrs({ colors: ['orange', 'red'] })
+const DefaultGradient = extendedWithLinear.LinearGradient({}).attrs({
+ colors: ['blue', 'green'],
})
const MyScreen = () => {
- const ref = useRef()
+ 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
+
+ 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 */}
Hello World
{/* @ts-expect-error -- big is only available on StyledScalableText */}
- Hello World
- Hi
+
+ Hello World
+
+
+ Hi
+
{/* @ts-expect-error -- transparent prop is not allowed */}
Hi
Hello Extended World
- Hello Extended World
- Text with inline style
+
+ Hello Extended World
+
+
+ Text with inline style
+
Just a View
-
- some text
-
-
+
+ some text
+
View with dynamic style
@@ -77,12 +114,14 @@ const MyScreen = () => {
Image with dynamic style
Touchable
- Touchable with dynamic style
+
+ Touchable with dynamic style
+
{/* @ts-expect-error -- missing "colors" */}
-
+
{/* doesn't require optional props */}
diff --git a/test/reanimated.test.js b/test/reanimated.test.js
index 9a424bf..bb365e2 100644
--- a/test/reanimated.test.js
+++ b/test/reanimated.test.js
@@ -1,48 +1,56 @@
import React from 'react'
-import Animated, {useSharedValue, useAnimatedStyle} from 'react-native-reanimated'
-import { render } from '@testing-library/react-native';
+import Animated, { useSharedValue, useAnimatedStyle } from 'react-native-reanimated'
+import { render } from '@testing-library/react-native'
import s from '../src/rn'
-
it('works with reanimated styles', () => {
const AnimatedComponent = () => {
const animatedValue = useSharedValue(0.5)
const Foo = s(Animated.View)({ flex: 1, opacity: 0 })
- const animatedStyle = useAnimatedStyle(() => ({opacity: animatedValue.value}), [animatedValue])
+ const animatedStyle = useAnimatedStyle(
+ () => ({ opacity: animatedValue.value }),
+ [animatedValue]
+ )
return
}
-
- const { getByTestId } = render();
- const element = getByTestId('foo');
- expect(element.props.style).toEqual({opacity: 0.5, flex: 1})
- expect(element).toHaveAnimatedStyle({opacity: 0.5})
+
+ const { getByTestId } = render()
+ const element = getByTestId('foo')
+ expect(element.props.style).toEqual({ opacity: 0.5, flex: 1 })
+ expect(element).toHaveAnimatedStyle({ opacity: 0.5 })
})
it('works with reanimated styles when use array', () => {
const AnimatedComponent = () => {
const animatedValue = useSharedValue(0.5)
const Foo = s(Animated.View)({ flex: 1, opacity: 0 })
- const animatedStyle = useAnimatedStyle(() => ({opacity: animatedValue.value}), [animatedValue])
- return
+ const animatedStyle = useAnimatedStyle(
+ () => ({ opacity: animatedValue.value }),
+ [animatedValue]
+ )
+ return
}
-
- const { getByTestId } = render();
- const element = getByTestId('foo');
- expect(element.props.style).toEqual({opacity: 0.5, flex: 1, width: 50})
- expect(element).toHaveAnimatedStyle({opacity: 0.5})
+
+ const { getByTestId } = render()
+ const element = getByTestId('foo')
+ expect(element.props.style).toEqual({ opacity: 0.5, flex: 1, width: 50 })
+ expect(element).toHaveAnimatedStyle({ opacity: 0.5 })
})
it('works with reanimated styles when use array and some style is undefined', () => {
const AnimatedComponent = () => {
const animatedValue = useSharedValue(0.5)
const Foo = s(Animated.View)({ flex: 1, opacity: 0 })
- const animatedStyle = useAnimatedStyle(() => ({opacity: animatedValue.value}), [animatedValue])
- return
+ const animatedStyle = useAnimatedStyle(
+ () => ({ opacity: animatedValue.value }),
+ [animatedValue]
+ )
+ return
}
-
- const { getByTestId } = render();
- const element = getByTestId('foo');
- expect(element.props.style).toEqual({opacity: 0.5, flex: 1, width: 50})
- expect(element).toHaveAnimatedStyle({opacity: 0.5})
+
+ const { getByTestId } = render()
+ const element = getByTestId('foo')
+ expect(element.props.style).toEqual({ opacity: 0.5, flex: 1, width: 50 })
+ expect(element).toHaveAnimatedStyle({ opacity: 0.5 })
})
diff --git a/test/stylesheet.test.js b/test/stylesheet.test.js
index 0babeaf..4220e46 100644
--- a/test/stylesheet.test.js
+++ b/test/stylesheet.test.js
@@ -3,58 +3,54 @@ import React from 'react'
import { render } from '@testing-library/react-native'
import s from '../src/rn'
-import {
- View,
- StyleSheet
-} from 'react-native'
+import { View, StyleSheet } from 'react-native'
it('works with stylesheet styles', () => {
const stylesheetStyle = StyleSheet.create({
foo: {
- marginBottom: 10
- }
+ marginBottom: 10,
+ },
})
const StyledComponent = () => {
const Foo = s(View)({
flex: 1,
- opacity: 0
+ opacity: 0,
})
- return
+ return
}
-
+
const { getByTestId } = render()
const element = getByTestId('foo')
expect(element.props.style).toEqual({
marginBottom: 10,
flex: 1,
- opacity: 0
+ opacity: 0,
})
})
it('works with stylesheet styles when use array', () => {
const stylesheetStyle = StyleSheet.create({
foo: {
- marginBottom: 10
- }
+ marginBottom: 10,
+ },
})
const StyledComponent = () => {
const Foo = s(View)({
flex: 1,
- opacity: 0
+ opacity: 0,
})
- return
+ return
}
-
+
const { getByTestId } = render()
const element = getByTestId('foo')
- expect(element.props.style).toEqual([{
- 'flex': 1,
- 'opacity': 0
- }, { 'marginBottom': 10 }, { 'width': 10 }, {}])
+ expect(element.props.style).toEqual([
+ {
+ flex: 1,
+ opacity: 0,
+ },
+ { marginBottom: 10 },
+ { width: 10 },
+ {},
+ ])
})
diff --git a/tsconfig.json b/tsconfig.json
index 8216542..15fbc49 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -9,6 +9,5 @@
"noEmit": false,
"declaration": true
},
-
"include": ["src/**/*"]
}
diff --git a/tsconfig.test.json b/tsconfig.test.json
index 573b11c..e1e1835 100644
--- a/tsconfig.test.json
+++ b/tsconfig.test.json
@@ -4,5 +4,8 @@
"rootDir": ".",
"noEmit": true
},
- "include": ["test/**/*.tsx"],
+ "include": [
+ "test/**/*.ts*",
+ "src/**/*.ts*"
+ ],
}