Skip to content

Commit 2370e19

Browse files
committed
convert files page for dark mode
1 parent 74a67ab commit 2370e19

59 files changed

Lines changed: 990 additions & 291 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

public/locales/en/settings.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
"removeAutoUpload": "Disable Auto Upload"
1414
},
1515
"language": "Language",
16+
"theme": "Theme",
17+
"themeDescription": "Choose between light, dark, or system theme (follows your operating system preference).",
1618
"analytics": "Analytics",
1719
"cliTutorMode": "CLI Tutor Mode",
1820
"config": "Kubo Config",

src/App.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import ComponentLoader from './loader/ComponentLoader.js'
1717
import Notify from './components/notify/Notify.js'
1818
import Connected from './components/connected/Connected.js'
1919
import TourHelper from './components/tour/TourHelper.js'
20+
import ThemeToggle from './components/theme-toggle/ThemeToggle.js'
2021
import FilesExploreForm from './files/explore-form/files-explore-form.tsx'
2122

2223
export class App extends Component {
@@ -69,16 +70,17 @@ export class App extends Component {
6970
{ canDrop && isOver && <div className='h-100 top-0 right-0 fixed appOverlay' style={{ background: 'rgba(99, 202, 210, 0.2)' }} /> }
7071
<div className='flex flex-row-reverse-l flex-column-reverse justify-end justify-start-l' style={{ minHeight: '100vh' }}>
7172
<div className='flex-auto-l'>
72-
<div className='flex items-center ph3 ph4-l' style={{ WebkitAppRegion: 'drag', height: 75, background: '#F0F6FA', paddingTop: '20px', paddingBottom: '15px' }}>
73+
<div className='flex items-center ph3 ph4-l' style={{ WebkitAppRegion: 'drag', height: 75, background: 'var(--theme-bg-tertiary)', paddingTop: '20px', paddingBottom: '15px' }}>
7374
<div className='joyride-app-explore' style={{ width: 560 }}>
7475
<FilesExploreForm onBrowse={doFilesNavigateTo} />
7576
</div>
7677
<div className='dn flex-ns flex-auto items-center justify-end'>
7778
{!url.startsWith('/diagnostics') && <TourHelper />}
7879
<Connected className='joyride-app-status' />
80+
<ThemeToggle className='ml2' />
7981
</div>
8082
</div>
81-
<main className='bg-white pv3 pa3 pa4-l'>
83+
<main className='pv3 pa3 pa4-l' style={{ background: 'var(--theme-bg-primary)' }}>
8284
{ (ipfsReady || url === '/welcome' || url.startsWith('/settings'))
8385
? <Page />
8486
: <ComponentLoader />
@@ -93,7 +95,17 @@ export class App extends Component {
9395
<ReactJoyride
9496
run={showTooltip}
9597
steps={appTour.getSteps({ t })}
96-
styles={appTour.styles}
98+
styles={{
99+
tooltipContent: { padding: '0 20px 0 0' },
100+
tooltipFooter: { display: 'none' },
101+
options: {
102+
width: '250px',
103+
backgroundColor: getComputedStyle(document.documentElement).getPropertyValue('--theme-brand-aqua').trim() || '#69c4cd',
104+
arrowColor: getComputedStyle(document.documentElement).getPropertyValue('--theme-brand-aqua').trim() || '#69c4cd',
105+
textColor: '#fff',
106+
zIndex: 999
107+
}
108+
}}
97109
callback={this.handleJoyrideCb}
98110
scrollToFirstStep
99111
disableOverlay

src/bundles/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import experimentsBundle from './experiments.js'
2323
import cliTutorModeBundle from './cli-tutor-mode.js'
2424
import gatewayBundle from './gateway.js'
2525
import ipnsBundle from './ipns.js'
26+
import themeBundle from './theme.js'
2627
import { contextBridge } from '../helpers/context-bridge'
2728

2829
export default composeBundles(
@@ -60,5 +61,6 @@ export default composeBundles(
6061
repoStats,
6162
cliTutorModeBundle,
6263
createAnalyticsBundle({}),
63-
ipnsBundle
64+
ipnsBundle,
65+
themeBundle
6466
)

src/bundles/theme.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { createSelector } from 'redux-bundler'
2+
import { readSetting, writeSetting } from './local-storage.js'
3+
4+
const THEME_KEY = 'ipfs-theme'
5+
const THEMES = {
6+
LIGHT: 'light',
7+
DARK: 'dark',
8+
SYSTEM: 'system'
9+
}
10+
11+
const getSystemTheme = () => {
12+
if (typeof window === 'undefined') return THEMES.LIGHT
13+
return window.matchMedia('(prefers-color-scheme: dark)').matches ? THEMES.DARK : THEMES.LIGHT
14+
}
15+
16+
const getInitialTheme = () => {
17+
const saved = readSetting(THEME_KEY)
18+
if (saved && Object.values(THEMES).includes(saved)) {
19+
return saved
20+
}
21+
// Default to dark theme instead of system
22+
return THEMES.DARK
23+
}
24+
25+
const applyTheme = (theme) => {
26+
const effectiveTheme = theme === THEMES.SYSTEM ? getSystemTheme() : theme
27+
document.documentElement.setAttribute('data-theme', effectiveTheme)
28+
document.documentElement.classList.remove('theme-light', 'theme-dark')
29+
document.documentElement.classList.add(`theme-${effectiveTheme}`)
30+
}
31+
32+
const bundle = {
33+
name: 'theme',
34+
35+
reducer: (state = getInitialTheme(), action) => {
36+
if (action.type === 'THEME_SET') {
37+
return action.payload
38+
}
39+
return state
40+
},
41+
42+
selectTheme: state => state.theme,
43+
44+
selectEffectiveTheme: createSelector(
45+
'selectTheme',
46+
(theme) => {
47+
return theme === THEMES.SYSTEM ? getSystemTheme() : theme
48+
}
49+
),
50+
51+
doSetTheme: (theme) => ({ dispatch }) => {
52+
if (!Object.values(THEMES).includes(theme)) {
53+
console.error(`Invalid theme: ${theme}`)
54+
return
55+
}
56+
writeSetting(THEME_KEY, theme)
57+
applyTheme(theme)
58+
dispatch({ type: 'THEME_SET', payload: theme })
59+
},
60+
61+
doToggleTheme: () => ({ dispatch, store }) => {
62+
const currentTheme = store.selectTheme()
63+
const themes = [THEMES.LIGHT, THEMES.DARK, THEMES.SYSTEM]
64+
const currentIndex = themes.indexOf(currentTheme)
65+
const nextTheme = themes[(currentIndex + 1) % themes.length]
66+
dispatch({ actionCreator: 'doSetTheme', args: [nextTheme] })
67+
},
68+
69+
init: (store) => {
70+
// Apply initial theme
71+
const theme = store.selectTheme()
72+
applyTheme(theme)
73+
74+
// Listen for system theme changes
75+
if (typeof window !== 'undefined') {
76+
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
77+
const handleChange = () => {
78+
const currentTheme = store.selectTheme()
79+
if (currentTheme === THEMES.SYSTEM) {
80+
applyTheme(THEMES.SYSTEM)
81+
}
82+
}
83+
84+
// Modern browsers
85+
if (mediaQuery.addEventListener) {
86+
mediaQuery.addEventListener('change', handleChange)
87+
} else {
88+
// Fallback for older browsers
89+
mediaQuery.addListener(handleChange)
90+
}
91+
}
92+
}
93+
}
94+
95+
export default bundle
96+
export { THEMES }

src/bundles/theme.test.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import themeBundle, { THEMES } from './theme.js'
2+
3+
describe('theme bundle', () => {
4+
it('should have correct name', () => {
5+
expect(themeBundle.name).toBe('theme')
6+
})
7+
8+
it('should export THEMES constants', () => {
9+
expect(THEMES.LIGHT).toBe('light')
10+
expect(THEMES.DARK).toBe('dark')
11+
expect(THEMES.SYSTEM).toBe('system')
12+
})
13+
14+
it('should have initial state', () => {
15+
const state = themeBundle.reducer(undefined, {})
16+
expect([THEMES.LIGHT, THEMES.DARK, THEMES.SYSTEM]).toContain(state)
17+
})
18+
19+
it('should handle THEME_SET action', () => {
20+
const state = themeBundle.reducer(THEMES.LIGHT, {
21+
type: 'THEME_SET',
22+
payload: THEMES.DARK
23+
})
24+
expect(state).toBe(THEMES.DARK)
25+
})
26+
27+
it('should select theme from state', () => {
28+
const state = { theme: THEMES.DARK }
29+
expect(themeBundle.selectTheme(state)).toBe(THEMES.DARK)
30+
})
31+
})

src/components/analytics-toggle/AnalyticsToggle.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Details from '../details/Details.js'
66

77
const ExampleRequest = ({ url, method = 'GET' }) => {
88
return (
9-
<pre className='overflow-x-scroll pa3 mr3 f6 ba b--black-10 br2 bg-snow-muted'>
9+
<pre className='overflow-x-scroll pa3 mr3 f6 ba br2' style={{ borderColor: 'var(--theme-border-primary)', backgroundColor: 'var(--theme-bg-secondary)' }}>
1010
<code className='green'>{method}</code> {url}
1111
</pre>
1212
)
@@ -17,7 +17,7 @@ const QueryParams = ({ url }) => {
1717
const params = (new URL(url)).searchParams
1818
const entries = [...params]
1919
return (
20-
<dl className='pa3 mr3 f7 overflow-x-scroll monospace nowrap ba b--black-10 br2 bg-snow-muted'>
20+
<dl className='pa3 mr3 f7 overflow-x-scroll monospace nowrap ba br2' style={{ borderColor: 'var(--theme-border-primary)', backgroundColor: 'var(--theme-bg-secondary)' }}>
2121
{entries.map(([key, value]) => (
2222
<div key={`QueryParams-${key}`}>
2323
<dt className='dib green'>{key}:</dt>
@@ -32,9 +32,9 @@ const AnalyticType = ({ children, onChange, enabled, label, summary, exampleRequ
3232
// show hide state. update react.
3333
const [isOpen, setOpen] = useState(false)
3434
return (
35-
<section className='bg-white bb b--black-10'>
35+
<section className='bb b--black-10' style={{ background: 'var(--theme-bg-primary)' }}>
3636
<div className='flex items-center'>
37-
<Checkbox className='pv3 pl3 pr1 bg-white flex-none' onChange={onChange} checked={enabled} label={
37+
<Checkbox className='pv3 pl3 pr1 flex-none' style={{ background: 'var(--theme-bg-primary)' }} onChange={onChange} checked={enabled} label={
3838
<span className='fw5 f6'>{label}</span>
3939
} />
4040
<div className='truncate fw4 f6 flex-auto charcoal-muted'>&ndash; {summary}</div>
@@ -119,7 +119,7 @@ const AnalyticsToggle = ({ analyticsActionsToRecord, analyticsConsent, doToggleC
119119
<ul>
120120
{analyticsActionsToRecord.map(name => (
121121
<li key={name} className='mb1'>
122-
<code className='f7 bg-snow-muted pa1 br2'>{name}</code>
122+
<code className='f7 pa1 br2' style={{ backgroundColor: 'var(--theme-bg-secondary)', color: 'var(--theme-text-primary)' }}>{name}</code>
123123
</li>
124124
))}
125125
</ul>

src/components/box/Box.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export const Box = ({
1313
children
1414
}) => {
1515
return (
16-
<section className={className} style={{ background: '#fbfbfb', ...style }}>
16+
<section className={className} style={{ background: 'var(--theme-bg-secondary)', ...style }}>
1717
<ErrorBoundary resetKeys={[globalThis.location?.pathname || '']}>
1818
{children}
1919
</ErrorBoundary>

src/components/card/card.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ export const Card: React.FC<{
1111
}> = ({ className = '', style, children }) => {
1212
return (
1313
<Box
14-
className={`ba b--black-10 br2 bg-white ${className}`}
15-
style={{ padding: 0, ...style }}
14+
className={`ba br2 bg-white ${className}`}
15+
style={{ padding: 0, borderColor: 'var(--theme-border-primary)', ...style }}
1616
>
1717
{children}
1818
</Box>
@@ -27,7 +27,7 @@ export const CardHeader: React.FC<{
2727
children: React.ReactNode
2828
}> = ({ className = '', children }) => {
2929
return (
30-
<div className={`pa3 bb b--black-10 ${className}`}>
30+
<div className={`pa3 bb ${className}`} style={{ borderColor: 'var(--theme-border-primary)' }}>
3131
{children}
3232
</div>
3333
)
@@ -97,7 +97,7 @@ export const CardFooter: React.FC<{
9797
children: React.ReactNode
9898
}> = ({ className = '', children }) => {
9999
return (
100-
<div className={`pa3 bt b--black-10 ${className}`}>
100+
<div className={`pa3 bt ${className}`} style={{ borderColor: 'var(--theme-border-primary)' }}>
101101
{children}
102102
</div>
103103
)

src/components/checkbox/Checkbox.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
.Checkbox > span:first-of-type {
2-
background-color: #DDE6EB;
2+
background-color: var(--theme-bg-secondary);
33
}
44

55
.Checkbox > input {
@@ -16,5 +16,5 @@
1616
}
1717

1818
.Checkbox input:focus + span {
19-
outline: 1px solid #bbb;
19+
outline: 1px solid var(--theme-border-primary);
2020
}

src/components/experiments/ExperimentsPanel.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ const Experiments = ({ doExpToggleAction, experiments, t }) => {
1818
return (
1919
<div
2020
key={key}
21-
className='pa3 mr3 mb3 mw6 br3 bg-white dib f6 ba b1 b--light-gray'
21+
className='pa3 mr3 mb3 mw6 br3 dib f6 ba b1 b--light-gray'
22+
style={{ background: 'var(--theme-bg-primary)' }}
2223
>
2324
<h3>{tkey('title', key)}</h3>
2425
<p className='charcoal'>{tkey('description', key)}</p>

0 commit comments

Comments
 (0)