nandorojo / dripsy

🍷 Responsive, unstyled UI primitives for React Native + Web.
https://dripsy.xyz
MIT License
2.01k stars 80 forks source link

Custom theme keys with typescript #140

Closed cmaycumber closed 2 years ago

cmaycumber commented 2 years ago

Are custom theme keys currently supported with typescript?

Using the following code:

export const theme = {
  Collapse: {},
};

const defaultTheme = makeTheme(theme);

type MyTheme = typeof defaultTheme;

declare module 'dripsy' {
  // eslint-disable-next-line @typescript-eslint/no-empty-interface
  interface DripsyCustomTheme extends MyTheme {}
}

I get the error { Collapse: {} } has no property in common w/ the theme.

I thought I had it working in a separate project. Could the error be because of where I'm creating my custom components?

nandorojo commented 2 years ago

You should be writing the theme directly inside of makeTheme, rather than as a separate variable.

that said, I don’t know if this is valid. I think you have to use the keys provided by the theme

cmaycumber commented 2 years ago

Got it. Do you have recommendations on how you would handle variants for a Button component for example like primary, secondary, etc.?

nandorojo commented 2 years ago

I've been thinking about this in general: is Dripsy theming enough to be the backbone of a scalable design system?

For example, take a card:

const theme = {
  cards: {
   container: { bg: '$muted' },
   content: { p: '$3', borderRadius: '$3' }
  }
}

const Card = styled(View, { themeKey: 'cards' })({ variant: 'container' })
Card.Content = styled(View, { themeKey: 'cards' })({ variant: 'content' })

<Card>
  <Card.Content></Card.Content>
</Card>

I think that works pretty well. The issue is, Dripsy's variant system follows the theme-ui spec, which is a bit static. They want your theme represented as a JSON file. This has its benefits: what you see is what you get.

But there are, of course, some limitations.

From the outside, I think the Stitches approach to variants is better: you can have many variant options, such as size, color, etc.

A key example is a Button, like @cmaycumber mentioned.

Say your color scheme looks like this:

const theme = {
  colors: {
    primary: mauve.mauve9,
    primaryText: mauve.mauve12,
    secondary: crimson.crimson9,
    secondaryText: crimson.crimson12
  }
}

Here we have a primary and a secondary. The palette I have in mind comes from Radix Colors.

So, how do we compose a button from this color scheme, such that the label uses the ${color}Text, and the button uses the ${color}.

A few options:

  1. Currently, this can be done with the styled API.
type Color = Theme['colors']

type Props = { color: Color }

const Button = styled(View)(({ color }: Props) => ({ bg: `${color}` }))
Button.Label = styled(Text)(({ color }: Props) => ({ color: `${color}Text` }))

<Button>
  <Button.Label>Text here</Button.Label>
</Button>

And, perhaps you'd wrap your button to pass the color prop to all of them together.

However, for a theme-based style system, I'm not sure if this is the optimal solution. Perhaps it is. But I wonder, could this logic, for such a reused component that literally every app needs, live in a theme instead?

After all, every React Native button needs a container and a label.

In the spirit of a theme-centric design system, I wonder if something more like this could work. It’s similar to the Card, except that it allows props to be passed to the theme…

const theme = {
  buttons: {
    container: ({ color }: Props) => ({ color: `${color}Background` })
  },
  text: {
    buttonLabel: ({ color }: Props) => ({ color: `${color}Text` })
  }
}

type Color = Theme['colors']

type Props = { color: Color }

const Button = styled(View, { themeKey: 'buttons' })({ variant: 'container' })
Button.Label = styled(Text, { themeKey: 'text' })({ variant: 'buttonLabel' })

<Button color="primary">
  <Button.Label color="primary"></Button.Label>
</Button>

This would be more inspired by the Stitches variants option in styled.

Notice here that buttons.container takes a function.

The difference with stitches being, you define these styles in theme, so you can port them from project to project easily. Now, I'm not sure if that's the best thing to optimize around or not. But maybe it is. I'm thinking out loud a bit here.

I mentioned this idea to @axeldelafosse a bit back. I'll keep playing with it...

cmaycumber commented 2 years ago

Yeah, this all makes sense to me. I'm running into this now more than ever because I'm finding it hard to make a component library on top of dripsy that's as extensible as possible.

I'm a huge fan of the stitches approach. I definitely think if there's a way to incorporate something similar into a theme-based approach that would be ideal for my use case.

nandorojo commented 2 years ago

I'll give it some thought.

Maybe I should convert this from an issue to a discussion?