feat(refactor): partially rewrite appearance settings

This commit is contained in:
Paul Makles 2022-07-05 15:49:08 +01:00
parent a24e027a48
commit 23dec32476
20 changed files with 443 additions and 507 deletions

View file

@ -73,7 +73,7 @@
"@hcaptcha/react-hcaptcha": "^0.3.6",
"@insertish/vite-plugin-babel-macros": "^1.0.5",
"@preact/preset-vite": "^2.0.0",
"@revoltchat/ui": "1.0.54",
"@revoltchat/ui": "1.0.58",
"@rollup/plugin-replace": "^2.4.2",
"@styled-icons/boxicons-logos": "^10.38.0",
"@styled-icons/boxicons-regular": "^10.38.0",

View 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 />
</>
);
}

View 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>
</>
);
}

View 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 />
</>
);
});

View file

@ -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)}
/>
);
});

View file

@ -1,170 +1,11 @@
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,
);
import Overrides from "./legacy/ThemeOverrides";
import ThemeTools from "./legacy/ThemeTools";
export default function ThemeOverrides() {
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>
<>
<ThemeTools />
<Overrides />
</>
);
});
}

View 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));
}}
/>
</>
);
}

View file

@ -7,7 +7,7 @@ import notoSVG from "./assets/noto_emoji.svg";
import openmojiSVG from "./assets/openmoji_emoji.svg";
import twemojiSVG from "./assets/twemoji_emoji.svg";
import { EmojiPack } from "../../common/Emoji";
import { EmojiPack } from "../../../common/Emoji";
const Container = styled.div`
gap: 12px;

View file

@ -0,0 +1 @@
These components need to be ported to @revoltchat/ui.

View 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>
);
});

View file

@ -5,11 +5,11 @@ import { Text } from "preact-i18n";
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`
gap: 8px;

View file

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View file

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View file

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View file

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View file

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View file

@ -3,65 +3,33 @@ import { observer } from "mobx-react-lite";
import styles from "./Panes.module.scss";
import { Text } from "preact-i18n";
import { Column } from "@revoltchat/ui";
import CollapsibleSection from "../../../components/common/CollapsibleSection";
import {
ShimThemeBaseSelector,
ShimThemeShop,
ShimThemeAccent,
ShimDisplayFont,
ShimDisplayMonospaceFont,
ShimDisplayLigatures,
ShimDisplayEmoji,
ShimThemeCustomCSS,
ShimDisplaySeasonal,
ShimDisplayTransparency,
ShimShowSendButton,
} from "../../../components/settings/appearance/Shims";
import AdvancedOptions from "../../../components/settings/appearance/AdvancedOptions";
import AppearanceOptions from "../../../components/settings/appearance/AppearanceOptions";
import ChatOptions from "../../../components/settings/appearance/ChatOptions";
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(() => {
return (
<div className={styles.appearance}>
<ShimThemeBaseSelector />
<ShimThemeShop />
<ThemeSelection />
<hr />
<ShimThemeAccent />
<AppearanceOptions />
<hr />
<h3>
<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 />
<ChatOptions />
<hr />
<CollapsibleSection
defaultValue={false}
id="settings_overrides"
summary={<Text id="app.settings.pages.appearance.overrides" />}>
<ThemeTools />
<h3>App</h3>
<ThemeOverrides />
</CollapsibleSection>
<CollapsibleSection
id="settings_advanced_appearance"
defaultValue={false}
summary={<Text id="app.settings.pages.appearance.advanced" />}>
<ShimDisplayMonospaceFont />
<ShimThemeCustomCSS />
<AdvancedOptions />
</CollapsibleSection>
</div>
);

View file

@ -2231,9 +2231,9 @@ __metadata:
languageName: node
linkType: hard
"@revoltchat/ui@npm:1.0.54":
version: 1.0.54
resolution: "@revoltchat/ui@npm:1.0.54"
"@revoltchat/ui@npm:1.0.58":
version: 1.0.58
resolution: "@revoltchat/ui@npm:1.0.58"
dependencies:
"@styled-icons/boxicons-logos": ^10.38.0
"@styled-icons/boxicons-regular": ^10.38.0
@ -2247,7 +2247,7 @@ __metadata:
react-virtuoso: ^2.12.0
peerDependencies:
revolt.js: "*"
checksum: 57014aa30f36272825b34e2dfa327b17f3e2b4efeba78f48e1805b5b30027f7dad4431f1c852816762e10643a9ef53b51a211d48ffc3bd14d07966fd5b3b183f
checksum: 1ebdb3963c77fbad11427963e27f1be1beb480e80360a59f06a3a2fd2d3f5e335ff33fbb8bf99f533549696848ae1d1db5072ff4d45e98d25c9b1b372f25d795
languageName: node
linkType: hard
@ -3554,7 +3554,7 @@ __metadata:
"@hcaptcha/react-hcaptcha": ^0.3.6
"@insertish/vite-plugin-babel-macros": ^1.0.5
"@preact/preset-vite": ^2.0.0
"@revoltchat/ui": 1.0.54
"@revoltchat/ui": 1.0.58
"@rollup/plugin-replace": ^2.4.2
"@styled-icons/boxicons-logos": ^10.38.0
"@styled-icons/boxicons-regular": ^10.38.0