-
-
Notifications
You must be signed in to change notification settings - Fork 8.6k
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
feat(v2): add support another Prism theme for dark mode #1984
Conversation
Deploy preview for docusaurus-2 ready! Built with commit 1cc3244 |
Deploy preview for docusaurus-preview ready! Built with commit d694a29 |
maybe our hooks need to be fixed to make it more flexible.Maybe we should have something like mutation observer to listen to dom changes ? hmm. Not really sure |
@endiliey I added the simplest event bus, now when you change the theme in the navbar, the Prism theme also changes dynamically. Can you take a look the implementation please? |
Maybe we can try with dogfooding it in netlify preview first, so that others could test it too. |
how often will we use the event bus? unless we will pick it up in multiple places i feel it's a bit overkill. i think the observer hook may suffice |
Makes sense, done ✔️ Perhaps in the future the event bus will be needed more, I can’t know for sure, if there is an alternative to the current solution, I only got the event bus in my mind.
Сan detail what you mean by that? UPD: If you mean |
8e97f92
to
cb8e98f
Compare
I'm not against event bus just wondering if this is the only use case. |
The downside of using this subscription method is that the theme selector has to dispatch when we change the theme. But generally that's okay because only 1 toggle though. mutationobserver advantage is that it can just listen to some random pseudocode that i made (might not work : // CodeBlock.js
useEffect(() => {
const observer = new MutationObserver(mutationsList => {
for (let mutation of mutationsList) {
if (mutation.attributeName !== 'data-theme') {
continue;
}
const { theme } = document.documentElement.dataset;
changePrismTheme(theme);
return;
}
});
observer.observe(document.documentElement, { attributes: true });
return () => observer.disconnect();
}, []); |
@endiliey thank you, this is a working solution, although the option with the event bus seems to me more understandable, then should I remove it and replace it with the MutationObserver? |
Hmm im not sure. Both approach works well for me. I just tried it locally like this, // CodeBlock.js
const [theme] = useTheme();
const lightThemePrism = prism.theme || defaultTheme;
const darkThemePrism = prism.darkTheme || defaultTheme;
const [prismTheme, setPrismTheme] = useState(
theme === 'dark' ? darkThemePrism : lightThemePrism,
);
useEffect(() => {
const observer = new MutationObserver(mutationsList => {
for (let mutation of mutationsList) {
if (mutation.attributeName !== 'data-theme') {
continue;
}
const {theme} = document.documentElement.dataset;
setPrismTheme(theme === 'dark' ? darkThemePrism : lightThemePrism);
return;
}
});
observer.observe(document.documentElement, {attributes: true});
return () => observer.disconnect();
}, []); And i was able to delete all the eventBus code & only CodeBlock.js need some code changes. It's much lesser code for now |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just gonna stamp. final call from other maintainers
I’d like to suggest not to merge this yet for now. Upon working on the no flash problem, its known that “document data-theme” should be the main source of truth of whether dark mode is on or not. This one requires toggle to dispatch an event, vanilla javascript cant dispatch a react event. Lets put this on hold till we solve the no flash problem (at least) and make sure it can work as well |
}; | ||
|
||
const useEventBus = (type, listener, deps = []) => { | ||
useEffect(() => subscribe(type, listener), deps); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe we can return unsubscribe method here for better cleanup?
useEffect(() => {
subscribe(type, listener);
return () => subscribers[type] = subscribers[type].filter(fn => fn !== listener);
}, deps);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might be missing something, but I think the event bus is not necessary, please refer to inline comments.
Not really in favor of event bus/emitter-style of notifications. It feels like we are back to the pre-React days where we have messy event handlers sprinkled throughout the app.
In the worst case, how about we add the theme into the context and listen to the props?
e => { | ||
const newTheme = e.target.checked ? 'dark' : ''; | ||
setTheme(newTheme); | ||
dispatch('docusaurus-change-theme', {newTheme}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this dispatch()
even needed? The useTheme
hook stores the theme and when it gets updated, them components which are using the value will also re-render. setTheme
will already trigger an update for those components using useTheme
. I think this is unnecessary and as a result, we probably don't need the event bus.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The useTheme hook stores the theme and when it gets updated, them components which are using the value will also re-render.
I tried go that way, but this method does not work for me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll patch and try it. Give me a few days, I'm moving countries today 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes please! Although it seems to me that this is not such a bad decision, but with hooks/context it not worked out for me ;(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yangshun any news?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
useTheme
hook stores the theme and when it gets updated, them components which are using the value will also re-render.
I was completely wrong (wonky memory). useTheme
is state and local to a component. setTheme
only updates the theme value within the component.
Sorry I haven't got to it. Gimme a while more. You might want to resolve the merge conflict first anyway. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I finally got the chance to take a good look at this. I apologize for the following:
- For taking so long to get back to you. Work has been really hectic and I'm busy getting my last minute #impacc for the half.
- My earlier comment that theme would automatically update was incorrect.
useTheme
would not sync state across components - I completely forgot thatuseTheme
usesuseState
, and component state is local. I found out that by doinguseTheme
in so many components in our code, we were initializing theme state around 7 times on certain pages (once for each codeblock). That's completely unnecessary and unintended. The proper way was for theme to be part of context. Heck, even the React context examples used theme as their example.
I'm not in favor of the event bus as a communication mechanism because the theme value is clearly part of state and we should use an idiomatic React approach.
I considered a few approaches, including listening to the window.localStorage
changes and syncing the theme state across components, but the window.addEventListener('storage', ...)
API doesn't allow listening for events on the same page. Hence I settled on creating a theme context that can be used by every component which needs the theme.
The good:
- Individual components now have control over the theme and can set the theme and update all places which are relying on that theme context/state.
- No duplicated theme state across components. It's all the way at the top of the component tree in the theme context provider
The bad:
- I had to inject the theme provider in
<Layout/>
. People who swizzled layout will not get the updates or their code might even break. Thankfully it's just a one-line change on their part and they can easily move to this theme context approach. - Users who create their own layout need to explicitly use the theme provider as well for theming functionality, whereas the event bus approach doesn't have to, as there is no central state (distributed across the components which use it).
I'm unable to push to your PR branch, so I made a new one on this repo and the changes can be found here. Let me know your thoughts!
@yangshun thanks for the detailed analysis! I fully support these changes, especially since you increased performance with context? Indeed, it is much better and more understandable! |
You did the bulk of the work and I was only improving on your work. I think it would be ok for you to patch the commit on your existing branch and retain this PR for the discussion. Alternatively, you can create a new PR using my branch. I prefer that the feature to be attributed to you 😄 |
@yangshun fine, I'll take care of the rest and complete PR :) P.S. If you took the time to research a long-standing issue with code block, it would be very nice! 😃 (just friendly reminder, because it still bothers me) |
Hi. When it will merged? |
@lex111 I can take over this and get it merged if you allow me access to the branch/repo. |
@yangshun you got it. Sorry that I could not do it myself quickly. |
d694a29
to
1cc3244
Compare
Finally merged this! I'll add docs and update our templates with this new feature in a follow-up PR. |
I think its still buggy If you go to https://v2.docusaurus.io/docs/installation or any link, set dark mode and refresh, it will still be light theme on first page reload. |
i think need add in deps of onToggleChange |
Hmm thanks for catching. I'll look into it. |
I think this only happens on the build version of the site. It works fine on development. The SSR step isn't theme-aware and always renders the light theme syntax highlighting to static HTML. The build step doesn't have access to the theme |
Root cause - React doesn't update styles that are rendered via SSR - facebook/react#11128 |
🎉 thanks for working on this, we're looking forward to this change. |
Motivation
I think it's worth considering the introduction of a separate Prism theme for dark mode.
This is proof of concept (I'm not sure about peft of this solution) and there is a issue with how to switch the Prism theme when changing dark mode toggle in header. Any ideas?
Have you read the Contributing Guidelines on pull requests?
Yes
Test Plan
Config: