feat(refactor): partially rewrite appearance settings
|
@ -73,7 +73,7 @@
|
||||||
"@hcaptcha/react-hcaptcha": "^0.3.6",
|
"@hcaptcha/react-hcaptcha": "^0.3.6",
|
||||||
"@insertish/vite-plugin-babel-macros": "^1.0.5",
|
"@insertish/vite-plugin-babel-macros": "^1.0.5",
|
||||||
"@preact/preset-vite": "^2.0.0",
|
"@preact/preset-vite": "^2.0.0",
|
||||||
"@revoltchat/ui": "1.0.54",
|
"@revoltchat/ui": "1.0.58",
|
||||||
"@rollup/plugin-replace": "^2.4.2",
|
"@rollup/plugin-replace": "^2.4.2",
|
||||||
"@styled-icons/boxicons-logos": "^10.38.0",
|
"@styled-icons/boxicons-logos": "^10.38.0",
|
||||||
"@styled-icons/boxicons-regular": "^10.38.0",
|
"@styled-icons/boxicons-regular": "^10.38.0",
|
||||||
|
|
63
src/components/settings/appearance/AdvancedOptions.tsx
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
|
||||||
|
import { Text } from "preact-i18n";
|
||||||
|
|
||||||
|
import { ObservedInputElement } from "@revoltchat/ui";
|
||||||
|
|
||||||
|
import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
|
||||||
|
|
||||||
|
import { useApplicationState } from "../../../mobx/State";
|
||||||
|
|
||||||
|
import {
|
||||||
|
MonospaceFonts,
|
||||||
|
MONOSPACE_FONTS,
|
||||||
|
MONOSPACE_FONT_KEYS,
|
||||||
|
} from "../../../context/Theme";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ! LEGACY
|
||||||
|
* Component providing a way to edit custom CSS.
|
||||||
|
*/
|
||||||
|
export const ShimThemeCustomCSS = observer(() => {
|
||||||
|
const theme = useApplicationState().settings.theme;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h3>
|
||||||
|
<Text id="app.settings.pages.appearance.custom_css" />
|
||||||
|
</h3>
|
||||||
|
<TextAreaAutoSize
|
||||||
|
maxRows={20}
|
||||||
|
minHeight={480}
|
||||||
|
code
|
||||||
|
value={theme.getCSS() ?? ""}
|
||||||
|
onChange={(ev) => theme.setCSS(ev.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function AdvancedOptions() {
|
||||||
|
const settings = useApplicationState().settings;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/** Combo box of available monospaced fonts */}
|
||||||
|
<h3>
|
||||||
|
<Text id="app.settings.pages.appearance.mono_font" />
|
||||||
|
</h3>
|
||||||
|
<ObservedInputElement
|
||||||
|
type="combo"
|
||||||
|
value={() => settings.theme.getMonospaceFont()}
|
||||||
|
onChange={(value) =>
|
||||||
|
settings.theme.setMonospaceFont(value as MonospaceFonts)
|
||||||
|
}
|
||||||
|
options={MONOSPACE_FONT_KEYS.map((value) => ({
|
||||||
|
value,
|
||||||
|
name: MONOSPACE_FONTS[value as keyof typeof MONOSPACE_FONTS]
|
||||||
|
.name,
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
{/** Custom CSS */}
|
||||||
|
<ShimThemeCustomCSS />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
63
src/components/settings/appearance/AppearanceOptions.tsx
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import { Text } from "preact-i18n";
|
||||||
|
|
||||||
|
import { Column, ObservedInputElement } from "@revoltchat/ui";
|
||||||
|
|
||||||
|
import { useApplicationState } from "../../../mobx/State";
|
||||||
|
|
||||||
|
export default function AppearanceOptions() {
|
||||||
|
const settings = useApplicationState().settings;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h3>
|
||||||
|
<Text id="app.settings.pages.appearance.appearance_options.title" />
|
||||||
|
</h3>
|
||||||
|
{/* Option to toggle "send message" button on desktop. */}
|
||||||
|
<ObservedInputElement
|
||||||
|
type="checkbox"
|
||||||
|
value={() =>
|
||||||
|
settings.get("appearance:show_send_button") ?? false
|
||||||
|
}
|
||||||
|
onChange={(v) => settings.set("appearance:show_send_button", v)}
|
||||||
|
title={
|
||||||
|
<Text id="app.settings.pages.appearance.appearance_options.show_send" />
|
||||||
|
}
|
||||||
|
description={
|
||||||
|
<Text id="app.settings.pages.appearance.appearance_options.show_send_desc" />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<hr />
|
||||||
|
<h3>
|
||||||
|
<Text id="app.settings.pages.appearance.theme_options.title" />
|
||||||
|
</h3>
|
||||||
|
<Column>
|
||||||
|
{/* Option to toggle transparency effects in-app. */}
|
||||||
|
<ObservedInputElement
|
||||||
|
type="checkbox"
|
||||||
|
value={() =>
|
||||||
|
settings.get("appearance:transparency") ?? true
|
||||||
|
}
|
||||||
|
onChange={(v) => settings.set("appearance:transparency", v)}
|
||||||
|
title={
|
||||||
|
<Text id="app.settings.pages.appearance.theme_options.transparency" />
|
||||||
|
}
|
||||||
|
description={
|
||||||
|
<Text id="app.settings.pages.appearance.theme_options.transparency_desc" />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{/* Option to toggle seasonal effects. */}
|
||||||
|
<ObservedInputElement
|
||||||
|
type="checkbox"
|
||||||
|
value={() => settings.get("appearance:seasonal") ?? true}
|
||||||
|
onChange={(v) => settings.set("appearance:seasonal", v)}
|
||||||
|
title={
|
||||||
|
<Text id="app.settings.pages.appearance.theme_options.seasonal" />
|
||||||
|
}
|
||||||
|
description={
|
||||||
|
<Text id="app.settings.pages.appearance.theme_options.seasonal_desc" />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Column>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
56
src/components/settings/appearance/ChatOptions.tsx
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
|
||||||
|
import { Text } from "preact-i18n";
|
||||||
|
|
||||||
|
import { Column, ObservedInputElement } from "@revoltchat/ui";
|
||||||
|
|
||||||
|
import { useApplicationState } from "../../../mobx/State";
|
||||||
|
|
||||||
|
import { FONTS, Fonts, FONT_KEYS } from "../../../context/Theme";
|
||||||
|
|
||||||
|
import { ShimDisplayEmoji } from "../appearance_legacy/Shims";
|
||||||
|
|
||||||
|
export default observer(() => {
|
||||||
|
const settings = useApplicationState().settings;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Column>
|
||||||
|
{/* Combo box of available fonts. */}
|
||||||
|
<h3>
|
||||||
|
<Text id="app.settings.pages.appearance.font" />
|
||||||
|
</h3>
|
||||||
|
<ObservedInputElement
|
||||||
|
type="combo"
|
||||||
|
value={() => settings.theme.getFont()}
|
||||||
|
onChange={(value) => settings.theme.setFont(value as Fonts)}
|
||||||
|
options={FONT_KEYS.map((value) => ({
|
||||||
|
value,
|
||||||
|
name: FONTS[value as keyof typeof FONTS].name,
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
{/* Option to toggle liagures for supported fonts. */}
|
||||||
|
{settings.theme.getFont() === "Inter" && (
|
||||||
|
<ObservedInputElement
|
||||||
|
type="checkbox"
|
||||||
|
value={() =>
|
||||||
|
settings.get("appearance:ligatures") ?? true
|
||||||
|
}
|
||||||
|
onChange={(v) =>
|
||||||
|
settings.set("appearance:ligatures", v)
|
||||||
|
}
|
||||||
|
title={
|
||||||
|
<Text id="app.settings.pages.appearance.ligatures" />
|
||||||
|
}
|
||||||
|
description={
|
||||||
|
<Text id="app.settings.pages.appearance.ligatures_desc" />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Column>
|
||||||
|
<hr />
|
||||||
|
{/* Emoji pack selector */}
|
||||||
|
<ShimDisplayEmoji />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
|
@ -1,290 +0,0 @@
|
||||||
import { Brush } from "@styled-icons/boxicons-solid";
|
|
||||||
import { observer } from "mobx-react-lite";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
// @ts-expect-error shade-blend-color does not have typings.
|
|
||||||
import pSBC from "shade-blend-color";
|
|
||||||
|
|
||||||
import { Text } from "preact-i18n";
|
|
||||||
|
|
||||||
import {
|
|
||||||
CategoryButton,
|
|
||||||
Checkbox,
|
|
||||||
ColourSwatches,
|
|
||||||
ComboBox,
|
|
||||||
Radio,
|
|
||||||
} from "@revoltchat/ui";
|
|
||||||
|
|
||||||
import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
|
|
||||||
|
|
||||||
import { useApplicationState } from "../../../mobx/State";
|
|
||||||
|
|
||||||
import {
|
|
||||||
Fonts,
|
|
||||||
FONTS,
|
|
||||||
FONT_KEYS,
|
|
||||||
MonospaceFonts,
|
|
||||||
MONOSPACE_FONTS,
|
|
||||||
MONOSPACE_FONT_KEYS,
|
|
||||||
} from "../../../context/Theme";
|
|
||||||
|
|
||||||
import { EmojiSelector } from "./EmojiSelector";
|
|
||||||
import { ThemeBaseSelector } from "./ThemeBaseSelector";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Component providing a way to switch the base theme being used.
|
|
||||||
*/
|
|
||||||
export const ShimThemeBaseSelector = observer(() => {
|
|
||||||
const theme = useApplicationState().settings.theme;
|
|
||||||
return (
|
|
||||||
<ThemeBaseSelector
|
|
||||||
value={theme.isModified() ? undefined : theme.getBase()}
|
|
||||||
setValue={(base) => {
|
|
||||||
theme.setBase(base);
|
|
||||||
theme.reset();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Component providing a link to the theme shop.
|
|
||||||
* Only appears if experiment is enabled.
|
|
||||||
*/
|
|
||||||
export const ShimThemeShop = () => {
|
|
||||||
return (
|
|
||||||
<Link to="/discover/themes" replace>
|
|
||||||
<CategoryButton
|
|
||||||
icon={<Brush size={24} />}
|
|
||||||
action="chevron"
|
|
||||||
description={
|
|
||||||
<Text id="app.settings.pages.appearance.discover.description" />
|
|
||||||
}>
|
|
||||||
<Text id="app.settings.pages.appearance.discover.title" />
|
|
||||||
</CategoryButton>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Component providing a way to change current accent colour.
|
|
||||||
*/
|
|
||||||
export const ShimThemeAccent = observer(() => {
|
|
||||||
const theme = useApplicationState().settings.theme;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h3>
|
|
||||||
<Text id="app.settings.pages.appearance.accent_selector" />
|
|
||||||
</h3>
|
|
||||||
<ColourSwatches
|
|
||||||
value={theme.getVariable("accent")}
|
|
||||||
onChange={(colour) => {
|
|
||||||
theme.setVariable("accent", colour as string);
|
|
||||||
theme.setVariable("scrollbar-thumb", pSBC(-0.2, colour));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Component providing a way to edit custom CSS.
|
|
||||||
*/
|
|
||||||
export const ShimThemeCustomCSS = observer(() => {
|
|
||||||
const theme = useApplicationState().settings.theme;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h3>
|
|
||||||
<Text id="app.settings.pages.appearance.custom_css" />
|
|
||||||
</h3>
|
|
||||||
<TextAreaAutoSize
|
|
||||||
maxRows={20}
|
|
||||||
minHeight={480}
|
|
||||||
code
|
|
||||||
value={theme.getCSS() ?? ""}
|
|
||||||
onChange={(ev) => theme.setCSS(ev.currentTarget.value)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Component providing a way to switch between compact and normal message view.
|
|
||||||
*/
|
|
||||||
export const ShimDisplayCompact = () => {
|
|
||||||
// TODO: WIP feature
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h3>
|
|
||||||
<Text id="app.settings.pages.appearance.message_display" />
|
|
||||||
</h3>
|
|
||||||
<div /* className={styles.display} */>
|
|
||||||
<Radio
|
|
||||||
title={
|
|
||||||
<Text id="app.settings.pages.appearance.display.default" />
|
|
||||||
}
|
|
||||||
description={
|
|
||||||
<Text id="app.settings.pages.appearance.display.default_description" />
|
|
||||||
}
|
|
||||||
value={true}
|
|
||||||
/>
|
|
||||||
<Radio
|
|
||||||
title={
|
|
||||||
<Text id="app.settings.pages.appearance.display.compact" />
|
|
||||||
}
|
|
||||||
description={
|
|
||||||
<Text id="app.settings.pages.appearance.display.compact_description" />
|
|
||||||
}
|
|
||||||
value={false}
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Component providing a way to change primary text font.
|
|
||||||
*/
|
|
||||||
export const ShimDisplayFont = observer(() => {
|
|
||||||
const theme = useApplicationState().settings.theme;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h3>
|
|
||||||
<Text id="app.settings.pages.appearance.font" />
|
|
||||||
</h3>
|
|
||||||
<ComboBox
|
|
||||||
value={theme.getFont()}
|
|
||||||
onChange={(e) => theme.setFont(e.currentTarget.value as Fonts)}>
|
|
||||||
{FONT_KEYS.map((key) => (
|
|
||||||
<option value={key} key={key}>
|
|
||||||
{FONTS[key as keyof typeof FONTS].name}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</ComboBox>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Component providing a way to change secondary, monospace text font.
|
|
||||||
*/
|
|
||||||
export const ShimDisplayMonospaceFont = observer(() => {
|
|
||||||
const theme = useApplicationState().settings.theme;
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<h3>
|
|
||||||
<Text id="app.settings.pages.appearance.mono_font" />
|
|
||||||
</h3>
|
|
||||||
<ComboBox
|
|
||||||
value={theme.getMonospaceFont()}
|
|
||||||
onChange={(e) =>
|
|
||||||
theme.setMonospaceFont(
|
|
||||||
e.currentTarget.value as MonospaceFonts,
|
|
||||||
)
|
|
||||||
}>
|
|
||||||
{MONOSPACE_FONT_KEYS.map((key) => (
|
|
||||||
<option value={key} key={key}>
|
|
||||||
{
|
|
||||||
MONOSPACE_FONTS[key as keyof typeof MONOSPACE_FONTS]
|
|
||||||
.name
|
|
||||||
}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</ComboBox>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Component providing a way to toggle font ligatures.
|
|
||||||
*/
|
|
||||||
export const ShimDisplayLigatures = observer(() => {
|
|
||||||
const settings = useApplicationState().settings;
|
|
||||||
if (settings.theme.getFont() !== "Inter") return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Checkbox
|
|
||||||
value={settings.get("appearance:ligatures") ?? false}
|
|
||||||
onChange={(v) => settings.set("appearance:ligatures", v)}
|
|
||||||
title={<Text id="app.settings.pages.appearance.ligatures" />}
|
|
||||||
description={
|
|
||||||
<Text id="app.settings.pages.appearance.ligatures_desc" />
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Component providing a way to toggle showing the send button on desktop.
|
|
||||||
*/
|
|
||||||
export const ShimShowSendButton = observer(() => {
|
|
||||||
const settings = useApplicationState().settings;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Checkbox
|
|
||||||
value={settings.get("appearance:show_send_button") ?? false}
|
|
||||||
onChange={(v) => settings.set("appearance:show_send_button", v)}
|
|
||||||
title={
|
|
||||||
<Text id="app.settings.pages.appearance.appearance_options.show_send" />
|
|
||||||
}
|
|
||||||
description={
|
|
||||||
<Text id="app.settings.pages.appearance.appearance_options.show_send_desc" />
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Component providing a way to toggle seasonal themes.
|
|
||||||
*/
|
|
||||||
export const ShimDisplaySeasonal = observer(() => {
|
|
||||||
const settings = useApplicationState().settings;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Checkbox
|
|
||||||
value={settings.get("appearance:seasonal") ?? true}
|
|
||||||
onChange={(v) => settings.set("appearance:seasonal", v)}
|
|
||||||
title={
|
|
||||||
<Text id="app.settings.pages.appearance.theme_options.seasonal" />
|
|
||||||
}
|
|
||||||
description={
|
|
||||||
<Text id="app.settings.pages.appearance.theme_options.seasonal_desc" />
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Component providing a way to toggle transparency effects.
|
|
||||||
*/
|
|
||||||
export const ShimDisplayTransparency = observer(() => {
|
|
||||||
const settings = useApplicationState().settings;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Checkbox
|
|
||||||
value={settings.get("appearance:transparency") ?? true}
|
|
||||||
onChange={(v) => settings.set("appearance:transparency", v)}
|
|
||||||
title={
|
|
||||||
<Text id="app.settings.pages.appearance.theme_options.transparency" />
|
|
||||||
}
|
|
||||||
description={
|
|
||||||
<Text id="app.settings.pages.appearance.theme_options.transparency_desc" />
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Component providing a way to change emoji pack.
|
|
||||||
*/
|
|
||||||
export const ShimDisplayEmoji = observer(() => {
|
|
||||||
const settings = useApplicationState().settings;
|
|
||||||
return (
|
|
||||||
<EmojiSelector
|
|
||||||
value={settings.get("appearance:emoji")}
|
|
||||||
setValue={(v) => settings.set("appearance:emoji", v)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
|
@ -1,170 +1,11 @@
|
||||||
import { Pencil } from "@styled-icons/boxicons-regular";
|
import Overrides from "./legacy/ThemeOverrides";
|
||||||
import { observer } from "mobx-react-lite";
|
import ThemeTools from "./legacy/ThemeTools";
|
||||||
import styled from "styled-components/macro";
|
|
||||||
|
|
||||||
import { InputBox } from "@revoltchat/ui";
|
|
||||||
|
|
||||||
import { useDebounceCallback } from "../../../lib/debounce";
|
|
||||||
|
|
||||||
import { useApplicationState } from "../../../mobx/State";
|
|
||||||
|
|
||||||
import { Variables } from "../../../context/Theme";
|
|
||||||
|
|
||||||
const Container = styled.div`
|
|
||||||
row-gap: 8px;
|
|
||||||
display: grid;
|
|
||||||
column-gap: 16px;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
||||||
margin-bottom: 20px;
|
|
||||||
|
|
||||||
.entry {
|
|
||||||
padding: 12px;
|
|
||||||
margin-top: 8px;
|
|
||||||
border: 1px solid black;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
|
|
||||||
span {
|
|
||||||
flex: 1;
|
|
||||||
display: block;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
text-transform: capitalize;
|
|
||||||
|
|
||||||
background: inherit;
|
|
||||||
background-clip: text;
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
}
|
|
||||||
|
|
||||||
.override {
|
|
||||||
gap: 8px;
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
.picker {
|
|
||||||
width: 38px;
|
|
||||||
height: 38px;
|
|
||||||
display: grid;
|
|
||||||
cursor: pointer;
|
|
||||||
place-items: center;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
background: var(--primary-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="text"] {
|
|
||||||
width: 0;
|
|
||||||
min-width: 0;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.input {
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
input {
|
|
||||||
opacity: 0;
|
|
||||||
border: none;
|
|
||||||
display: block;
|
|
||||||
cursor: pointer;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
top: 48px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default observer(() => {
|
|
||||||
const theme = useApplicationState().settings.theme;
|
|
||||||
const setVariable = useDebounceCallback(
|
|
||||||
(data) => {
|
|
||||||
const { key, value } = data as { key: Variables; value: string };
|
|
||||||
theme.setVariable(key, value);
|
|
||||||
},
|
|
||||||
[theme],
|
|
||||||
100,
|
|
||||||
);
|
|
||||||
|
|
||||||
|
export default function ThemeOverrides() {
|
||||||
return (
|
return (
|
||||||
<Container>
|
<>
|
||||||
{(
|
<ThemeTools />
|
||||||
[
|
<Overrides />
|
||||||
"accent",
|
</>
|
||||||
"background",
|
|
||||||
"foreground",
|
|
||||||
"primary-background",
|
|
||||||
"primary-header",
|
|
||||||
"secondary-background",
|
|
||||||
"secondary-foreground",
|
|
||||||
"secondary-header",
|
|
||||||
"tertiary-background",
|
|
||||||
"tertiary-foreground",
|
|
||||||
"block",
|
|
||||||
"message-box",
|
|
||||||
"mention",
|
|
||||||
"scrollbar-thumb",
|
|
||||||
"scrollbar-track",
|
|
||||||
"status-online",
|
|
||||||
"status-away",
|
|
||||||
"status-busy",
|
|
||||||
"status-streaming",
|
|
||||||
"status-invisible",
|
|
||||||
"success",
|
|
||||||
"warning",
|
|
||||||
"error",
|
|
||||||
"hover",
|
|
||||||
] as const
|
|
||||||
).map((key) => (
|
|
||||||
<div
|
|
||||||
className="entry"
|
|
||||||
key={key}
|
|
||||||
style={{ backgroundColor: theme.getVariable(key) }}>
|
|
||||||
<div className="input">
|
|
||||||
<input
|
|
||||||
type="color"
|
|
||||||
value={theme.getVariable(key)}
|
|
||||||
onChange={(el) =>
|
|
||||||
setVariable({
|
|
||||||
key,
|
|
||||||
value: el.currentTarget.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
color: theme.getContrastingVariable(
|
|
||||||
key,
|
|
||||||
theme.getVariable("primary-background"),
|
|
||||||
),
|
|
||||||
}}>
|
|
||||||
{key}
|
|
||||||
</span>
|
|
||||||
<div className="override">
|
|
||||||
<div
|
|
||||||
className="picker"
|
|
||||||
onClick={(e) =>
|
|
||||||
e.currentTarget.parentElement?.parentElement
|
|
||||||
?.querySelector("input")
|
|
||||||
?.click()
|
|
||||||
}>
|
|
||||||
<Pencil size={24} />
|
|
||||||
</div>
|
|
||||||
<InputBox
|
|
||||||
type="text"
|
|
||||||
className="text"
|
|
||||||
value={theme.getVariable(key)}
|
|
||||||
onChange={(el) =>
|
|
||||||
setVariable({
|
|
||||||
key,
|
|
||||||
value: el.currentTarget.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</Container>
|
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
64
src/components/settings/appearance/ThemeSelection.tsx
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import { Brush } from "@styled-icons/boxicons-solid";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
// @ts-expect-error shade-blend-color does not have typings.
|
||||||
|
import pSBC from "shade-blend-color";
|
||||||
|
|
||||||
|
import { Text } from "preact-i18n";
|
||||||
|
|
||||||
|
import { CategoryButton, ObservedInputElement } from "@revoltchat/ui";
|
||||||
|
|
||||||
|
import { useApplicationState } from "../../../mobx/State";
|
||||||
|
|
||||||
|
import { ThemeBaseSelector } from "./legacy/ThemeBaseSelector";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ! LEGACY
|
||||||
|
* Component providing a way to switch the base theme being used.
|
||||||
|
*/
|
||||||
|
export const ShimThemeBaseSelector = observer(() => {
|
||||||
|
const theme = useApplicationState().settings.theme;
|
||||||
|
return (
|
||||||
|
<ThemeBaseSelector
|
||||||
|
value={theme.isModified() ? undefined : theme.getBase()}
|
||||||
|
setValue={(base) => {
|
||||||
|
theme.setBase(base);
|
||||||
|
theme.reset();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function ThemeSelection() {
|
||||||
|
const theme = useApplicationState().settings.theme;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/** Allow users to change base theme */}
|
||||||
|
<ShimThemeBaseSelector />
|
||||||
|
{/** Provide a link to the theme shop */}
|
||||||
|
<Link to="/discover/themes" replace>
|
||||||
|
<CategoryButton
|
||||||
|
icon={<Brush size={24} />}
|
||||||
|
action="chevron"
|
||||||
|
description={
|
||||||
|
<Text id="app.settings.pages.appearance.discover.description" />
|
||||||
|
}>
|
||||||
|
<Text id="app.settings.pages.appearance.discover.title" />
|
||||||
|
</CategoryButton>
|
||||||
|
</Link>
|
||||||
|
<hr />
|
||||||
|
<h3>
|
||||||
|
<Text id="app.settings.pages.appearance.accent_selector" />
|
||||||
|
</h3>
|
||||||
|
<ObservedInputElement
|
||||||
|
type="colour"
|
||||||
|
value={theme.getVariable("accent")}
|
||||||
|
onChange={(colour) => {
|
||||||
|
theme.setVariable("accent", colour as string);
|
||||||
|
theme.setVariable("scrollbar-thumb", pSBC(-0.2, colour));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ import notoSVG from "./assets/noto_emoji.svg";
|
||||||
import openmojiSVG from "./assets/openmoji_emoji.svg";
|
import openmojiSVG from "./assets/openmoji_emoji.svg";
|
||||||
import twemojiSVG from "./assets/twemoji_emoji.svg";
|
import twemojiSVG from "./assets/twemoji_emoji.svg";
|
||||||
|
|
||||||
import { EmojiPack } from "../../common/Emoji";
|
import { EmojiPack } from "../../../common/Emoji";
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
gap: 12px;
|
gap: 12px;
|
1
src/components/settings/appearance/legacy/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
These components need to be ported to @revoltchat/ui.
|
170
src/components/settings/appearance/legacy/ThemeOverrides.tsx
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
import { Pencil } from "@styled-icons/boxicons-regular";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import styled from "styled-components/macro";
|
||||||
|
|
||||||
|
import { InputBox } from "@revoltchat/ui";
|
||||||
|
|
||||||
|
import { useDebounceCallback } from "../../../../lib/debounce";
|
||||||
|
|
||||||
|
import { useApplicationState } from "../../../../mobx/State";
|
||||||
|
|
||||||
|
import { Variables } from "../../../../context/Theme";
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
row-gap: 8px;
|
||||||
|
display: grid;
|
||||||
|
column-gap: 16px;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.entry {
|
||||||
|
padding: 12px;
|
||||||
|
margin-top: 8px;
|
||||||
|
border: 1px solid black;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
|
||||||
|
span {
|
||||||
|
flex: 1;
|
||||||
|
display: block;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
text-transform: capitalize;
|
||||||
|
|
||||||
|
background: inherit;
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.override {
|
||||||
|
gap: 8px;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.picker {
|
||||||
|
width: 38px;
|
||||||
|
height: 38px;
|
||||||
|
display: grid;
|
||||||
|
cursor: pointer;
|
||||||
|
place-items: center;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
background: var(--primary-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"] {
|
||||||
|
width: 0;
|
||||||
|
min-width: 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
input {
|
||||||
|
opacity: 0;
|
||||||
|
border: none;
|
||||||
|
display: block;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
top: 48px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default observer(() => {
|
||||||
|
const theme = useApplicationState().settings.theme;
|
||||||
|
const setVariable = useDebounceCallback(
|
||||||
|
(data) => {
|
||||||
|
const { key, value } = data as { key: Variables; value: string };
|
||||||
|
theme.setVariable(key, value);
|
||||||
|
},
|
||||||
|
[theme],
|
||||||
|
100,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
{(
|
||||||
|
[
|
||||||
|
"accent",
|
||||||
|
"background",
|
||||||
|
"foreground",
|
||||||
|
"primary-background",
|
||||||
|
"primary-header",
|
||||||
|
"secondary-background",
|
||||||
|
"secondary-foreground",
|
||||||
|
"secondary-header",
|
||||||
|
"tertiary-background",
|
||||||
|
"tertiary-foreground",
|
||||||
|
"block",
|
||||||
|
"message-box",
|
||||||
|
"mention",
|
||||||
|
"scrollbar-thumb",
|
||||||
|
"scrollbar-track",
|
||||||
|
"status-online",
|
||||||
|
"status-away",
|
||||||
|
"status-busy",
|
||||||
|
"status-streaming",
|
||||||
|
"status-invisible",
|
||||||
|
"success",
|
||||||
|
"warning",
|
||||||
|
"error",
|
||||||
|
"hover",
|
||||||
|
] as const
|
||||||
|
).map((key) => (
|
||||||
|
<div
|
||||||
|
className="entry"
|
||||||
|
key={key}
|
||||||
|
style={{ backgroundColor: theme.getVariable(key) }}>
|
||||||
|
<div className="input">
|
||||||
|
<input
|
||||||
|
type="color"
|
||||||
|
value={theme.getVariable(key)}
|
||||||
|
onChange={(el) =>
|
||||||
|
setVariable({
|
||||||
|
key,
|
||||||
|
value: el.currentTarget.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
color: theme.getContrastingVariable(
|
||||||
|
key,
|
||||||
|
theme.getVariable("primary-background"),
|
||||||
|
),
|
||||||
|
}}>
|
||||||
|
{key}
|
||||||
|
</span>
|
||||||
|
<div className="override">
|
||||||
|
<div
|
||||||
|
className="picker"
|
||||||
|
onClick={(e) =>
|
||||||
|
e.currentTarget.parentElement?.parentElement
|
||||||
|
?.querySelector("input")
|
||||||
|
?.click()
|
||||||
|
}>
|
||||||
|
<Pencil size={24} />
|
||||||
|
</div>
|
||||||
|
<InputBox
|
||||||
|
type="text"
|
||||||
|
className="text"
|
||||||
|
value={theme.getVariable(key)}
|
||||||
|
onChange={(el) =>
|
||||||
|
setVariable({
|
||||||
|
key,
|
||||||
|
value: el.currentTarget.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
});
|
|
@ -5,11 +5,11 @@ import { Text } from "preact-i18n";
|
||||||
|
|
||||||
import { Button } from "@revoltchat/ui";
|
import { Button } from "@revoltchat/ui";
|
||||||
|
|
||||||
import { useApplicationState } from "../../../mobx/State";
|
import { useApplicationState } from "../../../../mobx/State";
|
||||||
|
|
||||||
import { useIntermediate } from "../../../context/intermediate/Intermediate";
|
import { useIntermediate } from "../../../../context/intermediate/Intermediate";
|
||||||
|
|
||||||
import Tooltip from "../../common/Tooltip";
|
import Tooltip from "../../../common/Tooltip";
|
||||||
|
|
||||||
const Actions = styled.div`
|
const Actions = styled.div`
|
||||||
gap: 8px;
|
gap: 8px;
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.7 KiB |
|
@ -3,65 +3,33 @@ import { observer } from "mobx-react-lite";
|
||||||
import styles from "./Panes.module.scss";
|
import styles from "./Panes.module.scss";
|
||||||
import { Text } from "preact-i18n";
|
import { Text } from "preact-i18n";
|
||||||
|
|
||||||
import { Column } from "@revoltchat/ui";
|
|
||||||
|
|
||||||
import CollapsibleSection from "../../../components/common/CollapsibleSection";
|
import CollapsibleSection from "../../../components/common/CollapsibleSection";
|
||||||
import {
|
import AdvancedOptions from "../../../components/settings/appearance/AdvancedOptions";
|
||||||
ShimThemeBaseSelector,
|
import AppearanceOptions from "../../../components/settings/appearance/AppearanceOptions";
|
||||||
ShimThemeShop,
|
import ChatOptions from "../../../components/settings/appearance/ChatOptions";
|
||||||
ShimThemeAccent,
|
|
||||||
ShimDisplayFont,
|
|
||||||
ShimDisplayMonospaceFont,
|
|
||||||
ShimDisplayLigatures,
|
|
||||||
ShimDisplayEmoji,
|
|
||||||
ShimThemeCustomCSS,
|
|
||||||
ShimDisplaySeasonal,
|
|
||||||
ShimDisplayTransparency,
|
|
||||||
ShimShowSendButton,
|
|
||||||
} from "../../../components/settings/appearance/Shims";
|
|
||||||
import ThemeOverrides from "../../../components/settings/appearance/ThemeOverrides";
|
import ThemeOverrides from "../../../components/settings/appearance/ThemeOverrides";
|
||||||
import ThemeTools from "../../../components/settings/appearance/ThemeTools";
|
import ThemeSelection from "../../../components/settings/appearance/ThemeSelection";
|
||||||
|
|
||||||
export const Appearance = observer(() => {
|
export const Appearance = observer(() => {
|
||||||
return (
|
return (
|
||||||
<div className={styles.appearance}>
|
<div className={styles.appearance}>
|
||||||
<ShimThemeBaseSelector />
|
<ThemeSelection />
|
||||||
<ShimThemeShop />
|
|
||||||
<hr />
|
<hr />
|
||||||
<ShimThemeAccent />
|
<AppearanceOptions />
|
||||||
<hr />
|
<hr />
|
||||||
<h3>
|
<ChatOptions />
|
||||||
<Text id="app.settings.pages.appearance.appearance_options.title" />
|
|
||||||
</h3>
|
|
||||||
<ShimShowSendButton />
|
|
||||||
<hr />
|
|
||||||
<h3>
|
|
||||||
<Text id="app.settings.pages.appearance.theme_options.title" />
|
|
||||||
</h3>
|
|
||||||
<Column>
|
|
||||||
<ShimDisplayTransparency />
|
|
||||||
<ShimDisplaySeasonal />
|
|
||||||
</Column>
|
|
||||||
<hr />
|
|
||||||
<ShimDisplayFont />
|
|
||||||
<ShimDisplayLigatures />
|
|
||||||
<hr />
|
|
||||||
<ShimDisplayEmoji />
|
|
||||||
<hr />
|
<hr />
|
||||||
<CollapsibleSection
|
<CollapsibleSection
|
||||||
defaultValue={false}
|
defaultValue={false}
|
||||||
id="settings_overrides"
|
id="settings_overrides"
|
||||||
summary={<Text id="app.settings.pages.appearance.overrides" />}>
|
summary={<Text id="app.settings.pages.appearance.overrides" />}>
|
||||||
<ThemeTools />
|
|
||||||
<h3>App</h3>
|
|
||||||
<ThemeOverrides />
|
<ThemeOverrides />
|
||||||
</CollapsibleSection>
|
</CollapsibleSection>
|
||||||
<CollapsibleSection
|
<CollapsibleSection
|
||||||
id="settings_advanced_appearance"
|
id="settings_advanced_appearance"
|
||||||
defaultValue={false}
|
defaultValue={false}
|
||||||
summary={<Text id="app.settings.pages.appearance.advanced" />}>
|
summary={<Text id="app.settings.pages.appearance.advanced" />}>
|
||||||
<ShimDisplayMonospaceFont />
|
<AdvancedOptions />
|
||||||
<ShimThemeCustomCSS />
|
|
||||||
</CollapsibleSection>
|
</CollapsibleSection>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
10
yarn.lock
|
@ -2231,9 +2231,9 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@revoltchat/ui@npm:1.0.54":
|
"@revoltchat/ui@npm:1.0.58":
|
||||||
version: 1.0.54
|
version: 1.0.58
|
||||||
resolution: "@revoltchat/ui@npm:1.0.54"
|
resolution: "@revoltchat/ui@npm:1.0.58"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@styled-icons/boxicons-logos": ^10.38.0
|
"@styled-icons/boxicons-logos": ^10.38.0
|
||||||
"@styled-icons/boxicons-regular": ^10.38.0
|
"@styled-icons/boxicons-regular": ^10.38.0
|
||||||
|
@ -2247,7 +2247,7 @@ __metadata:
|
||||||
react-virtuoso: ^2.12.0
|
react-virtuoso: ^2.12.0
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
revolt.js: "*"
|
revolt.js: "*"
|
||||||
checksum: 57014aa30f36272825b34e2dfa327b17f3e2b4efeba78f48e1805b5b30027f7dad4431f1c852816762e10643a9ef53b51a211d48ffc3bd14d07966fd5b3b183f
|
checksum: 1ebdb3963c77fbad11427963e27f1be1beb480e80360a59f06a3a2fd2d3f5e335ff33fbb8bf99f533549696848ae1d1db5072ff4d45e98d25c9b1b372f25d795
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
@ -3554,7 +3554,7 @@ __metadata:
|
||||||
"@hcaptcha/react-hcaptcha": ^0.3.6
|
"@hcaptcha/react-hcaptcha": ^0.3.6
|
||||||
"@insertish/vite-plugin-babel-macros": ^1.0.5
|
"@insertish/vite-plugin-babel-macros": ^1.0.5
|
||||||
"@preact/preset-vite": ^2.0.0
|
"@preact/preset-vite": ^2.0.0
|
||||||
"@revoltchat/ui": 1.0.54
|
"@revoltchat/ui": 1.0.58
|
||||||
"@rollup/plugin-replace": ^2.4.2
|
"@rollup/plugin-replace": ^2.4.2
|
||||||
"@styled-icons/boxicons-logos": ^10.38.0
|
"@styled-icons/boxicons-logos": ^10.38.0
|
||||||
"@styled-icons/boxicons-regular": ^10.38.0
|
"@styled-icons/boxicons-regular": ^10.38.0
|
||||||
|
|