reactify create-channel overlay
also add refs
This commit is contained in:
parent
8e4a195464
commit
78093316ea
@ -1,4 +1,4 @@
|
||||
import React, { FC, useCallback, useMemo } from 'react';
|
||||
import React, { FC, Ref, useCallback, useMemo } from 'react';
|
||||
|
||||
export enum ButtonType {
|
||||
BRAND = '',
|
||||
@ -8,6 +8,8 @@ export enum ButtonType {
|
||||
}
|
||||
|
||||
interface ButtonProps {
|
||||
ref?: Ref<HTMLDivElement>;
|
||||
|
||||
type?: ButtonType;
|
||||
onClick?: () => void;
|
||||
shaking?: boolean;
|
||||
@ -18,7 +20,7 @@ const DefaultButtonProps: ButtonProps = {
|
||||
type: ButtonType.BRAND
|
||||
}
|
||||
|
||||
const Button: FC<ButtonProps> = (props: ButtonProps) => {
|
||||
const Button: FC<ButtonProps> = React.forwardRef((props: ButtonProps, ref: Ref<HTMLDivElement>) => {
|
||||
const { type, onClick, shaking, children } = { ...DefaultButtonProps, ...props };
|
||||
|
||||
const className = useMemo(
|
||||
@ -35,7 +37,7 @@ const Button: FC<ButtonProps> = (props: ButtonProps) => {
|
||||
if (onClick) onClick();
|
||||
}, [ shaking, onClick ]);
|
||||
|
||||
return <div className={className} onClick={clickHandler}>{children}</div>
|
||||
}
|
||||
return <div ref={ref} className={className} onClick={clickHandler}>{children}</div>
|
||||
});
|
||||
|
||||
export default Button;
|
@ -3,9 +3,11 @@ const electronConsole = electronRemote.getGlobal('console') as Console;
|
||||
import Logger from '../../../../logger/logger';
|
||||
const LOG = Logger.create(__filename, electronConsole);
|
||||
|
||||
import React, { FC, useEffect, useMemo, useState } from 'react';
|
||||
import React, { FC, Ref, useEffect, useMemo } from 'react';
|
||||
|
||||
export interface TextInputProps {
|
||||
ref?: Ref<HTMLInputElement>;
|
||||
|
||||
label: string;
|
||||
placeholder?: string;
|
||||
|
||||
@ -21,10 +23,11 @@ export interface TextInputProps {
|
||||
setMessage: React.Dispatch<React.SetStateAction<string | null>>;
|
||||
|
||||
onEnterKeyDown?: () => void;
|
||||
valueMap?: (value: string) => string; // useful to get rid of certain characters, convert to lower case, etc.
|
||||
}
|
||||
|
||||
const TextInput: FC<TextInputProps> = (props: TextInputProps) => {
|
||||
const { label, placeholder, noLabel, allowEmpty, maxLength, value, setValue, setValid, setMessage, onEnterKeyDown } = props;
|
||||
const TextInput: FC<TextInputProps> = React.forwardRef((props: TextInputProps, ref: Ref<HTMLInputElement>) => {
|
||||
const { label, placeholder, noLabel, allowEmpty, maxLength, value, setValue, setValid, setMessage, onEnterKeyDown, valueMap } = props;
|
||||
|
||||
useEffect(() => {
|
||||
if (maxLength !== undefined && value.length > maxLength) {
|
||||
@ -46,11 +49,17 @@ const TextInput: FC<TextInputProps> = (props: TextInputProps) => {
|
||||
}, [ label ]);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
let value = e.target.value;
|
||||
if (valueMap) value = valueMap(value);
|
||||
setValue(value);
|
||||
}
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
// const controlKeys = [ 'Backspace', 'Escape', 'Tab' ];
|
||||
// if (!controlKeys.includes(e.key) && validKeys && !validKeys.test(e.key)) {
|
||||
// e.preventDefault();
|
||||
// e.stopPropagation();
|
||||
// } else
|
||||
if (e.key === 'Enter') {
|
||||
if (onEnterKeyDown) onEnterKeyDown();
|
||||
}
|
||||
@ -59,9 +68,9 @@ const TextInput: FC<TextInputProps> = (props: TextInputProps) => {
|
||||
return (
|
||||
<div className="text-input-react">{/* TODO: remove -react */}
|
||||
{labelElement}
|
||||
<input type="text" placeholder={placeholder} onChange={handleChange} onKeyDown={handleKeyDown} value={value} />
|
||||
<input ref={ref} type="text" placeholder={placeholder} onChange={handleChange} onKeyDown={handleKeyDown} value={value} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default TextInput;
|
||||
|
@ -1,22 +1,22 @@
|
||||
import React, { FC, useMemo, useState } from 'react';
|
||||
import ElementsUtil from '../require/elements-util';
|
||||
import React, { FC, Ref, useMemo } from 'react';
|
||||
import Button from './button';
|
||||
|
||||
// Includes a submit button and error message
|
||||
|
||||
export interface SubmitOverlayLowerProps {
|
||||
ref?: Ref<HTMLDivElement>;
|
||||
|
||||
buttonMessage: string;
|
||||
doSubmit: () => Promise<boolean>;
|
||||
submitting: boolean;
|
||||
submitFailed: boolean;
|
||||
shaking: boolean;
|
||||
onSubmit: () => void;
|
||||
errorMessage: string | null;
|
||||
infoMessage?: string | null;
|
||||
}
|
||||
|
||||
const SubmitOverlayLower: FC<SubmitOverlayLowerProps> = (props: SubmitOverlayLowerProps) => {
|
||||
const { buttonMessage, doSubmit, errorMessage, infoMessage } = props;
|
||||
|
||||
const [ shaking, setShaking ] = useState<boolean>(false)
|
||||
const [ submitting, setSubmitting ] = useState<boolean>(false);
|
||||
const [ submitFailed, setSubmitFailed ] = useState<boolean>(false);
|
||||
const SubmitOverlayLower: FC<SubmitOverlayLowerProps> = React.forwardRef((props: SubmitOverlayLowerProps, ref: Ref<HTMLDivElement>) => {
|
||||
const { buttonMessage, submitting, submitFailed, shaking, onSubmit, errorMessage, infoMessage } = props;
|
||||
|
||||
const buttonText = useMemo(() => {
|
||||
if (submitting) {
|
||||
@ -28,17 +28,6 @@ const SubmitOverlayLower: FC<SubmitOverlayLowerProps> = (props: SubmitOverlayLow
|
||||
}
|
||||
}, [ submitting, submitFailed ]);
|
||||
|
||||
const onSubmit = async () => {
|
||||
setSubmitting(true);
|
||||
const succeeded = await doSubmit();
|
||||
setSubmitting(false);
|
||||
|
||||
setSubmitFailed(!succeeded);
|
||||
if (!succeeded) {
|
||||
await ElementsUtil.delayToggleState(setShaking, 400);
|
||||
}
|
||||
}
|
||||
|
||||
const message = useMemo(() => errorMessage ?? infoMessage ?? null, [ errorMessage, infoMessage ]);
|
||||
const isEmpty = useMemo(() => message === null, [ message ]);
|
||||
|
||||
@ -48,10 +37,10 @@ const SubmitOverlayLower: FC<SubmitOverlayLowerProps> = (props: SubmitOverlayLow
|
||||
<div className="error">{errorMessage ?? infoMessage ?? null}</div>
|
||||
</div>
|
||||
<div className="buttons">
|
||||
<Button onClick={onSubmit} shaking={shaking}>{buttonText}</Button>
|
||||
<Button ref={ref} onClick={onSubmit} shaking={shaking}>{buttonText}</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default SubmitOverlayLower;
|
||||
|
@ -1,9 +1,7 @@
|
||||
import React from 'react';
|
||||
import ReactHelper from './require/react-helper.js';
|
||||
import ElementsUtil from './require/elements-util.js';
|
||||
import BaseElements from './require/base-elements.js';
|
||||
|
||||
import createPersonalizeOverlay from './overlay-personalize.js';
|
||||
import Q from '../q-module.js';
|
||||
import UI from '../ui.js';
|
||||
import CombinedGuild from '../guild-combined.js';
|
||||
|
@ -18,6 +18,7 @@ import React from 'react';
|
||||
import ReactHelper from './require/react-helper';
|
||||
import GuildSettingsOverlay from './overlays/overlay-guild-settings';
|
||||
import ErrorMessageOverlay from './overlays/overlay-error-message';
|
||||
import CreateChannelOverlay from './overlays/overlay-create-channel';
|
||||
|
||||
export default function createGuildTitleContextMenu(document: Document, q: Q, ui: UI, guild: CombinedGuild): Element {
|
||||
if (ui.activeConnection === null) {
|
||||
@ -93,10 +94,7 @@ export default function createGuildTitleContextMenu(document: Document, q: Q, ui
|
||||
if (ui.activeConnection.privileges.includes('modify_channels')) {
|
||||
q.$$$(element, '.item.create-channel').addEventListener('click', () => {
|
||||
element.removeSelf();
|
||||
const overlay = createCreateChannelOverlay(document, q, guild);
|
||||
document.body.appendChild(overlay);
|
||||
q.$$$(overlay, '.text-input.channel-name').focus();
|
||||
ElementsUtil.setCursorToEnd(q.$$$(overlay, '.text-input.channel-name'));
|
||||
ElementsUtil.presentReactOverlay(document, <CreateChannelOverlay document={document} guild={guild} />);
|
||||
});
|
||||
}
|
||||
|
||||
|
128
src/client/webapp/elements/overlays/overlay-create-channel.tsx
Normal file
128
src/client/webapp/elements/overlays/overlay-create-channel.tsx
Normal file
@ -0,0 +1,128 @@
|
||||
import * as electronRemote from '@electron/remote';
|
||||
const electronConsole = electronRemote.getGlobal('console') as Console;
|
||||
import Logger from '../../../../logger/logger';
|
||||
const LOG = Logger.create(__filename, electronConsole);
|
||||
|
||||
import React, { createRef, FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import CombinedGuild from '../../guild-combined';
|
||||
import BaseElements from '../require/base-elements';
|
||||
import TextInput from '../components/input-text';
|
||||
import SubmitOverlayLower from '../components/submit-overlay-lower';
|
||||
import Globals from '../../globals';
|
||||
import ElementsUtil from '../require/elements-util';
|
||||
|
||||
export interface CreateChannelOverlayProps {
|
||||
document: Document;
|
||||
guild: CombinedGuild;
|
||||
}
|
||||
const CreateChannelOverlay: FC<CreateChannelOverlayProps> = (props: CreateChannelOverlayProps) => {
|
||||
const { document, guild } = props;
|
||||
|
||||
const nameInputRef = createRef<HTMLInputElement>();
|
||||
const flavorTextInputRef = createRef<HTMLInputElement>();
|
||||
const submitButtonRef = createRef<HTMLDivElement>();
|
||||
|
||||
const [ edited, setEdited ] = useState<boolean>(false);
|
||||
const [ submitting, setSubmitting ] = useState<boolean>(false);
|
||||
const [ submitFailed, setSubmitFailed ] = useState<boolean>(false);
|
||||
const [ shaking, setShaking ] = useState<boolean>(false);
|
||||
|
||||
const [ name, setName ] = useState<string>('');
|
||||
const [ flavorText, setFlavorText ] = useState<string>('');
|
||||
|
||||
const [ queryFailed, setQueryFailed ] = useState<boolean>(false);
|
||||
|
||||
const [ nameInputValid, setNameInputValid ] = useState<boolean>(false);
|
||||
const [ nameInputMessage, setNameInputMessage ] = useState<string | null>(null);
|
||||
|
||||
const [ flavorTextInputValid, setFlavorTextInputValid ] = useState<boolean>(false);
|
||||
const [ flavorTextInputMessage, setFlavorTextInputMessage ] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
nameInputRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
const errorMessage = useMemo(() => {
|
||||
if (!edited) return null;
|
||||
if ( !nameInputValid && nameInputMessage) return nameInputMessage;
|
||||
if (!flavorTextInputValid && flavorTextInputMessage) return flavorTextInputMessage;
|
||||
if (queryFailed) return 'Unable to create new channel';
|
||||
return null;
|
||||
}, [ nameInputValid, nameInputMessage, flavorTextInputValid, flavorTextInputMessage, queryFailed ]);
|
||||
|
||||
const infoMessage = useMemo(() => {
|
||||
if ( nameInputValid && nameInputMessage) return nameInputMessage;
|
||||
if (flavorTextInputValid && flavorTextInputMessage) return flavorTextInputMessage;
|
||||
return null;
|
||||
}, [ nameInputValid, nameInputMessage, flavorTextInputValid, flavorTextInputMessage ]);
|
||||
|
||||
useEffect(() => {
|
||||
if (name.length > 0 || flavorText.length > 0) {
|
||||
setEdited(true);
|
||||
}
|
||||
}, [ name, flavorText ]);
|
||||
|
||||
const doSubmit = useCallback(async (): Promise<boolean> => {
|
||||
LOG.debug('submitting');
|
||||
if (!edited) return false;
|
||||
if (errorMessage) return false;
|
||||
|
||||
try {
|
||||
// Make sure to null out flavor text if empty
|
||||
await guild.requestDoCreateChannel(name, flavorText === '' ? null : flavorText);
|
||||
} catch (e: unknown) {
|
||||
LOG.error('error creating channel', e);
|
||||
setQueryFailed(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
ElementsUtil.closeReactOverlay(document);
|
||||
|
||||
return true;
|
||||
}, [ errorMessage, name, flavorText ]);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
ElementsUtil.createShakingOnSubmit({ doSubmit, setShaking, setSubmitFailed, setSubmitting }),
|
||||
[ doSubmit, shaking, submitFailed, submitFailed, submitting ]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="content submit-dialog modify-channel">
|
||||
<div className="preview channel-title">
|
||||
<div className="channel-icon">{BaseElements.TEXT_CHANNEL_ICON}</div>
|
||||
<div className="channel-name">{name}</div>
|
||||
<div className="channel-flavor-divider"></div>
|
||||
<div className="channel-flavor-text">{flavorText}</div>
|
||||
</div>
|
||||
<div className="channel-name">
|
||||
<TextInput
|
||||
ref={nameInputRef}
|
||||
label="Channel Name" placeholder="channel-name"
|
||||
maxLength={Globals.MAX_CHANNEL_NAME_LENGTH}
|
||||
value={name} setValue={setName}
|
||||
setValid={setNameInputValid} setMessage={setNameInputMessage}
|
||||
valueMap={value => value.toLowerCase().replace(' ', '-').replace(/[^a-z0-9-]/, '')}
|
||||
onEnterKeyDown={() => flavorTextInputRef.current?.focus()}
|
||||
/>
|
||||
</div>
|
||||
<div className="flavor-text">
|
||||
<TextInput
|
||||
ref={flavorTextInputRef}
|
||||
label="Flavor Text" placeholder="Flavor text (optional)"
|
||||
maxLength={Globals.MAX_CHANNEL_FLAVOR_TEXT_LENGTH}
|
||||
allowEmpty={true}
|
||||
value={flavorText} setValue={setFlavorText}
|
||||
setValid={setFlavorTextInputValid} setMessage={setFlavorTextInputMessage}
|
||||
onEnterKeyDown={() => { submitButtonRef.current?.click(); }}
|
||||
/>
|
||||
</div>
|
||||
<SubmitOverlayLower
|
||||
ref={submitButtonRef}
|
||||
submitting={submitting} submitFailed={submitFailed} shaking={shaking}
|
||||
buttonMessage="Create Channel" onSubmit={onSubmit}
|
||||
errorMessage={errorMessage} infoMessage={infoMessage}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default CreateChannelOverlay;
|
@ -4,7 +4,7 @@ import Logger from '../../../../logger/logger';
|
||||
const LOG = Logger.create(__filename, electronConsole);
|
||||
|
||||
import moment from 'moment';
|
||||
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import React, { createRef, FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { ConnectionInfo } from '../../data-types';
|
||||
import Globals from '../../globals';
|
||||
import CombinedGuild from '../../guild-combined';
|
||||
@ -27,6 +27,12 @@ const PersonalizeOverlay: FC<PersonalizeOverlayProps> = (props: PersonalizeOverl
|
||||
|
||||
const avatarResourceId = connection.avatarResourceId;
|
||||
|
||||
const displayNameInputRef = createRef<HTMLInputElement>();
|
||||
|
||||
const [ submitting, setSubmitting ] = useState<boolean>(false);
|
||||
const [ submitFailed, setSubmitFailed ] = useState<boolean>(false);
|
||||
const [ shaking, setShaking ] = useState<boolean>(false);
|
||||
|
||||
const [ savedDisplayName, setSavedDisplayName ] = useState<string>(connection.displayName);
|
||||
const [ savedAvatarBuff, setSavedAvatarBuff ] = useState<Buffer | null>(null);
|
||||
|
||||
@ -55,28 +61,22 @@ const PersonalizeOverlay: FC<PersonalizeOverlayProps> = (props: PersonalizeOverl
|
||||
})();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
displayNameInputRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
const errorMessage = useMemo(() => {
|
||||
if (loadAvatarFailed) {
|
||||
return 'Unable to load avatar';
|
||||
} else if (!avatarInputValid && avatarInputMessage) {
|
||||
return avatarInputMessage;
|
||||
} else if (!displayNameInputValid && displayNameInputMessage) {
|
||||
return displayNameInputMessage;
|
||||
} else if (saveFailed) {
|
||||
return 'Unable to save personalization';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
if (loadAvatarFailed) return 'Unable to load avatar';
|
||||
if ( !avatarInputValid && avatarInputMessage) return avatarInputMessage;
|
||||
if (!displayNameInputValid && displayNameInputMessage) return displayNameInputMessage;
|
||||
if (saveFailed) return 'Unable to save personalization';
|
||||
return null;
|
||||
}, [ saveFailed, loadAvatarFailed, displayNameInputValid, displayNameInputMessage, avatarInputValid, avatarInputMessage ]);
|
||||
|
||||
const infoMessage = useMemo(() => {
|
||||
if (avatarInputValid && avatarInputMessage) {
|
||||
return avatarInputMessage;
|
||||
} else if (displayNameInputValid && displayNameInputMessage) {
|
||||
return displayNameInputMessage;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
if (avatarInputValid && avatarInputMessage) return avatarInputMessage;
|
||||
if (displayNameInputValid && displayNameInputMessage) return displayNameInputMessage;
|
||||
return null;
|
||||
}, [ displayNameInputValid, displayNameInputMessage, avatarInputValid, avatarInputMessage ]);
|
||||
|
||||
const doSubmit = useCallback(async (): Promise<boolean> => {
|
||||
@ -111,6 +111,11 @@ const PersonalizeOverlay: FC<PersonalizeOverlayProps> = (props: PersonalizeOverl
|
||||
return true;
|
||||
}, [ errorMessage, displayName, savedDisplayName, avatarBuff, savedAvatarBuff ]);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
ElementsUtil.createShakingOnSubmit({ doSubmit, setShaking, setSubmitFailed, setSubmitting }),
|
||||
[ doSubmit, shaking, submitFailed, submitFailed, submitting ]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="content personalize">
|
||||
<div className="personalization">
|
||||
@ -123,14 +128,20 @@ const PersonalizeOverlay: FC<PersonalizeOverlayProps> = (props: PersonalizeOverl
|
||||
</div>
|
||||
<div className="display-name">
|
||||
<TextInput
|
||||
ref={displayNameInputRef}
|
||||
label="Display Name" placeholder={savedDisplayName}
|
||||
maxLength={Globals.MAX_DISPLAY_NAME_LENGTH}
|
||||
value={displayName} setValue={setDisplayName}
|
||||
setValid={setDisplayNameInputValid} setMessage={setDisplayNameInputMessage}
|
||||
onEnterKeyDown={onSubmit}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<SubmitOverlayLower buttonMessage="Save" doSubmit={doSubmit} errorMessage={errorMessage} infoMessage={infoMessage} />
|
||||
<SubmitOverlayLower
|
||||
submitting={submitting} submitFailed={submitFailed} shaking={shaking}
|
||||
buttonMessage="Save" onSubmit={onSubmit}
|
||||
errorMessage={errorMessage} infoMessage={infoMessage}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -51,6 +51,13 @@ interface CreateDownloadListenerProps {
|
||||
successFunc: ((path: string) => Promise<void> | void);
|
||||
}
|
||||
|
||||
interface ShakingOnSubmitProps {
|
||||
doSubmit: () => Promise<boolean>,
|
||||
setSubmitting: React.Dispatch<React.SetStateAction<boolean>>,
|
||||
setSubmitFailed: React.Dispatch<React.SetStateAction<boolean>>,
|
||||
setShaking: React.Dispatch<React.SetStateAction<boolean>>
|
||||
}
|
||||
|
||||
async function sleep(ms: number): Promise<unknown> {
|
||||
return await new Promise((resolve, reject) => {
|
||||
setTimeout(resolve, ms);
|
||||
@ -98,6 +105,21 @@ export default class ElementsUtil {
|
||||
setState(old => !start);
|
||||
}
|
||||
|
||||
static createShakingOnSubmit(props: ShakingOnSubmitProps): () => Promise<void> {
|
||||
const { doSubmit, setSubmitting, setSubmitFailed, setShaking } = props;
|
||||
|
||||
return async () => {
|
||||
setSubmitting(true);
|
||||
const succeeded = await doSubmit();
|
||||
setSubmitting(false);
|
||||
setSubmitFailed(!succeeded);
|
||||
if (!succeeded) {
|
||||
await ElementsUtil.delayToggleState(setShaking, 400);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static async getImageBufferSrc(buffer: Buffer): Promise<string> {
|
||||
const result = await FileType.fromBuffer(buffer);
|
||||
switch (result && result.mime) {
|
||||
|
@ -195,7 +195,6 @@ body > .overlay,
|
||||
}
|
||||
|
||||
.buttons {
|
||||
margin-left: 16px;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
@ -221,7 +220,7 @@ body > .overlay,
|
||||
> .content.add-guild {
|
||||
min-width: 350px;
|
||||
background-color: $background-secondary;
|
||||
border-radius: 12px;
|
||||
border-radius: 8px;
|
||||
|
||||
.divider {
|
||||
margin: 16px;
|
||||
@ -266,27 +265,6 @@ body > .overlay,
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.lower {
|
||||
padding: 16px;
|
||||
border-bottom-left-radius: 8px;
|
||||
border-bottom-right-radius: 8px;
|
||||
background-color: $background-tertiary;
|
||||
|
||||
.error {
|
||||
background-color: $background-secondary-alt;
|
||||
color: $text-normal;
|
||||
}
|
||||
|
||||
.error::first-letter {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
margin-left: 16px;
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Modify Channel Overlay */
|
||||
@ -296,13 +274,14 @@ body > .overlay,
|
||||
max-width: calc(100vw - 80px);
|
||||
|
||||
.preview.channel-title {
|
||||
border-top-left-radius: 12px;
|
||||
border-top-right-radius: 12px;
|
||||
border-top-left-radius: 8px;
|
||||
border-top-right-radius: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.text-input.channel-name {
|
||||
text-transform: lowercase;
|
||||
> .channel-name,
|
||||
> .flavor-text {
|
||||
margin: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user