Originally posted by **pedronauck** February 20, 2023
## Summary
This RFC proposes an API change to our `@fuel-ui/css` package, allowing for greater flexibility and customization in defining themes, component props, styles, and variants. Our library only allows the `theme` property inside the ThemeProvider to be set to one of two pre-defined themes (`dark` and `light`). Since we need to adapt our component library to the new Fuel's rebrand, with this new API, it will be possible to define entire new themes with custom tokens (font sizes, colors, fonts, etc) and define custom default component props, styles, and variants.
## Motivation
One of the main motivations for this API change is to adapt our component library design to our new brand. With the current API, we're limited to only two pre-defined themes, which doesn't allow for the level of customization we need to accurately reflect our new brand. By providing a more flexible and customizable API, we'll be able to define themes that more accurately reflect our brand, and make it easier for other projects that depend on the library to also adapt to our new brand without requiring significant changes.
Additionally, our current API is limiting in terms of its ability to be customized to suit different project needs. By only providing two pre-defined themes, we're essentially forcing users to choose between a "light" or "dark" theme without allowing for any further customization. This doesn't suit the needs of all projects, which may require more specific theming options.
By allowing users to define their own themes with custom tokens and default props/styles/variants, we'll be opening up our library to a wider range of use cases, and will be possible for us to make gradual modifications in our current projects without the need to change anything right now inside them.
## Proposal
The proposed API change will involve passing the entire theme returned from the `createTheme` to the `ThemeProvider` component, instead of just the actual theme names. This function takes a `className` and an object that defines the theme's tokens, components, and colors.
The actual theme definition also just accepts [tokens](https://github.com/FuelLabs/fuel-ui/blob/master/design-system/css/src/theme.ts#L19-L21) definition and the idea of this RFC are to make it possible to accept component-level properties and styles also. Something heavily inspired by [Mantine](https://mantine.dev/theming/theme-object/) and [Chakra](https://chakra-ui.com/docs/styled-system/component-style) theme features.
Here's an example of what the new API could look like:
```tsx
import { createTheme, themes } from '@fuel-ui/css'
const myThemeTokens = {
fontSizes: {
// ...
},
colors: {
// ...
},
fonts: {
// ...
},
}
const myThemeButton = {
// this will override the default button props
defaultProps: {
variant: 'ghost'
},
// this will override default button styles
styles: {
root: {
borderRadius: '$sm',
variants: {
size: {
sm: {
padding: '$1 $2',
}
}
}
}
}
}
const myNewTheme = createTheme('my-new-theme', {
// this will override the default theme tokens
tokens: myThemeTokens,
// this will set default component props and styles
components: {
Button: myThemeButton
},
})
// Inside themes object, has the default darkTheme and lightTheme, but of course, isn't necessary to pass them.
{children}
```
Besides this, it will be introduced some new hooks, functions and type helpers that will help us to pass all right definitions for the component and improve they creational pattern:
```tsx
import { createElement } from "react"
import { createStyle, css } from "@fuel-ui/css"
import { Components as Comps } from '../../types'
import {
createComponent,
useStyles,
useComponentProps,
ComponentDef,
} from "@fuel-ui/react"
import { ButtonGroup } from "./ButtonGroup"
// this is a Enum helper to get components names
const NAME = Comps.Button
// ----------------------------------------------------------------------------
// Styles-related stuff
// ----------------------------------------------------------------------------
type ButtonProps = {
size?: "sm" | "md" | "lg",
variant?: "solid" | "ghost" | "outline" | "link" | "unstyled"
}
type ButtonStyle = {
props: ButtonProps
styles: "icon" | "loader"
}
const styles = createStyle(NAME, {
base: css({
// ...
}),
icon: css({
// ...
}),
loader: css({
// ...
}),
})
// ----------------------------------------------------------------------------
// Component-related stuff
// ----------------------------------------------------------------------------
type ButtonComponents = {
Group: typeof ButtonGroup
}
type ButtonDefinition = ComponentDef<
HTMLButtonElement,
ButtonProps,
ButtonComponents
>
// createComponent() now will just have one type of argument to simplify it,
// also it will implicitly check as a prop and accept another component as
// a value and make the original component composable.
// This will imply a lot of typing changes internally.
const Button = createComponent(
({ as = "button", size = "sm", variant = "solid", css, ...props }) => {
// get all props from theme props and component and merge them together
const baseProps = useComponentProps(NAME, {
...props,
size,
variant,
})
// create all classNames for each style using component props
const { classes } = useStyles(NAME, baseProps)
// create component elements based on definition, in this case
// the component will have an `icon` and `loader` styles
const children = someFunctionThatCreateChildren(baseProps)
// use default createElement from react
return createElement(as, { className: classes.root, ...baseProps }, children)
},
)
Button.Group = ButtonGroup
```
## Tasks
- [x] Change `ThemeProvider` to accept a theme definition object
- [x] Adapt theme definition to accept `components` prop and also [global tokens](_https://stitches.dev/docs/tokens#locally-scoped-tokens_) (`$$primaryColor`, `$$secondaryColor`, etc)
- [x] Create `createStyle` function
- [x] Use `zustand` as a store to manage all component styles and props from the theme definition
- [x] Create `useStyles` hook
- [x] Create `useTheme` hook
- [x] Create `useThemeProps` hook
- [x] Create `useComponentProps` hook
- [x] Create `ComponentDef` type helper
- [x] Fork `stitches` to fix classes generation and typing
- [x] Create `createPolymorphic` component helper
- [ ] Adapt all components to use the new API
### Improvements
- [x] Modify `createComponent` type definition according to the new API, also improve it to generate polymorphic components based on Mantine’s API
- [x] Pass `css` property from component to component `style` definition (info [here](_https://stitches.dev/docs/framework-agnostic#the-css-prop_))
- [ ] Adapt to parts inside the component to be customizable by `styles` props on `theme`.
- [ ] Use `createElement` on each component instead of `Box` to clean generated classes.
## Next Steps
> It's fundamental after all these improvements that we start to create better documentation for our Design System. It's no use having the best technical system and possible standards if people have no idea how to use them.
Discussed in https://github.com/FuelLabs/fuel-ui/discussions/212