mirror of
https://github.com/gnmyt/myspeed.git
synced 2026-02-11 08:08:49 -06:00
Remove InputDialog and replace with new AlertContext implementation
This commit is contained in:
178
client/src/common/contexts/Alert/AlertContext.jsx
Normal file
178
client/src/common/contexts/Alert/AlertContext.jsx
Normal file
@@ -0,0 +1,178 @@
|
||||
import React, {createContext, useCallback, useContext, useEffect, useMemo, useRef, useState} from "react";
|
||||
import {createPortal} from "react-dom";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faClose} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
const AlertContext = createContext(null);
|
||||
|
||||
export const useAlert = () => {
|
||||
const context = useContext(AlertContext);
|
||||
if (!context) throw new Error("useAlert must be used within AlertProvider");
|
||||
return context;
|
||||
};
|
||||
|
||||
export const AlertProvider = ({children}) => {
|
||||
const [alerts, setAlerts] = useState([]);
|
||||
const alertIdRef = useRef(0);
|
||||
const resolversRef = useRef(new Map());
|
||||
|
||||
const showAlert = useCallback((config) => {
|
||||
return new Promise((resolve) => {
|
||||
const id = ++alertIdRef.current;
|
||||
resolversRef.current.set(id, resolve);
|
||||
setAlerts(prev => [...prev, {...config, id}]);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const closeAlert = useCallback((id, result = null) => {
|
||||
const resolver = resolversRef.current.get(id);
|
||||
if (resolver) {
|
||||
resolver(result);
|
||||
resolversRef.current.delete(id);
|
||||
}
|
||||
setAlerts(prev => prev.filter(a => a.id !== id));
|
||||
}, []);
|
||||
|
||||
const openAlert = useCallback((title, description, options = {}) =>
|
||||
showAlert({
|
||||
type: "alert",
|
||||
title,
|
||||
description,
|
||||
buttonText: options.buttonText || "OK", ...options
|
||||
}), [showAlert]);
|
||||
|
||||
const openInput = useCallback((title, options = {}) =>
|
||||
showAlert({type: "input", title, ...options}), [showAlert]);
|
||||
|
||||
const openSelect = useCallback((title, selectOptions, options = {}) =>
|
||||
showAlert({
|
||||
type: "select",
|
||||
title,
|
||||
options: selectOptions,
|
||||
value: options.value || Object.keys(selectOptions)[0], ...options
|
||||
}), [showAlert]);
|
||||
|
||||
const openConfirm = useCallback((title, description, options = {}) =>
|
||||
showAlert({
|
||||
type: "confirm",
|
||||
title,
|
||||
description,
|
||||
buttonText: options.buttonText || "OK", ...options
|
||||
}), [showAlert]);
|
||||
|
||||
const contextValue = useMemo(() => ({
|
||||
openAlert, openInput, openSelect, openConfirm
|
||||
}), [openAlert, openInput, openSelect, openConfirm]);
|
||||
|
||||
return (
|
||||
<AlertContext.Provider value={contextValue}>
|
||||
{children}
|
||||
{alerts.map(alert => (
|
||||
<AlertRenderer key={alert.id} alert={alert} onClose={(result) => closeAlert(alert.id, result)}/>
|
||||
))}
|
||||
</AlertContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const AlertRenderer = ({alert, onClose}) => {
|
||||
const areaRef = useRef();
|
||||
const dialogRef = useRef();
|
||||
const [inputValue, setInputValue] = useState(alert.value || "");
|
||||
const [inputError, setInputError] = useState(false);
|
||||
const closeResultRef = useRef(null);
|
||||
const isClosingRef = useRef(false);
|
||||
|
||||
const close = useCallback((result = null) => {
|
||||
if (alert.disableClose && result === null) return;
|
||||
if (isClosingRef.current) return;
|
||||
isClosingRef.current = true;
|
||||
closeResultRef.current = result;
|
||||
areaRef.current?.classList.add("dialog-area-hidden");
|
||||
dialogRef.current?.classList.add("dialog-hidden");
|
||||
}, [alert.disableClose]);
|
||||
|
||||
const handleAnimationEnd = (e) => {
|
||||
if (e.animationName === "fadeOut") onClose(closeResultRef.current);
|
||||
};
|
||||
|
||||
const handleBackdropClick = (e) => {
|
||||
if (e.target === areaRef.current) close();
|
||||
};
|
||||
|
||||
const handleKeyDown = useCallback((e) => {
|
||||
if (e.key === "Escape" && !alert.disableClose) {
|
||||
e.preventDefault();
|
||||
close();
|
||||
}
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
handleSubmit();
|
||||
}
|
||||
}, [alert, inputValue]);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
return () => document.removeEventListener("keydown", handleKeyDown);
|
||||
}, [handleKeyDown]);
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (alert.type === "input" && alert.required && !inputValue) {
|
||||
setInputError(true);
|
||||
return;
|
||||
}
|
||||
const result = alert.type === "input" || alert.type === "select" ? inputValue : true;
|
||||
alert.onSuccess?.(result);
|
||||
close(result);
|
||||
};
|
||||
|
||||
return createPortal(
|
||||
<div className="dialog-area" ref={areaRef} onClick={handleBackdropClick}>
|
||||
<div className="dialog" ref={dialogRef} onAnimationEnd={handleAnimationEnd}>
|
||||
<div className="dialog-header">
|
||||
<h4 className="dialog-text">{alert.title}</h4>
|
||||
{!alert.disableClose &&
|
||||
<FontAwesomeIcon icon={faClose} className="dialog-text dialog-icon" onClick={() => close()}/>}
|
||||
</div>
|
||||
<div className="dialog-main">
|
||||
{alert.description && <p className="dialog-description">{alert.description}</p>}
|
||||
{alert.type === "input" && (
|
||||
<input className={`dialog-input${inputError ? " input-error" : ""}`}
|
||||
type={alert.inputType || "text"}
|
||||
placeholder={alert.placeholder} value={inputValue} autoFocus
|
||||
onChange={(e) => {
|
||||
setInputValue(e.target.value);
|
||||
setInputError(false);
|
||||
}}/>
|
||||
)}
|
||||
{alert.type === "select" && (
|
||||
<select className="dialog-input" value={inputValue}
|
||||
onChange={(e) => setInputValue(e.target.value)}>
|
||||
{Object.entries(alert.options || {}).map(([key, label]) => (
|
||||
<option key={key} value={key}>{label}</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
</div>
|
||||
<div className="dialog-buttons">
|
||||
{alert.clearButton && (
|
||||
<button className="dialog-btn dialog-secondary" onClick={() => {
|
||||
alert.onClear?.();
|
||||
close();
|
||||
}}>
|
||||
{alert.clearButton}
|
||||
</button>
|
||||
)}
|
||||
{alert.type === "confirm" && (
|
||||
<button className="dialog-btn dialog-secondary" onClick={() => close(false)}>
|
||||
{alert.cancelText || "Cancel"}
|
||||
</button>
|
||||
)}
|
||||
<button className={`dialog-btn${alert.danger ? " dialog-danger" : ""}`} onClick={handleSubmit}>
|
||||
{alert.buttonText || "OK"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
};
|
||||
1
client/src/common/contexts/Alert/index.js
Normal file
1
client/src/common/contexts/Alert/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export {AlertProvider, useAlert} from './AlertContext';
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, {createContext, useContext, useEffect, useState} from "react";
|
||||
import {InputDialogContext} from "../InputDialog";
|
||||
import React, {createContext, useEffect, useState} from "react";
|
||||
import {useAlert} from "../Alert";
|
||||
import {request} from "@/common/utils/RequestUtil";
|
||||
import {apiErrorDialog, passwordRequiredDialog} from "@/common/contexts/Config/dialog";
|
||||
import WelcomeDialog from "@/common/components/WelcomeDialog";
|
||||
@@ -9,7 +9,7 @@ export const ConfigContext = createContext({});
|
||||
|
||||
export const ConfigProvider = (props) => {
|
||||
const [config, setConfig] = useState({});
|
||||
const [setDialog] = useContext(InputDialogContext);
|
||||
const alert = useAlert();
|
||||
const [welcomeShown, setWelcomeShown] = useState(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -30,10 +30,31 @@ export const ConfigProvider = (props) => {
|
||||
? navigate("/nodes") : setConfig(result);
|
||||
}).catch((code) => {
|
||||
localStorage.getItem("currentNode") !== null && localStorage.getItem("currentNode") !== "0"
|
||||
? navigate("/nodes") : setDialog(code === 1 ? passwordRequiredDialog() : apiErrorDialog());
|
||||
? navigate("/nodes") : showErrorDialog(code);
|
||||
});
|
||||
}
|
||||
|
||||
const showErrorDialog = async (code) => {
|
||||
const dialogConfig = code === 1 ? passwordRequiredDialog() : apiErrorDialog();
|
||||
|
||||
if (code === 1) {
|
||||
const result = await alert.openInput(dialogConfig.title, {
|
||||
placeholder: dialogConfig.placeholder,
|
||||
description: dialogConfig.description,
|
||||
inputType: dialogConfig.type,
|
||||
buttonText: dialogConfig.buttonText,
|
||||
disableClose: dialogConfig.disableCloseButton
|
||||
});
|
||||
if (result) dialogConfig.onSuccess(result);
|
||||
} else {
|
||||
await alert.openAlert(dialogConfig.title, dialogConfig.description, {
|
||||
buttonText: dialogConfig.buttonText,
|
||||
disableClose: dialogConfig.disableCloseButton
|
||||
});
|
||||
dialogConfig.onSuccess();
|
||||
}
|
||||
};
|
||||
|
||||
const checkConfig = async () => (await request("/config")).json();
|
||||
|
||||
useEffect(reloadConfig, []);
|
||||
@@ -45,7 +66,7 @@ export const ConfigProvider = (props) => {
|
||||
|
||||
return (
|
||||
<ConfigContext.Provider value={[config, reloadConfig, checkConfig]}>
|
||||
{welcomeShown && <WelcomeDialog onClose={() => setWelcomeShown(false)}/>}
|
||||
<WelcomeDialog open={welcomeShown} onClose={() => setWelcomeShown(false)}/>
|
||||
{props.children}
|
||||
</ConfigContext.Provider>
|
||||
)
|
||||
|
||||
@@ -1,54 +1,77 @@
|
||||
import React, {createContext, useEffect, useRef} from "react";
|
||||
import React, {useCallback, useEffect, useRef, useState} from "react";
|
||||
import {createPortal} from "react-dom";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faClose} from "@fortawesome/free-solid-svg-icons";
|
||||
import "./styles.sass";
|
||||
|
||||
export const DialogContext = createContext({});
|
||||
|
||||
export const DialogProvider = (props) => {
|
||||
export const Dialog = ({open, onClose, className, disableClose, children}) => {
|
||||
const areaRef = useRef();
|
||||
const ref = useRef();
|
||||
|
||||
const close = (force = false) => {
|
||||
if (props.disableClosing && !force) return;
|
||||
areaRef.current?.classList.add("dialog-area-hidden");
|
||||
ref.current?.classList.add("dialog-hidden");
|
||||
}
|
||||
|
||||
const onClose = (e) => {
|
||||
if (e.animationName === "fadeOut") {
|
||||
hideTooltips(false);
|
||||
props?.close();
|
||||
}
|
||||
}
|
||||
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.code === "Enter" && props.submit) props.submit();
|
||||
}
|
||||
|
||||
const hideTooltips = (state) => Array.from(document.getElementsByClassName("tooltip")).forEach(element => {
|
||||
if (state && !element.classList.contains("tooltip-invisible"))
|
||||
element.classList.add("tooltip-invisible");
|
||||
if (!state && element.classList.contains("tooltip-invisible"))
|
||||
element.classList.remove("tooltip-invisible");
|
||||
});
|
||||
const dialogRef = useRef();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const isClosingRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClick = (event) => {
|
||||
if (!ref.current?.contains(event.target)) close();
|
||||
if (open && !visible) {
|
||||
setVisible(true);
|
||||
isClosingRef.current = false;
|
||||
} else if (!open && visible && !isClosingRef.current) {
|
||||
isClosingRef.current = true;
|
||||
areaRef.current?.classList.add("dialog-area-hidden");
|
||||
dialogRef.current?.classList.add("dialog-hidden");
|
||||
}
|
||||
}, [open, visible]);
|
||||
|
||||
document.addEventListener("mousedown", handleClick);
|
||||
const handleClose = useCallback(() => {
|
||||
if (disableClose || isClosingRef.current) return;
|
||||
isClosingRef.current = true;
|
||||
areaRef.current?.classList.add("dialog-area-hidden");
|
||||
dialogRef.current?.classList.add("dialog-hidden");
|
||||
}, [disableClose]);
|
||||
|
||||
return () => document.removeEventListener("mousedown", handleClick);
|
||||
}, [ref]);
|
||||
const handleAnimationEnd = (e) => {
|
||||
if (e.animationName === "fadeOut") {
|
||||
setVisible(false);
|
||||
isClosingRef.current = false;
|
||||
onClose?.();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogContext.Provider value={close}>
|
||||
<div className="dialog-area" ref={areaRef}>
|
||||
<div className={"dialog" + (props.customClass ? " " + props.customClass : "")} ref={ref}
|
||||
onAnimationEnd={onClose} onKeyDown={handleKeyDown} onAnimationStart={() => hideTooltips(true)}>
|
||||
{props.children}
|
||||
</div>
|
||||
const handleBackdropClick = (e) => {
|
||||
if (e.target === areaRef.current) handleClose();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!visible) return;
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.key === "Escape" && !disableClose) {
|
||||
e.preventDefault();
|
||||
handleClose();
|
||||
}
|
||||
};
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
return () => document.removeEventListener("keydown", handleKeyDown);
|
||||
}, [visible, disableClose, handleClose]);
|
||||
|
||||
if (!visible) return null;
|
||||
|
||||
return createPortal(
|
||||
<div className="dialog-area" ref={areaRef} onClick={handleBackdropClick}>
|
||||
<div className={`dialog${className ? ` ${className}` : ""}`} ref={dialogRef}
|
||||
onAnimationEnd={handleAnimationEnd}>
|
||||
{typeof children === "function" ? children({close: handleClose}) : children}
|
||||
</div>
|
||||
</DialogContext.Provider>
|
||||
)
|
||||
}
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
};
|
||||
|
||||
export const DialogHeader = ({children, onClose, disableClose}) => (
|
||||
<div className="dialog-header">
|
||||
<h4 className="dialog-text">{children}</h4>
|
||||
{!disableClose && <FontAwesomeIcon icon={faClose} className="dialog-text dialog-icon" onClick={onClose}/>}
|
||||
</div>
|
||||
);
|
||||
|
||||
export const DialogBody = ({children}) => <div className="dialog-main">{children}</div>;
|
||||
|
||||
export const DialogFooter = ({children}) => <div className="dialog-buttons">{children}</div>;
|
||||
|
||||
@@ -28,9 +28,16 @@
|
||||
-webkit-backdrop-filter: blur(4px)
|
||||
border: 1px solid $light-gray
|
||||
border-radius: 1rem
|
||||
transition: all 0.2s
|
||||
animation: fadeIn 0.3s
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3)
|
||||
min-width: 300px
|
||||
max-width: min(500px, 90vw)
|
||||
box-sizing: border-box
|
||||
|
||||
&.storage-dialog-wrapper,
|
||||
&.provider-dialog-wrapper,
|
||||
&.integration-dialog
|
||||
max-width: 90vw
|
||||
|
||||
.dialog-hidden
|
||||
visibility: hidden
|
||||
@@ -56,6 +63,8 @@
|
||||
justify-content: center
|
||||
align-items: center
|
||||
flex-direction: column
|
||||
width: 100%
|
||||
box-sizing: border-box
|
||||
|
||||
.dialog-buttons
|
||||
display: flex
|
||||
@@ -69,10 +78,13 @@
|
||||
margin: 0
|
||||
|
||||
.dialog-description
|
||||
font-size: 15pt
|
||||
font-size: 13pt
|
||||
margin: 12px 2px 2px
|
||||
color: $subtext
|
||||
line-height: 1.5
|
||||
word-wrap: break-word
|
||||
overflow-wrap: break-word
|
||||
max-width: 100%
|
||||
|
||||
.dialog-description a
|
||||
color: $accent-primary
|
||||
@@ -115,4 +127,56 @@
|
||||
.dialog-secondary
|
||||
&:hover
|
||||
border-color: $accent-danger
|
||||
color: $accent-danger
|
||||
color: $accent-danger
|
||||
|
||||
.dialog-input
|
||||
font-size: 0.9rem
|
||||
padding: 0.6rem 0.875rem
|
||||
font-weight: 500
|
||||
margin-top: 15px
|
||||
margin-bottom: 15px
|
||||
width: 100%
|
||||
background-color: $darker-gray
|
||||
color: $white
|
||||
border: 1px solid $light-gray
|
||||
border-radius: 0.5rem
|
||||
text-align: center
|
||||
box-sizing: border-box
|
||||
outline: none
|
||||
transition: all 0.15s ease
|
||||
|
||||
.dialog-input::placeholder
|
||||
color: $subtext
|
||||
|
||||
.dialog-input:focus
|
||||
border-color: $accent-primary
|
||||
|
||||
.input-error
|
||||
border-color: $accent-danger
|
||||
|
||||
.input-error:focus
|
||||
border-color: $accent-danger
|
||||
|
||||
.dialog-loading
|
||||
min-width: 200px
|
||||
min-height: 100px
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
|
||||
@media (max-width: 600px)
|
||||
.dialog
|
||||
min-width: 280px
|
||||
max-width: 95vw
|
||||
padding: 1rem
|
||||
font-size: 0.9rem
|
||||
|
||||
.dialog-text
|
||||
font-size: 13pt
|
||||
|
||||
.dialog-description
|
||||
font-size: 12pt
|
||||
|
||||
.dialog-btn
|
||||
font-size: 11pt
|
||||
padding: 8px 14px
|
||||
@@ -1,119 +0,0 @@
|
||||
import React, {createContext, useContext, useEffect, useState} from "react";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import {faClose} from "@fortawesome/free-solid-svg-icons";
|
||||
import {t} from "i18next";
|
||||
import {DialogContext, DialogProvider} from "@/common/contexts/Dialog";
|
||||
import "./styles.sass";
|
||||
|
||||
export const InputDialogContext = createContext({});
|
||||
|
||||
const DialogArea = ({dialog}) => {
|
||||
const close = useContext(DialogContext);
|
||||
const [value, setValue] = useState("");
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (dialog.value) setValue(dialog.value);
|
||||
}, [dialog.value]);
|
||||
|
||||
useEffect(() => {
|
||||
document.onkeyup = e => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
submit();
|
||||
}
|
||||
if (e.key === "Escape" && !dialog.disableCloseButton) {
|
||||
e.preventDefault();
|
||||
closeDialog();
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.onkeyup = null;
|
||||
}
|
||||
});
|
||||
|
||||
function updateValue(e) {
|
||||
if (dialog.updateDescription) dialog.description = dialog.updateDescription(e.target.value);
|
||||
setValue(e.target.value);
|
||||
}
|
||||
|
||||
function closeDialog() {
|
||||
close();
|
||||
if (dialog.onClose) dialog.onClose();
|
||||
}
|
||||
|
||||
function submit() {
|
||||
if (!dialog.description && !value) {
|
||||
setError(true);
|
||||
return;
|
||||
}
|
||||
close(true);
|
||||
if (dialog.onSuccess) dialog.onSuccess(value);
|
||||
}
|
||||
|
||||
function clear() {
|
||||
close();
|
||||
if (dialog.onClear) dialog.onClear();
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="dialog-header">
|
||||
<h4 className="dialog-text">{dialog.title}</h4>
|
||||
{!dialog.disableCloseButton ?
|
||||
<FontAwesomeIcon icon={faClose} className="dialog-text dialog-icon" onClick={closeDialog}/> : <></>}
|
||||
</div>
|
||||
<div className="dialog-main">
|
||||
{dialog.description ? <h3 className="dialog-description">{dialog.description}</h3> : ""}
|
||||
{dialog.placeholder ? <input className={"dialog-input" + (error ? " input-error" : "")}
|
||||
type={dialog.type ? dialog.type : "text"}
|
||||
placeholder={dialog.placeholder} value={value}
|
||||
onChange={updateValue}/> : ""}
|
||||
{dialog.select ? <select value={value} onChange={updateValue} className="dialog-input">
|
||||
{Object.keys(dialog.selectOptions).map(key => <option key={key}
|
||||
value={key}>{dialog.selectOptions[key]}</option>)}
|
||||
</select> : ""}
|
||||
</div>
|
||||
<div className="dialog-buttons">
|
||||
{dialog.unsetButton ? <button className="dialog-btn dialog-secondary"
|
||||
onClick={clear}>{dialog.unsetButton || t("dialog.unset")}</button> : ""}
|
||||
<button className={"dialog-btn"+(dialog.mainRed ? " dialog-secondary" : "")} onClick={submit}>{dialog.buttonText || t("dialog.update")}</button>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const InputDialogProvider = (props) => {
|
||||
const [dialog, setDialog] = useState();
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
const [dialogList, setDialogList] = useState([]);
|
||||
|
||||
const updateDialog = (newDialog) => newDialog ? setDialogList([...dialogList, newDialog]) : "";
|
||||
|
||||
useEffect(() => {
|
||||
if (dialogList.length === 0) return;
|
||||
if ((!isDialogOpen && dialogList[0]) || dialogList[0].replace) {
|
||||
setDialog(dialogList[0]);
|
||||
setDialogList(dialogList.slice(1));
|
||||
setIsDialogOpen(true);
|
||||
}
|
||||
}, [isDialogOpen, dialogList]);
|
||||
|
||||
const handleClose = () => {
|
||||
setIsDialogOpen(false);
|
||||
setDialog();
|
||||
};
|
||||
|
||||
return (
|
||||
<InputDialogContext.Provider value={[updateDialog]}>
|
||||
{dialog && (
|
||||
<DialogProvider close={handleClose} customClass="input-dialog"
|
||||
disableClosing={dialog.disableCloseButton}>
|
||||
<DialogArea dialog={dialog}/>
|
||||
</DialogProvider>
|
||||
)}
|
||||
{props.children}
|
||||
</InputDialogContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from './InputDialog';
|
||||
@@ -1,32 +0,0 @@
|
||||
@use "@/common/styles/colors" as *
|
||||
|
||||
.input-dialog
|
||||
width: 400px
|
||||
|
||||
.dialog-input
|
||||
font-size: 0.9rem
|
||||
padding: 0.6rem 0.875rem
|
||||
font-weight: 500
|
||||
margin-top: 15px
|
||||
margin-bottom: 15px
|
||||
width: 100%
|
||||
background-color: $darker-gray
|
||||
color: $white
|
||||
border: 1px solid $light-gray
|
||||
border-radius: 0.5rem
|
||||
text-align: center
|
||||
box-sizing: border-box
|
||||
outline: none
|
||||
transition: all 0.15s ease
|
||||
|
||||
.dialog-input::placeholder
|
||||
color: $subtext
|
||||
|
||||
.dialog-input:focus
|
||||
border-color: $accent-primary
|
||||
|
||||
.input-error
|
||||
border-color: $accent-danger
|
||||
|
||||
.input-error:focus
|
||||
border-color: $accent-danger
|
||||
Reference in New Issue
Block a user