mirror of
https://github.com/mue/mue.git
synced 2026-07-25 17:51:14 +02:00
refactor(weather options): New tile-focused approach to options pages
Co-authored-by: David Ralph <me@davidcralph.co.uk>
This commit is contained in:
@@ -170,7 +170,10 @@ const TabNavbar = ({ modalClose }) => {
|
|||||||
onClick={() => setSubSection('')}
|
onClick={() => setSubSection('')}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'text-xl capitalize tracking-normal transition-all duration-150 ease-in-out',
|
'text-xl capitalize tracking-normal transition-all duration-150 ease-in-out',
|
||||||
{ 'text-neutral-300 cursor-pointer hover:text-neutral-100': subSection !== '' },
|
{
|
||||||
|
'dark:text-neutral-300 text-neutral-600 cursor-pointer hover:text-black dark:hover:text-neutral-100':
|
||||||
|
subSection !== '',
|
||||||
|
},
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{subTab}
|
{subTab}
|
||||||
@@ -218,8 +221,8 @@ const TabNavbar = ({ modalClose }) => {
|
|||||||
setSelectedCollection(null);
|
setSelectedCollection(null);
|
||||||
}}
|
}}
|
||||||
className={`${
|
className={`${
|
||||||
activeTab === tab.id ? '' : 'hover:text-white/70'
|
activeTab === tab.id ? '' : 'dark:hover:text-white/70 hover:text-black/70'
|
||||||
} transition-all duration-800 ease-in-out flex flex-row gap-2 items-center relative rounded-sm px-3 py-1.5 text-sm text-white outline-sky-400 transition focus-visible:outline-2`}
|
} dark:text-white text-black transition-all duration-800 ease-in-out flex flex-row gap-2 items-center relative rounded-sm px-3 py-1.5 text-sm outline-sky-400 transition focus-visible:outline-2`}
|
||||||
style={{
|
style={{
|
||||||
WebkitTapHighlightColor: 'transparent',
|
WebkitTapHighlightColor: 'transparent',
|
||||||
}}
|
}}
|
||||||
@@ -235,7 +238,7 @@ const TabNavbar = ({ modalClose }) => {
|
|||||||
{variables.getMessage(`modals.main.navbar.${tab.id}`)}
|
{variables.getMessage(`modals.main.navbar.${tab.id}`)}
|
||||||
{tab.id === 'addons' && (
|
{tab.id === 'addons' && (
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
<div className="px-3 py-1 bg-[#424242] rounded-lg text-xs">
|
<div className="px-3 py-1 dark:bg-[#424242] bg-neutral-300 rounded-lg text-xs">
|
||||||
<motion.span
|
<motion.span
|
||||||
key={installedItems.length}
|
key={installedItems.length}
|
||||||
initial={{ opacity: 0, y: -10 }}
|
initial={{ opacity: 0, y: -10 }}
|
||||||
@@ -248,7 +251,7 @@ const TabNavbar = ({ modalClose }) => {
|
|||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
)}
|
)}
|
||||||
{tab.id === 'marketplace' && (
|
{tab.id === 'marketplace' && (
|
||||||
<span className="px-3 py-1 bg-rose-800 rounded-lg text-xs border border-rose-700">
|
<span className="px-3 py-1 bg-rose-800 rounded-lg text-xs border border-rose-700 text-white">
|
||||||
NEW
|
NEW
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -59,17 +59,19 @@ function SliderComponent(props) {
|
|||||||
{variables.getMessage('settings:buttons.reset')}
|
{variables.getMessage('settings:buttons.reset')}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<Slider
|
<div class="mx-auto">
|
||||||
value={Number(value)}
|
<Slider
|
||||||
onChange={handleChange}
|
value={Number(value)}
|
||||||
valueLabelDisplay="auto"
|
onChange={handleChange}
|
||||||
default={Number(props.default)}
|
valueLabelDisplay="auto"
|
||||||
min={Number(props.min)}
|
default={Number(props.default)}
|
||||||
max={Number(props.max)}
|
min={Number(props.min)}
|
||||||
step={Number(props.step) || 1}
|
max={Number(props.max)}
|
||||||
getAriaValueText={(value) => `${value}`}
|
step={Number(props.step) || 1}
|
||||||
marks={props.marks || []}
|
getAriaValueText={(value) => `${value}`}
|
||||||
/>
|
marks={props.marks || []}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
99
src/components/Layout/Settings/Hero/Hero.jsx
Normal file
99
src/components/Layout/Settings/Hero/Hero.jsx
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import variables from 'config/variables';
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import EventBus from 'utils/eventbus';
|
||||||
|
import { Button } from 'components/Elements';
|
||||||
|
import { MdFlag, MdOutlineVisibilityOff, MdOutlineVisibility } from 'react-icons/md';
|
||||||
|
import Slider from '../../../Form/Settings/Slider/Slider';
|
||||||
|
import values from 'utils/data/slider_values.json';
|
||||||
|
|
||||||
|
const Preview = (props) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1 className="py-3 uppercase tracking-tight text-neutral-300">Preview</h1>
|
||||||
|
<div className="bg-modal-content-light dark:bg-modal-content-dark p-10 rounded">
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Controls = (props) => {
|
||||||
|
const [setting, setSetting] = useState(localStorage.getItem(props.setting) === 'true');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSetting(localStorage.getItem(props.setting) === 'true');
|
||||||
|
}, [props.setting]);
|
||||||
|
|
||||||
|
const changeSetting = () => {
|
||||||
|
const toggle = localStorage.getItem(props.setting) === 'true';
|
||||||
|
localStorage.setItem(props.setting, !toggle);
|
||||||
|
setSetting(!toggle);
|
||||||
|
|
||||||
|
variables.stats.postEvent(
|
||||||
|
'setting',
|
||||||
|
`${props.name} ${setting === true ? 'enabled' : 'disabled'}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
EventBus.emit('toggle', props.setting);
|
||||||
|
|
||||||
|
if (props.element) {
|
||||||
|
if (!document.querySelector(props.element)) {
|
||||||
|
document.querySelector('.reminder-info').style.display = 'flex';
|
||||||
|
return localStorage.setItem('showReminder', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EventBus.emit('refresh', props.category);
|
||||||
|
};
|
||||||
|
|
||||||
|
const VisibilityToggle = () => (
|
||||||
|
<Button
|
||||||
|
type="settings"
|
||||||
|
onClick={changeSetting}
|
||||||
|
icon={setting ? <MdOutlineVisibilityOff /> : <MdOutlineVisibility />}
|
||||||
|
label={setting ? 'Hide' : 'Show'}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const ReportButton = () => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
type="settings"
|
||||||
|
onClick={() =>
|
||||||
|
window.open(variables.constants.BUG_REPORT + props.title.split(' ').join('+'), '_blank')
|
||||||
|
}
|
||||||
|
icon={<MdFlag />}
|
||||||
|
label={variables.getMessage('settings:sections.header.report_issue')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1 className="py-3 uppercase tracking-tight text-neutral-300">Controls</h1>
|
||||||
|
<div className="bg-modal-content-light dark:bg-modal-content-dark p-10 rounded flex flex-col gap-10">
|
||||||
|
{props.visibilityToggle && <VisibilityToggle />}
|
||||||
|
{props.report !== false && <ReportButton />}
|
||||||
|
{(props.zoomSetting !== null || props.zoomSetting !== '') && (
|
||||||
|
<Slider
|
||||||
|
name={props.zoomSetting}
|
||||||
|
min="10"
|
||||||
|
max="400"
|
||||||
|
default="100"
|
||||||
|
display="%"
|
||||||
|
marks={values.zoom}
|
||||||
|
category={props.zoomCategory || props.category}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Hero = (props) => {
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-2 w-full rounded gap-10 auto-rows-[1fr] ">{props.children}</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { Hero, Preview, Controls };
|
||||||
1
src/components/Layout/Settings/Hero/index.jsx
Normal file
1
src/components/Layout/Settings/Hero/index.jsx
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './Hero';
|
||||||
@@ -32,7 +32,7 @@ const PreferencesWrapper = ({ children, ...props }) => {
|
|||||||
<div
|
<div
|
||||||
className={`preferences transition-opacity duration-700 ease-in-out ${
|
className={`preferences transition-opacity duration-700 ease-in-out ${
|
||||||
shown ? 'opacity-100 pointer-events-auto' : 'opacity-50 pointer-events-none'
|
shown ? 'opacity-100 pointer-events-auto' : 'opacity-50 pointer-events-none'
|
||||||
} bg-modal-content-light dark:bg-modal-content-dark p-10 rounded divide-y divide-gray-500`}
|
} bg-modal-content-light dark:bg-modal-content-dark p-10 rounded`}
|
||||||
>
|
>
|
||||||
{props.zoomSetting && (
|
{props.zoomSetting && (
|
||||||
<Row>
|
<Row>
|
||||||
|
|||||||
144
src/features/appearance/Appearance.jsx
Normal file
144
src/features/appearance/Appearance.jsx
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
import { memo, useState } from 'react';
|
||||||
|
|
||||||
|
import variables from 'config/variables';
|
||||||
|
import EventBus from 'utils/eventbus';
|
||||||
|
|
||||||
|
import { Checkbox, Dropdown, Radio, Slider, Text } from 'components/Form/Settings';
|
||||||
|
import {
|
||||||
|
Header,
|
||||||
|
Section,
|
||||||
|
Row,
|
||||||
|
Content,
|
||||||
|
Action,
|
||||||
|
PreferencesWrapper,
|
||||||
|
} from 'components/Layout/Settings';
|
||||||
|
import { useTab } from 'components/Elements/MainModal/backend/TabContext';
|
||||||
|
|
||||||
|
import { AccessibilityOptions, FontOptions } from './sections/';
|
||||||
|
|
||||||
|
import { MdAccessibility, MdFormatSize } from 'react-icons/md';
|
||||||
|
|
||||||
|
import values from 'utils/data/slider_values.json';
|
||||||
|
|
||||||
|
function AppearanceOptions() {
|
||||||
|
const [accessibility, setAccessibility] = useState(false);
|
||||||
|
const [currentTheme, setCurrentTheme] = useState(localStorage.getItem('theme') || 'auto');
|
||||||
|
const { subSection } = useTab();
|
||||||
|
|
||||||
|
const WidgetStyle = () => {
|
||||||
|
return (
|
||||||
|
<Row final={true}>
|
||||||
|
<Content
|
||||||
|
title={variables.getMessage('settings:sections.appearance.style.title')}
|
||||||
|
subtitle={variables.getMessage('settings:sections.appearance.style.description')}
|
||||||
|
/>
|
||||||
|
<Action>
|
||||||
|
<Radio
|
||||||
|
name="widgetStyle"
|
||||||
|
element=".other"
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
name: variables.getMessage('settings:sections.appearance.style.legacy'),
|
||||||
|
value: 'legacy',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: variables.getMessage('settings:sections.appearance.style.new'),
|
||||||
|
value: 'new',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
category="widgets"
|
||||||
|
/>
|
||||||
|
</Action>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ThemeSelector = (currentTheme) => {
|
||||||
|
const themes = [
|
||||||
|
{
|
||||||
|
example: 'light.jpg',
|
||||||
|
name: variables.getMessage('settings:sections.appearance.theme.light'),
|
||||||
|
value: 'light',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
example: 'dark.jpg',
|
||||||
|
name: variables.getMessage('settings:sections.appearance.theme.dark'),
|
||||||
|
value: 'dark',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
example: 'light.jpg',
|
||||||
|
name: variables.getMessage('settings:sections.appearance.theme.auto'),
|
||||||
|
value: 'auto',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const ThemeCard = ({ example, name, value }) => {
|
||||||
|
const handleClick = () => {
|
||||||
|
localStorage.setItem('theme', value);
|
||||||
|
setCurrentTheme(value);
|
||||||
|
EventBus.emit('refresh', 'other');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
'flex flex-col items-center rounded p-2 hover:bg-background-light hover:dark:bg-background-dark cursor-pointer' +
|
||||||
|
(currentTheme === value ? ' border-2 border-neutral-100' : '')
|
||||||
|
}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
<img src={`theme-examples/${example}`} alt="Light theme" className="rounded-t" />
|
||||||
|
<div className="py-4">{name}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-modal-content-light dark:bg-modal-content-dark p-10 rounded divide-gray-500 flex flex-col gap-5">
|
||||||
|
<h1 className="text-3xl tracking-tight font-semibold">
|
||||||
|
{variables.getMessage('settings:sections.appearance.theme.title')}
|
||||||
|
</h1>
|
||||||
|
<p className="text-neutral-800 dark:text-neutral-300">
|
||||||
|
{variables.getMessage('settings:sections.appearance.theme.description')}
|
||||||
|
</p>
|
||||||
|
<div className="grid grid-cols-3 items-center gap-28">
|
||||||
|
{themes.map((theme) => (
|
||||||
|
<ThemeCard key={theme.value} {...theme} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{subSection === 'font' && <FontOptions />}
|
||||||
|
{subSection === 'accessibility' && <AccessibilityOptions />}
|
||||||
|
{subSection === '' && (
|
||||||
|
<>
|
||||||
|
<Section
|
||||||
|
id="accessibility"
|
||||||
|
title={variables.getMessage('settings:sections.appearance.accessibility.title')}
|
||||||
|
subtitle={variables.getMessage(
|
||||||
|
'settings:sections.appearance.accessibility.description',
|
||||||
|
)}
|
||||||
|
icon={<MdAccessibility />}
|
||||||
|
/>
|
||||||
|
<Section
|
||||||
|
id="font"
|
||||||
|
title={variables.getMessage('settings:sections.appearance.font.title')}
|
||||||
|
subtitle={variables.getMessage('settings:sections.appearance.font.description')}
|
||||||
|
icon={<MdFormatSize />}
|
||||||
|
/>
|
||||||
|
{ThemeSelector(currentTheme)}
|
||||||
|
<PreferencesWrapper>
|
||||||
|
<WidgetStyle />
|
||||||
|
</PreferencesWrapper>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const MemoizedAppearanceOptions = memo(AppearanceOptions);
|
||||||
|
export { MemoizedAppearanceOptions as default, MemoizedAppearanceOptions as AppearanceOptions };
|
||||||
1
src/features/appearance/index.jsx
Normal file
1
src/features/appearance/index.jsx
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './Appearance';
|
||||||
63
src/features/appearance/sections/Accessibility.jsx
Normal file
63
src/features/appearance/sections/Accessibility.jsx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import variables from 'config/variables';
|
||||||
|
import { Checkbox, Dropdown, Slider } from 'components/Form/Settings';
|
||||||
|
import values from 'utils/data/slider_values.json';
|
||||||
|
import { Row, Content, Action } from 'components/Layout/Settings';
|
||||||
|
|
||||||
|
const AccessibilityOptions = () => {
|
||||||
|
return (
|
||||||
|
<Row final={true}>
|
||||||
|
<Content
|
||||||
|
title={variables.getMessage('settings:sections.appearance.accessibility.title')}
|
||||||
|
subtitle={variables.getMessage('settings:sections.appearance.accessibility.description')}
|
||||||
|
/>
|
||||||
|
<Action>
|
||||||
|
<Dropdown
|
||||||
|
label={variables.getMessage(
|
||||||
|
'settings:sections.appearance.accessibility.text_shadow.title',
|
||||||
|
)}
|
||||||
|
name="textBorder"
|
||||||
|
category="other"
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
value: 'new',
|
||||||
|
text: variables.getMessage(
|
||||||
|
'settings:sections.appearance.accessibility.text_shadow.new',
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'true',
|
||||||
|
text: variables.getMessage(
|
||||||
|
'settings:sections.appearance.accessibility.text_shadow.old',
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'none',
|
||||||
|
text: variables.getMessage(
|
||||||
|
'settings:sections.appearance.accessibility.text_shadow.none',
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
text={variables.getMessage('settings:sections.appearance.accessibility.animations')}
|
||||||
|
name="animations"
|
||||||
|
category="other"
|
||||||
|
/>
|
||||||
|
<Slider
|
||||||
|
title={variables.getMessage('settings:sections.appearance.accessibility.toast_duration')}
|
||||||
|
name="toastDisplayTime"
|
||||||
|
default="2500"
|
||||||
|
step="100"
|
||||||
|
min="500"
|
||||||
|
max="5000"
|
||||||
|
marks={values.toast}
|
||||||
|
display={
|
||||||
|
' ' + variables.getMessage('settings:sections.appearance.accessibility.milliseconds')
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Action>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { AccessibilityOptions as default, AccessibilityOptions };
|
||||||
113
src/features/appearance/sections/FontOptions.jsx
Normal file
113
src/features/appearance/sections/FontOptions.jsx
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import variables from 'config/variables';
|
||||||
|
import { Checkbox, Dropdown, Text } from 'components/Form/Settings';
|
||||||
|
import { Row, Content, Action, PreferencesWrapper } from 'components/Layout/Settings';
|
||||||
|
|
||||||
|
const FontOptions = () => {
|
||||||
|
const fontWeight = 'settings:sections.appearance.font.weight';
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PreferencesWrapper>
|
||||||
|
<Row>
|
||||||
|
<Content
|
||||||
|
title={variables.getMessage('settings:sections.appearance.font.title')}
|
||||||
|
subtitle={variables.getMessage('settings:sections.appearance.font.description')}
|
||||||
|
/>
|
||||||
|
<Action>
|
||||||
|
<Checkbox
|
||||||
|
name="fontGoogle"
|
||||||
|
text={variables.getMessage('settings:sections.appearance.font.google')}
|
||||||
|
category="other"
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
title={variables.getMessage('settings:sections.appearance.font.custom')}
|
||||||
|
name="font"
|
||||||
|
upperCaseFirst={true}
|
||||||
|
category="other"
|
||||||
|
/>
|
||||||
|
</Action>
|
||||||
|
</Row>
|
||||||
|
</PreferencesWrapper>
|
||||||
|
<PreferencesWrapper>
|
||||||
|
<Row>
|
||||||
|
<Content
|
||||||
|
title={variables.getMessage('settings:sections.appearance.font.title')}
|
||||||
|
subtitle={variables.getMessage('settings:sections.appearance.font.description')}
|
||||||
|
/>
|
||||||
|
<Action>
|
||||||
|
{/* names are taken from https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight */}
|
||||||
|
<Dropdown
|
||||||
|
label={variables.getMessage('settings:sections.appearance.font.weight.title')}
|
||||||
|
name="fontweight"
|
||||||
|
category="other"
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
value: '100',
|
||||||
|
text: variables.getMessage(fontWeight + '.thin'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '200',
|
||||||
|
text: variables.getMessage(fontWeight + '.extra_light'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '300',
|
||||||
|
text: variables.getMessage(fontWeight + '.light'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '400',
|
||||||
|
text: variables.getMessage(fontWeight + '.normal'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '500',
|
||||||
|
text: variables.getMessage(fontWeight + '.medium'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '600',
|
||||||
|
text: variables.getMessage(fontWeight + '.semi_bold'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '700',
|
||||||
|
text: variables.getMessage(fontWeight + '.bold'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '800',
|
||||||
|
text: variables.getMessage(fontWeight + '.extra_bold'),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Action>
|
||||||
|
</Row>
|
||||||
|
</PreferencesWrapper>
|
||||||
|
<PreferencesWrapper>
|
||||||
|
<Row>
|
||||||
|
<Content
|
||||||
|
title={variables.getMessage('settings:sections.appearance.font.title')}
|
||||||
|
subtitle={variables.getMessage('settings:sections.appearance.font.description')}
|
||||||
|
/>
|
||||||
|
<Action>
|
||||||
|
<Dropdown
|
||||||
|
label={variables.getMessage('settings:sections.appearance.font.style.title')}
|
||||||
|
name="fontstyle"
|
||||||
|
category="other"
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
value: 'normal',
|
||||||
|
text: variables.getMessage('settings:sections.appearance.font.style.normal'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'italic',
|
||||||
|
text: variables.getMessage('settings:sections.appearance.font.style.italic'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'oblique',
|
||||||
|
text: variables.getMessage('settings:sections.appearance.font.style.oblique'),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Action>
|
||||||
|
</Row>
|
||||||
|
</PreferencesWrapper>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { FontOptions as default, FontOptions };
|
||||||
2
src/features/appearance/sections/index.jsx
Normal file
2
src/features/appearance/sections/index.jsx
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './Accessibility';
|
||||||
|
export * from './FontOptions';
|
||||||
@@ -1,327 +0,0 @@
|
|||||||
import { memo, useState } from 'react';
|
|
||||||
|
|
||||||
import variables from 'config/variables';
|
|
||||||
|
|
||||||
import { Checkbox, Dropdown, Radio, Slider, Text } from 'components/Form/Settings';
|
|
||||||
import {
|
|
||||||
Header,
|
|
||||||
Section,
|
|
||||||
Row,
|
|
||||||
Content,
|
|
||||||
Action,
|
|
||||||
PreferencesWrapper,
|
|
||||||
} from 'components/Layout/Settings';
|
|
||||||
import { useTab } from 'components/Elements/MainModal/backend/TabContext';
|
|
||||||
|
|
||||||
import { MdAccessibility } from 'react-icons/md';
|
|
||||||
|
|
||||||
import values from 'utils/data/slider_values.json';
|
|
||||||
|
|
||||||
function AppearanceOptions() {
|
|
||||||
const [accessibility, setAccessibility] = useState(false);
|
|
||||||
const [currentTheme, setCurrentTheme] = useState(localStorage.getItem('theme') || 'auto');
|
|
||||||
const { subSection } = useTab();
|
|
||||||
|
|
||||||
const ThemeSelection = () => {
|
|
||||||
return (
|
|
||||||
<Row>
|
|
||||||
<Content
|
|
||||||
title={variables.getMessage('settings:sections.appearance.theme.title')}
|
|
||||||
subtitle={variables.getMessage('settings:sections.appearance.theme.description')}
|
|
||||||
/>
|
|
||||||
<Action>
|
|
||||||
<Radio
|
|
||||||
name="theme"
|
|
||||||
options={[
|
|
||||||
{
|
|
||||||
name: variables.getMessage('settings:sections.appearance.theme.auto'),
|
|
||||||
value: 'auto',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: variables.getMessage('settings:sections.appearance.theme.light'),
|
|
||||||
value: 'light',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: variables.getMessage('settings:sections.appearance.theme.dark'),
|
|
||||||
value: 'dark',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
category="other"
|
|
||||||
/>
|
|
||||||
</Action>
|
|
||||||
</Row>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const FontOptions = () => {
|
|
||||||
const fontWeight = 'settings:sections.appearance.font.weight';
|
|
||||||
return (
|
|
||||||
<Row>
|
|
||||||
<Content
|
|
||||||
title={variables.getMessage('settings:sections.appearance.font.title')}
|
|
||||||
subtitle={variables.getMessage('settings:sections.appearance.font.description')}
|
|
||||||
/>
|
|
||||||
<Action>
|
|
||||||
<Checkbox
|
|
||||||
name="fontGoogle"
|
|
||||||
text={variables.getMessage('settings:sections.appearance.font.google')}
|
|
||||||
category="other"
|
|
||||||
/>
|
|
||||||
<Text
|
|
||||||
title={variables.getMessage('settings:sections.appearance.font.custom')}
|
|
||||||
name="font"
|
|
||||||
upperCaseFirst={true}
|
|
||||||
category="other"
|
|
||||||
/>
|
|
||||||
{/* names are taken from https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight */}
|
|
||||||
<Dropdown
|
|
||||||
label={variables.getMessage('settings:sections.appearance.font.weight.title')}
|
|
||||||
name="fontweight"
|
|
||||||
category="other"
|
|
||||||
items={[
|
|
||||||
{
|
|
||||||
value: '100',
|
|
||||||
text: variables.getMessage(fontWeight + '.thin'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: '200',
|
|
||||||
text: variables.getMessage(fontWeight + '.extra_light'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: '300',
|
|
||||||
text: variables.getMessage(fontWeight + '.light'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: '400',
|
|
||||||
text: variables.getMessage(fontWeight + '.normal'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: '500',
|
|
||||||
text: variables.getMessage(fontWeight + '.medium'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: '600',
|
|
||||||
text: variables.getMessage(fontWeight + '.semi_bold'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: '700',
|
|
||||||
text: variables.getMessage(fontWeight + '.bold'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: '800',
|
|
||||||
text: variables.getMessage(fontWeight + '.extra_bold'),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<Dropdown
|
|
||||||
label={variables.getMessage('settings:sections.appearance.font.style.title')}
|
|
||||||
name="fontstyle"
|
|
||||||
category="other"
|
|
||||||
items={[
|
|
||||||
{
|
|
||||||
value: 'normal',
|
|
||||||
text: variables.getMessage('settings:sections.appearance.font.style.normal'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'italic',
|
|
||||||
text: variables.getMessage('settings:sections.appearance.font.style.italic'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'oblique',
|
|
||||||
text: variables.getMessage('settings:sections.appearance.font.style.oblique'),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</Action>
|
|
||||||
</Row>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const WidgetStyle = () => {
|
|
||||||
return (
|
|
||||||
<Row final={true}>
|
|
||||||
<Content
|
|
||||||
title={variables.getMessage('settings:sections.appearance.style.title')}
|
|
||||||
subtitle={variables.getMessage('settings:sections.appearance.style.description')}
|
|
||||||
/>
|
|
||||||
<Action>
|
|
||||||
<Radio
|
|
||||||
name="widgetStyle"
|
|
||||||
element=".other"
|
|
||||||
options={[
|
|
||||||
{
|
|
||||||
name: variables.getMessage('settings:sections.appearance.style.legacy'),
|
|
||||||
value: 'legacy',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: variables.getMessage('settings:sections.appearance.style.new'),
|
|
||||||
value: 'new',
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
category="widgets"
|
|
||||||
/>
|
|
||||||
</Action>
|
|
||||||
</Row>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const AccessibilityOptions = () => {
|
|
||||||
return (
|
|
||||||
<Row final={true}>
|
|
||||||
<Content
|
|
||||||
title={variables.getMessage('settings:sections.appearance.accessibility.title')}
|
|
||||||
subtitle={variables.getMessage('settings:sections.appearance.accessibility.description')}
|
|
||||||
/>
|
|
||||||
<Action>
|
|
||||||
<Dropdown
|
|
||||||
label={variables.getMessage(
|
|
||||||
'settings:sections.appearance.accessibility.text_shadow.title',
|
|
||||||
)}
|
|
||||||
name="textBorder"
|
|
||||||
category="other"
|
|
||||||
items={[
|
|
||||||
{
|
|
||||||
value: 'new',
|
|
||||||
text: variables.getMessage(
|
|
||||||
'settings:sections.appearance.accessibility.text_shadow.new',
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'true',
|
|
||||||
text: variables.getMessage(
|
|
||||||
'settings:sections.appearance.accessibility.text_shadow.old',
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'none',
|
|
||||||
text: variables.getMessage(
|
|
||||||
'settings:sections.appearance.accessibility.text_shadow.none',
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<Checkbox
|
|
||||||
text={variables.getMessage('settings:sections.appearance.accessibility.animations')}
|
|
||||||
name="animations"
|
|
||||||
category="other"
|
|
||||||
/>
|
|
||||||
<Slider
|
|
||||||
title={variables.getMessage(
|
|
||||||
'settings:sections.appearance.accessibility.toast_duration',
|
|
||||||
)}
|
|
||||||
name="toastDisplayTime"
|
|
||||||
default="2500"
|
|
||||||
step="100"
|
|
||||||
min="500"
|
|
||||||
max="5000"
|
|
||||||
marks={values.toast}
|
|
||||||
display={
|
|
||||||
' ' + variables.getMessage('settings:sections.appearance.accessibility.milliseconds')
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Action>
|
|
||||||
</Row>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const newThemeSelector = (currentTheme) => {
|
|
||||||
const themes = [
|
|
||||||
{
|
|
||||||
example: 'light.jpg',
|
|
||||||
name: variables.getMessage('settings:sections.appearance.theme.light'),
|
|
||||||
value: 'light',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
example: 'dark.jpg',
|
|
||||||
name: variables.getMessage('settings:sections.appearance.theme.dark'),
|
|
||||||
value: 'dark',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
example: 'light.jpg',
|
|
||||||
name: variables.getMessage('settings:sections.appearance.theme.auto'),
|
|
||||||
value: 'auto',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
console.log(currentTheme);
|
|
||||||
|
|
||||||
const ThemeCard = ({ example, name, value }) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
class={
|
|
||||||
'flex flex-col items-center rounded p-2 hover:bg-background-light hover:dark:bg-background-dark cursor-pointer' +
|
|
||||||
(currentTheme === value ? ' border-2 border-neutral-100' : '')
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<img src={`theme-examples/${example}`} alt="Light theme" class="rounded-t" />
|
|
||||||
<div class="py-4">{name}</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class="bg-modal-content-light dark:bg-modal-content-dark p-10 rounded divide-gray-500 flex flex-col gap-5">
|
|
||||||
<h1 class="text-3xl tracking-tight font-semibold">
|
|
||||||
{variables.getMessage('settings:sections.appearance.theme.title')}
|
|
||||||
</h1>
|
|
||||||
<p class="text-neutral-300">
|
|
||||||
{variables.getMessage('settings:sections.appearance.theme.description')}
|
|
||||||
</p>
|
|
||||||
<div class="grid grid-cols-3 items-center gap-28">
|
|
||||||
{themes.map((theme) => (
|
|
||||||
<ThemeCard key={theme.value} {...theme} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
let header;
|
|
||||||
if (accessibility) {
|
|
||||||
header = (
|
|
||||||
<Header
|
|
||||||
title={variables.getMessage('settings:sections.appearance.title')}
|
|
||||||
secondaryTitle={variables.getMessage('settings:sections.appearance.accessibility.title')}
|
|
||||||
goBack={() => setAccessibility(false)}
|
|
||||||
report={false}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
header = (
|
|
||||||
<Header title={variables.getMessage('settings:sections.appearance.title')} report={false} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{/*{header}*/}
|
|
||||||
{subSection === 'accessibility' ? (
|
|
||||||
<AccessibilityOptions />
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Section
|
|
||||||
id="accessibility"
|
|
||||||
title={variables.getMessage('settings:sections.appearance.accessibility.title')}
|
|
||||||
subtitle={variables.getMessage(
|
|
||||||
'settings:sections.appearance.accessibility.description',
|
|
||||||
)}
|
|
||||||
icon={<MdAccessibility />}
|
|
||||||
onClick={() => setAccessibility(true)}
|
|
||||||
/>
|
|
||||||
{newThemeSelector(currentTheme)}
|
|
||||||
<PreferencesWrapper>
|
|
||||||
<ThemeSelection />
|
|
||||||
</PreferencesWrapper>
|
|
||||||
<PreferencesWrapper>
|
|
||||||
<FontOptions />
|
|
||||||
</PreferencesWrapper>
|
|
||||||
<PreferencesWrapper>
|
|
||||||
<WidgetStyle />
|
|
||||||
</PreferencesWrapper>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const MemoizedAppearanceOptions = memo(AppearanceOptions);
|
|
||||||
export { MemoizedAppearanceOptions as default, MemoizedAppearanceOptions as AppearanceOptions };
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
export * from './About';
|
export * from './About';
|
||||||
export * from './Advanced';
|
export * from './Advanced';
|
||||||
export * from './Appearance';
|
|
||||||
export * from './Changelog';
|
export * from './Changelog';
|
||||||
export * from './Experimental';
|
export * from './Experimental';
|
||||||
export * from './Language';
|
export * from './Language';
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ import { MessageOptions } from 'features/message';
|
|||||||
import { BackgroundOptions } from 'features/background';
|
import { BackgroundOptions } from 'features/background';
|
||||||
import { SearchOptions } from 'features/search';
|
import { SearchOptions } from 'features/search';
|
||||||
import { WeatherOptions } from 'features/weather';
|
import { WeatherOptions } from 'features/weather';
|
||||||
|
import { AppearanceOptions } from 'features/appearance';
|
||||||
import { Stats } from 'features/stats';
|
import { Stats } from 'features/stats';
|
||||||
import {
|
import {
|
||||||
About,
|
About,
|
||||||
AdvancedOptions,
|
AdvancedOptions,
|
||||||
AppearanceOptions,
|
|
||||||
Changelog,
|
Changelog,
|
||||||
ExperimentalOptions,
|
ExperimentalOptions,
|
||||||
LanguageOptions,
|
LanguageOptions,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import defaults from './options/default';
|
|||||||
|
|
||||||
import './weather.scss';
|
import './weather.scss';
|
||||||
|
|
||||||
const WeatherWidget = () => {
|
const WeatherWidget = (props) => {
|
||||||
const [location, setLocation] = useState(localStorage.getItem('location') || defaults.location);
|
const [location, setLocation] = useState(localStorage.getItem('location') || defaults.location);
|
||||||
const [done, setDone] = useState(false);
|
const [done, setDone] = useState(false);
|
||||||
const [weatherData, setWeatherData] = useState(null);
|
const [weatherData, setWeatherData] = useState(null);
|
||||||
@@ -48,7 +48,7 @@ const WeatherWidget = () => {
|
|||||||
const weatherType = localStorage.getItem('weatherType') || defaults.weatherType;
|
const weatherType = localStorage.getItem('weatherType') || defaults.weatherType;
|
||||||
|
|
||||||
if (!done) {
|
if (!done) {
|
||||||
return <WeatherSkeleton weatherType={weatherType} />;
|
return <WeatherSkeleton weatherType={weatherType} preview={true} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!weatherData) {
|
if (!weatherData) {
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
|
||||||
function WeatherSkeleton({ weatherType }) {
|
function WeatherSkeleton({ weatherType, preview }) {
|
||||||
return (
|
return (
|
||||||
<div className="weather skeleton">
|
<div
|
||||||
|
className="weather skeleton"
|
||||||
|
style={{
|
||||||
|
position: preview ? 'relative' : '',
|
||||||
|
right: preview ? '0' : '',
|
||||||
|
bottom: preview ? '0' : '',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div className="weatherCore">
|
<div className="weatherCore">
|
||||||
<div className="iconAndTemps">
|
<div className="iconAndTemps">
|
||||||
<div className="weathericon">
|
<div className="weathericon">
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ import { MdAutoAwesome } from 'react-icons/md';
|
|||||||
import { Header, Row, Content, Action, PreferencesWrapper } from 'components/Layout/Settings';
|
import { Header, Row, Content, Action, PreferencesWrapper } from 'components/Layout/Settings';
|
||||||
import { useLocalStorageState } from 'utils/useLocalStorageState';
|
import { useLocalStorageState } from 'utils/useLocalStorageState';
|
||||||
import { Radio, Dropdown, Checkbox } from 'components/Form/Settings';
|
import { Radio, Dropdown, Checkbox } from 'components/Form/Settings';
|
||||||
|
import { Hero, Preview, Controls } from 'components/Layout/Settings/Hero';
|
||||||
import { TextField } from '@mui/material';
|
import { TextField } from '@mui/material';
|
||||||
|
import { WeatherWidget } from 'features/weather';
|
||||||
import variables from 'config/variables';
|
import variables from 'config/variables';
|
||||||
|
|
||||||
const useWeatherSettings = () => {
|
const useWeatherSettings = () => {
|
||||||
@@ -176,26 +178,46 @@ const WeatherOptions = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header
|
{/*<Header
|
||||||
title={variables.getMessage(`${WEATHER_SECTION}.title`)}
|
title={variables.getMessage(`${WEATHER_SECTION}.title`)}
|
||||||
setting="weatherEnabled"
|
setting="weatherEnabled"
|
||||||
category="widgets"
|
category="widgets"
|
||||||
zoomSetting="zoomWeather"
|
zoomSetting="zoomWeather"
|
||||||
zoomCategory="weather"
|
zoomCategory="weather"
|
||||||
visibilityToggle={true}
|
visibilityToggle={true}
|
||||||
/>
|
/>*/}
|
||||||
<PreferencesWrapper
|
<Hero>
|
||||||
|
<Preview>
|
||||||
|
<WeatherWidget />
|
||||||
|
</Preview>
|
||||||
|
<Controls
|
||||||
|
setting="weatherEnabled"
|
||||||
|
category="widgets"
|
||||||
|
zoomSetting="zoomWeather"
|
||||||
|
zoomCategory="weather"
|
||||||
|
visibilityToggle={true}
|
||||||
|
/>
|
||||||
|
</Hero>
|
||||||
|
{/*<PreferencesWrapper
|
||||||
setting="weatherEnabled"
|
setting="weatherEnabled"
|
||||||
zoomSetting="zoomWeather"
|
zoomSetting="zoomWeather"
|
||||||
zoomCategory="weather"
|
zoomCategory="weather"
|
||||||
visibilityToggle={true}
|
visibilityToggle={true}
|
||||||
>
|
/>*/}
|
||||||
|
<h1 className="py-3 uppercase tracking-tight text-neutral-300">options</h1>
|
||||||
|
<PreferencesWrapper setting="weatherEnabled">
|
||||||
<WidgetType />
|
<WidgetType />
|
||||||
{/* https://stackoverflow.com/a/65328486 when using inputs it may defocus so we do the {} instead of <> */}
|
|
||||||
{LocationSetting()}
|
|
||||||
<TemperatureFormat />
|
|
||||||
{weatherType === '4' && <CustomOptions />}
|
|
||||||
</PreferencesWrapper>
|
</PreferencesWrapper>
|
||||||
|
{/* https://stackoverflow.com/a/65328486 when using inputs it may defocus so we do the {} instead of <> */}
|
||||||
|
<PreferencesWrapper setting="weatherEnabled">{LocationSetting()}</PreferencesWrapper>
|
||||||
|
<PreferencesWrapper setting="weatherEnabled">
|
||||||
|
<TemperatureFormat />
|
||||||
|
</PreferencesWrapper>
|
||||||
|
{weatherType === '4' && (
|
||||||
|
<PreferencesWrapper setting="weatherEnabled">
|
||||||
|
<CustomOptions />
|
||||||
|
</PreferencesWrapper>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
131
vite.config.mjs.timestamp-1724429169339-d5c13b36ff5e7.mjs
Normal file
131
vite.config.mjs.timestamp-1724429169339-d5c13b36ff5e7.mjs
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user