Skip to content

Commit

Permalink
added custom props and fixed type bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
andrew-levy committed Jun 26, 2023
1 parent 9dcdb4a commit 0c1b9b9
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 44 deletions.
68 changes: 55 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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';
Expand Down Expand Up @@ -49,26 +52,32 @@ 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',
m: 'margin',
mt: 'marginTop',
},
});

// Use your custom aliases
function App() {
return (
<StyledView bg="blue" p={10} m={10} mt={20}>
<Text color="white">Hello World!</Text>
</StyledView>
);
}
```

### Default Styles
Expand All @@ -86,15 +95,48 @@ const StyledTouchableOpacity = styled(TouchableOpacity, {
borderRadius: 8,
},
});

// Default styles will be applies, and can be overridden with props
function App() {
return (
<StyledTouchableOpacity padding={5}>
<Text color="white">Press Me!</Text>
</StyledTouchableOpacity>
);
}
```

## 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 <StyledText xl>Hello World!</StyledText>;
}
```

## 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.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
25 changes: 21 additions & 4 deletions src/aliases.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export const defaultAlias = {
import { TextStyle, ViewStyle } from 'react-native';

export const defaultViewStyleAliases: Record<string, keyof ViewStyle> = {
bg: 'backgroundColor',
p: 'padding',
pt: 'paddingTop',
Expand Down Expand Up @@ -34,6 +36,21 @@ export const defaultAlias = {
shrink: 'flexShrink',
} as const;

// size: 'fontSize',
// weight: 'fontWeight',
// align: 'textAlign',
export const defaultTextStyleAliases: Record<string, keyof TextStyle> = {
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;
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { styled } from './styled';
export { defaultAlias } from './aliases';
export { defaultTextStyleAliases, defaultViewStyleAliases } from './aliases';
100 changes: 75 additions & 25 deletions src/styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,26 @@ import { allowedProps } from './allowedProps';
import { StyleProp, Text, View } from 'react-native';

type Options<T extends React.ComponentType<any>> = {
alias?: Record<string, keyof AliasMap<T>>;
defaultStyles?: AliasMap<T>;
aliases?: Record<string, keyof StyleObject<T>>;
defaultStyles?: StyleObject<T>;
customProps?: Record<string, StyleObject<T>>;
};

type AliasMap<T extends React.ComponentType<any>> = Exclude<
type AliasedStyles<
T extends React.ComponentType<any>,
U extends Options<T>
> = Omit<
StyleObject<T>,
U['aliases'][keyof U['aliases']] extends string
? U['aliases'][keyof U['aliases']]
: never
> & {
[k in keyof U['aliases']]?: StyleObject<T>[U['aliases'][k] extends keyof StyleObject<T>
? U['aliases'][k]
: never];
};

type StyleObject<T extends React.ComponentType<any>> = Exclude<
ComponentProps<T> extends {
style?: infer S;
}
Expand All @@ -18,45 +33,80 @@ type AliasMap<T extends React.ComponentType<any>> = Exclude<
(...args: any[]) => any
>;

type CustomStyles<
T extends React.ComponentType<any>,
U extends Options<T>
> = U['customProps'] extends Record<string, StyleObject<T>>
? {
[k in keyof U['customProps']]?: boolean;
}
: {};

type StyledComponent<
T extends React.ComponentType<any>,
U extends Options<T>
> = (
props: ComponentProps<T> | AliasedStyles<T, U> | CustomStyles<T, U>
) => JSX.Element;

type StyledFunction = {
<T extends React.ComponentType<any>>(component: T): StyledComponent<T, {}>;
<T extends React.ComponentType<any>, U extends Options<T>>(
component: T,
options: U
): StyledComponent<T, U>;
Text: StyledComponent<typeof Text, {}>;
View: StyledComponent<typeof View, {}>;
};

/**
* 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<any>,
U extends Options<T> = {}
>(component: T, options: U = {} as U) {
type BaseProps = ComponentProps<T>;
type PropsWithAliasMap = BaseProps &
// @ts-ignore
Omit<AliasMap<T>, U['alias'][keyof U['alias']]> & {
// @ts-ignore
[k in keyof U['alias']]?: AliasMap<T>[U['alias'][k]];
};
export const styled: StyledFunction = (
component: React.ComponentType<any>,
options?: Options<typeof component>
) => {
type BaseProps = ComponentProps<typeof component>;
type AliasedProps = AliasedStyles<
typeof component,
Exclude<typeof options, undefined>
>;
type CustomProps = CustomStyles<
typeof component,
Exclude<typeof options, undefined>
>;

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<string, any>,
aliasMap?: Record<string, any>
aliases?: Record<string, any>,
customProps?: Record<string, any>
) {
const styleProps: Record<string, any> = {};
let styleProps: Record<string, any> = {};
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];
}
}
});
}
Expand Down

0 comments on commit 0c1b9b9

Please sign in to comment.