export const meta = {
  title: 'Theming specification',
}

## Goals and requirements

Components should have support for both CSS stylesheets and CSSinJS approaches. The theming mechanism as part of Fluent UI should be flexible and provide support for CSSinJS theme and all existing customization scenarios. Users can opt-out if the theming approach does not meet their needs.

##### Support for CSS stylesheets

Тhe users should be able to specify mappings from properties to classnames.

**IMPORTANT**: applications which load their own CSS in other means should not pay the penalty of loading a runtime CSS-in-JS package, especially if the class names are known at time of authoring.

##### Support for CSSinJS

CSSinJS support that will not tie the users to just one CSS in JS framework. The users should be able to define their styles and specify the engine that they want to use, which will be aligned to some defined interface. The users are responsible for making sure that their styles are compatible with the CSSinJS engine that they use. Fluent will not support any compatibility between different engines.

#### Application developers can get small bundles when only using a subset of components

If the client's application contains only a subset of the Fluent components, we should provide a mechanism for the application developers to register the theme only for the components used.

#### A component should be able to render any theme (including tokens, styles, icons and animations)

The client should be able to reuse the components between different themes. Supporting new theme in the application should not require building new components.

If new variants are added in the new theme, then the clients should be able to compose the existing component, by adding new design terms (via the props API). If the new theme doesn't need to support any new variant, nothing should be required from the client's side.

##### Variants

Variants are a way of allowing the users to specify new design terms for their components (among other customizations).

In the CSS scenario, it is basically a way of allowing user to specify mapping to className for a new prop.

In the CSSinJS scenario, it is allowing user to have new properties (design terms) inside the styles functions.

#### Themes should cascade and merge allowing subtree themes to be updated at runtime and have the ability to adjust the incoming theme.

If some parameters/tokens are changed on the root of the application, the changes should be propagated to all nested subtrees.

#### RTL and disable animation modes should be configurable for the entire application or a subtree of the application.

It is important to allow the users to specify the RTL/LTR mode for the entire application once, but if necessary they can specify it only in a subtree of the application (for example one chat message).

The same is true for the disable animations mode. It is important to allow the users to specify it for the entire application once, but if necessary they can specify it only in a subtree of the application (for example the `Loader` component should always be animated).

#### Parent components should be able to customize the styles for the child components

The parent component should be able to customize the styles for it's children/slots, usually for positioning scenarios, or contextual changes that should be applied when some design term is applied on the parent component. This is especially required for "recursive component"'s scenarios

## Technical proposal

In the rest of the specification, there will be some proposals of how the themes, mappings and utilities can be structured.

#### CSS stylesheets usage

Users should be able to define mapping from props to classNames per component level.

