-
Notifications
You must be signed in to change notification settings - Fork 534
feat: add Auto/Light/Dark theme support (fixes #1702) #2496
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
base: main
Are you sure you want to change the base?
Changes from all commits
dc59b7d
b2100b4
83d9e2b
6579866
ad9d080
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import React from 'react' | ||
| import { withTranslation } from 'react-i18next' | ||
| import { useTheme } from '../../contexts/theme-context.js' | ||
| import Title from '../../settings/Title.js' | ||
|
|
||
| const ThemeSelector = ({ t }) => { | ||
| const { theme, setTheme } = useTheme() | ||
|
|
||
| const onChange = (e) => { | ||
| setTheme(e.target.value) | ||
| } | ||
|
|
||
| return ( | ||
| <div className='joyride-settings-theme'> | ||
| <Title>{t('theme') || 'Theme'}</Title> | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added all keys to settings.json, options use t('themeAuto') etc |
||
| <div className='flex items-center'> | ||
| <select | ||
| className='w-100 mw4 pa2 ba b--black-20 br1 charcoal bg-white focus-outline focus-outline-blue' | ||
| value={theme} | ||
| onChange={onChange} | ||
| > | ||
| <option value='auto'>{t('themeAuto')}</option> | ||
| <option value='light'>{t('themeLight')}</option> | ||
| <option value='dark'>{t('themeDark')}</option> | ||
| </select> | ||
| <span className='ml3 f6 charcoal-muted'> | ||
| {t('themeDescription') || 'Choose a theme or sync with your system preference.'} | ||
| </span> | ||
| </div> | ||
| </div> | ||
| ) | ||
| } | ||
|
|
||
| export default withTranslation('settings')(ThemeSelector) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| // @ts-check | ||
| import ThemeSelector from './ThemeSelector.js' | ||
| import { ThemeProvider } from '../../contexts/theme-context.js' | ||
|
|
||
| /** | ||
| * @type {import('@storybook/react').Meta} | ||
| */ | ||
| export default { | ||
| title: 'Settings/Theme Selector', | ||
| component: ThemeSelector, | ||
| decorators: [ | ||
| (Story) => ( | ||
| <ThemeProvider> | ||
| <div className="pa4 bg-white" style={{ maxWidth: '400px' }}> | ||
| <Story /> | ||
| </div> | ||
| </ThemeProvider> | ||
| ) | ||
| ] | ||
| } | ||
|
|
||
| /** | ||
| * @type {import('@storybook/react').StoryObj} | ||
| */ | ||
| export const Default = {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import React from 'react' | ||
| import '@testing-library/jest-dom' | ||
| import { render, screen } from '@testing-library/react' | ||
| import ThemeSelector from './ThemeSelector.js' | ||
| import { ThemeProvider } from '../../contexts/theme-context.js' | ||
| import { I18nextProvider } from 'react-i18next' | ||
| import i18n from '../../i18n.js' | ||
|
|
||
| describe('ThemeSelector', () => { | ||
| it('renders correctly', () => { | ||
| render( | ||
| <I18nextProvider i18n={i18n}> | ||
| <ThemeProvider> | ||
| <ThemeSelector /> | ||
| </ThemeProvider> | ||
| </I18nextProvider> | ||
| ) | ||
|
|
||
| expect(screen.getByRole('combobox')).toBeInTheDocument() | ||
| expect(screen.getByText('Auto')).toBeInTheDocument() | ||
| expect(screen.getByText('Light')).toBeInTheDocument() | ||
| expect(screen.getByText('Dark')).toBeInTheDocument() | ||
| }) | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| import React from 'react' | ||
| import { useTranslation } from 'react-i18next' | ||
| import { useTheme } from '../../contexts/theme-context.js' | ||
| import StrokeMonitor from '../../icons/StrokeMonitor.js' | ||
|
|
||
| const SunIcon = ({ className, width, height }) => ( | ||
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className} width={width} height={height}> | ||
| <circle cx="12" cy="12" r="5"></circle> | ||
| <line x1="12" y1="1" x2="12" y2="3"></line> | ||
| <line x1="12" y1="21" x2="12" y2="23"></line> | ||
| <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line> | ||
| <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line> | ||
| <line x1="1" y1="12" x2="3" y2="12"></line> | ||
| <line x1="21" y1="12" x2="23" y2="12"></line> | ||
| <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line> | ||
| <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line> | ||
| </svg> | ||
| ) | ||
|
|
||
| const MoonIcon = ({ className, width, height }) => ( | ||
| <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className} width={width} height={height}> | ||
| <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path> | ||
| </svg> | ||
| ) | ||
|
|
||
| export const TopBarThemeToggle = () => { | ||
| const { theme, setTheme } = useTheme() | ||
| const { t } = useTranslation('settings') | ||
|
|
||
| const cycleTheme = () => { | ||
| if (theme === 'auto') setTheme('light') | ||
| else if (theme === 'light') setTheme('dark') | ||
| else setTheme('auto') | ||
| } | ||
|
|
||
| let Icon = StrokeMonitor | ||
| if (theme === 'light') Icon = SunIcon | ||
| if (theme === 'dark') Icon = MoonIcon | ||
|
|
||
| return ( | ||
| <button | ||
| className="button-reset bg-transparent bn p0 m0 mr3 flex items-center justify-center pointer charcoal-muted hover-navy transition-all glow focus-outline" | ||
| onClick={cycleTheme} | ||
| title={t('themeDescription')} | ||
| aria-label={`${t('theme')}: ${theme}`} | ||
| style={{ width: 28, height: 28 }} | ||
| > | ||
| <Icon width={24} height={24} className="fill-current-color" /> | ||
| </button> | ||
| ) | ||
| } | ||
|
|
||
| export default TopBarThemeToggle |
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 margin changes in this file look unrelated to the theme work. Mind reverting them, or explaining why they are needed?