Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add multi-theme support to Polaris #10290

Merged
merged 12 commits into from
Sep 7, 2023
Merged

Add multi-theme support to Polaris #10290

merged 12 commits into from
Sep 7, 2023

Conversation

aaronccasanova
Copy link
Member

@aaronccasanova aaronccasanova commented Aug 31, 2023

This PR consolidates the integration of three feature branches that collectively introduce a new base/variant theme architecture to Shopify Polaris. The merged PRs introduce multi-theme support, updated build artifacts, and a new useTheme hook for runtime Polaris tokens access. Below, you will find a brief overview of each PR, with links to the original PRs for a more detailed review.

Add multi-theme support to Polaris tokens

This PR introduces multi-theme support to @shopify/polaris-tokens by implementing a base/variant architecture. The base theme serves as the foundation for multiple variant themes, which extend the base theme and are the only themes exposed to consumers. Simplifying system changes by flattening the relationship between themes is the primary advantage of this pattern. The PR also includes implementation details like directory structure, stylesheet generation, and variant theme functionality.

Example styles.scss updates

:root {
  /* Default variant theme custom properties (light) */
}

html.Polaris-Summer-Editions-2023 {
  /* Variant theme custom properties */
  /*
     Note: The above selector is special
     cased for backward compatibility. See
     below for updated theme selector structure.
  */
}

html.p-theme-light-high-contrast {
  /* Variant theme custom properties */
  /*
     Note: Variant theme selectors
     contain a subset of custom properties
     overriding the base theme
  */
}

html.p-theme-dark {/* ... */}
html.p-theme-dark-high-contrast {/* ... */}
html.p-theme-dim {/* ... */}

Add multi-theme build artifacts to Polaris tokens

This PR is a rework of #10153, redefining how the themes are accessed from @shopify/polaris-tokens. It introduces changes on how asset builder functions are written and replaces reference to themes.light with a themeDefault alias.

import {themes} from '@shopify/polaris-tokens';

themes['Polaris-Summer-Editions-2023'].color['color-bg']

Note: Future releases can reduce the bundle size impact of including all themes on one object and enable more flexibility (for example with a createTheme util that deep merges variant theme partials). However, that is not needed at this time and can be introduced in a minor release.

Add useTheme hook for runtime Polaris tokens access

This PR is a rework of #10229, brings updates to the AppProvider by incorporating a new ThemeContext.Provider and an associated useTheme hook.

import {useTheme} from '@shopify/polaris';

function App() {
  const theme = useTheme()

  theme.color['color-bg'] // '#123'
}

This PR introduces multi-theme support to `@shopify/polaris-tokens` by
implementing a base/variant architecture. The base theme serves as the
foundation for any number of variant themes. Variant themes have two
primary constraints in that they can only extend the base theme and are
the only themes exposed to consumers.

One advantage of this pattern is that it simplifies system changes by
flattening the relationship between themes. There is no need to consider
deeply inherited themes, making updates more predictable.

<details>
  <summary>Deeply inherited vs base/variant themes</summary>

**❌ With deeply inherited themes**
```mermaid
flowchart TD
    A[Base Theme]
    A --> B[Light Theme]
    B --> C[Light High Contrast Theme]
    A --> D[Dark Theme]
    D --> E[Dark High Contrast Theme]
    A --> F[Dim Theme]
    A --> G[etc...]
```

**✅ With base/variant themes**
```mermaid
flowchart TD
    A[Base Theme]
    A --> B[Light Theme]
    A --> C[Light High Contrast Theme]
    A --> D[Dark Theme]
    A --> E[Dark High Contrast Theme]
    A --> F[Dim Theme]
    A --> G[etc...]
```
 
</details>


## Implementation Details

This PR focuses on implementing backward compatible multi-theme support
for Polaris tokens. It enables the addition of new variant themes while
promoting incremental adoption and ensuring compatibility with existing
implementations.

### Directory Structure

The `src/themes` directory consists of the following components:

- Internal base theme and public variant themes.
- Default variant theme, which is essentially an alias of the base
theme, aliased to the light theme.
- Light uplift theme, which is the first variant theme that simply
ported `valueExperimental` tokens from our token groups.
`valueExperimental` properties were initially introduced as a short-term
solution during the SE23 upgrades, allowing token overrides by replacing
`value` with `valueExperimental` at build time.
- Note: `valueExperimental` references in token groups were kept in this
PR to prevent breaking changes and maintain backward compatibility.
Although these properties should be allowed to remove, a couple usages
were found across the organization.
- TypeScript definitions describing the shape of themes, partial themes,
theme names, etc.
- Utilities for validating themes and consistently creating theme
classes/selectors.