For inspiration we can get some ideas from [React Semantic UI's className builders](https://github.com/Semantic-Org/Semantic-UI-React/blob/master/src/lib/classNameBuilders.js)

#### CSSinJS usage

If users decide to use CSSinJS we have to propose some structure of how they can define all things required for theming their components.

Here is one proposal of how the theme object can look like:

##### Global tokens

These are tokens that can be reused in many components, things like density, font sizes, font families, colors, shadows etc. Having these global tokens, can help designers to have more consistent look and feel on the components which are part of one application/theme.

The global tokens can be defined as a plain javascript object. We can offer some utilities for generating color palettes if there is an agreement of how those should be generated, but anyway, the global tokens should be defined by the users in any format they would like to structure them, and our framework should not depend on something that is defined (or not defined) there.

##### Component tokens

These are tokens which are specific for a component. They can use some of the global tokens for their values.

**Open for a debate here**

We have the components tokens in Teams theme and those were shown as a good abstraction. On the other hand, people overused them for adding inline overrides, because these tokens can be overridden on any instance of a component. So, maybe the best option will be to restrict the ways of where these tokens can be defined (only in the theme and not on instance level), so we can avoid similar misusages in future.

##### Component styles

The component styles should be functions that return CSSinJS definition that is compatible with the CSSinJS engine used. The input parameters of these functions are:

- **design terms** - in the react implementation, these are basically subset of the component's properties. It is important to note here that for better performance we need to restrict these to be primitive values like: booleans, strings, numbers, so we can easily hash based on them.
- **design tokens** - these are basically the component's tokens. If we support inline overrides on these tokens, it will have some performance degradation

As we mention before, parents component should be able to specify styles for their slots. We can have two ways of how this can be implemented

- define slots' styles inside the styles of the parent component

```
const buttonStyles = {
  root: ({ terms, tokens }) => ({
    // some css in js definition
  }),
  icon: ({ terms, tokens }) => ({
    // some css in js definition
  }),
}
```

- create composed components for the slots if they require some additional styles

```
const buttonStyles = {
  root: ({ terms, tokens }) => ({
    // some css in js definition
  }),
}

const buttonIconStyles = {
  root: ({ terms, tokens }) => ({
    // some css in js definition
  }),
}
```

There are props and cons to both approaches, so I will just try to list them all and we can discuss them:

| Description                                                                                                       | Define slots' styles in the parent's styles | Why                                                                                                                                            | Create composed components for the slots | Why                                                                                                                     |
| ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| All styles for a component are easily discoverable                                                                | YES                                         | They are defined in one object/one component styles object                                                                                     | NO                                       | The styles are spreaded across different components for the parent and slots components                                 |
| Changing the look and feel of a component will require composing only one component                               | YES                                         | We can override the styles for the component and it's slots in one place                                                                       | NO                                       | We will have to override the styles of the parent component, and all slots components used inside                       |
| Children theme needs to know what are all the component's variants used in order to override correctly the styles | NO                                          | Because the slots styles were defined via the styles in the parent, not via new composed components                                            | YES                                      | Because we are using composed components for the slots                                                                  |
| The styles defined for a "slot" component can be reused in different places                                       | NO                                          | The slot's styles are defined only in the context of the parent component                                                                      | YES                                      | They can be reused easily, by using the composed component as a slot in different components                            |
| Simple caching based on the styles defined always on the root                                                     | NO                                          | We have to consider for the slots the styles of the base component plus the slot's styles                                                      | YES                                      | The styles are defined in one place (always the root)                                                                   |
| Simple definition of tokens per parent component                                                                  | YES                                         | The parent component can define tokens for all children                                                                                        | NO                                       | Each component can define it's own token, which can be problematic when we want to override something on parent's lavel |
| Adding new design term in the parent requires re-composing all slots components                                   | NO                                          | We can just override styles in the slot's styles inside the parents                                                                            | YES                                      | We need to re-compose if we want new design terms in the slots components                                               |
| Requires knowledge of the internals of another component, which creates coupling                                  | PARTIALLY                                   | The parent can define styles only for the root (no other slots), but it needs to know some details for the root's styles of the slot component | NO                                       | All styles are defined in the composed component                                                                        |
| Easy to swap a slot into another component which might want all its own styles.                                   | NO                                          |                                                                                                                                                | YES                                      |

##### Variants

Variants are just mechanism for allowing users do add or remove some design terms from their components.

In order to enable this, the compose mechanism should provide to the users adding/removing some of the design terms. This can be easily done, by allowing the user to define mapping from props to design terms when they define the composed components.

```
export interface FluentButton extends ButtonProps {
  primary?: boolean
  circular?: boolean
}

// these code snippet is just illustrating a possible
// mechanism of how the design tokens can be created
// based on the props
const FluentButton = compose(Button, {
  displayName: 'FluentButton',
  mapPropsToTerms: props => ({
    primary: props.primary,
    circular: props.circular,
    hasIcon: !!props.icon,
  }),
})
```

##### Animations

Animation should be defined as part of the theme, but they should not be all loaded if they are not used. Child themes should be able to customize them.

```
// TBD
```

##### Icons

Icons should be defined as part of the theme, but they should not loaded if not used. Child themes should be able to customize them.

```
// TBD
```

##### Files and packaging

```
// themes/fluent/globalTokens.ts

const globalTokens = {
  colors: {
    brandForeground: 'white',
    brandBackground: 'red',

    defaultBackground: 'white',
    defaultForeground: 'black',
  }
}

export default globalTokens

// themes/fluent/buttonTokens.ts

const buttonTokens = globalTokens => ({
  defaultBackgroundColor: globalTokens.defaultBackground,
  defaultForegroundColor: globalTokens.defaultForeground,

  primaryBackgroundColor: globalTokens.brandBackground,
  primaryForegroundColor: globalTokens.brandForeground,
})

export default buttonTokens

// themes/fluent/buttonStyles.ts

// avoiding types for simplicity

const buttonStyles = {
  root: ({terms, tokens}) => ({
    color: tokens.defaultForegroundColor,
    backgroundColor: tokens.defaultBackgroundColor,

    ...(terms.primary && {
      color: tokens.brandForegroundColor,
      backgroundColor: tokens.brandBackgroundColor,
    }),
  }),
  icon: ({terms, tokens}) => ({
    color: tokens.defaultForegroundColor,
    backgroundColor: tokens.defaultBackgroundColor,

    ...(terms.primary && {
      color: tokens.brandForegroundColor,
      backgroundColor: tokens.brandBackgroundColor,
    }),
  }),
}

// react/components/Button.tsx

const Button = props => {
  // Not a real implementation
  // avoiding details as it is not the main point in this spec
  return <button>{props.children}</button>
}

export default Button

// fluent/components/Button.tsx

import { Button as BaseButton } from 'react/components'

const Button = compose(BaseButton, {
  displayName: 'Button',
  mapPropsToTerms: props => ({
    primary: props.primary,
  })
})

export default Button
```

Possible folder structure

```
packages
├── themes
│   ├── fluent
│   │   ├── index.ts
│   │   ├── globalTokens.ts
│   │   ├── components
│   │   └── Button
│   │       ├── buttonStyles.ts
│   │       └── buttonTokens.ts
│   ├── teams
│   │   ├── index.ts
│   │   ├── globalTokens.ts
│   │   ├── components
│   │   └── Button
│   │       ├── buttonStyles.ts
│   │       └── buttonTokens.ts
│   └── outlook
│       ├── index.ts
│       └── globalTokens.ts
│
├── react
│   └── components
│       └── Button.tsx
│
└── fluent
    └── components
        └── Button.tsx
```
