Shopify / restyle

A type-enforced system for building UI components in React Native with TypeScript.
https://shopify.github.io/restyle/
MIT License
2.96k stars 133 forks source link

Using styles within custom components #55

Open AAfify1 opened 4 years ago

AAfify1 commented 4 years ago

Below is my custom button component.

const Button = ({onPress, label, ...rest}: Props) => {
  const props = useRestyle([buttonVariants as any], rest);

  return (
    <TouchableOpacity onPress={onPress}>
      <Box alignItems="center" {...props}>
        <Text variant="body" style={{color: props.style[0]?.color }}>
          {label}
        </Text>
      </Box>
    </TouchableOpacity>
  );
};

I am trying to change the Text color based on the chosen variant, how can I access the restyle 'color' property directly from the props rather than using the style attribute. So for example I want to use <Text variant="body" color= {props.color}> which maps to the color specified in the variant e.g primrayDark. Thanks!

JoelBesada commented 4 years ago

There's unfortunately no good way to do this right now, but I'll keep this shortcoming in mind and see if we can solve this in a nice way in future versions.

AAfify1 commented 4 years ago

Okay thank you!

Charly6596 commented 4 years ago

I also had issues figuring out a way to make my own button. What I had to do for my button label to match the button variant style (for example, I want the primary button to be blue and the text white) is to have the same variant name in both components. You could define your button properties as following:

type Props = VariantProps<Theme, "buttonVariants"> & {
    label: string;
    onPress?: () => void;
  };

So, for example, you can create a ButtonContainer component,

const ButtonContainer = createRestyleComponent<
  VariantProps<Theme, "buttonVariants"> & React.ComponentProps<typeof Box>,
  Theme
>([createVariant({ themeKey: "buttonVariants" })], Box);

and use it in your Button component <ButtonContainer variant={variant}> <Text variant={variant}>{label}</Text></ButtonContainer>

Instead of just passing the VariantProps you could also pass your ButtonContainer props, to be more flexible So at the end you could have:

type Props = React.ComponentProps<typeof ButtonContainer> & {
    label: string;
    onPress?: () => void;
  };

const Button = ({ label, variant, onPress, ...rest }: Props) => (
  <ButtonContainer variant={variant} {...rest}>
    <Touchableopacity onPress={onPress}>
      <Text variant={variant}>
        {label}
      </Text>
    </Touchableopacity>
  </ButtonContainer>
)

The only downside i see is having to manually maintain the same variant names in both a Button container (just a box with variants) and the Text component, but it works... There might be some coding mistakes since, but that's the idea Hope this helps someone looking for a way to define a button component

minawalphonce commented 3 years ago

@Charly6596 can you please add your theme, I have the same problem, having hard time understanding how did you define your variants

Charly6596 commented 3 years ago

@minawalphonce sure thing, here's a simplified version of my theme

const theme = createTheme({
  colors: {
    buttonPrimary: palette.blue,
    buttonPrimaryForeground: palette.white,
    buttonSecondary: palette.white,
    buttonSecondaryForeground: palette.darkBlue,
  },
  spacing: {}, // omitted
  breakpoints: {}, // omitted
  textVariants: {
    buttonPrimary: {
      color: "buttonPrimaryForeground",
      fontSize: 16,
    },
    buttonSecondary: {
      color: "buttonSecondaryForeground",
      fontSize: 16,
    },
  },
  buttonVariants: {
    buttonPrimary: {
      backgroundColor: "buttonPrimary",
      padding: "m",
    },
    buttonSecondary: {
      backgroundColor: "buttonSecondary",
      padding: "m",
    },
  },
});
clementoriol commented 2 years ago

The ability to define styles for subcomponents when using variants would be a great addition to the library.

Not being able to do so feels limitating when trying to build component like : <Message variant="error">Something went wrong</Message>, that renders a Box and a Text under the hood in the appropriate styles.

These kind of components, where you don't have to thing too much about the moving parts, are a good practice (IMO), resulting in a low overhead for developers when building screens, and overall more consistent styles.