### Stylesheet Generation

The `toStyleSheet` build step has been updated to generate a
backward-compatible
[styles.scss](https://unpkg.com/browse/@shopify/polaris-tokens@7.5.2/dist/css/styles.css)
file. This file assigns the default variant theme custom properties
(`light`) to the `:root` selector and the `light-uplift` variant theme
custom properties to the `html.Polaris-Summer-Editions-2023` selector.

It should be noted that while the stylesheet remains backward compatible
in this PR, the plan is to propose removing the `:root` selector in the
next major release. This change aims to flatten the specificity of all
variant themes since the `:where()` selector cannot currently be
leveraged. For example:

- Current specificity breakdown: `:root` has a specificity of 10,
`html.Polaris-Summer-Editions-2023` has a specificity of 11.
- Next specificity breakdown: `html.p-theme-light` has a specificity of
11, `html.Polaris-Summer-Editions-2023` has a specificity of 11, etc.

### Variant Theme Functionality

Variant themes override a subset of the base theme tokens by assigning
them to a `partials` object. This `partials` object is then deep merged
with the base theme for JavaScript access. Additionally, the `partials`
object is used in the `toStyleSheet` build step to generate custom
properties and insert them into the variant theme selector.
Consequently, variant theme selectors only contain the subset of
overridden custom properties, ensuring a minimal stylesheet.

It should be noted that if the strategy shifts from global themes to
allowing nested themes, the approach would likely change to inserting
all custom properties in each class. However, even in such cases, the
performance impact of having a large collection of custom properties is
expected to be minimal.

**Example `styles.scss` updates**

```scss
:root {
  /* Default variant theme custom properties (light) */
}

html.Polaris-Summer-Editions-2023 {
  /* Variant theme custom properties */
  /*
     Note: The above selector is special
     cased for backward compatibility. See
     below for updated theme selector structure.
  */
}

html.p-theme-light-high-contrast {
  /* Variant theme custom properties */
  /*
     Note: Variant theme selectors
     contain a subset of custom properties
     overriding the base theme
  */
}

html.p-theme-dark {/* ... */}
html.p-theme-dark-high-contrast {/* ... */}
html.p-theme-dim {/* ... */}
```
aaronccasanova and others added 5 commits August 31, 2023 14:51
Rework of PR #10153:

- The dedicated `@shopify/polaris-tokens/themes` entry point has been
removed in favor of accessing the `themes` directly from
`@shopify/polaris-tokens`, avoiding `package.json` `exports` and
`typesVersions` changes.

- Instead of using arguments for each asset builder function, the
dependent modules are now accessed directly, reducing unnecessary
TypeScript boilerplate.

- References to `themes.light` have been replaced with a `themeDefault`
alias throughout the library and build scripts. This change should
improve clarity and consistency in communicating the behavior.

- The build function `toTokenValues` has been renamed to `toValues` as
it now operates on `themes` rather than `tokens`.

- The root entry point has been updated to expose both `themes` and
`themePartials`. The `themes` object contains complete theme objects for
each theme, serving as a replacement for the previously proposed
`/themes` entry point. Currently, it is designed for building low-level
integrations, such as React Native themes. On the other hand, the
`themePartials` object contains partial override objects for each theme.
Originally used in the `toStyleSheet` build script to generate the
smallest possible CSS rule of themed custom properties, it is now being
exposed to enable the creation of client-side integrations with minimal
impact on bundle size. Note that a separate PR will introduce a
`createThemeVariant` hook that accepts partials and dynamically
constructs the complete theme object at runtime.

**Before:** 
```ts
import {metadata} from '@shopify/polaris-tokens';

metadata.color['color-bg'].valueExperimental
```
```ts
import * as themes from '@shopify/polaris-tokens/themes';

themes.polarisSummerEditions2023.color['color-bg']
```

**After:**
```ts
import {themes} from '@shopify/polaris-tokens';

themes['Polaris-Summer-Editions-2023'].color['color-bg']
```

---------

Co-authored-by: Laura Griffee <laura@mailzone.com>
Rework of #10229.

- To enhance clarity and avoid ambiguity, we have reintroduced the
convention of prefixing all `theme` related objects and types with
`meta`. While I was trying to steer us away from this convention,
keeping it establishes consistency and makes it easier to differentiate
between `theme` constructs with and without metadata.

- Introduced a new `createThemeVariant` utility, which accepts a theme
partial and constructs a complete `theme` object. This utility
simplifies the process of creating theme variants and ensures that all
required properties are included.

- The AppProvider has been updated to incorporate a new
`ThemeContext.Provider` and associated `useTheme` hook. This provider
utilizes the `createThemeVariant` utility to expose the active theme to
child components. This eliminates the need for several previously
proposed exports and hooks, including `themeVars`, `useThemeName`,
`useThemeVarDecl`, and `useCSSDecl`.

**Before:**

```ts
import {themeVars} from '@shopify/polaris';

themeVars.color['color-bg'] // 'var(--p-color-bg)'
```
```ts
import {useThemeVarDecl} from '@shopify/polaris';

function App() {
  const colorBgDecl = useThemeVarDecl('--p-color-bg');

  colorBgDecl // { status: 'success', value: '#123', ... }
}
```

**After:**
```ts
import {useTheme} from '@shopify/polaris';

function App() {
  const theme = useTheme()

  theme.color['color-bg'] // '#123'
}
```

---------

Co-authored-by: Laura Griffee <laura@mailzone.com>
@aaronccasanova
Copy link
Member Author

/snapit

@github-actions
Copy link
Contributor

🫰✨ Thanks @aaronccasanova! Your snapshots have been published to npm.

Test the snapshots by updating your package.json with the newly published versions:

yarn add @shopify/polaris-migrator@0.0.0-snapshot-release-20230831232228
yarn add @shopify/polaris@0.0.0-snapshot-release-20230831232228
yarn add @shopify/polaris-tokens@0.0.0-snapshot-release-20230831232228
yarn add @shopify/stylelint-polaris@0.0.0-snapshot-release-20230831232228

@aaronccasanova aaronccasanova marked this pull request as ready for review September 5, 2023 17:51
]
],
"dependencies": {
"deepmerge": "^4.3.1"
Copy link
Member Author

Choose a reason for hiding this comment

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

This is needed for backward compatibility with the existing metadata export and can be moved to a devDependency post v12.

Comment on lines +23 to +24
Object.entries(themeDefault).map(createExport),
createExport(['tokens', themeDefault]),
Copy link
Member Author

@aaronccasanova aaronccasanova Sep 5, 2023

Choose a reason for hiding this comment

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

Note: These exports will be removed in the v12 release as they will no longer be relevant in the new theming architecture.

@aaronccasanova
Copy link
Member Author

/snapit

@github-actions
Copy link
Contributor

github-actions bot commented Sep 5, 2023

🫰✨ Thanks @aaronccasanova! Your snapshots have been published to npm.

Test the snapshots by updating your package.json with the newly published versions:

yarn add @shopify/polaris-migrator@0.0.0-snapshot-release-20230905190500
yarn add @shopify/polaris@0.0.0-snapshot-release-20230905190500
yarn add @shopify/polaris-tokens@0.0.0-snapshot-release-20230905190500
yarn add @shopify/stylelint-polaris@0.0.0-snapshot-release-20230905190500

@aaronccasanova aaronccasanova merged commit 5939b49 into main Sep 7, 2023
17 checks passed
@aaronccasanova aaronccasanova deleted the multi-theme-support branch September 7, 2023 19:38
aaronccasanova pushed a commit that referenced this pull request Sep 7, 2023
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @shopify/polaris@11.16.0

### Minor Changes

- [#10290](#10290)
[`5939b49cf`](5939b49)
Thanks [@aaronccasanova](https://github.com/aaronccasanova)! - - Added
multi-theme support
    -   Added multi-theme build artifacts
    -   Added multi-theme runtime access

### Patch Changes

- Updated dependencies
\[[`5939b49cf`](5939b49)]:
    -   @shopify/polaris-tokens@7.6.0

## @shopify/polaris-tokens@7.6.0

### Minor Changes

- [#10290](#10290)
[`5939b49cf`](5939b49)
Thanks [@aaronccasanova](https://github.com/aaronccasanova)! - - Added
multi-theme support
    -   Added multi-theme build artifacts
    -   Added multi-theme runtime access

## @shopify/polaris-migrator@0.22.1

### Patch Changes

- Updated dependencies
\[[`5939b49cf`](5939b49)]:
    -   @shopify/polaris-tokens@7.6.0
    -   @shopify/stylelint-polaris@14.0.1

## @shopify/stylelint-polaris@14.0.1

### Patch Changes

- Updated dependencies
\[[`5939b49cf`](5939b49)]:
    -   @shopify/polaris-tokens@7.6.0

## polaris.shopify.com@0.57.4

### Patch Changes

- Updated dependencies
\[[`5939b49cf`](5939b49)]:
    -   @shopify/polaris@11.16.0
    -   @shopify/polaris-tokens@7.6.0

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
aaronccasanova added a commit that referenced this pull request Sep 20, 2023
Continuation of #10290

This PR contains breaking changes in both Polaris and Polaris tokens to
enable global multi-theme support.

### Polaris changes:
- Updated the `AppProvider` component
- **Breaking:** Removed `features` class name toggle from
`setRootAttributes`
- Added a new `theme` prop which accepts a Polaris tokens `ThemeName`.
This prop is used for:
    - Toggling the theme class names on the root element, and
    - Setting the theme object on the root `ThemeContext.Provider`
- Added global theme toggle to Storybook

### Polaris tokens changes:
- **Breaking:** Moved the `light-uplift` overrides to base token groups
(e.g. `light-uplift` is now the default theme)
- Normalized specificity of the default theme

> [!NOTE]
> Currently, our browser support requirements don't allow us to use the
`:where()` selector to flatten our theme class name specificity to `0`.
Therefore, I'm proposing a solution that normalizes the specificity to
`11`.

**Theme selector breakdown:**
- `:root, html.p-<default-variant-theme>{...}` - The generated
stylesheet outputs the default theme custom properties to both the
`:root` selector and a dedicated `html.p-*` selector. This allows the
page to apply the default theme via `:root` initially (specificity `10`)
and then `html.p-*` when the `AppProvider` initializes (specificity
`11`)
- `html.p-<variant-theme-1>{...}html.p-<variant-theme-2>
{...}html.p-...` - All other variant themes have specificity `11`
- Thus, theme classes in the Admin should always have specificity `11`
after rendering the `AppProvider`

**Example** theme selector output:

![Screenshot 2023-09-12 at 3 12 07
PM](https://github.com/Shopify/polaris/assets/32409546/88166226-512c-4cd5-a1d0-b77305929d5b)

- Initialized a new `light-high-contrast-experimental` theme


https://github.com/Shopify/polaris/assets/32409546/ad228532-e277-4107-af5c-95511f3e3297

---------

Co-authored-by: Lo Kim <lo.kim@shopify.com>
AnnaCheba pushed a commit to AnnaCheba/polaris that referenced this pull request Apr 22, 2024
This PR consolidates the integration of three feature branches that
collectively introduce a new base/variant theme architecture to Shopify
Polaris. The merged PRs introduce multi-theme support, updated build
artifacts, and a new `useTheme` hook for runtime Polaris tokens access.
Below, you will find a brief overview of each PR, with links to the
original PRs for a more detailed review.

## [Add multi-theme support to Polaris
tokens](Shopify#10103)

This PR introduces multi-theme support to `@shopify/polaris-tokens` by
implementing a base/variant architecture. The base theme serves as the
foundation for multiple variant themes, which extend the base theme and
are the only themes exposed to consumers. Simplifying system changes by
flattening the relationship between themes is the primary advantage of
this pattern. The PR also includes implementation details like directory
structure, stylesheet generation, and variant theme functionality.

**Example `styles.scss` updates**

```scss
:root {
  /* Default variant theme custom properties (light) */
}

html.Polaris-Summer-Editions-2023 {
  /* Variant theme custom properties */
  /*
     Note: The above selector is special
     cased for backward compatibility. See
     below for updated theme selector structure.
  */
}

html.p-theme-light-high-contrast {
  /* Variant theme custom properties */
  /*
     Note: Variant theme selectors
     contain a subset of custom properties
     overriding the base theme
  */
}

html.p-theme-dark {/* ... */}
html.p-theme-dark-high-contrast {/* ... */}
html.p-theme-dim {/* ... */}
```

## [Add multi-theme build artifacts to Polaris
tokens](Shopify#10250)

This PR is a rework of Shopify#10153, redefining how the `themes` are accessed
from `@shopify/polaris-tokens`. It introduces changes on how asset
builder functions are written and replaces reference to `themes.light`
with a `themeDefault` alias.

```ts
import {themes} from '@shopify/polaris-tokens';

themes['Polaris-Summer-Editions-2023'].color['color-bg']
```

> Note: Future releases can reduce the bundle size impact of including
all themes on one object and enable more flexibility (for example with a
`createTheme` util that deep merges variant theme partials). However,
that is not needed at this time and can be introduced in a minor
release.

## [Add `useTheme` hook for runtime Polaris tokens
access](Shopify#10252)

This PR is a rework of Shopify#10229, brings updates to the `AppProvider` by
incorporating a new `ThemeContext.Provider` and an associated `useTheme`
hook.

```tsx
import {useTheme} from '@shopify/polaris';

function App() {
  const theme = useTheme()

  theme.color['color-bg'] // 'Shopify#123'
}
```

---------

Co-authored-by: Laura Griffee <laura@mailzone.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants