FuelLabs / fuel-ui

Fuel design system
https://design.fuel.network
Apache License 2.0
85 stars 29 forks source link

RFC: Flexible and customizable API #224

Closed pedronauck closed 1 year ago

pedronauck commented 1 year ago

Discussed in https://github.com/FuelLabs/fuel-ui/discussions/212

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.