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

feat: add dark mode #2322

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open

Conversation

kaf-lamed-beyt
Copy link

fixes #1702

Hi @lidel, This is what I have so far. I'm currently using an approach involving a Context Provider to access the theme value across the entire application.

I'm also adding the styles individually in index.css by targeting the data-theme attribute. This is nice, but it can be quite tasking, hence my move towards a custom context hook. This would allow us to dynamically change colors across components in the application.

2025-01-3015-52-55-ezgif com-video-to-gif-converter

I ran into a slight blocker though. App.js uses a legacy React pattern — a class-based component which makes it a bit difficult to use the hook in this component, so I resorted to using the value from localStorage which is outside of the component/app life-cycle. So even if the value of theme in localStorage changes, the color — based on the ternary operation below — of the header won't change, because we do not trigger a re-render. Which is what the hook helps us achieve.

<div
     className="flex items-center ph3 ph4-l"
      style={{
         WebkitAppRegion: "drag",
          height: 75,
          background: currentTheme === "dark" ? "red" : "#F0F6FA",
          paddingTop: "20px",
          paddingBottom: "15px",
        }}
>

So, my question now is if it is possible to refactor App.js into a function-based component

@kaf-lamed-beyt kaf-lamed-beyt requested a review from a team as a code owner January 30, 2025 15:07
Copy link

welcome bot commented Jan 30, 2025

Thank you for submitting this PR!
A maintainer will be here shortly to review it.
We are super grateful, but we are also overloaded! Help us by making sure that:

  • The context for this PR is clear, with relevant discussion, decisions
    and stakeholders linked/mentioned.

  • Your contribution itself is clear (code comments, self-review for the
    rest) and in its best form. Follow the code contribution
    guidelines

    if they apply.

Getting other community members to do a review would be great help too on complex PRs (you can ask in the chats/forums). If you are unsure about something, just leave us a comment.
Next steps:

  • A maintainer will triage and assign priority to this PR, commenting on
    any missing things and potentially assigning a reviewer for high
    priority items.

  • The PR gets reviews, discussed and approvals as needed.

  • The PR is merged by maintainers when it has been approved and comments addressed.

We currently aim to provide initial feedback/triaging within two business days. Please keep an eye on any labelling actions, as these will indicate priorities and status of your contribution.
We are very grateful for your contribution!

@kaf-lamed-beyt kaf-lamed-beyt changed the title feat: add theme provider feat: add dark mode Jan 30, 2025
@kaf-lamed-beyt
Copy link
Author

Hi @lidel, kindly take a look at this when you get the chance. Thanks.

@SgtPooki
Copy link
Member

SgtPooki commented Feb 5, 2025

So, my question now is if it is possible to refactor App.js into a function-based component

@kaf-lamed-beyt I would love for all components in ipfs-webui to be functional components. That is the direction we're moving. However, since that will likely be a significant change, we should probably do that as a separate PR.

You can see an example of a simple function component that is also using redux-bundler-react at

export default connect(

@kaf-lamed-beyt
Copy link
Author

Oh! Thanks for the feedback, @SgtPooki! I'll take a look at the file you pointed me to now.

So, from the looks of things now, are you suggesting that i use the manual approach of targeting each element based on the styles in [data-theme="dark|light"] in index.css?

We can always keep the hook.

Comment on lines 77 to 79
<div className='mb4'>
<ThemeToggle />
</div>
Copy link
Member

@lidel lidel Feb 5, 2025

Choose a reason for hiding this comment

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

💭 the space in the left NavBar is limited especially in ipfs-desktop where UI acts as native app. and window height is limited by default.

we want to add diagnostics screen at some point

perhaps it is better to move this toggle to top right corner where we already have two items:

image

thoughts?

Copy link
Author

Choose a reason for hiding this comment

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

oh, yeah! that sounds goood, @lidel!

i originally thought of placing it there, but couldn't locate the icons in the code. I'll try looking for them and update the theme toggle's position

Copy link
Member

@SgtPooki SgtPooki left a comment

Choose a reason for hiding this comment

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

some comments

src/index.css Outdated Show resolved Hide resolved
src/index.css Outdated Show resolved Hide resolved
src/index.css Show resolved Hide resolved
Comment on lines 19 to 27
const [theme, setTheme] = React.useState<boolean>(() => {
const savedTheme =
typeof window !== 'undefined' && localStorage.getItem('theme')
if (savedTheme) return savedTheme === 'dark'
return window.matchMedia('prefers-color-scheme: dark').matches
})
React.useEffect(() => {
const htmlElem = document.documentElement
const currentTheme = theme ? 'dark' : 'light'
Copy link
Member

Choose a reason for hiding this comment

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

seems like we can use better names for theme and setTheme since it seems to be a boolean indicating whether dark theme is selected or not

Copy link
Author

Choose a reason for hiding this comment

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

true. how does isDarkTheme and setDarkTheme sound?

Copy link
Member

Choose a reason for hiding this comment

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

perfect

src/hooks/theme.ts Outdated Show resolved Hide resolved
}
}
const values: ThemeContextValues = {
currentTheme: theme as unknown as Theme,
Copy link
Member

Choose a reason for hiding this comment

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

theme is boolean, not a Theme, so setting it to as unknown as Theme is wrong.

Copy link
Member

Choose a reason for hiding this comment

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

ideally, use of as (typescript casting) should be limited as much as possible.

Copy link
Author

Choose a reason for hiding this comment

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

thank you for catching this. I'll update it.

Comment on lines +3 to +5
export interface ThemeProviderProps {
children: React.ReactNode
}
Copy link
Member

Choose a reason for hiding this comment

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

instead of explicitly setting children for props, you can extend React.FC<PropsWithChildren> or similar..

or, my preference since there are no custom props, remove this and do it below

Suggested change
export interface ThemeProviderProps {
children: React.ReactNode
}

Copy link
Author

Choose a reason for hiding this comment

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

I think there's this minor issue with using React.FC in newer versions of React and I understand that it sits well with this component since it expects children which React.FC<PropsWithChildren> provides implicitly.

why shouldn't we set the children prop explicitly?

Copy link
Author

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

@kaf-lamed-beyt no huge reason, but here are a few:

  • prevent issues if React changes the type of the children prop
  • less code to maintain
  • using prop types from react directly prevents needing to add other props if we need to extend them (adding className or other common props in the future)

Copy link
Member

Choose a reason for hiding this comment

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

I think there's this minor issue with using React.FC in newer versions of React

I haven't ran into this, can you link to examples?

Copy link
Author

Choose a reason for hiding this comment

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

huh. makes sense.

for this 👇🏼

using prop types from react directly prevents needing to add other props if we need to extend them (adding className or other common props in the future)

I may be wrong... but, can't we do ...props?

I haven't ran into this, can you link to examples?

This issue from facebook/create-react-app sheds more light on why they deprecated React.FC @SgtPooki

const createThemeContext = () => React.createContext<ThemeContextValues | null>(null)
export const ThemeContext = createThemeContext()

export const ThemeProvider = ({ children }: ThemeProviderProps) => {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
export const ThemeProvider = ({ children }: ThemeProviderProps) => {
export const ThemeProvider: React.FC<PropsWithChildren> = ({ children }) => {

src/context/theme-provider.tsx Outdated Show resolved Hide resolved
Comment on lines 11 to 12
toggleTheme: () => void;
toggleThemeWithKey: (event: React.KeyboardEvent<HTMLButtonElement>) => void;
Copy link
Member

Choose a reason for hiding this comment

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

I'm not convinced we need both of these. Can toggleTheme accept an optional event and handle expected keys?

Copy link
Author

Choose a reason for hiding this comment

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

i'll try refactoring this.

kaf-lamed-beyt and others added 8 commits February 11, 2025 21:06
@kaf-lamed-beyt
Copy link
Author

@lidel, I've moved the toggle from the navbar. Here's what it looks like now.

ipfs-webui-dark-theme-ezgif com-video-to-gif-converter

I still have to wrap up with the other routes. When I do, I'll push the changes.

cc: @SgtPooki

- previously, pretty much all the keys on the keyboard can trigger the theme toggle when it is focused. we don't want that. Instead we should only limit it to specific keys (spacebar & enter).
- include all the varying colors when the theme is dark on all routes
- extend the Box component to use the theme value instead of explicitly passing it in the style prop across the codebase
@kaf-lamed-beyt
Copy link
Author

done with this. @SgtPooki, @lidel, kindly review.
2025-02-1216-36-22-ezgif com-video-to-gif-converter

the explore route was quite tricky to work on. I couldn't locate the elements/components that are used to render data in the UI, that's why the one on the right still retained a white background.

perhaps, if anyone can point me to where they are exactly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Feature: dark mode
3 participants