Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions rfcs/convergence/make-styles-no-functions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# RFC: No functions in `makeStyles()`

---

@layershifter @khmakoto

## Summary/problem

Currently `makeStyles()` allows to define style rules in two different ways:

```ts
makeStyles({
// 👇 as an object
rootA: { color: 'red' },
// 👇 as a function
rootB: theme => ({ color: theme.tokenB }),
});
```

- `rootB` uses a function where `theme` is typed and coupled to tokens from `@fluentui/react-theme`. This tokens shape is not extensible for customers.

# Proposed solution

- Remove functions from `makeStyles()` calls
- Export the `tokens` object with CSS variables

## Export tokens separately

Initially we planed to support IE11 via runtime tricks, but with the deprecation of IE11, we are now able to leverage CSS Variables for tokens and theming purposes. We communicate this fact to customers so that they understand clearly what they are using.

The proposal is to export `tokens` as a plain object:

```ts
import type { Theme } from '@fluentui/react-theme';

const tokens: Theme = {
borderRadiusNone: 'var(--borderRadiusNone)',
borderRadiusSmall: 'var(--borderRadiusSmall)',
/* ... */
};
```

This also removes the need of using the `useTheme()` hook for customers as tokens can be accessed directly, for example:

```tsx
import { tokens } from '@fluentui/react-theme';

function CustomComponent() {
return <div style={{ color: tokens.borderRadiusNone /* is "var(--borderRadiusNone)" */ }} />;
}
```

That simplifies also integration with other CSS-in-JS from Fluent UI v8 or Fluent UI Northstar.

## Remove functions in `makeStyles()`

Once `tokens` are available there is no more need for functional style rules in `makeStyles()`:

```diff
import { makeStyles } from '@fluentui/react-make-styles';
+import { tokens } from '@fluentui/react-theme';

makeStyles({
- root: theme => ({ color: theme.tokenB }),
+ root: { color: tokens.tokenB },
});
```

## Simplify types in `FluentProvider`

Currently `FluentProvider` only supports the `Theme` type from `@fluentui/react-theme`.

If we simplify this to `Record<string, string | number>`, we enable consumers to extend the default theme:

```tsx
import { FluentProvider } from '@fluentui/react-provider';
import { mergeThemes, teamsLightTheme, Theme } from '@fluentui/react-theme';

type CustomTokens = {
tokenA: string;
};
type CustomTheme = CustomTokens & Theme;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought (out of topic): it would be nice if we could introduce more self explanatory API for consumers to create custom theme like following:

import type {CreateCustomThemeShape} from '@fui/react-theme'

type CustomTokens = {...}
type CustomTheme = CreateCustomThemeShape<CustomTokens >

or even more explicit, with less API surface needed

import { createCustomTheme } from '@fui/react-theme'

const customTokens = {...}
const customTheme = createCustomTheme(teamsLightTheme, customTokens)
// easily accessible shape inferred from source of truth - actual implementation
type CustomTheme = typeof customTheme

WDYT?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the second option and we could even make some changes to mergeThemes to allow for this type of functionality:

export function mergeThemes<TTheme extends Theme = Theme, TPartialTheme extends PartialTheme = PartialTheme>(a: TTheme | undefined, b: TPartialTheme | undefined): TTheme & TPartialTheme {
   ...
}

This would leverage our existing mergeThemes functionality so we don't have to create a separate createCustomTheme function.

WDYT of this approach? If you like it, I can go ahead and update the RFC with this info.

Copy link
Copy Markdown
Member

@khmakoto khmakoto Jan 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm spinning this up into a separate issue so we can continue the conversation without blocking this PR.

#21181


const extendedTheme: CustomTheme = mergeThemes(teamsLightTheme, { tokenA: 'red' });

function App() {
return <FluentProvider theme={extendedTheme} />;
}
```

We could alternatively make the theme property in `FluentProvider` extend from `PartialTheme` if we want to ensure that the theme that is passed in always has the keys for the default tokens we provide.

The type of `FluentProviderProps` would then be:

```ts
export interface FluentProviderProps<TTheme extends PartialTheme = PartialTheme>
extends Omit<ComponentProps<FluentProviderSlots>, 'dir'>,
Partial<FluentProviderCommons> {
theme?: TTheme;
}
```

And we could then use it as follows:

```tsx
import { FluentProvider } from '@fluentui/react-provider';
import { mergeThemes, teamsLightTheme, Theme } from '@fluentui/react-theme';

type CustomTokens = {
tokenA: string;
};
type CustomTheme = CustomTokens & Theme;

const extendedTheme: CustomTheme = mergeThemes(teamsLightTheme, { tokenA: 'red' });

function App() {
return <FluentProvider theme={extendedTheme} />;
}
```

In both scenarios above, `FluentProvider` will still inject all customer tokens properly including scenarios with React Portals.

Also, in case there is an ask for it, we can decide to export a primitive component in the future (named for example `TokensProvider`) whose only purpose would be to render variables for `Record<string, string | number>`.

## Using custom tokens in `makeStyles()`

Once tokens are injected through the `FluentProvider` they can be used in `makeStyles()`.

```tsx
import { tokens } from '@fluentui/react-theme';
import type { CustomTokens } from './custom-theme';

const customTokens: CustomTokens = {
tokenA: 'var(--tokenA)',
};

makeStyles({
root: {
backgroundColor: customTokens.tokenA,
color: tokens.tokenB,
},
});
```