mirror of
https://github.com/Vencord/Vesktop.git
synced 2025-02-22 21:35:08 +00:00
clean up and optimise Screenshare UI & CSS
This commit is contained in:
parent
67a1847cea
commit
00fb658355
4 changed files with 136 additions and 140 deletions
|
@ -23,11 +23,13 @@ import { Node } from "@vencord/venmic";
|
||||||
import type { Dispatch, SetStateAction } from "react";
|
import type { Dispatch, SetStateAction } from "react";
|
||||||
import { addPatch } from "renderer/patches/shared";
|
import { addPatch } from "renderer/patches/shared";
|
||||||
import { useSettings } from "renderer/settings";
|
import { useSettings } from "renderer/settings";
|
||||||
import { isLinux, isWindows } from "renderer/utils";
|
import { classNameFactory, isLinux, isWindows } from "renderer/utils";
|
||||||
|
|
||||||
const StreamResolutions = ["480", "720", "1080", "1440", "2160"] as const;
|
const StreamResolutions = ["480", "720", "1080", "1440", "2160"] as const;
|
||||||
const StreamFps = ["15", "30", "60"] as const;
|
const StreamFps = ["15", "30", "60"] as const;
|
||||||
|
|
||||||
|
const cl = classNameFactory("vcd-screen-picker-");
|
||||||
|
|
||||||
const MediaEngineStore = findStoreLazy("MediaEngineStore");
|
const MediaEngineStore = findStoreLazy("MediaEngineStore");
|
||||||
|
|
||||||
export type StreamResolution = (typeof StreamResolutions)[number];
|
export type StreamResolution = (typeof StreamResolutions)[number];
|
||||||
|
@ -161,13 +163,21 @@ export function openScreenSharePicker(screens: Source[], skipPicker: boolean) {
|
||||||
|
|
||||||
function ScreenPicker({ screens, chooseScreen }: { screens: Source[]; chooseScreen: (id: string) => void }) {
|
function ScreenPicker({ screens, chooseScreen }: { screens: Source[]; chooseScreen: (id: string) => void }) {
|
||||||
return (
|
return (
|
||||||
<div className="vcd-screen-picker-grid">
|
<div className={cl("screen-grid")}>
|
||||||
{screens.map(({ id, name, url }) => (
|
{screens.map(({ id, name, url }) => (
|
||||||
<label key={id}>
|
<label key={id} className={cl("screen-label")}>
|
||||||
<input type="radio" name="screen" value={id} onChange={() => chooseScreen(id)} />
|
<input
|
||||||
|
type="radio"
|
||||||
|
className={cl("screen-radio")}
|
||||||
|
name="screen"
|
||||||
|
value={id}
|
||||||
|
onChange={() => chooseScreen(id)}
|
||||||
|
/>
|
||||||
|
|
||||||
<img src={url} alt="" />
|
<img src={url} alt="" />
|
||||||
<Text variant="text-sm/normal">{name}</Text>
|
<Text className={cl("screen-name")} variant="text-sm/normal">
|
||||||
|
{name}
|
||||||
|
</Text>
|
||||||
</label>
|
</label>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -187,11 +197,13 @@ function AudioSettingsModal({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
|
<Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
|
||||||
<Modals.ModalHeader className="vcd-screen-picker-header">
|
<Modals.ModalHeader className={cl("header")}>
|
||||||
<Forms.FormTitle tag="h2">Venmic Settings</Forms.FormTitle>
|
<Forms.FormTitle tag="h2" className={cl("header-title")}>
|
||||||
|
Venmic Settings
|
||||||
|
</Forms.FormTitle>
|
||||||
<Modals.ModalCloseButton onClick={close} />
|
<Modals.ModalCloseButton onClick={close} />
|
||||||
</Modals.ModalHeader>
|
</Modals.ModalHeader>
|
||||||
<Modals.ModalContent className="vcd-screen-picker-modal">
|
<Modals.ModalContent className={cl("modal")}>
|
||||||
<Switch
|
<Switch
|
||||||
hideBorder
|
hideBorder
|
||||||
onChange={v => (Settings.audio = { ...Settings.audio, workaround: v })}
|
onChange={v => (Settings.audio = { ...Settings.audio, workaround: v })}
|
||||||
|
@ -295,7 +307,7 @@ function AudioSettingsModal({
|
||||||
Device Selection
|
Device Selection
|
||||||
</Switch>
|
</Switch>
|
||||||
</Modals.ModalContent>
|
</Modals.ModalContent>
|
||||||
<Modals.ModalFooter className="vcd-screen-picker-footer">
|
<Modals.ModalFooter className={cl("footer")}>
|
||||||
<Button color={Button.Colors.TRANSPARENT} onClick={close}>
|
<Button color={Button.Colors.TRANSPARENT} onClick={close}>
|
||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -304,6 +316,34 @@ function AudioSettingsModal({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function OptionRadio(props: {
|
||||||
|
options: Array<string> | ReadonlyArray<string>;
|
||||||
|
labels?: Array<string>;
|
||||||
|
settingsKey: string;
|
||||||
|
settings: StreamSettings;
|
||||||
|
setSettings: Dispatch<SetStateAction<StreamSettings>>;
|
||||||
|
}) {
|
||||||
|
const { options, setSettings, settings, settingsKey, labels } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cl("option-radios")}>
|
||||||
|
{(options as string[]).map((option, idx) => (
|
||||||
|
<label className={cl("option-radio")} data-checked={settings[settingsKey] === option} key={option}>
|
||||||
|
<Text variant="text-sm/bold">{labels?.[idx] ?? option}</Text>
|
||||||
|
<input
|
||||||
|
className={cl("option-input")}
|
||||||
|
type="radio"
|
||||||
|
name="fps"
|
||||||
|
value={option}
|
||||||
|
checked={settings[settingsKey] === option}
|
||||||
|
onChange={() => setSettings(s => ({ ...s, [settingsKey]: option }))}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function StreamSettings({
|
function StreamSettings({
|
||||||
source,
|
source,
|
||||||
settings,
|
settings,
|
||||||
|
@ -340,88 +380,47 @@ function StreamSettings({
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Forms.FormTitle>What you're streaming</Forms.FormTitle>
|
<Forms.FormTitle>What you're streaming</Forms.FormTitle>
|
||||||
<Card className="vcd-screen-picker-card vcd-screen-picker-preview">
|
<Card className={cl("card", "preview")}>
|
||||||
<img
|
<img src={thumb} alt="" className={cl(isLinux ? "preview-img-linux" : "preview-img")} />
|
||||||
src={thumb}
|
|
||||||
alt=""
|
|
||||||
className={isLinux ? "vcd-screen-picker-preview-img-linux" : "vcd-screen-picker-preview-img"}
|
|
||||||
/>
|
|
||||||
<Text variant="text-sm/normal">{source.name}</Text>
|
<Text variant="text-sm/normal">{source.name}</Text>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Forms.FormTitle>Stream Settings</Forms.FormTitle>
|
<Forms.FormTitle>Stream Settings</Forms.FormTitle>
|
||||||
|
|
||||||
<Card className="vcd-screen-picker-card">
|
<Card className={cl("card")}>
|
||||||
<div className="vcd-screen-picker-quality">
|
<div className={cl("quality")}>
|
||||||
<section>
|
<section className={cl("quality-section")}>
|
||||||
<Forms.FormTitle>Resolution</Forms.FormTitle>
|
<Forms.FormTitle>Resolution</Forms.FormTitle>
|
||||||
<div className="vcd-screen-picker-radios">
|
<OptionRadio
|
||||||
{StreamResolutions.map(res => (
|
options={StreamResolutions}
|
||||||
<label className="vcd-screen-picker-radio" data-checked={settings.resolution === res}>
|
settingsKey="resolution"
|
||||||
<Text variant="text-sm/bold">{res}</Text>
|
settings={settings}
|
||||||
<input
|
setSettings={setSettings}
|
||||||
type="radio"
|
/>
|
||||||
name="resolution"
|
|
||||||
value={res}
|
|
||||||
checked={settings.resolution === res}
|
|
||||||
onChange={() => setSettings(s => ({ ...s, resolution: res }))}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section className={cl("quality-section")}>
|
||||||
<Forms.FormTitle>Frame Rate</Forms.FormTitle>
|
<Forms.FormTitle>Frame Rate</Forms.FormTitle>
|
||||||
<div className="vcd-screen-picker-radios">
|
<OptionRadio
|
||||||
{StreamFps.map(fps => (
|
options={StreamFps}
|
||||||
<label className="vcd-screen-picker-radio" data-checked={settings.fps === fps}>
|
settingsKey="fps"
|
||||||
<Text variant="text-sm/bold">{fps}</Text>
|
settings={settings}
|
||||||
<input
|
setSettings={setSettings}
|
||||||
type="radio"
|
/>
|
||||||
name="fps"
|
|
||||||
value={fps}
|
|
||||||
checked={settings.fps === fps}
|
|
||||||
onChange={() => setSettings(s => ({ ...s, fps }))}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div className="vcd-screen-picker-quality">
|
<div className={cl("quality")}>
|
||||||
<section>
|
<section className={cl("quality-section")}>
|
||||||
<Forms.FormTitle>Content Type</Forms.FormTitle>
|
<Forms.FormTitle>Content Type</Forms.FormTitle>
|
||||||
<div>
|
<div>
|
||||||
<div className="vcd-screen-picker-radios">
|
<OptionRadio
|
||||||
<label
|
options={["motion", "detail"]}
|
||||||
className="vcd-screen-picker-radio"
|
labels={["Prefer Smoothness", "Prefer Clarity"]}
|
||||||
data-checked={settings.contentHint === "motion"}
|
settingsKey="contentHint"
|
||||||
>
|
settings={settings}
|
||||||
<Text variant="text-sm/bold">Prefer Smoothness</Text>
|
setSettings={setSettings}
|
||||||
<input
|
/>
|
||||||
type="radio"
|
<div className={cl("hint-description")}>
|
||||||
name="contenthint"
|
|
||||||
value="motion"
|
|
||||||
checked={settings.contentHint === "motion"}
|
|
||||||
onChange={() => setSettings(s => ({ ...s, contentHint: "motion" }))}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<label
|
|
||||||
className="vcd-screen-picker-radio"
|
|
||||||
data-checked={settings.contentHint === "detail"}
|
|
||||||
>
|
|
||||||
<Text variant="text-sm/bold">Prefer Clarity</Text>
|
|
||||||
<input
|
|
||||||
type="radio"
|
|
||||||
name="contenthint"
|
|
||||||
value="detail"
|
|
||||||
checked={settings.contentHint === "detail"}
|
|
||||||
onChange={() => setSettings(s => ({ ...s, contentHint: "detail" }))}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className="vcd-screen-picker-hint-description">
|
|
||||||
<p>
|
<p>
|
||||||
Choosing "Prefer Clarity" will result in a significantly lower framerate in exchange
|
Choosing "Prefer Clarity" will result in a significantly lower framerate in exchange
|
||||||
for a much sharper and clearer image.
|
for a much sharper and clearer image.
|
||||||
|
@ -433,7 +432,7 @@ function StreamSettings({
|
||||||
value={settings.audio}
|
value={settings.audio}
|
||||||
onChange={checked => setSettings(s => ({ ...s, audio: checked }))}
|
onChange={checked => setSettings(s => ({ ...s, audio: checked }))}
|
||||||
hideBorder
|
hideBorder
|
||||||
className="vcd-screen-picker-audio"
|
className={cl("audio")}
|
||||||
>
|
>
|
||||||
Stream With Audio
|
Stream With Audio
|
||||||
</Switch>
|
</Switch>
|
||||||
|
@ -639,7 +638,7 @@ function AudioSourcePickerLinux({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={includeSources === "Entire System" ? "vcd-screen-picker-quality" : undefined}>
|
<div className={cl({ quality: includeSources === "Entire System" })}>
|
||||||
<section>
|
<section>
|
||||||
<Forms.FormTitle>{loading ? "Loading Sources..." : "Audio Sources"}</Forms.FormTitle>
|
<Forms.FormTitle>{loading ? "Loading Sources..." : "Audio Sources"}</Forms.FormTitle>
|
||||||
<Select
|
<Select
|
||||||
|
@ -675,11 +674,7 @@ function AudioSourcePickerLinux({
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button color={Button.Colors.TRANSPARENT} onClick={openSettings} className={cl("settings-button")}>
|
||||||
color={Button.Colors.TRANSPARENT}
|
|
||||||
onClick={openSettings}
|
|
||||||
className="vcd-screen-picker-settings-button"
|
|
||||||
>
|
|
||||||
Open Audio Settings
|
Open Audio Settings
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
|
@ -710,11 +705,11 @@ function ModalComponent({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
|
<Modals.ModalRoot {...modalProps} size={ModalSize.MEDIUM}>
|
||||||
<Modals.ModalHeader className="vcd-screen-picker-header">
|
<Modals.ModalHeader className={cl("header")}>
|
||||||
<Forms.FormTitle tag="h2">ScreenShare</Forms.FormTitle>
|
<Forms.FormTitle tag="h2">ScreenShare</Forms.FormTitle>
|
||||||
<Modals.ModalCloseButton onClick={close} />
|
<Modals.ModalCloseButton onClick={close} />
|
||||||
</Modals.ModalHeader>
|
</Modals.ModalHeader>
|
||||||
<Modals.ModalContent className="vcd-screen-picker-modal">
|
<Modals.ModalContent className={cl("modal")}>
|
||||||
{!selected ? (
|
{!selected ? (
|
||||||
<ScreenPicker screens={screens} chooseScreen={setSelected} />
|
<ScreenPicker screens={screens} chooseScreen={setSelected} />
|
||||||
) : (
|
) : (
|
||||||
|
@ -726,7 +721,7 @@ function ModalComponent({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Modals.ModalContent>
|
</Modals.ModalContent>
|
||||||
<Modals.ModalFooter className="vcd-screen-picker-footer">
|
<Modals.ModalFooter className={cl("footer")}>
|
||||||
<Button
|
<Button
|
||||||
disabled={!selected}
|
disabled={!selected}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vcd-screen-picker-header h1 {
|
.vcd-screen-picker-header-title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,23 +15,20 @@
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vcd-screen-picker-grid {
|
|
||||||
|
/* Screen Grid */
|
||||||
|
.vcd-screen-picker-screen-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 2em 1em;
|
gap: 2em 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vcd-screen-picker-grid input {
|
.vcd-screen-picker-screen-radio {
|
||||||
appearance: none;
|
appearance: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vcd-screen-picker-selected img {
|
.vcd-screen-picker-screen-label {
|
||||||
border: 2px solid var(--brand-500);
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vcd-screen-picker-grid label {
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -39,11 +36,11 @@
|
||||||
justify-items: center;
|
justify-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vcd-screen-picker-grid label:hover {
|
.vcd-screen-picker-screen-label:hover {
|
||||||
outline: 2px solid var(--brand-500);
|
outline: 2px solid var(--brand-500);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vcd-screen-picker-grid div {
|
.vcd-screen-picker-screen-name {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -75,37 +72,48 @@
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vcd-screen-picker-radio input {
|
|
||||||
display: none;
|
/* Option Radios */
|
||||||
|
|
||||||
|
.vcd-screen-picker-option-radios {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vcd-screen-picker-radio {
|
.vcd-screen-picker-option-radio {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
text-align: center;
|
||||||
background-color: var(--background-secondary);
|
background-color: var(--background-secondary);
|
||||||
border: 1px solid var(--primary-800);
|
border: 1px solid var(--primary-800);
|
||||||
padding: 0.3em;
|
padding: 0.3em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vcd-screen-picker-radio h2 {
|
.vcd-screen-picker-option-radio:first-child {
|
||||||
margin: 0;
|
border-radius: 3px 0 0 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vcd-screen-picker-radio[data-checked="true"] {
|
.vcd-screen-picker-option-radio:last-child {
|
||||||
|
border-radius: 0 3px 3px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vcd-screen-picker-option-input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vcd-screen-picker-option-radio[data-checked="true"] {
|
||||||
background-color: var(--brand-500);
|
background-color: var(--brand-500);
|
||||||
border-color: var(--brand-500);
|
border-color: var(--brand-500);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vcd-screen-picker-radio[data-checked="true"] h2 {
|
|
||||||
color: var(--interactive-active);
|
|
||||||
}
|
|
||||||
|
|
||||||
.vcd-screen-picker-quality {
|
.vcd-screen-picker-quality {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 1em;
|
gap: 1em;
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vcd-screen-picker-quality section {
|
.vcd-screen-picker-quality-section {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,24 +122,6 @@
|
||||||
margin-top: 0.3rem;
|
margin-top: 0.3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vcd-screen-picker-radios {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vcd-screen-picker-radios label {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vcd-screen-picker-radios label:first-child {
|
|
||||||
border-radius: 3px 0 0 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vcd-screen-picker-radios label:last-child {
|
|
||||||
border-radius: 0 3px 3px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vcd-screen-picker-audio {
|
.vcd-screen-picker-audio {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
|
|
@ -1,15 +1,3 @@
|
||||||
/* Download Desktop button in guilds list */
|
|
||||||
[class^=listItem_]:has([data-list-item-id=guildsnav___app-download-button]),
|
|
||||||
[class^=listItem_]:has(+ [class^=listItem_] [data-list-item-id=guildsnav___app-download-button]) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* FIXME: remove this once Discord fixes their css to not explode scrollbars on chromium >=121 */
|
|
||||||
* {
|
|
||||||
scrollbar-width: unset !important;
|
|
||||||
scrollbar-color: unset !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Workaround for making things in the draggable area clickable again on macOS */
|
/* Workaround for making things in the draggable area clickable again on macOS */
|
||||||
.platform-osx [class*=topic_], .platform-osx [class*=notice_] button {
|
.platform-osx [class*=topic_], .platform-osx [class*=notice_] button {
|
||||||
-webkit-app-region: no-drag;
|
-webkit-app-region: no-drag;
|
||||||
|
|
|
@ -18,3 +18,26 @@ const { platform } = navigator;
|
||||||
export const isWindows = platform.startsWith("Win");
|
export const isWindows = platform.startsWith("Win");
|
||||||
export const isMac = platform.startsWith("Mac");
|
export const isMac = platform.startsWith("Mac");
|
||||||
export const isLinux = platform.startsWith("Linux");
|
export const isLinux = platform.startsWith("Linux");
|
||||||
|
|
||||||
|
type ClassNameFactoryArg = string | string[] | Record<string, unknown> | false | null | undefined | 0 | "";
|
||||||
|
/**
|
||||||
|
* @param prefix The prefix to add to each class, defaults to `""`
|
||||||
|
* @returns A classname generator function
|
||||||
|
* @example
|
||||||
|
* const cl = classNameFactory("plugin-");
|
||||||
|
*
|
||||||
|
* cl("base", ["item", "editable"], { selected: null, disabled: true })
|
||||||
|
* // => "plugin-base plugin-item plugin-editable plugin-disabled"
|
||||||
|
*/
|
||||||
|
export const classNameFactory =
|
||||||
|
(prefix: string = "") =>
|
||||||
|
(...args: ClassNameFactoryArg[]) => {
|
||||||
|
const classNames = new Set<string>();
|
||||||
|
for (const arg of args) {
|
||||||
|
if (arg && typeof arg === "string") classNames.add(arg);
|
||||||
|
else if (Array.isArray(arg)) arg.forEach(name => classNames.add(name));
|
||||||
|
else if (arg && typeof arg === "object")
|
||||||
|
Object.entries(arg).forEach(([name, value]) => value && classNames.add(name));
|
||||||
|
}
|
||||||
|
return Array.from(classNames, name => prefix + name).join(" ");
|
||||||
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue