Skip to content

Commit

Permalink
Theme bridge to v5.0.0 (#2373)
Browse files Browse the repository at this point in the history
Co-authored-by: Mayank <[email protected]>
  • Loading branch information
r100-stack and mayank99 authored Feb 11, 2025
1 parent 8aba6f2 commit 6fda72a
Show file tree
Hide file tree
Showing 19 changed files with 621 additions and 24 deletions.
9 changes: 9 additions & 0 deletions .changeset/few-llamas-smell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@itwin/itwinui-react': minor
---

Added a **theme bridge** which adjusts iTwinUI v3 visuals to blend in with iTwinUI v5's new look-and-feel.

To enable the theme bridge, use the `future.themeBridge` flag on `<ThemeProvider>`. See [full documentation](https://github.com/iTwin/iTwinUI/wiki/iTwinUI-v5-theme-bridge) for more detailed instructions.

**Note**: The theme bridge is still considered an _alpha_ feature, so there may be breaking changes until it's stabilized.
5 changes: 5 additions & 0 deletions .changeset/funny-singers-think.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@itwin/itwinui-react': minor
---

Fixed an issue in `InputWithDecorations` and `SearchBox` where the component-level focus styling was colliding with global focus styling, leading to double focus outlines.
5 changes: 5 additions & 0 deletions .changeset/rotten-turkeys-wave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@itwin/itwinui-css': minor
---

Fixed an issue in `iui-input-flex-container` where the component-level focus styling was colliding with global focus styling, leading to double focus outlines.
3 changes: 2 additions & 1 deletion apps/css-workshop/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { defineConfig } from 'astro/config';
import relativeLinks from 'astro-relative-links';
import react from '@astrojs/react';

// https://astro.build/config
export default defineConfig({
Expand All @@ -13,5 +14,5 @@ export default defineConfig({
server: {
port: 3050,
},
integrations: [relativeLinks()],
integrations: [relativeLinks(), react()],
});
6 changes: 6 additions & 0 deletions apps/css-workshop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@
"@fontsource/noto-sans": "5",
"@fontsource/noto-sans-mono": "5",
"@itwin/itwinui-icons-elements": "0.21.0",
"@itwin/itwinui-react": "npm:@itwin/[email protected]",
"astro": "^5",
"astro-relative-links": "^0.4.2",
"@astrojs/react": "^4.1.2",
"@types/react": "^19",
"@types/react-dom": "^19",
"react": "^19",
"react-dom": "^19",
"backstopjs": "~6.3.25",
"npm-run-all": "^4.1.5"
},
Expand Down
21 changes: 21 additions & 0 deletions apps/css-workshop/public/assets/demo.css
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,24 @@ section[id^='demo'] {
width: -moz-fit-content; /* stylelint-disable */
width: fit-content;
}

ul {
padding-inline-start: revert;
margin-block-start: revert;
margin-block-end: revert;
}

:where(input, button, textarea, select) {
font: revert;
color: revert;
}

:where(img, picture, svg, video) {
display: revert;
max-inline-size: revert;
block-size: revert;
}

html {
background-color: revert;
}
34 changes: 29 additions & 5 deletions apps/css-workshop/src/components/theme.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class ThemeButton extends HTMLElement {
<label tabindex="-1"><input type="radio" name="theme" value="dark" /><span>Dark</span></label>
<label tabindex="-1"><input type="radio" name="theme" value="light-hc" /><span>High contrast light</span></label>
<label tabindex="-1"><input type="radio" name="theme" value="dark-hc" /><span>High contrast dark</span></label>
<br />
<label tabindex="-1"><input type="checkbox" name="theme-bridge" /><span>Theme Bridge</span></label>
</fieldset>
<fieldset>
Expand Down Expand Up @@ -137,16 +139,26 @@ class ThemeButton extends HTMLElement {
this.shadowRoot.querySelector(
`input[value=${prefersDark ? 'dark' : 'light'}${prefersHC ? '-hc' : ''}`,
).checked = true;
document.body.dataset.iuiTheme = prefersDark ? 'dark' : 'light';
document.body.dataset.iuiContrast = prefersHC ? 'high' : undefined;
document.body.classList.toggle('iui-root', true);

const root = document.body;
const v5Root = document.documentElement;
const theme = prefersDark ? 'dark' : 'light';

root.dataset.iuiTheme = theme;
v5Root.dataset.colorScheme = theme;
root.dataset.iuiContrast = prefersHC ? 'high' : undefined;
root.classList.toggle('iui-root', true);
}

changeTheme = ({ target: { value: _theme } }) => {
const root = document.body;
const v5Root = document.documentElement;

const isHighContrast = _theme.endsWith('-hc');
const theme = isHighContrast ? _theme.split('-')[0] : _theme;
document.body.dataset.iuiTheme = theme;
document.body.dataset.iuiContrast = isHighContrast ? 'high' : undefined;
root.dataset.iuiTheme = theme;
v5Root.dataset.colorScheme = theme;
root.dataset.iuiContrast = isHighContrast ? 'high' : undefined;
this.shadowRoot.querySelector('#theme-color-scheme').innerHTML = `
:host {
color-scheme: ${theme.includes('light') ? 'light' : 'dark'};
Expand All @@ -162,6 +174,10 @@ class ThemeButton extends HTMLElement {
}
};

changeBridge = ({ target: { checked } }) => {
document.body.dataset.iuiBridge = checked ? 'true' : 'false';
};

connectedCallback() {
this.shadowRoot.querySelectorAll('input[name="theme"]').forEach((radio) => {
radio.addEventListener('change', this.changeTheme);
Expand All @@ -170,6 +186,10 @@ class ThemeButton extends HTMLElement {
this.shadowRoot.querySelectorAll('input[name="background"]').forEach((radio) => {
radio.addEventListener('change', this.changeBackground);
});

this.shadowRoot
.querySelector('input[name="theme-bridge"]')
.addEventListener('change', this.changeBridge);
}

disconnectedCallback() {
Expand All @@ -180,6 +200,10 @@ class ThemeButton extends HTMLElement {
this.shadowRoot.querySelectorAll('input[name="background"]').forEach((radio) => {
radio.removeEventListener('change', this.changeBackground);
});

this.shadowRoot
.querySelector('input[name="theme-bridge"]')
.removeEventListener('change', this.changeBridge);
}
}
customElements.define('theme-button', ThemeButton);
4 changes: 4 additions & 0 deletions apps/css-workshop/src/pages/_layout.astro
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
---
import { Root as ITwinUIV5Root } from '@itwin/itwinui-react-v5/bricks';
type Props = { title?: string };
const { title } = Astro.props;
Expand Down Expand Up @@ -39,6 +41,7 @@ const slug = Astro.url.pathname.replace('/', '');
<link rel='canonical' href=`https://itwin.github.io/iTwinUI/${slug}` />

<style is:global>
@layer reset, kiwi;
@import '/assets/demo.css' layer(demo);
@import '@itwin/itwinui-variables' layer(variables);
@import '@itwin/itwinui-css/css/all.css';
Expand All @@ -51,6 +54,7 @@ const slug = Astro.url.pathname.replace('/', '');
</head>

<body class='iui-root' data-iui-theme>
<ITwinUIV5Root client:load colorScheme='dark' density='dense' />
<theme-button></theme-button>
<slot />
</body>
Expand Down
7 changes: 7 additions & 0 deletions apps/css-workshop/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "astro/tsconfigs/base",
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "react"
}
}
41 changes: 31 additions & 10 deletions apps/react-workshop/.ladle/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,24 @@
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import * as React from 'react';
import { ThemeProvider } from '@itwin/itwinui-react';
import {
useLadleContext,
ActionType,
ThemeState,
type GlobalProvider,
} from '@ladle/react';
import '@itwin/itwinui-variables';
import '@itwin/itwinui-react/styles.css';
import './global.css';
import '@itwin/itwinui-react/styles.css';
import { ThemeProvider } from '@itwin/itwinui-react';
import { Root as ITwinUiV5Root } from '@itwin/itwinui-react-v5/bricks';

const prefersDark = matchMedia('(prefers-color-scheme: dark)').matches;

export const Provider: GlobalProvider = ({ children }) => {
const { globalState, dispatch } = useLadleContext();
const theme = globalState.theme === 'dark' ? 'dark' : 'light';
const highContrast = globalState.control?.['high-contrast']?.value;
const futureThemeBridge = globalState.control?.['future.themeBridge']?.value;

// default to OS theme
React.useLayoutEffect(() => {
Expand All @@ -31,11 +32,12 @@ export const Provider: GlobalProvider = ({ children }) => {

// propagate theme to <html> element for page background
React.useLayoutEffect(() => {
document.documentElement.dataset.colorScheme = theme;
document.documentElement.dataset.iuiTheme = theme;
document.documentElement.dataset.iuiContrast = highContrast
? 'high'
: 'default';
}, [theme, highContrast]);
}, [theme, highContrast, futureThemeBridge]);

// redirect old storybook paths to new ones
React.useEffect(() => {
Expand All @@ -49,14 +51,29 @@ export const Provider: GlobalProvider = ({ children }) => {
}
}, []);

const themeProviderProps = {
theme,
themeOptions: {
applyBackground: false,
highContrast,
},
future: { themeBridge: futureThemeBridge },
children,
} satisfies React.ComponentProps<typeof ThemeProvider>;

return (
<React.StrictMode>
<ThemeProvider
theme={theme}
themeOptions={{ applyBackground: false, highContrast }}
>
{children}
</ThemeProvider>
{futureThemeBridge ? (
<ThemeProvider
as={ITwinUiV5Root}
colorScheme={theme}
density='dense'
synchronizeColorScheme
{...themeProviderProps}
/>
) : (
<ThemeProvider {...themeProviderProps} />
)}
</React.StrictMode>
);
};
Expand All @@ -74,4 +91,8 @@ export const argTypes = {
control: { type: 'boolean' },
defaultValue: false,
},
'future.themeBridge': {
control: { type: 'boolean' },
defaultValue: false,
},
};
4 changes: 4 additions & 0 deletions apps/react-workshop/.ladle/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/

@layer reset, variables, itwinui, kiwi;

@import '@itwin/itwinui-variables' layer(variables);

@import '@fontsource/noto-sans';
@import '@fontsource/noto-sans-mono';
@import '@fontsource/noto-sans/400-italic.css';
Expand Down
3 changes: 2 additions & 1 deletion apps/react-workshop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
"@fontsource/noto-sans-mono": "5",
"@itwin/itwinui-icons-react": "2.9.0",
"@itwin/itwinui-variables": "*",
"@itwin/itwinui-react-v5": "npm:@itwin/[email protected]",
"@itwin/itwinui-react": "*",
"@ladle/react": "^5.0.1",
"@types/node": "*",
"@types/react": "*",
"@types/react-dom": "*",
"cypress": "13.8.1",
"cypress": "14.0.1",
"cypress-image-diff-js": "1.30.1",
"eslint": "*",
"eslint-config-prettier": "^8.8.0",
Expand Down
4 changes: 4 additions & 0 deletions packages/itwinui-css/src/all.scss
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,7 @@
@include meta.load-css('utils/utils');
@include meta.load-css('workflow-diagram/workflow-diagram');
}

@layer itwinui.bridge {
@include meta.load-css('./bridge');
}
Loading

0 comments on commit 6fda72a

Please sign in to comment.