diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ce0dd021..3cbb23ce 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - alpha jobs: publish-packages: @@ -45,4 +46,11 @@ jobs: GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} with: - branches: 'main' \ No newline at end of file + branches: | + [ + 'main', + { + name: 'alpha', + prerelease: true + } + ] \ No newline at end of file diff --git a/doc/docs/usage/component.mdx b/doc/docs/usage/component.mdx index 013e19b1..9fddbc0c 100644 --- a/doc/docs/usage/component.mdx +++ b/doc/docs/usage/component.mdx @@ -42,8 +42,8 @@ import { SxProps, useSx } from 'react-native-themed-styled-system'; type StyledViewProps = PropsWithChildren; const StyledView = forwardRef((props: StyledViewProps, ref: Ref) => { - const { viewStyle, filteredProps } = useSx(props); - return ; + const { getStyle, filteredProps } = useSx(props); + return ; }); export { StyledView }; @@ -52,7 +52,7 @@ export type { StyledViewProps }; `useSx` is responsible for receiving `SxProps` and converting it to `ViewStyle`. -You can call the `viewStyle` function returned by `useSx` and pass it to the `style` prop of the desired view. +You can call the `getStyle` function returned by `useSx` and pass it to the `style` prop of the desired view. :::warning The prop type of a component containing the `SxProps` type includes all the keys included in `SxProps`. @@ -60,7 +60,7 @@ The prop type of a component containing the `SxProps` type includes all the keys Therefore, if all props from the parent are passed through object destruction like in the existing `{...props}`, the keys do not overlap. You need to be careful not to do so. -Also, when using `props` as is, it must always come before `style` so as not to overwrite the `style` of `viewStyle()`. +Also, when using `props` as is, it must always come before `style` so as not to overwrite the `style` of `getStyle()`. To prevent this, you can use `filteredProps` in the return value of `useSx`. @@ -102,11 +102,11 @@ const ScreenErrorFallback = (props: Props) => { //highlight-next-line const { title, body } = props; //highlight-next-line - const { viewStyle } = useSx(props); + const { getStyle } = useSx(props); return ( ``` @@ -117,7 +117,7 @@ There are a few things to keep in mind. There is no need to add `style` as a prop to `style` of `View`. 2. Always pass all `props` objects themselves to `useSx` to avoid missing any properties. `useSx` Properties that are not used internally are ignored and not changed. -3. `viewStyle` can receive `SxProps` as an argument and consider it in the result. +3. `getStyle` can receive `SxProps` as an argument and consider it in the result. 4. `center` is a shortcut for `justifyContent: center`, `alignItems: center`. ## Example without Props destruction @@ -137,10 +137,10 @@ type StyledScrollViewProps = PropsWithChildren< SxProps >; const StyledScrollView = forwardRef((props: StyledScrollViewProps, ref: Ref) => { - const { viewStyle, filteredProps } = useSx(props); - const { viewStyle: contentContainerStyle } = useSx(props.contentContainerSx); + const { getStyle, filteredProps } = useSx(props); + const { getStyle: contentContainerStyle } = useSx(props.contentContainerSx); return ( - + ); }); diff --git a/doc/docs/usage/style-parsing.mdx b/doc/docs/usage/style-parsing.mdx index ffa70eb1..4b21137b 100644 --- a/doc/docs/usage/style-parsing.mdx +++ b/doc/docs/usage/style-parsing.mdx @@ -20,7 +20,7 @@ const props = { 1. Props’ `style` prop 2. Styles of `SxProps`, such as `ml` and `w` of Props 3. Styles inside the `sx` prop of Props -4. First argument of `viewStyle(sx?: SxProps)` +4. First argument of `getStyle(sx?: SxProps)` **The priority is calculated as 1 > 3 > 2 > 4.** @@ -34,12 +34,12 @@ describe('style parse priority', () => { expectResult(emptyTheme, { w: 1, sx: { w: 2 } }, { width: 2 }); }); - it('prop property > viewStyle parameter', () => { - expectResult(emptyTheme, { w: 1, viewStyleSx: { w: 2 } }, { width: 1 }); + it('prop property > getStyle parameter', () => { + expectResult(emptyTheme, { w: 1, getStyleSx: { w: 2 } }, { width: 1 }); }); - it('style prop property > viewStyle parameter', () => { - expectResult(emptyTheme, { style: { width: 1 }, viewStyleSx: { w: 2 } }, { width: 1 }); + it('style prop property > getStyle parameter', () => { + expectResult(emptyTheme, { style: { width: 1 }, getStyleSx: { w: 2 } }, { width: 1 }); }); }); ``` diff --git a/doc/i18n/ko/docusaurus-plugin-content-docs/current/usage/component.mdx b/doc/i18n/ko/docusaurus-plugin-content-docs/current/usage/component.mdx index 580ed963..2d01602c 100644 --- a/doc/i18n/ko/docusaurus-plugin-content-docs/current/usage/component.mdx +++ b/doc/i18n/ko/docusaurus-plugin-content-docs/current/usage/component.mdx @@ -42,8 +42,8 @@ import { SxProps, useSx } from 'react-native-themed-styled-system'; type StyledViewProps = PropsWithChildren; const StyledView = forwardRef((props: StyledViewProps, ref: Ref) => { - const { viewStyle, filteredProps } = useSx(props); - return ; + const { getStyle, filteredProps } = useSx(props); + return ; }); export { StyledView }; @@ -52,7 +52,7 @@ export type { StyledViewProps }; `useSx`는 `SxProps`를 받아 이를 `ViewStyle`로 변환하는 역할을 합니다. -`useSx`가 반환하는 `viewStyle` 함수를 호출해 원하는 뷰의 `style` prop에 전달할 수 있습니다. +`useSx`가 반환하는 `getStyle` 함수를 호출해 원하는 뷰의 `style` prop에 전달할 수 있습니다. :::warning `SxProps` 타입을 포함한 컴포넌트의 prop type은 `SxProps`에 포함되는 키들이 모두 포함됩니다. @@ -60,7 +60,7 @@ export type { StyledViewProps }; 따라서 기존에 `{...props}` 처럼 object destruction을 통해 부모로부터의 prop이 모두 전달되는 경우 키가 겹치지 않는지 주의가 필요합니다. -또한 `props`를 그대로 쓸 시, `style`보다 항상 이전에 와서 `viewStyle()`의 `style`을 덮어씌우지 않게 해야합니다. +또한 `props`를 그대로 쓸 시, `style`보다 항상 이전에 와서 `getStyle()`의 `style`을 덮어씌우지 않게 해야합니다. 이것을 방지하려면 `useSx`의 반환값 중 `filteredProps`를 사용하면 됩니다. @@ -102,11 +102,11 @@ const ScreenErrorFallback = (props: Props) => { // highlight-next-line const { title, body } = props; // highlight-next-line - const { viewStyle } = useSx(props); + const { getStyle } = useSx(props); return ( ``` @@ -117,7 +117,7 @@ const ScreenErrorFallback = (props: Props) => { `View`의 `style`에 prop으로 들어온 `style`도 같이 넣어줄 필요가 없습니다. 2. 누락되는 속성이 없게 항상 모든 `props` 객체 자체를 `useSx`로 넘기도록 합니다. `useSx`는 내부적으로 쓰이지 않는 속성들은 무시하고 변경시키지 않습니다. -3. `viewStyle`은 `SxProps`를 인자로 받아 결과에 고려시킬 수 있습니다. +3. `getStyle`은 `SxProps`를 인자로 받아 결과에 고려시킬 수 있습니다. 4. `center`는 `justifyContent: center`, `alignItems: center` 의 단축 속성입니다. ## Props destruction을 사용하지 않는 예시 @@ -137,10 +137,10 @@ type StyledScrollViewProps = PropsWithChildren< SxProps >; const StyledScrollView = forwardRef((props: StyledScrollViewProps, ref: Ref) => { - const { viewStyle, filteredProps } = useSx(props); - const { viewStyle: contentContainerStyle } = useSx(props.contentContainerSx); + const { getStyle, filteredProps } = useSx(props); + const { getStyle: contentContainerStyle } = useSx(props.contentContainerSx); return ( - + ); }); diff --git a/doc/i18n/ko/docusaurus-plugin-content-docs/current/usage/style-parsing.mdx b/doc/i18n/ko/docusaurus-plugin-content-docs/current/usage/style-parsing.mdx index f3c1d6e7..b3b6b30a 100644 --- a/doc/i18n/ko/docusaurus-plugin-content-docs/current/usage/style-parsing.mdx +++ b/doc/i18n/ko/docusaurus-plugin-content-docs/current/usage/style-parsing.mdx @@ -20,7 +20,7 @@ const props = { 1. Props의 `style` prop 2. Props의 `ml`, `w` 같은 `SxProps`의 스타일들 3. Props의 `sx` prop 내부의 스타일들 -4. `viewStyle(sx?: SxProps)`의 첫 인자 +4. `getStyle(sx?: SxProps)`의 첫 인자 **이에 따른 우선순위는 1 > 3 > 2 > 4 > 로 계산됩니다.** @@ -34,12 +34,12 @@ describe('style parse priority', () => { expectResult(emptyTheme, { w: 1, sx: { w: 2 } }, { width: 2 }); }); - it('prop property > viewStyle parameter', () => { - expectResult(emptyTheme, { w: 1, viewStyleSx: { w: 2 } }, { width: 1 }); + it('prop property > getStyle parameter', () => { + expectResult(emptyTheme, { w: 1, getStyleSx: { w: 2 } }, { width: 1 }); }); - it('style prop property > viewStyle parameter', () => { - expectResult(emptyTheme, { style: { width: 1 }, viewStyleSx: { w: 2 } }, { width: 1 }); + it('style prop property > getStyle parameter', () => { + expectResult(emptyTheme, { style: { width: 1 }, getStyleSx: { w: 2 } }, { width: 1 }); }); }); ``` diff --git a/src/@types/SxProps.ts b/src/@types/SxProps.ts index 2fbdb0a7..e697e4ac 100644 --- a/src/@types/SxProps.ts +++ b/src/@types/SxProps.ts @@ -1,13 +1,15 @@ -import type { ViewStyle } from 'react-native'; +import type { TextStyle, ViewStyle } from 'react-native'; import type { Token } from './Token'; -export type SxPropKeys = keyof SxProps; +export type SxPropsKeys = keyof SxProps; +export type TextSxPropsKey = keyof TextSxProps; /** * Always modify if you change API */ -export const _allPropList = [ +export const _viewStylePropList = [ 'style', + 'sx', 'backgroundColor', 'bg', 'borderColor', @@ -58,7 +60,6 @@ export const _allPropList = [ 'minHeight', 'maxH', 'maxHeight', - 'sx', 'flex', 'alignItems', 'alignContent', @@ -89,13 +90,37 @@ export const _allPropList = [ 'borderTopRightRadius', 'borderBottomLeftRadius', 'borderBottomRightRadius', -] satisfies (SxPropKeys | 'style')[]; - +] satisfies (SxPropsKeys | 'style')[]; +export const _textStylePropList = [ + 'color', + 'textDecorationColor', + 'textShadowColor', + 'fontFamily', + 'fontSize', + 'fontStyle', + 'fontWeight', + 'weight', + 'letterSpacing', + 'lineHeight', + 'textAlign', + 'align', + 'textDecorationLine', + 'textDecorationStyle', + 'textShadowOffset', + 'textShadowRadius', + 'textTransform', + 'userSelect', +] satisfies (Omit | 'style')[]; type ThemedColorTokenProps = { backgroundColor: Token<'colors'>; bg: Token<'colors'>; // backgroundColor borderColor: Token<'colors'>; }; +type ThemedColorTokenTextProps = { + color: Token<'colors'>; + textDecorationColor: Token<'colors'>; + textShadowColor: Token<'colors'>; +}; type ThemedSpaceTokenProps = { margin: Token<'space'>; @@ -163,38 +188,60 @@ type ThemedRadiiTokenProps = { bottomRightRadius: Token<'radii'>; // borderBottomRightRadius }; +type ThemedViewStyleProps = { + flex: ViewStyle['flex']; + alignItems: ViewStyle['alignItems']; + alignContent: ViewStyle['alignContent']; + justifyContent: ViewStyle['justifyContent']; + flexWrap: ViewStyle['flexWrap']; + flexDirection: ViewStyle['flexDirection']; + flexGrow: ViewStyle['flexGrow']; + flexShrink: ViewStyle['flexShrink']; + flexBasis: ViewStyle['flexBasis']; + alignSelf: ViewStyle['alignSelf']; + position: ViewStyle['position']; + pos: ViewStyle['position']; // position + borderWidth: ViewStyle['borderWidth']; + borderTopWidth: ViewStyle['borderTopWidth']; + borderRightWidth: ViewStyle['borderRightWidth']; + borderBottomWidth: ViewStyle['borderBottomWidth']; + borderLeftWidth: ViewStyle['borderLeftWidth']; + opacity: ViewStyle['opacity']; + overflow: ViewStyle['overflow']; + transform: ViewStyle['transform']; + aspectRatio: ViewStyle['aspectRatio']; + display: ViewStyle['display']; + elevation: ViewStyle['elevation']; + zIndex: ViewStyle['zIndex']; + absoluteFill?: boolean; // shortcut - position: absoulte, t,r,b,l: 0 + center?: boolean; // shortcut - justifyContent, alignItems: center +}; + +type ThemedTextStyleProps = { + fontFamily: TextStyle['fontFamily']; + fontSize: TextStyle['fontSize']; + fontStyle: TextStyle['fontStyle']; + fontWeight: TextStyle['fontWeight']; + weight: TextStyle['fontWeight']; // fontWeight + letterSpacing: TextStyle['letterSpacing']; + lineHeight: TextStyle['lineHeight']; + textAlign: TextStyle['textAlign']; + align: TextStyle['textAlign']; // textAlign + textDecorationLine: TextStyle['textDecorationLine']; + textDecorationStyle: TextStyle['textDecorationStyle']; + textShadowOffset: TextStyle['textShadowOffset']; + textShadowRadius: TextStyle['textShadowRadius']; + textTransform: TextStyle['textTransform']; + userSelect: TextStyle['userSelect']; +}; + type BaseSxProps = Partial< - { - flex: ViewStyle['flex']; - alignItems: ViewStyle['alignItems']; - alignContent: ViewStyle['alignContent']; - justifyContent: ViewStyle['justifyContent']; - flexWrap: ViewStyle['flexWrap']; - flexDirection: ViewStyle['flexDirection']; - flexGrow: ViewStyle['flexGrow']; - flexShrink: ViewStyle['flexShrink']; - flexBasis: ViewStyle['flexBasis']; - alignSelf: ViewStyle['alignSelf']; - position: ViewStyle['position']; - pos: ViewStyle['position']; // position - borderWidth: ViewStyle['borderWidth']; - borderTopWidth: ViewStyle['borderTopWidth']; - borderRightWidth: ViewStyle['borderRightWidth']; - borderBottomWidth: ViewStyle['borderBottomWidth']; - borderLeftWidth: ViewStyle['borderLeftWidth']; - opacity: ViewStyle['opacity']; - overflow: ViewStyle['overflow']; - transform: ViewStyle['transform']; - aspectRatio: ViewStyle['aspectRatio']; - display: ViewStyle['display']; - elevation: ViewStyle['elevation']; - zIndex: ViewStyle['zIndex']; - absoluteFill?: boolean; // shortcut - position: absoulte, t,r,b,l: 0 - center?: boolean; // shortcut - justifyContent, alignItems: center - } & ThemedSpaceTokenProps & + ThemedViewStyleProps & + ThemedSpaceTokenProps & ThemedSizeTokenProps & ThemedColorTokenProps & ThemedRadiiTokenProps >; export type SxProps = BaseSxProps & { sx?: BaseSxProps }; +export type TextSxProps = SxProps & Partial; diff --git a/src/hook/useSx.test.ts b/src/hook/useSx.test.ts index efb521ba..272a08f2 100644 --- a/src/hook/useSx.test.ts +++ b/src/hook/useSx.test.ts @@ -1,25 +1,41 @@ -import type { StyleProp, ViewStyle } from 'react-native'; +import type { StyleProp } from 'react-native'; import { StyleSheet } from 'react-native'; import { renderHook } from '@testing-library/react-hooks'; -import type { SxProps } from '../@types/SxProps'; +import type { SxProps, TextSxProps } from '../@types/SxProps'; import type { ThemedDict } from '../@types/ThemedDict'; import { emptyThemedDict } from '../@types/ThemedDict'; +import type { ThemedStyleType } from '../util/propsToThemedStyle'; import { useSx } from './useSx'; export function expectResult( theme: ThemedDict, - props: { style?: StyleProp; viewStyleSx?: SxProps } & SxProps, - expectation: ViewStyle, + props: { style?: StyleProp; getStyleSx?: SxProps & TextSxProps } & TextSxProps & + Record, + { + expectation, + filteredPropsExpectation, + styleType, + }: { + expectation: object; + filteredPropsExpectation?: object; + styleType?: ThemedStyleType; + }, ) { const { result: { - current: { viewStyle }, + current: { getStyle, filteredProps }, }, - } = renderHook(() => useSx(props, { theme })); + } = renderHook(() => useSx(props, { theme, styleType })); + + if (expectation) { + expect(StyleSheet.flatten(getStyle(props.getStyleSx))).toEqual(expectation); + } - return expect(StyleSheet.flatten(viewStyle(props.viewStyleSx))).toEqual(expectation); + if (filteredPropsExpectation) { + expect(filteredProps).toEqual(filteredPropsExpectation); + } } const emptyTheme = emptyThemedDict; @@ -46,145 +62,215 @@ const baseTheme: ThemedDict = { describe('simple usages', () => { it('handle empty', () => { - expectResult(baseTheme, {}, {}); + expectResult(baseTheme, {}, { expectation: {} }); + }); + + it('colors match', () => { + expectResult(baseTheme, { bg: 'red' }, { expectation: { backgroundColor: 'red' } }); + expectResult(baseTheme, { bg: '#ffffff' }, { expectation: { backgroundColor: '#ffffff' } }); }); - it('color', () => { - expectResult(baseTheme, { bg: 'red' }, { backgroundColor: 'red' }); - expectResult(baseTheme, { bg: '#ffffff' }, { backgroundColor: '#ffffff' }); + it('border widths should be match', () => { + expectResult( + emptyTheme, + { borderTopWidth: 1, borderWidth: 1 }, + { expectation: { borderTopWidth: 1, borderWidth: 1 } }, + ); + + expectResult(baseTheme, { borderLeftWidth: 1 }, { expectation: { borderLeftWidth: 1 } }); }); }); describe('edge case', () => { it('invalid px suffix', () => { - expectResult(baseTheme, { w: 'undefinedpx' as any }, {}); - expectResult(baseTheme, { w: 'nullpx' as any }, {}); - expectResult(baseTheme, { w: '-px' as any }, {}); - expectResult(baseTheme, { w: '-1px' as any }, { width: -1 }); + expectResult(baseTheme, { w: 'undefinedpx' as any }, { expectation: {} }); + expectResult(baseTheme, { w: 'nullpx' as any }, { expectation: {} }); + expectResult(baseTheme, { w: '-px' as any }, { expectation: {} }); + expectResult(baseTheme, { w: '-1px' as any }, { expectation: { width: -1 } }); }); it('if token is undefined, baseStyle is returned', () => { - expectResult(undefined as any, {}, {}); - expectResult(undefined as any, { style: { width: 1 } }, { width: 1 }); + expectResult(undefined as any, {}, { expectation: {} }); + expectResult(undefined as any, { style: { width: 1 } }, { expectation: { width: 1 } }); }); - it('if prop and viewStyle sx parameter are nullish, return undefined', () => { + it('if prop and getStyle sx parameter are nullish, return undefined', () => { const { result: { - current: { viewStyle }, + current: { getStyle }, }, } = renderHook(() => useSx(null, { theme: baseTheme })); - return expect(viewStyle()).toEqual(undefined); + return expect(getStyle()).toEqual(undefined); }); - it('if prop is nullish and viewStyle sx parameter is not nullish, return style', () => { + it('if prop is nullish and getStyle sx parameter is not nullish, return style', () => { const { result: { - current: { viewStyle }, + current: { getStyle }, }, } = renderHook(() => useSx(null, { theme: baseTheme })); - return expect(viewStyle({ width: 1 })).toEqual({ width: 4 }); + return expect(getStyle({ width: 1 })).toEqual({ width: 4 }); }); it("gap doesn't accept not number value", () => { - expectResult(baseTheme, { gap: 'full' as any }, {}); + expectResult(baseTheme, { gap: 'full' as any }, { expectation: {} }); }); }); describe('space parsing', () => { it('if number token not found, return itself', () => { - expectResult(emptyTheme, { m: 1 }, { margin: 1 }); + expectResult(emptyTheme, { m: 1 }, { expectation: { margin: 1 } }); }); it('px suffix string, return parsed pixel number value', () => { - expectResult(emptyTheme, { m: '15px' }, { margin: 15 }); - expectResult(emptyTheme, { m: '-1.5px' }, { margin: -1.5 }); - expectResult(emptyTheme, { m: '-0px' }, { margin: -0 }); - expectResult(emptyTheme, { m: '0px' }, { margin: 0 }); - expectResult(emptyTheme, { m: '-1px' }, { margin: -1 }); + expectResult(emptyTheme, { m: '15px' }, { expectation: { margin: 15 } }); + expectResult(emptyTheme, { m: '-1.5px' }, { expectation: { margin: -1.5 } }); + expectResult(emptyTheme, { m: '-0px' }, { expectation: { margin: -0 } }); + expectResult(emptyTheme, { m: '0px' }, { expectation: { margin: 0 } }); + expectResult(emptyTheme, { m: '-1px' }, { expectation: { margin: -1 } }); }); it('percentage', () => { - expectResult(emptyTheme, { m: '100%' }, { margin: '100%' }); + expectResult(emptyTheme, { m: '100%' }, { expectation: { margin: '100%' } }); }); it('number, string parsing', () => { - expectResult(baseTheme, { m: 1 }, { margin: 4 }); - expectResult(baseTheme, { m: '1' }, { margin: 4 }); + expectResult(baseTheme, { m: 1 }, { expectation: { margin: 4 } }); + expectResult(baseTheme, { m: '1' }, { expectation: { margin: 4 } }); }); it('negative works', () => { - expectResult(baseTheme, { m: -1 }, { margin: -4 }); + expectResult(baseTheme, { m: -1 }, { expectation: { margin: -4 } }); }); it('negative number as key', () => { - expectResult(baseTheme, { m: -1 }, { margin: -4 }); + expectResult(baseTheme, { m: -1 }, { expectation: { margin: -4 } }); }); it('minus prefixed string', () => { - expectResult(baseTheme, { m: '-pagePadding' as any }, { margin: -20 }); + expectResult(baseTheme, { m: '-pagePadding' as any }, { expectation: { margin: -20 } }); }); it('gap accepts number value', () => { - expectResult(baseTheme, { gap: 1 }, { gap: 4 }); + expectResult(baseTheme, { gap: 1 }, { expectation: { gap: 4 } }); }); }); describe('sizes parsing', () => { it("negative doesn't work", () => { - expectResult(baseTheme, { w: -1 }, { width: -1 }); + expectResult(baseTheme, { w: -1 }, { expectation: { width: -1 } }); }); }); describe('shortcut priority', () => { - it('bg', () => { - expectResult(baseTheme, { bg: 'red', backgroundColor: 'blue' }, { backgroundColor: 'blue' }); + it('backgroundColor', () => { + expectResult( + baseTheme, + { bg: 'red', backgroundColor: 'blue' }, + { expectation: { backgroundColor: 'blue' } }, + ); + expectResult( baseTheme, { bg: 'red', backgroundColor: '#ffffff' }, - { backgroundColor: '#ffffff' }, + { expectation: { backgroundColor: '#ffffff' } }, ); }); - it('w', () => { - expectResult(emptyTheme, { w: 1, width: 2 }, { width: 2 }); + it('width', () => { + expectResult(emptyTheme, { w: 1, width: 2 }, { expectation: { width: 2 } }); + }); + + it('borderRadius', () => { + expectResult(emptyTheme, { radius: 1, borderRadius: 2 }, { expectation: { borderRadius: 2 } }); }); - it('radius', () => { - expectResult(emptyTheme, { radius: 1, borderRadius: 2 }, { borderRadius: 2 }); + it('align', () => { + expectResult( + emptyTheme, + { textAlign: 'center', align: 'left' }, + { expectation: { textAlign: 'center' }, styleType: 'TextStyle' }, + ); }); }); describe('style parse priority', () => { it('sx prop property > prop property', () => { - expectResult(emptyTheme, { w: 1, sx: { w: 2 } }, { width: 2 }); + expectResult(emptyTheme, { w: 1, sx: { w: 2 } }, { expectation: { width: 2 } }); }); - it('prop property > viewStyle parameter', () => { - expectResult(emptyTheme, { w: 1, viewStyleSx: { w: 2 } }, { width: 1 }); + it('prop property > getStyle parameter', () => { + expectResult(emptyTheme, { w: 1, getStyleSx: { w: 2 } }, { expectation: { width: 1 } }); }); - it('style prop property > viewStyle parameter', () => { - expectResult(emptyTheme, { style: { width: 1 }, viewStyleSx: { w: 2 } }, { width: 1 }); + it('style prop property > getStyle parameter', () => { + expectResult( + emptyTheme, + { style: { width: 1 }, getStyleSx: { w: 2 } }, + { expectation: { width: 1 } }, + ); }); }); -it('border widths should be match', () => { - expectResult( - emptyTheme, - { borderTopWidth: 1, borderWidth: 1 }, - { borderTopWidth: 1, borderWidth: 1 }, - ); +describe('radii', () => { + it('simple case check', () => { + expectResult(baseTheme, { topLeftRadius: '1px' }, { expectation: { borderTopLeftRadius: 1 } }); + expectResult(baseTheme, { topLeftRadius: '1' }, { expectation: { borderTopLeftRadius: 1 } }); + expectResult( + baseTheme, + { topLeftRadius: 'sm' as any }, + { expectation: { borderTopLeftRadius: 8 } }, + ); + }); +}); - expectResult(baseTheme, { borderLeftWidth: 1 }, { borderLeftWidth: 1 }); +describe('filteredProps', () => { + it("filteredProps shouldn't have not style props - ViewStyle", () => { + expectResult( + baseTheme, + { shouldBePersist: true, backgroundColor: 'red' }, + { + expectation: { backgroundColor: 'red' }, + filteredPropsExpectation: { shouldBePersist: true }, + }, + ); + }); + + it("styleTheme includes only ViewStyle, then props in TextStyle shouldn't be filtered", () => { + expectResult( + baseTheme, + { color: 'red' }, + { + expectation: {}, + filteredPropsExpectation: { color: 'red' }, + }, + ); + }); + + it("filteredProps shouldn't have not style props - TextStyle", () => { + expectResult( + baseTheme, + { color: 'red' }, + { + expectation: { color: 'red' }, + filteredPropsExpectation: {}, + styleType: 'TextStyle', + }, + ); + }); }); -describe('radii', () => { - it('simple case check', () => { - expectResult(baseTheme, { topLeftRadius: '1px' }, { borderTopLeftRadius: 1 }); - expectResult(baseTheme, { topLeftRadius: '1' }, { borderTopLeftRadius: 1 }); - expectResult(baseTheme, { topLeftRadius: 'sm' as any }, { borderTopLeftRadius: 8 }); +describe('TextStyle', () => { + it('TextStyle props are parsed', () => { + expectResult( + baseTheme, + { textShadowColor: 'red' }, + { + expectation: { textShadowColor: 'red' }, + styleType: 'TextStyle', + }, + ); }); }); diff --git a/src/hook/useSx.ts b/src/hook/useSx.ts index d13cb57e..56173cd4 100644 --- a/src/hook/useSx.ts +++ b/src/hook/useSx.ts @@ -1,27 +1,35 @@ import { useContext, useMemo } from 'react'; -import type { StyleProp, ViewStyle } from 'react-native'; +import type { StyleProp } from 'react-native'; import { StyleSheet } from 'react-native'; -import type { SxProps } from '../@types/SxProps'; -import { _allPropList } from '../@types/SxProps'; +import type { SxProps, SxPropsKeys } from '../@types/SxProps'; +import { _textStylePropList, _viewStylePropList } from '../@types/SxProps'; import type { ThemedDict } from '../@types/ThemedDict'; import { useStableCallback } from '../internal/useStableCallback'; import { StyledSystemContext } from '../provider/StyledSystemProvider'; +import type { InferStyleType, InferSxPropsType, ThemedStyleType } from '../util/propsToThemedStyle'; import { propsToThemedStyle } from '../util/propsToThemedStyle'; type Props = { style?: StyleProp } & SxProps; export type UseSxOptions = { theme?: ThemedDict; + styleType?: ThemedStyleType; }; +const defaultUseSxOptions: UseSxOptions = { styleType: 'ViewStyle' }; export const useSx =

( props?: P | null, - { theme: optionTheme }: UseSxOptions = {}, + { + theme: optionTheme, + styleType = defaultUseSxOptions.styleType, + }: UseSxOptions = defaultUseSxOptions, ) => { const styledSystemContext = useContext(StyledSystemContext); - const viewStyle = useStableCallback( - (sx?: Omit): StyleProp | undefined => { + const getStyle = useStableCallback( + ( + sx?: Omit, 'sx'>, + ): StyleProp> | undefined => { const skip = !props && !sx; if (skip) { @@ -33,6 +41,7 @@ export const useSx =

( const ret = propsToThemedStyle({ theme: optionTheme ?? styledSystemContext?.theme, sx: mergedSx, + styleType, }); if (!ret) { @@ -45,12 +54,16 @@ export const useSx =

( }, ); - const filteredProps: Omit = useMemo(() => { + const filteredProps: Omit = useMemo(() => { const ret = { ...props }; - _allPropList.forEach((keyName) => delete ret[keyName]); + + _viewStylePropList.forEach((keyName) => delete ret[keyName]); + if (styleType === 'TextStyle') { + _textStylePropList.forEach((keyName) => delete ret[keyName]); + } return ret as Omit; - }, [props]); + }, [props, styleType]); - return { viewStyle, filteredProps }; + return { getStyle, filteredProps }; }; diff --git a/src/internal/TokenParser/ColorsParser.ts b/src/internal/TokenParser/ColorsParser.ts new file mode 100644 index 00000000..5764c13f --- /dev/null +++ b/src/internal/TokenParser/ColorsParser.ts @@ -0,0 +1,18 @@ +import type { ThemedDict } from '../../@types/ThemedDict'; +import type { Token } from '../../@types/Token'; + +export const createColorsParser = (theme: ThemedDict) => { + return (token?: Token<'colors'>) => parseColors(theme, token); +}; + +const parseColors = (theme: ThemedDict, token?: Token<'colors'>): string | undefined => { + if (!token) { + return; + } + + if (token in theme.colors) { + return theme.colors[token]; + } else { + return token; + } +}; diff --git a/src/internal/TokenParser/RadiiParser.ts b/src/internal/TokenParser/RadiiParser.ts new file mode 100644 index 00000000..92bc8eff --- /dev/null +++ b/src/internal/TokenParser/RadiiParser.ts @@ -0,0 +1,44 @@ +import { is } from '@mj-studio/js-util'; + +import type { ThemedDict } from '../../@types/ThemedDict'; +import type { Token } from '../../@types/Token'; +import { parsePxSuffixNumber } from '../util/parsePxSuffixNumber'; + +export const createRadiiParser = (theme: ThemedDict) => { + return (token?: Token<'radii'>) => parseRadii(theme, token); +}; + +const parseRadii = (theme: ThemedDict, token?: Token<'radii'>): number | undefined => { + if (is.nullOrUndefined(token)) { + return; + } + + const px = parsePxSuffixNumber(token); + if (is.number(px)) { + return px; + } + + // end with px but not number parsed + if (is.string(token) && token.endsWith('px')) { + return; + } + + const radii = theme.radii; + + if ((is.string(token) || is.number(token)) && token in radii) { + return radii[token] as number; + } + + if (is.number(token)) { + const stringKey = `${token}`; + if (stringKey in radii) { + return radii[stringKey] as number; + } + } + + if (is.numberString(token)) { + return Number(token); + } + + return token; +}; diff --git a/src/internal/TokenParser/SizesParser.ts b/src/internal/TokenParser/SizesParser.ts new file mode 100644 index 00000000..3db9bc3d --- /dev/null +++ b/src/internal/TokenParser/SizesParser.ts @@ -0,0 +1,45 @@ +import type { DimensionValue } from 'react-native'; +import { is } from '@mj-studio/js-util'; + +import type { ThemedDict } from '../../@types/ThemedDict'; +import type { Token } from '../../@types/Token'; +import { parsePxSuffixNumber } from '../util/parsePxSuffixNumber'; + +export const createSizesParser = (theme: ThemedDict) => { + return (token?: Token<'sizes'>) => parseSizes(theme, token); +}; + +const parseSizes = (theme: ThemedDict, token?: Token<'sizes'>): DimensionValue | undefined => { + if (is.nullOrUndefined(token)) { + return; + } + + const px = parsePxSuffixNumber(token); + if (is.number(px)) { + return px; + } + + // end with px but not number parsed + if (is.string(token) && token.endsWith('px')) { + return; + } + + const sizes = theme.sizes; + + if ((is.string(token) || is.number(token)) && token in sizes) { + return sizes[token] as DimensionValue; + } + + if (is.number(token)) { + const stringKey = `${token}`; + if (stringKey in sizes) { + return sizes[stringKey] as DimensionValue; + } + } + + if (is.numberString(token)) { + return Number(token); + } + + return token; +}; diff --git a/src/internal/TokenParser/SpaceParser.ts b/src/internal/TokenParser/SpaceParser.ts new file mode 100644 index 00000000..9ad42d4d --- /dev/null +++ b/src/internal/TokenParser/SpaceParser.ts @@ -0,0 +1,96 @@ +import type { DimensionValue } from 'react-native'; +import { is } from '@mj-studio/js-util'; + +import type { ThemedDict } from '../../@types/ThemedDict'; +import type { Token } from '../../@types/Token'; +import { parsePxSuffixNumber } from '../util/parsePxSuffixNumber'; + +export const createSpaceParser = (theme: ThemedDict) => { + return (token?: Token<'space'>) => parseSpace(theme, token); +}; + +export const createSpaceAsNumberOnlyParser = (theme: ThemedDict) => { + return (token?: Token<'space'>) => parseSpaceAsNumberOnly(theme, token); +}; + +/** + * Space should be handle negative string like '-1' as well + */ +const parseSpace = (theme: ThemedDict, token?: Token<'space'>): DimensionValue | undefined => { + if (is.nullOrUndefined(token)) { + return; + } + + const px = parsePxSuffixNumber(token); + if (is.number(px)) { + return px; + } + + // end with px but not number parsed + if (is.string(token) && token.endsWith('px')) { + return; + } + + const spaces = theme.space; + + if ((is.string(token) || is.number(token)) && token in spaces) { + return spaces[token] as DimensionValue; + } + + // Parse is number + if (is.number(token)) { + const stringKey = `${token}`; + if (stringKey in spaces) { + return spaces[stringKey] as DimensionValue; + } + + const negativeNumberKey = -token; + if (negativeNumberKey in spaces) { + const value = spaces[negativeNumberKey]; + if (is.number(value)) { + return -value; + } + } + + const negativeStringKey = + stringKey.charAt(0) === '-' ? stringKey.substring(1) : `-${stringKey}`; + + if (negativeStringKey in spaces) { + const value = spaces[negativeStringKey]; + if (is.number(value)) { + return -value; + } + } + } + + // Parse prefix minus token string + if (is.string(token) && token.startsWith('-')) { + const originalToken = token.substring(1); + if (originalToken in spaces) { + const value = spaces[originalToken]; + if (is.number(value)) { + return -value; + } + // not invert sign percent string yet(will be supported). + } + + // don't return malformed string. It is not acceptable as DimensionValue + return; + } + + if (is.numberString(token)) { + return Number(token); + } + + return token; +}; + +/** + * Parse space and filter if it is number for number only prop like gap + */ +const parseSpaceAsNumberOnly = (theme: ThemedDict, token?: Token<'space'>): number | undefined => { + const ret = parseSpace(theme, token); + if (is.number(ret)) { + return ret; + } +}; diff --git a/src/internal/TokenParser/TokenParser.ts b/src/internal/TokenParser/TokenParser.ts new file mode 100644 index 00000000..f39ccad2 --- /dev/null +++ b/src/internal/TokenParser/TokenParser.ts @@ -0,0 +1,16 @@ +import type { ThemedDict } from '../../@types/ThemedDict'; + +import { createColorsParser } from './ColorsParser'; +import { createRadiiParser } from './RadiiParser'; +import { createSizesParser } from './SizesParser'; +import { createSpaceAsNumberOnlyParser, createSpaceParser } from './SpaceParser'; + +export const createTokenParsers = (theme: ThemedDict) => { + return { + colors: createColorsParser(theme), + space: createSpaceParser(theme), + spaceAsNumberOnly: createSpaceAsNumberOnlyParser(theme), + sizes: createSizesParser(theme), + radii: createRadiiParser(theme), + }; +}; diff --git a/src/internal/util/fillStyleIfNotNullish.ts b/src/internal/util/fillStyleIfNotNullish.ts new file mode 100644 index 00000000..7a48530b --- /dev/null +++ b/src/internal/util/fillStyleIfNotNullish.ts @@ -0,0 +1,26 @@ +import type { TextStyle, ViewStyle } from 'react-native'; +import { is } from '@mj-studio/js-util'; + +export function fillViewStyleIfNotNullish( + into: ViewStyle, + key: K, + value?: ViewStyle[K] | null, +) { + if (is.undefined(value) || is.null(value)) { + return; + } + + into[key] = value; +} + +export function fillTextStyleIfNotNullish>( + into: TextStyle, + key: K, + value?: TextStyle[K] | null, +) { + if (is.undefined(value) || is.null(value)) { + return; + } + + into[key] = value; +} diff --git a/src/internal/util/fillViewStyleIfNotNullish.ts b/src/internal/util/fillViewStyleIfNotNullish.ts deleted file mode 100644 index f97dae75..00000000 --- a/src/internal/util/fillViewStyleIfNotNullish.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { ViewStyle } from 'react-native'; -import { is } from '@mj-studio/js-util'; - -export function fillViewStyleIfNotNullish( - into: ViewStyle, - key: T, - value?: ViewStyle[T] | null, -) { - if (is.undefined(value) || is.null(value)) { - return; - } - - into[key] = value; -} diff --git a/src/util/propsToThemedStyle.ts b/src/util/propsToThemedStyle.ts index afc1dfc5..856a0953 100644 --- a/src/util/propsToThemedStyle.ts +++ b/src/util/propsToThemedStyle.ts @@ -1,22 +1,32 @@ /* eslint-disable padding-line-between-statements */ -import type { DimensionValue, StyleProp, ViewStyle } from 'react-native'; -import { is } from '@mj-studio/js-util'; +import type { TextStyle, ViewStyle } from 'react-native'; -import type { SxProps } from '../@types/SxProps'; +import type { SxProps, TextSxProps } from '../@types/SxProps'; import type { ThemedDict } from '../@types/ThemedDict'; -import type { Token } from '../@types/Token'; -import { fillViewStyleIfNotNullish } from '../internal/util/fillViewStyleIfNotNullish'; -import { parsePxSuffixNumber } from '../internal/util/parsePxSuffixNumber'; +import { createTokenParsers } from '../internal/TokenParser/TokenParser'; +import { + fillTextStyleIfNotNullish, + fillViewStyleIfNotNullish, +} from '../internal/util/fillStyleIfNotNullish'; import { printWarning } from '../internal/util/printWarning'; +export type ThemedStyleType = 'ViewStyle' | 'TextStyle'; +export type InferStyleType = T extends 'TextStyle' + ? TextStyle + : ViewStyle; +export type InferSxPropsType = T extends 'TextStyle' + ? TextSxProps + : SxProps; export const propsToThemedStyle = ({ theme, sx, + styleType = 'ViewStyle', }: { theme?: ThemedDict; - sx?: SxProps; -}): StyleProp | undefined => { - const ret: ViewStyle = {}; + sx?: TextSxProps; + styleType?: ThemedStyleType; +}): InferStyleType | undefined => { + const ret: InferStyleType = {}; if (!theme) { printWarning('theme not found'); @@ -27,243 +37,72 @@ export const propsToThemedStyle = ({ return; } - const parseColor = (token?: Token<'colors'>): string | undefined => { - if (!token) { - return; - } - - if (token in theme.colors) { - return theme.colors[token]; - } else { - return token; - } - }; - - /** - * Space should be handle negative string like '-1' as well - */ - const parseSpace = (token?: Token<'space'>): DimensionValue | undefined => { - if (is.nullOrUndefined(token)) { - return; - } - - const px = parsePxSuffixNumber(token); - if (is.number(px)) { - return px; - } - // end with px but not number parsed - if (is.string(token) && token.endsWith('px')) { - return; - } - - const spaces = theme.space; - - if ((is.string(token) || is.number(token)) && token in spaces) { - return spaces[token] as DimensionValue; - } - - // Parse is number - if (is.number(token)) { - const stringKey = `${token}`; - if (stringKey in spaces) { - return spaces[stringKey] as DimensionValue; - } - - const negativeNumberKey = -token; - if (negativeNumberKey in spaces) { - const value = spaces[negativeNumberKey]; - if (is.number(value)) { - return -value; - } - } - - const negativeStringKey = - stringKey.charAt(0) === '-' ? stringKey.substring(1) : `-${stringKey}`; - - if (negativeStringKey in spaces) { - const value = spaces[negativeStringKey]; - if (is.number(value)) { - return -value; - } - } - } - - // Parse prefix minus token string - if (is.string(token) && token.startsWith('-')) { - const originalToken = token.substring(1); - if (originalToken in spaces) { - const value = spaces[originalToken]; - if (is.number(value)) { - return -value; - } - // not invert sign percent string yet(will be supported). - } - - // don't return malformed string. It is not acceptable as DimensionValue - return; - } - - if (is.numberString(token)) { - return Number(token); - } - - return token; - }; - - /** - * Parse space and filter if it is number for number only prop like gap - */ - const parseSpaceAsNumberOnly = (token?: Token<'space'>): number | undefined => { - const ret = parseSpace(token); - if (is.number(ret)) { - return ret; - } - }; - - const parseSizes = (token?: Token<'sizes'>): DimensionValue | undefined => { - if (is.nullOrUndefined(token)) { - return; - } - - const px = parsePxSuffixNumber(token); - if (is.number(px)) { - return px; - } - - // end with px but not number parsed - if (is.string(token) && token.endsWith('px')) { - return; - } - - const sizes = theme.sizes; - - if ((is.string(token) || is.number(token)) && token in sizes) { - return sizes[token] as DimensionValue; - } - - if (is.number(token)) { - const stringKey = `${token}`; - if (stringKey in sizes) { - return sizes[stringKey] as DimensionValue; - } - } - - if (is.numberString(token)) { - return Number(token); - } - - return token; - }; - - const parseRadii = (token?: Token<'radii'>): number | undefined => { - if (is.nullOrUndefined(token)) { - return; - } - - const px = parsePxSuffixNumber(token); - if (is.number(px)) { - return px; - } - - // end with px but not number parsed - if (is.string(token) && token.endsWith('px')) { - return; - } - - const radii = theme.radii; - - if ((is.string(token) || is.number(token)) && token in radii) { - return radii[token] as number; - } - - if (is.number(token)) { - const stringKey = `${token}`; - if (stringKey in radii) { - return radii[stringKey] as number; - } - } - - if (is.numberString(token)) { - return Number(token); - } - - return token; - }; + const { colors, radii, sizes, space, spaceAsNumberOnly } = createTokenParsers(theme); // region colors - fillViewStyleIfNotNullish(ret, 'backgroundColor', parseColor(sx.backgroundColor ?? sx.bg)); - fillViewStyleIfNotNullish(ret, 'borderColor', parseColor(sx.borderColor)); + fillViewStyleIfNotNullish(ret, 'backgroundColor', colors(sx.backgroundColor ?? sx.bg)); + fillViewStyleIfNotNullish(ret, 'borderColor', colors(sx.borderColor)); // endregion // region space - fillViewStyleIfNotNullish(ret, 'margin', parseSpace(sx.margin ?? sx.m)); - fillViewStyleIfNotNullish(ret, 'marginTop', parseSpace(sx.marginTop ?? sx.mt)); - fillViewStyleIfNotNullish(ret, 'marginRight', parseSpace(sx.marginRight ?? sx.mr)); - fillViewStyleIfNotNullish(ret, 'marginBottom', parseSpace(sx.marginBottom ?? sx.mb)); - fillViewStyleIfNotNullish(ret, 'marginLeft', parseSpace(sx.marginLeft ?? sx.ml)); - fillViewStyleIfNotNullish(ret, 'marginVertical', parseSpace(sx.marginY ?? sx.my)); - fillViewStyleIfNotNullish(ret, 'marginHorizontal', parseSpace(sx.marginX ?? sx.mx)); - - fillViewStyleIfNotNullish(ret, 'padding', parseSpace(sx.padding ?? sx.p)); - fillViewStyleIfNotNullish(ret, 'paddingTop', parseSpace(sx.paddingTop ?? sx.pt)); - fillViewStyleIfNotNullish(ret, 'paddingRight', parseSpace(sx.paddingRight ?? sx.pr)); - fillViewStyleIfNotNullish(ret, 'paddingBottom', parseSpace(sx.paddingBottom ?? sx.pb)); - fillViewStyleIfNotNullish(ret, 'paddingLeft', parseSpace(sx.paddingLeft ?? sx.pl)); - fillViewStyleIfNotNullish(ret, 'paddingVertical', parseSpace(sx.paddingY ?? sx.py)); - fillViewStyleIfNotNullish(ret, 'paddingHorizontal', parseSpace(sx.paddingX ?? sx.px)); - - fillViewStyleIfNotNullish(ret, 'top', parseSpace(sx.top ?? (sx.absoluteFill ? 0 : undefined))); - fillViewStyleIfNotNullish( - ret, - 'right', - parseSpace(sx.right ?? (sx.absoluteFill ? 0 : undefined)), - ); - fillViewStyleIfNotNullish( - ret, - 'bottom', - parseSpace(sx.bottom ?? (sx.absoluteFill ? 0 : undefined)), - ); - fillViewStyleIfNotNullish(ret, 'left', parseSpace(sx.left ?? (sx.absoluteFill ? 0 : undefined))); - + fillViewStyleIfNotNullish(ret, 'margin', space(sx.margin ?? sx.m)); + fillViewStyleIfNotNullish(ret, 'marginTop', space(sx.marginTop ?? sx.mt)); + fillViewStyleIfNotNullish(ret, 'marginRight', space(sx.marginRight ?? sx.mr)); + fillViewStyleIfNotNullish(ret, 'marginBottom', space(sx.marginBottom ?? sx.mb)); + fillViewStyleIfNotNullish(ret, 'marginLeft', space(sx.marginLeft ?? sx.ml)); + fillViewStyleIfNotNullish(ret, 'marginVertical', space(sx.marginY ?? sx.my)); + fillViewStyleIfNotNullish(ret, 'marginHorizontal', space(sx.marginX ?? sx.mx)); + + fillViewStyleIfNotNullish(ret, 'padding', space(sx.padding ?? sx.p)); + fillViewStyleIfNotNullish(ret, 'paddingTop', space(sx.paddingTop ?? sx.pt)); + fillViewStyleIfNotNullish(ret, 'paddingRight', space(sx.paddingRight ?? sx.pr)); + fillViewStyleIfNotNullish(ret, 'paddingBottom', space(sx.paddingBottom ?? sx.pb)); + fillViewStyleIfNotNullish(ret, 'paddingLeft', space(sx.paddingLeft ?? sx.pl)); + fillViewStyleIfNotNullish(ret, 'paddingVertical', space(sx.paddingY ?? sx.py)); + fillViewStyleIfNotNullish(ret, 'paddingHorizontal', space(sx.paddingX ?? sx.px)); + + fillViewStyleIfNotNullish(ret, 'top', space(sx.top ?? (sx.absoluteFill ? 0 : undefined))); + fillViewStyleIfNotNullish(ret, 'right', space(sx.right ?? (sx.absoluteFill ? 0 : undefined))); + fillViewStyleIfNotNullish(ret, 'bottom', space(sx.bottom ?? (sx.absoluteFill ? 0 : undefined))); + fillViewStyleIfNotNullish(ret, 'left', space(sx.left ?? (sx.absoluteFill ? 0 : undefined))); + + fillViewStyleIfNotNullish(ret, 'gap', spaceAsNumberOnly(sx.gap)); + fillViewStyleIfNotNullish(ret, 'columnGap', spaceAsNumberOnly(sx.gapX)); + fillViewStyleIfNotNullish(ret, 'rowGap', spaceAsNumberOnly(sx.gapY)); // endregion // region sizes - fillViewStyleIfNotNullish(ret, 'width', parseSizes(sx.width ?? sx.w)); - fillViewStyleIfNotNullish(ret, 'minWidth', parseSizes(sx.minWidth ?? sx.minW)); - fillViewStyleIfNotNullish(ret, 'maxWidth', parseSizes(sx.maxWidth ?? sx.maxW)); - - fillViewStyleIfNotNullish(ret, 'height', parseSizes(sx.height ?? sx.h)); - fillViewStyleIfNotNullish(ret, 'minHeight', parseSizes(sx.minHeight ?? sx.minH)); - fillViewStyleIfNotNullish(ret, 'maxHeight', parseSizes(sx.maxHeight ?? sx.maxH)); + fillViewStyleIfNotNullish(ret, 'width', sizes(sx.width ?? sx.w)); + fillViewStyleIfNotNullish(ret, 'minWidth', sizes(sx.minWidth ?? sx.minW)); + fillViewStyleIfNotNullish(ret, 'maxWidth', sizes(sx.maxWidth ?? sx.maxW)); - fillViewStyleIfNotNullish(ret, 'gap', parseSpaceAsNumberOnly(sx.gap)); - fillViewStyleIfNotNullish(ret, 'columnGap', parseSpaceAsNumberOnly(sx.gapX)); - fillViewStyleIfNotNullish(ret, 'rowGap', parseSpaceAsNumberOnly(sx.gapY)); + fillViewStyleIfNotNullish(ret, 'height', sizes(sx.height ?? sx.h)); + fillViewStyleIfNotNullish(ret, 'minHeight', sizes(sx.minHeight ?? sx.minH)); + fillViewStyleIfNotNullish(ret, 'maxHeight', sizes(sx.maxHeight ?? sx.maxH)); // endregion // region radii - fillViewStyleIfNotNullish(ret, 'borderRadius', parseRadii(sx.borderRadius ?? sx.radius)); + fillViewStyleIfNotNullish(ret, 'borderRadius', radii(sx.borderRadius ?? sx.radius)); fillViewStyleIfNotNullish( ret, 'borderTopLeftRadius', - parseRadii(sx.borderTopLeftRadius ?? sx.topLeftRadius), + radii(sx.borderTopLeftRadius ?? sx.topLeftRadius), ); fillViewStyleIfNotNullish( ret, 'borderTopRightRadius', - parseRadii(sx.borderTopRightRadius ?? sx.topRightRadius), + radii(sx.borderTopRightRadius ?? sx.topRightRadius), ); fillViewStyleIfNotNullish( ret, 'borderBottomLeftRadius', - parseRadii(sx.borderBottomLeftRadius ?? sx.bottomLeftRadius), + radii(sx.borderBottomLeftRadius ?? sx.bottomLeftRadius), ); fillViewStyleIfNotNullish( ret, 'borderBottomRightRadius', - parseRadii(sx.borderBottomRightRadius ?? sx.bottomRightRadius), + radii(sx.borderBottomRightRadius ?? sx.bottomRightRadius), ); - // endregion // region styles @@ -301,5 +140,29 @@ export const propsToThemedStyle = ({ fillViewStyleIfNotNullish(ret, 'zIndex', sx.zIndex); // endregion + if (styleType === 'TextStyle') { + // region colors + fillTextStyleIfNotNullish(ret, 'color', colors(sx.color)); + fillTextStyleIfNotNullish(ret, 'textDecorationColor', colors(sx.textDecorationColor)); + fillTextStyleIfNotNullish(ret, 'textShadowColor', colors(sx.textShadowColor)); + // endregion + + // region styles + fillTextStyleIfNotNullish(ret, 'fontFamily', colors(sx.fontFamily)); + fillTextStyleIfNotNullish(ret, 'fontSize', sx.fontSize); + fillTextStyleIfNotNullish(ret, 'fontStyle', sx.fontStyle); + fillTextStyleIfNotNullish(ret, 'fontWeight', sx.fontWeight ?? sx.weight); + fillTextStyleIfNotNullish(ret, 'letterSpacing', sx.letterSpacing); + fillTextStyleIfNotNullish(ret, 'lineHeight', sx.lineHeight); + fillTextStyleIfNotNullish(ret, 'textAlign', sx.textAlign ?? sx.align); + fillTextStyleIfNotNullish(ret, 'textDecorationLine', sx.textDecorationLine); + fillTextStyleIfNotNullish(ret, 'textDecorationStyle', sx.textDecorationStyle); + fillTextStyleIfNotNullish(ret, 'textShadowOffset', sx.textShadowOffset); + fillTextStyleIfNotNullish(ret, 'textShadowRadius', sx.textShadowRadius); + fillTextStyleIfNotNullish(ret, 'textTransform', sx.textTransform); + fillTextStyleIfNotNullish(ret, 'userSelect', sx.userSelect); + // endregion + } + return ret; };