pl-fe: Allow adjusting interface size
Signed-off-by: Nicole Mikołajczyk <git@mkljczk.pl>
This commit is contained in:
@ -59,12 +59,15 @@ const Avatar = (props: IAvatar) => {
|
||||
}).catch(() => setColor(undefined));
|
||||
}, [src, isCat]);
|
||||
|
||||
const style: React.CSSProperties = React.useMemo(() => ({
|
||||
width: size,
|
||||
height: size,
|
||||
fontSize: size,
|
||||
color,
|
||||
}), [size, color]);
|
||||
const style: React.CSSProperties = React.useMemo(() => {
|
||||
const value = `${size / 16}rem`;
|
||||
return {
|
||||
width: value,
|
||||
height: value,
|
||||
fontSize: value,
|
||||
color,
|
||||
};
|
||||
}, [size, color]);
|
||||
|
||||
if (disableUserProvidedMedia) {
|
||||
if (isAvatarMissing || !alt || isDefaultAvatar(src)) return null;
|
||||
|
||||
81
packages/pl-fe/src/components/ui/step-slider.tsx
Normal file
81
packages/pl-fe/src/components/ui/step-slider.tsx
Normal file
@ -0,0 +1,81 @@
|
||||
import throttle from 'lodash/throttle';
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
import { getPointerPosition } from 'pl-fe/features/video';
|
||||
|
||||
interface IStepSlider {
|
||||
/** Value between 0 and the amount of steps minus one. */
|
||||
value: number;
|
||||
/** Steps available in the slider. */
|
||||
steps: number;
|
||||
/** Callback when the value changes. */
|
||||
onChange(value: number): void;
|
||||
}
|
||||
|
||||
/** Slider allowing selecting integers in a given range. */
|
||||
const StepSlider: React.FC<IStepSlider> = ({ value, steps, onChange }) => {
|
||||
const node = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleMouseDown: React.MouseEventHandler = e => {
|
||||
document.addEventListener('mousemove', handleMouseSlide, true);
|
||||
document.addEventListener('mouseup', handleMouseUp, true);
|
||||
document.addEventListener('touchmove', handleMouseSlide, true);
|
||||
document.addEventListener('touchend', handleMouseUp, true);
|
||||
|
||||
handleMouseSlide(e);
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
document.removeEventListener('mousemove', handleMouseSlide, true);
|
||||
document.removeEventListener('mouseup', handleMouseUp, true);
|
||||
document.removeEventListener('touchmove', handleMouseSlide, true);
|
||||
document.removeEventListener('touchend', handleMouseUp, true);
|
||||
};
|
||||
|
||||
const handleMouseSlide = throttle(e => {
|
||||
if (node.current) {
|
||||
const { x } = getPointerPosition(node.current, e);
|
||||
|
||||
if (!isNaN(x)) {
|
||||
let slideamt = x;
|
||||
|
||||
if (x > 1) {
|
||||
slideamt = 1;
|
||||
} else if (x < 0) {
|
||||
slideamt = 0;
|
||||
}
|
||||
|
||||
slideamt = Math.floor((slideamt + 0.5 / steps) * (steps - 1));
|
||||
onChange(slideamt);
|
||||
}
|
||||
}
|
||||
}, 60);
|
||||
|
||||
return (
|
||||
<div
|
||||
className='relative inline-flex h-6 cursor-pointer transition'
|
||||
onMouseDown={handleMouseDown}
|
||||
ref={node}
|
||||
>
|
||||
<div className='absolute top-1/2 h-1 w-full -translate-y-1/2 rounded-full bg-primary-200 dark:bg-primary-700' />
|
||||
<div className='absolute top-1/2 h-1 -translate-y-1/2 rounded-full bg-accent-500' style={{ width: `${value / (steps - 1) * 100}%` }} />
|
||||
{[...Array(steps).fill(undefined)].map((_, step) => (
|
||||
<span
|
||||
key={step}
|
||||
className='absolute top-1/2 z-10 h-3 w-1 -translate-y-1/2 bg-accent-300'
|
||||
style={{ left: `${(step) / (steps - 1) * 100}%` }}
|
||||
/>
|
||||
))}
|
||||
<span
|
||||
className='absolute top-1/2 z-10 -ml-1.5 size-3 -translate-y-1/2 rounded-full bg-accent-500 shadow'
|
||||
tabIndex={0}
|
||||
style={{ left: `${value / (steps - 1) * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { StepSlider as default };
|
||||
@ -7,6 +7,7 @@ import List, { ListItem } from 'pl-fe/components/list';
|
||||
import Button from 'pl-fe/components/ui/button';
|
||||
import Form from 'pl-fe/components/ui/form';
|
||||
import HStack from 'pl-fe/components/ui/hstack';
|
||||
import StepSlider from 'pl-fe/components/ui/step-slider';
|
||||
import { Mutliselect, SelectDropdown } from 'pl-fe/features/forms';
|
||||
import SettingToggle from 'pl-fe/features/settings/components/setting-toggle';
|
||||
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
||||
@ -90,6 +91,8 @@ const languages = {
|
||||
|
||||
type Language = keyof typeof languages;
|
||||
|
||||
const INTERFACE_SIZES = ['sm', 'md', 'lg', 'xl'] as const;
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.preferences', defaultMessage: 'Preferences' },
|
||||
displayPostsDefault: { id: 'preferences.fields.display_media.default', defaultMessage: 'Hide posts marked as sensitive' },
|
||||
@ -139,6 +142,11 @@ const Preferences = () => {
|
||||
debouncedSave(dispatch);
|
||||
};
|
||||
|
||||
const onInterfaceSizeChange = (value: number) => {
|
||||
dispatch(changeSetting(['theme', 'interfaceSize'], INTERFACE_SIZES[value], { showAlert: true, save: false }));
|
||||
debouncedSave(dispatch);
|
||||
};
|
||||
|
||||
const onThemeReset = () => {
|
||||
dispatch(changeSetting(['themeMode'], plFeConfig.defaultSettings.themeMode, { save: false }));
|
||||
dispatch(changeSetting(['theme', 'brandColor'], undefined, { showAlert: true }));
|
||||
@ -192,6 +200,11 @@ const Preferences = () => {
|
||||
onChange={(palette) => onBrandColorChange(palette['500'])}
|
||||
allowTintChange={false}
|
||||
/>
|
||||
<ListItem label={<div className='whitespace-nowrap'><FormattedMessage id='preferences.fields.interface_size' defaultMessage='Interface size' /></div>}>
|
||||
<div className='flex w-full flex-col'>
|
||||
<StepSlider value={INTERFACE_SIZES.indexOf(settings.theme?.interfaceSize || 'md')} steps={4} onChange={onInterfaceSizeChange} />
|
||||
</div>
|
||||
</ListItem>
|
||||
</List>
|
||||
|
||||
<HStack justifyContent='end'>
|
||||
|
||||
@ -15,7 +15,7 @@ const Helmet = React.lazy(() => import('pl-fe/components/helmet'));
|
||||
const PlFeHead = () => {
|
||||
const locale = useLocale();
|
||||
const direction = useLocaleDirection(locale);
|
||||
const { reduceMotion, underlineLinks, demetricator, systemFont } = useSettings();
|
||||
const { reduceMotion, underlineLinks, demetricator, systemFont, theme: themeSettings } = useSettings();
|
||||
const plFeConfig = usePlFeConfig();
|
||||
const theme = useTheme();
|
||||
|
||||
@ -40,7 +40,13 @@ const PlFeHead = () => {
|
||||
|
||||
return (
|
||||
<Helmet>
|
||||
<html lang={locale} className={clsx('h-full', { 'dark': theme === 'dark', 'dark black': theme === 'black' })} />
|
||||
<html
|
||||
lang={locale}
|
||||
className={clsx('h-full', `text-${themeSettings?.interfaceSize || 'md'}`, {
|
||||
'dark': theme === 'dark',
|
||||
'dark black': theme === 'black',
|
||||
})}
|
||||
/>
|
||||
<body className={bodyClass} dir={direction} />
|
||||
{themeCss && <style id='theme' type='text/css'>{`:root{${themeCss}}`}</style>}
|
||||
{['dark', 'black'].includes(theme) && <style type='text/css'>{':root { color-scheme: dark; }'}</style>}
|
||||
|
||||
@ -1345,6 +1345,7 @@
|
||||
"preferences.fields.display_media.hide_all": "Always hide media posts",
|
||||
"preferences.fields.display_media.show_all": "Always show posts",
|
||||
"preferences.fields.implicit_addressing_label": "Include mentions in post content when replying",
|
||||
"preferences.fields.interface_size": "Interface size",
|
||||
"preferences.fields.known_languages_label": "Languages you know",
|
||||
"preferences.fields.language_label": "Display language",
|
||||
"preferences.fields.media_display_label": "Sensitive content",
|
||||
|
||||
@ -254,7 +254,7 @@ interface IPaletteListItem {
|
||||
|
||||
/** Palette editor inside a ListItem. */
|
||||
const PaletteListItem: React.FC<IPaletteListItem> = ({ label, palette, onChange, resetKey, allowTintChange }) => typeof palette === 'string' ? null : (
|
||||
<ListItem label={<div className='w-20'>{label}</div>}>
|
||||
<ListItem label={<div className='whitespace-nowrap'>{label}</div>}>
|
||||
<Palette palette={palette} onChange={onChange} resetKey={resetKey} allowTintChange={allowTintChange} />
|
||||
</ListItem>
|
||||
);
|
||||
|
||||
@ -55,6 +55,7 @@ const settingsSchema = v.object({
|
||||
brandColor: v.fallback(v.string(), ''),
|
||||
accentColor: v.fallback(v.string(), ''),
|
||||
colors: v.any(),
|
||||
interfaceSize: v.fallback(v.picklist(['sm', 'md', 'lg', 'xl']), 'md'),
|
||||
})), undefined),
|
||||
|
||||
systemFont: v.fallback(v.boolean(), false),
|
||||
|
||||
Reference in New Issue
Block a user