@Charly6596 workaround works but it breaks the colocation of styles, which sounds like hard to maintain in the long run with hundreds of components.

vonkanehoffen commented 2 years ago

I had this problem too and solved it by accessing the variant prop from useTheme like so:

import {
  SpacingProps,
  createRestyleComponent,
  VariantProps,
  createVariant,
  useTheme,
  spacing,
} from "@shopify/restyle";
import React from "react";
import { TouchableOpacity } from "react-native";

import { Text } from "./Text";
import { Theme } from "./theme";

export const BaseButton = createRestyleComponent<
  VariantProps<Theme, "buttonVariants"> &
    SpacingProps<Theme> &
    React.ComponentProps<typeof TouchableOpacity>,
  Theme
>([createVariant({ themeKey: "buttonVariants" }), spacing], TouchableOpacity);

type ButtonProps = React.ComponentProps<typeof BaseButton> & {
  label: string;
};

export const Button = ({ label, ...rest }: ButtonProps) => {
  const theme = useTheme<Theme>();
  return (
    <BaseButton {...rest}>
      <Text
        variant="buttonLabel"
        color={theme.buttonVariants[rest.variant].color}
      >
        {label}
      </Text>
    </BaseButton>
  );
};

Theme:

export const theme = createTheme({
  ...
  buttonVariants: {
    primary: {
      backgroundColor: "primary",
      borderColor: "primary",
      color: "white",
    },
    outlined: {
      borderColor: "primary",
      color: "secondary",
    },
  },
  ...

That buttonVariants[rest.variant] still throws a TS error about index types that I'm not sure how to fix though.

flexbox commented 2 years ago

That buttonVariants[rest.variant] still throws a TS error about index types that I'm not sure how to fix though.

@vonkanehoffen Have you tried to force with type with as like

// not sure if it will work but something like that 😅
color={theme.buttonVariants[rest.variant].color as ColorProps<Theme>} 
mlecoq commented 2 years ago

That buttonVariants[rest.variant] still throws a TS error about index types that I'm not sure how to fix though.

@vonkanehoffen Have you tried to force with type with as like

// not sure if it will work but something like that 😅
color={theme.buttonVariants[rest.variant].color as ColorProps<Theme>} 

Even if we add as there is a typescript issue with color

Type 'ColorProps<{ breakpoints: { phone: number; tablet: number; desktop: number; }; spacing: { none: number; '3xs': number; xxs: number; xs: number; s: number; m: number; l: number; xl: number; xxl: number; '3xl': number; '4xl': number; '6xl': number; '8xl': number; }; ... 5 more ...; buttonVariants: { ...; }; }>' is not assignable to type 'ResponsiveValue<"dark" | "primaryLow" | "primary" | "primaryHigh" | "primaryTransparent" | "blue" | "green" | "orange" | "orangeLow" | "red" | "red2" | "red3" | "purple" | "redLow" | "yellow" | ... 26 more ... | "shadowEnd", { ...; }> | undefined'.

I have tried with another property (fontSize) and it works fine with it. It seems to be specific to color

mlecoq commented 2 years ago

Here is how I fix the TS issue

  <Text
          variant="itemTitle"
          fontSize={theme.buttonVariants[variant as keyof Theme['buttonVariants']].fontSize}
          color={theme.buttonVariants[variant as keyof Theme['buttonVariants']].color as keyof Theme['colors']}>
          {children}
        </Text>
nemethricsi commented 1 year ago

Here is how I fix the TS issue

  <Text
          variant="itemTitle"
          fontSize={theme.buttonVariants[variant as keyof Theme['buttonVariants']].fontSize}
          color={theme.buttonVariants[variant as keyof Theme['buttonVariants']].color as keyof Theme['colors']}>
          {children}
        </Text>

I had some trouble with the fontWeight prop and solved like this:

fontWeight={
  theme.buttonVariants[variant as keyof Theme['buttonVariants']]
    .fontWeight as keyof TypographyProps<Theme>['fontWeight']
}
lucas-garrido commented 1 year ago

i did something like this :

image

what do you think ?