-
Notifications
You must be signed in to change notification settings - Fork 34
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
Changing theme dynamically #8
Comments
This is definitely a common concern for modern use cases. Users are more and more frequently coming to expect websites and apps to have a dark mode option that they can simply turn on or off. |
Any thoughts on how we could achieve this in Ant @peisenmann? As a workaround, you could create two different versions of your app (with different CSS), and deploy them at:
|
@veeral-patel I haven't looked into it at all myself, so I don't know how it could possible work from the Ant side. A simple class at the highest level, like This would allow any number of themes to exist and be swappable at runtime in the browser with a simple class name change. I have no idea if that's feasible, but in lieu of a better solution, that could be adequate. EDIT: I changed the wording of the suggestion above while keeping the concept. |
In case anyone is looking for a guide, I followed the below and have dynamic switching themes. Also gave the user control of individual colors (like primary). https://medium.com/@mzohaib.qc/ant-design-live-theme-588233ea2bbc You have to use LESS and add less.min.js to your index file. I also changed from the default react-scripts to rescripts. I had only ever used SCSS with create-react-app prior to this. Medium difficulty, but worth it! |
How is this not in v4? Please 🙏 ant design team, add a way to dynamically switch themes without the use of less variables. |
Check out my article on how to dynamically switch between themes! Also, if you'd like to jump in right away to a package that can help you, you can check out react-css-theme-switcher. |
This is GEM! ✨ But one query tho, why do I have to run can we hot reload it while the virtual dom changes? |
You could with Webpack! You'd have to create your own plugin though. However, I would discourage you from doing it since it will slow down your development build by a lot. If you don't want to use Gulp, you could create a custom script using vanilla Javascript and add a hook in your The reason that I used Gulp is because of how well it handles file streams operations. On the other hand, if you meant to change the theme on runtime in the browser, you could do it by following this article. I stopped using this solution because it was CPU demanding which is really bad for the user. Thankfully, I had predefined themes (light and dark) so I found a way to calculate the styles on build time instead of runtime, and just change between these generated styles. |
this is another good option to implement theme switcher https://github.com/mzohaibqc/antd-theme-webpack-plugin |
@JoseRFelix I found your article. Brilliant stuff. One thing missing was the ability to keep the selected dark mode. If I refreshed the browser, it would default to light again. I then found the https://github.com/donavon/use-dark-mode package, which keeps the selected theme in localstorage and it also supports the ability to detect if a Mac system is using dark mode, and chose that automatically. So I made a few changes to the gulpfile. See the Gist here: https://gist.github.com/imCorfitz/5a3724308a0b258a27f407ab659f91fd This way, I have the theme css files, and then I can use the |
Ow man, that sound awesome. |
Given the size of Antd and the sheer amount of development time already invested in the less styles I find it pretty unlikely that Antd will switch to anything else any time soon. I think css variables would help a lot but switching to those is equally unlikely given that there are still browsers out there which don't support it (IE and many mobile browsers). Using the We had to support dynamic themes the other day and solved it by using Solution 1: No module imports / link element in document head / via <link rel="stylesheet" href="<path-to-the-css-file-as-statically-delivered-by-the-browser>" /> Note, that the uri in Solution 2: No module imports / link element in body / using <link ... /> element instead of hooks return (
<link rel="stylesheet" href="<path-to-the-css-file-as-statically-delivered-by-the-browser>" />
) Again the major drawback of having to manage the relation of less and css files manually remains. Solution 3: use style-loader and LazyStlyeTag / via {
// also make sure not to include these files in the other less loader rule lest they
// end up being bundled twice i. e. here and inside the default css bundle
test: /\.theme\.less$/i,
use: [
{
loader: 'style-loader',
options: { injectType: 'lazyStyleTag' },
},
'css-loader',
{
loader: 'less-loader',
options: {
lessOptions: {
env: mode,
javascriptEnabled: true,
},
sourceMap: true,
},
},
],
} The resulting css module has two functions, ...
// the hook below applies the theme corresponding to
// themeId which is the currently active theme
useAntdTheme(themeId);
return (
<div>
...
</div>
); The ...
export function useAntdTheme(themeId) { // themeId is the active (i. e. selected) theme
useEffect(() => {
const { styles } = themes[themeId]; // 2. retrieve the imported style module for current themeId
styles?.use(); // 3. apply the styling
return () => styles?.unuse(); // 1. unapply the previous styling (if any)
}, [themeId]);
} Last but not least: the mapping of theme Ids to style modules. In other words the import compactStyles from '../themes/compact.theme.less';
import darkStyles from '../themes/dark.theme.less';
...
export const themes = {
'compact': {
id: 'compact', // used as value in the select
displayName: 'Compact', // used as label in the select
styles: compactStyles, // the style module
},
'dark': {
id: 'dark',
displayName: 'Dark',
styles: darkStyles,
},
}; Edit: |
I tried to use your method @momesana , but in Umi.js only 'chainWebpack' is to change the original webpack settings. I am not familiar with the library so here's my code when I try to implement lazyStyleTag // .umirc.ts
chainWebpack(config: Config) {
config.module
.rule('lazyless')
.test(/\.lazy\.less$/)
.oneOf('')
.use('less-load')
.loader('less-loader')
.options({
lessOptions: {
env: 'development',
javascriptEnabled: true,
},
sourceMap: true,
})
.end()
.use('css-load')
.after('less-loader')
.loader('css-loader')
.end()
.use('style-load')
.after('css-loader')
.loader('style-loader')
.options({ injectType: 'lazyStyleTag' })
} |
I have never worked with Umj.js, @peiwen-pts so I can't say much without seeing the code or even better trying it out. From what I can see the files can't be found when webpack tries to build the bundle. Webpack here fails to find In any case I will upload a repository today or in the coming days with the solution I proposed so you can try it out and hopefully figure out what the differences are that leads to the above failure. |
@peiwen-pts: here you go: https://github.com/momesana/dynamic-antd-theme-demo Of interest are:
Here is a working example on codesandbox: If you like, you can also checkout the repository yourself and run it with or this if you go with the Light theme: |
@momesana Thank you so much for the feedback. I temporarily solved the problem by employing Gulp: if anyone interested please check out this post by Jose Felix. The thing might be of one's concern is that the css files might contain redundant code since Umi.js uses webpack to pack up original antd less files as well as the additional less files (which are to customise ant design components) into one file I would not know how to optimise this... It would be AWESOME if someone can point out where to start! THANKS!!! |
Still actual! |
I use CRA + CRACO. Your approach with App crashes on the client side:Issue is also discussed here: webpack-contrib/style-loader#81 |
@momesana how you deploy the app ? |
@smitroshin npm run build
npx http-server ./dist The first command generates the javascript bundle inside the |
@momesana Anyway, I didn't found any solution for my case. Seems like some configs from CRA or CRACO doesn't work well with There is repo with my case. But maybe somebody will find a way :) |
@momesana Thanks for all the time you put into recording your findings here. I was wondering if you had any advice on how your Solution 3 (or other solutions) could work with dynamic variables loaded from an api request at run time? Ex. We have organizations with different theme settings. When a page is loaded, we fetch the org settings and retrieve a various theme settings (like primaryColor). Would your solution work for something like that? Maybe by generating a less file on the fly? |
It looks like 4.17.0-alpha.5 introduces support for css variables for overriding themes. https://codesandbox.io/s/global-theme-antd-4-17-0-alpha-5-forked-ey3qq?file=/index.js:2290-2304 |
True, but it isn't the solution.
I'm using the craco-less solution with less.js to change theme at runtime, but i'd like to switch to css variables solution. |
The theme files are generated by webpack using the styled loader, so the benefit is that we can import the less file in our react application as an asset and this is then basically turned into a javascript module that can then be loaded (and the styles applied) when the app is running. That does however require that this module is generated during compile-time. In other words: you can dynamically load the themes at run-time but they need to already exist as [javascript] style modules. In the scenario you described, theme parameters are obtained via an API during run-time. With the newest version of antd (i. e. >= 4.17.0), you can utilize the CSS variables to achieve what you outlined though unfortunately only a small subset of the variables is currently exposed by antd. Another way would be to return the name of the theme instead of the variables. That does of course preclude the ability to dynamically customize themes in detail, but allows you to provide a predefined set of themes and use the API to decide which one to use. You can of course always look up what CSS rules are generated by antd when the less files are compiled to CSS and use specificity rules to override them by creating a corresponding style tag on the fly and attaching it to the DOM, overriding the variables via Javascript or use a CSS-in-JS solution (like |
Deciding when to apply a theme is the duty of the developer. This is not something antd could or should do. Besides countless existing react hooks for that, a custom hook that achieves this can be realized in a few lines of code. Changing the value of the variables is also trivial. The only problem is that only a small subset of the variables is currently exposed. |
As of now the latest available version of antd is 4.18.2. Running
That is just a small subset of the available less variables and notably the background colors are entirely absent. That's not nearly enough to support a dark theme and only lends itself for minor tweaks like for example changing the primary color. Hopefully more variables will be exposed in the future. |
Hey, so.... I created a working example of how to get this to work with a custom provider wrapped around the Config provider //// ThemeProvider interface ThemeProviderProps extends ConfigProviderProps { const getColorScheme = (mode: Theme | undefined) => { return { interface IThemeContext { const defaultState = { export const ThemeContext = React.createContext(defaultState); const ThemeProvider: React.FC = ({ theme, children, ...props }) => { const toggleDark = () => { React.useMemo(() => ConfigProvider.config({ theme: getColorScheme(newTheme) }), [newTheme]); return ( export default ThemeProvider; //// Page.tsx - Consuming the Context const PageView = () => { export default PageView;` |
The key is to utilize useMemo in the ConfigProvider.config() options. Specifically only calling when the global theme prop changes. |
If I wanted to toggle my React app's theme by pressing a button in the app, without restarting my web server, would that be possible?
The text was updated successfully, but these errors were encountered: