Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions public/locales/en/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
"removeAutoUpload": "Disable Auto Upload"
},
"language": "Language",
"theme": "Theme",
"themeDescription": "Choose between light, dark, or system theme (follows your operating system preference).",
"analytics": "Analytics",
"cliTutorMode": "CLI Tutor Mode",
"config": "Kubo Config",
Expand Down
18 changes: 15 additions & 3 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import ComponentLoader from './loader/ComponentLoader.js'
import Notify from './components/notify/Notify.js'
import Connected from './components/connected/Connected.js'
import TourHelper from './components/tour/TourHelper.js'
import ThemeToggle from './components/theme-toggle/ThemeToggle.js'
import FilesExploreForm from './files/explore-form/files-explore-form.tsx'

export class App extends Component {
Expand Down Expand Up @@ -69,16 +70,17 @@ export class App extends Component {
{ canDrop && isOver && <div className='h-100 top-0 right-0 fixed appOverlay' style={{ background: 'rgba(99, 202, 210, 0.2)' }} /> }
<div className='flex flex-row-reverse-l flex-column-reverse justify-end justify-start-l' style={{ minHeight: '100vh' }}>
<div className='flex-auto-l'>
<div className='flex items-center ph3 ph4-l' style={{ WebkitAppRegion: 'drag', height: 75, background: '#F0F6FA', paddingTop: '20px', paddingBottom: '15px' }}>
<div className='flex items-center ph3 ph4-l' style={{ WebkitAppRegion: 'drag', height: 75, background: 'var(--theme-bg-tertiary)', paddingTop: '20px', paddingBottom: '15px' }}>
<div className='joyride-app-explore' style={{ width: 560 }}>
<FilesExploreForm onBrowse={doFilesNavigateTo} />
</div>
<div className='dn flex-ns flex-auto items-center justify-end'>
{!url.startsWith('/diagnostics') && <TourHelper />}
<Connected className='joyride-app-status' />
<ThemeToggle className='ml2' />
</div>
</div>
<main className='bg-white pv3 pa3 pa4-l'>
<main className='pv3 pa3 pa4-l' style={{ background: 'var(--theme-bg-primary)' }}>
{ (ipfsReady || url === '/welcome' || url.startsWith('/settings'))
? <Page />
: <ComponentLoader />
Expand All @@ -93,7 +95,17 @@ export class App extends Component {
<ReactJoyride
run={showTooltip}
steps={appTour.getSteps({ t })}
styles={appTour.styles}
styles={{
tooltipContent: { padding: '0 20px 0 0' },
tooltipFooter: { display: 'none' },
options: {
width: '250px',
backgroundColor: getComputedStyle(document.documentElement).getPropertyValue('--theme-brand-aqua').trim() || '#69c4cd',
arrowColor: getComputedStyle(document.documentElement).getPropertyValue('--theme-brand-aqua').trim() || '#69c4cd',
textColor: '#fff',
zIndex: 999
}
}}
callback={this.handleJoyrideCb}
scrollToFirstStep
disableOverlay
Expand Down
4 changes: 3 additions & 1 deletion src/bundles/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import experimentsBundle from './experiments.js'
import cliTutorModeBundle from './cli-tutor-mode.js'
import gatewayBundle from './gateway.js'
import ipnsBundle from './ipns.js'
import themeBundle from './theme.js'
import { contextBridge } from '../helpers/context-bridge'

export default composeBundles(
Expand Down Expand Up @@ -60,5 +61,6 @@ export default composeBundles(
repoStats,
cliTutorModeBundle,
createAnalyticsBundle({}),
ipnsBundle
ipnsBundle,
themeBundle
)
96 changes: 96 additions & 0 deletions src/bundles/theme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { createSelector } from 'redux-bundler'
import { readSetting, writeSetting } from './local-storage.js'

const THEME_KEY = 'ipfs-theme'
const THEMES = {
LIGHT: 'light',
DARK: 'dark',
SYSTEM: 'system'
}

const getSystemTheme = () => {
if (typeof window === 'undefined') return THEMES.LIGHT
return window.matchMedia('(prefers-color-scheme: dark)').matches ? THEMES.DARK : THEMES.LIGHT
}

const getInitialTheme = () => {
const saved = readSetting(THEME_KEY)
if (saved && Object.values(THEMES).includes(saved)) {
return saved
}
// Default to dark theme instead of system
return THEMES.DARK
}

const applyTheme = (theme) => {
const effectiveTheme = theme === THEMES.SYSTEM ? getSystemTheme() : theme
document.documentElement.setAttribute('data-theme', effectiveTheme)
document.documentElement.classList.remove('theme-light', 'theme-dark')
document.documentElement.classList.add(`theme-${effectiveTheme}`)
}

const bundle = {
name: 'theme',

reducer: (state = getInitialTheme(), action) => {
if (action.type === 'THEME_SET') {
return action.payload
}
return state
},

selectTheme: state => state.theme,

selectEffectiveTheme: createSelector(
'selectTheme',
(theme) => {
return theme === THEMES.SYSTEM ? getSystemTheme() : theme
}
),

doSetTheme: (theme) => ({ dispatch }) => {
if (!Object.values(THEMES).includes(theme)) {
console.error(`Invalid theme: ${theme}`)
return
}
writeSetting(THEME_KEY, theme)
applyTheme(theme)
dispatch({ type: 'THEME_SET', payload: theme })
},

doToggleTheme: () => ({ dispatch, store }) => {
const currentTheme = store.selectTheme()
const themes = [THEMES.LIGHT, THEMES.DARK, THEMES.SYSTEM]
const currentIndex = themes.indexOf(currentTheme)
const nextTheme = themes[(currentIndex + 1) % themes.length]
dispatch({ actionCreator: 'doSetTheme', args: [nextTheme] })
},

init: (store) => {
// Apply initial theme
const theme = store.selectTheme()
applyTheme(theme)

// Listen for system theme changes
if (typeof window !== 'undefined') {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
const handleChange = () => {
const currentTheme = store.selectTheme()
if (currentTheme === THEMES.SYSTEM) {
applyTheme(THEMES.SYSTEM)
}
}

// Modern browsers
if (mediaQuery.addEventListener) {
mediaQuery.addEventListener('change', handleChange)
} else {
// Fallback for older browsers
mediaQuery.addListener(handleChange)
}
}
}
}

export default bundle
export { THEMES }
31 changes: 31 additions & 0 deletions src/bundles/theme.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import themeBundle, { THEMES } from './theme.js'

describe('theme bundle', () => {
it('should have correct name', () => {
expect(themeBundle.name).toBe('theme')
})

it('should export THEMES constants', () => {
expect(THEMES.LIGHT).toBe('light')
expect(THEMES.DARK).toBe('dark')
expect(THEMES.SYSTEM).toBe('system')
})

it('should have initial state', () => {
const state = themeBundle.reducer(undefined, {})
expect([THEMES.LIGHT, THEMES.DARK, THEMES.SYSTEM]).toContain(state)
})

it('should handle THEME_SET action', () => {
const state = themeBundle.reducer(THEMES.LIGHT, {
type: 'THEME_SET',
payload: THEMES.DARK
})
expect(state).toBe(THEMES.DARK)
})

it('should select theme from state', () => {
const state = { theme: THEMES.DARK }
expect(themeBundle.selectTheme(state)).toBe(THEMES.DARK)
})
})
10 changes: 5 additions & 5 deletions src/components/analytics-toggle/AnalyticsToggle.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Details from '../details/Details.js'

const ExampleRequest = ({ url, method = 'GET' }) => {
return (
<pre className='overflow-x-scroll pa3 mr3 f6 ba b--black-10 br2 bg-snow-muted'>
<pre className='overflow-x-scroll pa3 mr3 f6 ba br2' style={{ borderColor: 'var(--theme-border-primary)', backgroundColor: 'var(--theme-bg-secondary)' }}>
<code className='green'>{method}</code> {url}
</pre>
)
Expand All @@ -17,7 +17,7 @@ const QueryParams = ({ url }) => {
const params = (new URL(url)).searchParams
const entries = [...params]
return (
<dl className='pa3 mr3 f7 overflow-x-scroll monospace nowrap ba b--black-10 br2 bg-snow-muted'>
<dl className='pa3 mr3 f7 overflow-x-scroll monospace nowrap ba br2' style={{ borderColor: 'var(--theme-border-primary)', backgroundColor: 'var(--theme-bg-secondary)' }}>
{entries.map(([key, value]) => (
<div key={`QueryParams-${key}`}>
<dt className='dib green'>{key}:</dt>
Expand All @@ -32,9 +32,9 @@ const AnalyticType = ({ children, onChange, enabled, label, summary, exampleRequ
// show hide state. update react.
const [isOpen, setOpen] = useState(false)
return (
<section className='bg-white bb b--black-10'>
<section className='bb b--black-10' style={{ background: 'var(--theme-bg-primary)' }}>
<div className='flex items-center'>
<Checkbox className='pv3 pl3 pr1 bg-white flex-none' onChange={onChange} checked={enabled} label={
<Checkbox className='pv3 pl3 pr1 flex-none' style={{ background: 'var(--theme-bg-primary)' }} onChange={onChange} checked={enabled} label={
<span className='fw5 f6'>{label}</span>
} />
<div className='truncate fw4 f6 flex-auto charcoal-muted'>&ndash; {summary}</div>
Expand Down Expand Up @@ -119,7 +119,7 @@ const AnalyticsToggle = ({ analyticsActionsToRecord, analyticsConsent, doToggleC
<ul>
{analyticsActionsToRecord.map(name => (
<li key={name} className='mb1'>
<code className='f7 bg-snow-muted pa1 br2'>{name}</code>
<code className='f7 pa1 br2' style={{ backgroundColor: 'var(--theme-bg-secondary)', color: 'var(--theme-text-primary)' }}>{name}</code>
</li>
))}
</ul>
Expand Down
2 changes: 1 addition & 1 deletion src/components/box/Box.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const Box = ({
children
}) => {
return (
<section className={className} style={{ background: '#fbfbfb', ...style }}>
<section className={className} style={{ background: 'var(--theme-bg-secondary)', ...style }}>
<ErrorBoundary resetKeys={[globalThis.location?.pathname || '']}>
{children}
</ErrorBoundary>
Expand Down
8 changes: 4 additions & 4 deletions src/components/card/card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ export const Card: React.FC<{
}> = ({ className = '', style, children }) => {
return (
<Box
className={`ba b--black-10 br2 bg-white ${className}`}
style={{ padding: 0, ...style }}
className={`ba br2 bg-white ${className}`}
style={{ padding: 0, borderColor: 'var(--theme-border-primary)', ...style }}
>
{children}
</Box>
Expand All @@ -27,7 +27,7 @@ export const CardHeader: React.FC<{
children: React.ReactNode
}> = ({ className = '', children }) => {
return (
<div className={`pa3 bb b--black-10 ${className}`}>
<div className={`pa3 bb ${className}`} style={{ borderColor: 'var(--theme-border-primary)' }}>
{children}
</div>
)
Expand Down Expand Up @@ -97,7 +97,7 @@ export const CardFooter: React.FC<{
children: React.ReactNode
}> = ({ className = '', children }) => {
return (
<div className={`pa3 bt b--black-10 ${className}`}>
<div className={`pa3 bt ${className}`} style={{ borderColor: 'var(--theme-border-primary)' }}>
{children}
</div>
)
Expand Down
4 changes: 2 additions & 2 deletions src/components/checkbox/Checkbox.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.Checkbox > span:first-of-type {
background-color: #DDE6EB;
background-color: var(--theme-bg-secondary);
}

.Checkbox > input {
Expand All @@ -16,5 +16,5 @@
}

.Checkbox input:focus + span {
outline: 1px solid #bbb;
outline: 1px solid var(--theme-border-primary);
}
3 changes: 2 additions & 1 deletion src/components/experiments/ExperimentsPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ const Experiments = ({ doExpToggleAction, experiments, t }) => {
return (
<div
key={key}
className='pa3 mr3 mb3 mw6 br3 bg-white dib f6 ba b1 b--light-gray'
className='pa3 mr3 mb3 mw6 br3 dib f6 ba b1 b--light-gray'
style={{ background: 'var(--theme-bg-primary)' }}
>
<h3>{tkey('title', key)}</h3>
<p className='charcoal'>{tkey('description', key)}</p>
Expand Down
2 changes: 1 addition & 1 deletion src/components/ipns-manager/IpnsManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export const IpnsManager = ({ t, ipfsReady, doFetchIpnsKeys, doGenerateIpnsKey,
return (
<Fragment>
<div className="mv4 pinningManager">
<div className='ph1 ph3-l flex items-center bg-white lh-copy charcoal f6 fw5'>
<div className='ph1 ph3-l flex items-center lh-copy charcoal f6 fw5' style={{ background: 'var(--theme-bg-primary)' }}>
<AutoSizer disableHeight>
{({ width }) => (
<Table
Expand Down
9 changes: 5 additions & 4 deletions src/components/modal/modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const ModalActions: React.FC<ModalActionsProps> = ({
}) => (
<div
className={`flex justify-${justify} pa2 ${className}`}
style={{ backgroundColor: '#f4f6f8' }}
style={{ backgroundColor: 'var(--theme-bg-secondary)' }}
{...props}
>
{children}
Expand All @@ -49,8 +49,8 @@ export const ModalBody: React.FC<ModalBodyProps> = ({
<div className={`ph4 pv3 tc ${className}`} {...props}>
{Icon && (
<div
className='center bg-snow br-100 flex justify-center items-center'
style={{ width: '80px', height: '80px' }}
className='center br-100 flex justify-center items-center'
style={{ width: '80px', height: '80px', backgroundColor: 'var(--theme-bg-secondary)' }}
>
<Icon className='fill-gray w3' />
</div>
Expand Down Expand Up @@ -83,7 +83,8 @@ export const Modal: React.FC<ModalProps> = ({
>
{onCancel && (
<CancelIcon
className='absolute pointer w2 h2 top-0 right-0 fill-gray'
className='absolute pointer w2 h2 top-1 right-1'
style={{ fill: 'var(--theme-text-secondary)' }}
onClick={onCancel}
/>
)}
Expand Down
2 changes: 1 addition & 1 deletion src/components/pinning-manager/PinningManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const PinningManager = ({ pinningServices, ipfsReady, arePinningServicesS
return (
<Fragment>
<div className="mv4 pinningManager">
<div className='ph1 ph3-l flex items-center bg-white lh-copy charcoal f6 fw5'>
<div className='ph1 ph3-l flex items-center lh-copy charcoal f6 fw5' style={{ background: 'var(--theme-bg-primary)' }}>
<AutoSizer disableHeight>
{({ width }) => (
<Table
Expand Down
6 changes: 4 additions & 2 deletions src/components/popover/Popover.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import './Popover.css'

const Popover = ({ show, children, top, right, bottom, left, align, handleMouseEnter, handleMouseLeave }) => {
return (
<div className={ classNames('popover absolute bg-white shadow-3', align && `popover--align-${align}`) }
aria-hidden={ show ? 'false' : 'true' } style={{
<div className={ classNames('popover absolute shadow-3', align && `popover--align-${align}`) }
aria-hidden={ show ? 'false' : 'true' }
style={{
background: 'var(--theme-bg-modal)',
...(top && { top }),
...(right && { right }),
...(bottom && { bottom }),
Expand Down
2 changes: 1 addition & 1 deletion src/components/progress-bar/ProgressBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import './ProgressBar.css'

const ProgressBar = ({ bg, br, className, style, width, height, progress, time, ...props }) => {
return (
<div className={`ProgressBar sans-serif overflow-hidden ${br} dib ${className} ${width} ${height}`} style={{ background: '#DDE6EB', ...style }} {...props}>
<div className={`ProgressBar sans-serif overflow-hidden ${br} dib ${className} ${width} ${height}`} style={{ background: 'var(--theme-bg-secondary)', ...style }} {...props}>
{time
? <div className={`${br} h-100 ${bg}`} style={{ animation: `progressBar ${time}s ease-in-out` }} />
: <div className={`${br} h-100 ${bg}`} style={{ width: `${progress}%` }} />}
Expand Down
4 changes: 2 additions & 2 deletions src/components/radio/radio.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.Radio > span:first-of-type {
background-color: #DDE6EB;
background-color: var(--theme-bg-secondary);
}

.Radio > input {
Expand All @@ -16,5 +16,5 @@
}

.Radio input:focus + span {
outline: 1px solid #bbb;
outline: 1px solid var(--theme-border-primary);
}
Loading
Loading