From 0c1b9b9448df26360e0308c2a263803fc60e70c5 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 26 Jun 2023 16:05:25 -0400 Subject: [PATCH] added custom props and fixed type bugs --- README.md | 68 ++++++++++++++++++++++++++------- package.json | 2 +- src/aliases.ts | 25 +++++++++++-- src/index.ts | 2 +- src/styled.ts | 100 ++++++++++++++++++++++++++++++++++++------------- 5 files changed, 153 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index f8c0cf6..e153bfe 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,11 @@ Apply styles directly to your React Native components via props! - Fully typed -- No more `StyleSheet.create` or `styles={{ ... }}` +- No more `StyleSheet.create()` or `style={{ ... }}` - Faster prototyping - Custom aliases - Default styles +- Custom shorthand props ## Installation @@ -16,6 +17,8 @@ yarn add style-direct-club ## Usage +The `styled` function takes in a React Native component and returns a styled component. The styled component can be used in place of the original component and will accept style props. + ```tsx import { TouchableOpacity } from 'react-native'; import { styled } from 'style-direct-club'; @@ -49,19 +52,16 @@ function App() { ### Aliases -You can use aliases to make the style prop names more readable. Agree upon standards with your team and stay consistent! - -Format your custom alias object like this: `{ aliasName: originalStyleName }` +Custom aliases allow you to define alternative names for each style prop. This is useful if you want to shorten the names for a more concise syntax. These are fully customizable, so agree upon standards with your team and stay consistent! In the aliases object, the key is the custom name, and the value is the original style prop name. ```tsx -import { styled, defaultAlias } from 'style-direct-club'; +import { styled, defaultViewStyleAlias } from 'style-direct-club'; import { View } from 'react-native'; -// Use the default aliases const StyledView = styled(View, { aliases: { // Use the default aliases - ...defaultAlias, + ...defaultViewStyleAlias, // Or add your own! bg: 'backgroundColor', p: 'padding', @@ -69,6 +69,15 @@ const StyledView = styled(View, { mt: 'marginTop', }, }); + +// Use your custom aliases +function App() { + return ( + + Hello World! + + ); +} ``` ### Default Styles @@ -86,15 +95,48 @@ const StyledTouchableOpacity = styled(TouchableOpacity, { borderRadius: 8, }, }); + +// Default styles will be applies, and can be overridden with props +function App() { + return ( + + Press Me! + + ); +} ``` -## Gotchas +### Custom Props -- The `style` prop is still supported, but styles passed in as props will take precedence over the `style` prop object. -- If you want to add these props to a custom component, the component must pass its props to the underlying view. -- Default styles will not respect aliases. You must use the original style prop name. -- When using the built in components, you won't be able to pass additional options. +Custom props allow you to apply multiple styles with a single prop. These props are fully customizable and will be available as props in your component. -``` +```tsx +import { styled } from 'style-direct-club'; +import { Text } from 'react-native'; + +const StyledText = styled(Text, { + customProps: { + sm: { + fontSize: 12, + marginBottom: 5, + }, + xl: { + fontSize: 36, + marginBottom: 10, + fontWeight: 'bold', + }, + }, +}); +function App() { + return Hello World!; +} ``` + +## Gotchas + +- The `style` prop is still supported, but props will take priority over the `style` prop object. +- If you want to add these props to a custom component, the component must pass its `style` prop to the underlying view. +- Default styles and custom props will not respect aliases. You must use the original style prop name. +- When using the built in components, you won't be able to pass additional options. +- If you use the out of the box Text and View components, you will not be able to pass in options like custom aliases. diff --git a/package.json b/package.json index 94e9085..4f06751 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "style-direct-club", - "version": "0.0.5", + "version": "0.0.6", "description": "Apply styles directly to your React Native components via props!", "main": "lib/commonjs/index", "module": "lib/module/index", diff --git a/src/aliases.ts b/src/aliases.ts index 20db990..92918a3 100644 --- a/src/aliases.ts +++ b/src/aliases.ts @@ -1,4 +1,6 @@ -export const defaultAlias = { +import { TextStyle, ViewStyle } from 'react-native'; + +export const defaultViewStyleAliases: Record = { bg: 'backgroundColor', p: 'padding', pt: 'paddingTop', @@ -34,6 +36,21 @@ export const defaultAlias = { shrink: 'flexShrink', } as const; -// size: 'fontSize', -// weight: 'fontWeight', -// align: 'textAlign', +export const defaultTextStyleAliases: Record = { + size: 'fontSize', + family: 'fontFamily', + weight: 'fontWeight', + spacing: 'letterSpacing', + lh: 'lineHeight', + ta: 'textAlign', + tdl: 'textDecorationLine', + tds: 'textDecorationStyle', + tt: 'textTransform', + wd: 'writingDirection', + tav: 'textAlignVertical', + ifp: 'includeFontPadding', + tsc: 'textShadowColor', + tso: 'textShadowOffset', + tsr: 'textShadowRadius', + tdc: 'textDecorationColor', +} as const; diff --git a/src/index.ts b/src/index.ts index c240535..beae3b3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,2 @@ export { styled } from './styled'; -export { defaultAlias } from './aliases'; +export { defaultTextStyleAliases, defaultViewStyleAliases } from './aliases'; diff --git a/src/styled.ts b/src/styled.ts index a184249..a13104f 100644 --- a/src/styled.ts +++ b/src/styled.ts @@ -3,11 +3,26 @@ import { allowedProps } from './allowedProps'; import { StyleProp, Text, View } from 'react-native'; type Options> = { - alias?: Record>; - defaultStyles?: AliasMap; + aliases?: Record>; + defaultStyles?: StyleObject; + customProps?: Record>; }; -type AliasMap> = Exclude< +type AliasedStyles< + T extends React.ComponentType, + U extends Options +> = Omit< + StyleObject, + U['aliases'][keyof U['aliases']] extends string + ? U['aliases'][keyof U['aliases']] + : never +> & { + [k in keyof U['aliases']]?: StyleObject[U['aliases'][k] extends keyof StyleObject + ? U['aliases'][k] + : never]; +}; + +type StyleObject> = Exclude< ComponentProps extends { style?: infer S; } @@ -18,45 +33,80 @@ type AliasMap> = Exclude< (...args: any[]) => any >; +type CustomStyles< + T extends React.ComponentType, + U extends Options +> = U['customProps'] extends Record> + ? { + [k in keyof U['customProps']]?: boolean; + } + : {}; + +type StyledComponent< + T extends React.ComponentType, + U extends Options +> = ( + props: ComponentProps | AliasedStyles | CustomStyles +) => JSX.Element; + +type StyledFunction = { + >(component: T): StyledComponent; + , U extends Options>( + component: T, + options: U + ): StyledComponent; + Text: StyledComponent; + View: StyledComponent; +}; + /** * Creates a styled component, allowing you to pass style props directly to the component - * instead of using the `style` prop or `StyleSheet.create`. Prop names can be aliased, and - * default styles can be provided. + * instead of using the `style` prop or `StyleSheet.create`. Provide options to customize + * your styling experience. */ -export function styled< - T extends React.ComponentType, - U extends Options = {} ->(component: T, options: U = {} as U) { - type BaseProps = ComponentProps; - type PropsWithAliasMap = BaseProps & - // @ts-ignore - Omit, U['alias'][keyof U['alias']]> & { - // @ts-ignore - [k in keyof U['alias']]?: AliasMap[U['alias'][k]]; - }; +export const styled: StyledFunction = ( + component: React.ComponentType, + options?: Options +) => { + type BaseProps = ComponentProps; + type AliasedProps = AliasedStyles< + typeof component, + Exclude + >; + type CustomProps = CustomStyles< + typeof component, + Exclude + >; - return function StyledComponent(props: PropsWithAliasMap) { + return function StyledComponent( + props: BaseProps | AliasedProps | CustomProps + ) { return createElement(component, { ...props, style: { ...(options?.defaultStyles || {}), ...props.style, - ...mapStyleProps(props, options?.alias), + ...mapPropsToStyle(props, options?.aliases, options?.customProps), }, }); }; -} +}; -function mapStyleProps( +function mapPropsToStyle( props: Record, - aliasMap?: Record + aliases?: Record, + customProps?: Record ) { - const styleProps: Record = {}; + let styleProps: Record = {}; if (props) { Object.keys(props).forEach((key) => { - const keyOrAlias = aliasMap?.[key] || key; - if (allowedProps.includes(keyOrAlias)) { - styleProps[keyOrAlias] = props[key]; + if (customProps?.[key]) { + styleProps = { ...styleProps, ...customProps[key] }; + } else { + const keyOrAlias = aliases?.[key] || key; + if (allowedProps.includes(keyOrAlias)) { + styleProps[keyOrAlias] = props[key]; + } } }); }