shrink tos modal and make it scrollable when vertical space is not enough (#373)

This commit is contained in:
Red J Adaya 2024-09-13 14:10:18 +08:00 committed by GitHub
parent 04924870c1
commit 2715c2ce30
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 143 additions and 89 deletions

View File

@ -18,6 +18,10 @@ body {
transform: translateZ(0); transform: translateZ(0);
} }
a.plain-link {
color: var(--secondary-text-color);
}
*::-webkit-scrollbar { *::-webkit-scrollbar {
width: 4px; width: 4px;
height: 4px; height: 4px;

View File

@ -3,10 +3,65 @@
import { Button } from "@/app/element/button"; import { Button } from "@/app/element/button";
import clsx from "clsx"; import clsx from "clsx";
import { forwardRef } from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
import "./modal.less"; import "./modal.less";
interface ModalProps {
children?: React.ReactNode;
description?: string;
okLabel?: string;
cancelLabel?: string;
className?: string;
onClickBackdrop?: () => void;
onOk?: () => void;
onCancel?: () => void;
onClose?: () => void;
}
const Modal = forwardRef<HTMLDivElement, ModalProps>(
(
{
children,
className,
description,
cancelLabel,
okLabel,
onCancel,
onOk,
onClose,
onClickBackdrop,
}: ModalProps,
ref
) => {
const renderBackdrop = (onClick) => <div className="modal-backdrop" onClick={onClick}></div>;
const renderFooter = () => {
return onOk || onCancel;
};
const renderModal = () => (
<div className="modal-wrapper">
{renderBackdrop(onClickBackdrop)}
<div ref={ref} className={clsx(`modal`, className)}>
<Button className="secondary ghost modal-close-btn" onClick={onClose} title="Close (ESC)">
<i className="fa-sharp fa-solid fa-xmark"></i>
</Button>
<div className="content-wrapper">
<ModalContent>{children}</ModalContent>
</div>
{renderFooter() && (
<ModalFooter onCancel={onCancel} onOk={onOk} cancelLabel={cancelLabel} okLabel={okLabel} />
)}
</div>
</div>
);
return ReactDOM.createPortal(renderModal(), document.getElementById("main"));
}
);
interface ModalContentProps { interface ModalContentProps {
children: React.ReactNode; children: React.ReactNode;
} }
@ -39,75 +94,36 @@ const ModalFooter = ({ onCancel, onOk, cancelLabel = "Cancel", okLabel = "Ok" }:
); );
}; };
interface ModalProps {
children?: React.ReactNode;
description?: string;
okLabel?: string;
cancelLabel?: string;
className?: string;
onClickBackdrop?: () => void;
onOk?: () => void;
onCancel?: () => void;
onClose?: () => void;
}
const Modal = ({
children,
className,
description,
cancelLabel,
okLabel,
onCancel,
onOk,
onClose,
onClickBackdrop,
}: ModalProps) => {
const renderBackdrop = (onClick) => <div className="modal-backdrop" onClick={onClick}></div>;
const renderFooter = () => {
return onOk || onCancel;
};
const renderModal = () => (
<div className="modal-wrapper">
{renderBackdrop(onClickBackdrop)}
<div className={clsx(`modal`, className)}>
<Button className="secondary ghost modal-close-btn" onClick={onClose} title="Close (ESC)">
<i className="fa-sharp fa-solid fa-xmark"></i>
</Button>
<div className="content-wrapper">
<ModalContent>{children}</ModalContent>
</div>
{renderFooter() && (
<ModalFooter onCancel={onCancel} onOk={onOk} cancelLabel={cancelLabel} okLabel={okLabel} />
)}
</div>
</div>
);
return ReactDOM.createPortal(renderModal(), document.getElementById("main"));
};
interface FlexiModalProps { interface FlexiModalProps {
children?: React.ReactNode; children?: React.ReactNode;
className?: string; className?: string;
onClickBackdrop?: () => void; onClickBackdrop?: () => void;
} }
const FlexiModal = ({ children, className, onClickBackdrop }: FlexiModalProps) => { interface FlexiModalComponent
const renderBackdrop = (onClick) => <div className="modal-backdrop" onClick={onClick}></div>; extends React.ForwardRefExoticComponent<FlexiModalProps & React.RefAttributes<HTMLDivElement>> {
Content: typeof ModalContent;
Footer: typeof ModalFooter;
}
const renderModal = () => ( const FlexiModal = forwardRef<HTMLDivElement, FlexiModalProps>(
<div className="modal-wrapper"> ({ children, className, onClickBackdrop }: FlexiModalProps, ref) => {
{renderBackdrop(onClickBackdrop)} const renderBackdrop = (onClick: () => void) => <div className="modal-backdrop" onClick={onClick}></div>;
<div className={`modal ${className}`}>{children}</div>
</div>
);
return ReactDOM.createPortal(renderModal(), document.getElementById("main")); const renderModal = () => (
}; <div className="modal-wrapper">
{renderBackdrop(onClickBackdrop)}
<div className={`modal ${className}`} ref={ref}>
{children}
</div>
</div>
);
FlexiModal.Content = ModalContent; return ReactDOM.createPortal(renderModal(), document.getElementById("main")!);
FlexiModal.Footer = ModalFooter; }
);
(FlexiModal as FlexiModalComponent).Content = ModalContent;
(FlexiModal as FlexiModalComponent).Footer = ModalFooter;
export { FlexiModal, Modal }; export { FlexiModal, Modal };

View File

@ -2,21 +2,22 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
.tos-modal { .tos-modal {
width: 640px; width: 560px;
border-radius: 10px; border-radius: 10px;
padding: 0;
.modal-inner { .modal-inner {
padding: 40px 76px;
gap: 32px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow-y: auto;
padding: 30px;
header.tos-header { header.tos-header {
flex-direction: column; flex-direction: column;
gap: var(--sizing-sm, 12px); gap: 8px;
border-bottom: none; border-bottom: none;
padding: 0; padding: 0;
margin-bottom: 20px; margin-bottom: 36px;
.logo { .logo {
margin-bottom: 10px; margin-bottom: 10px;
@ -48,12 +49,17 @@
gap: 32px; gap: 32px;
width: 100%; width: 100%;
margin-bottom: 0; margin-bottom: 0;
margin-bottom: 20px;
.check-toggle-wrapper .toggle-label {
color: var(--secondary-text-color);
}
.content-section { .content-section {
display: flex; display: flex;
width: 100%; width: 100%;
align-items: center; align-items: center;
gap: 32px; gap: 18px;
.icon-wrapper { .icon-wrapper {
.icon { .icon {
@ -82,9 +88,13 @@
} }
.content-section-text { .content-section-text {
color: rgba(255, 255, 255, 0.7); color: var(--secondary-text-color);
font-style: normal; font-style: normal;
line-height: 20px; line-height: 20px;
b {
color: var(--main-text-color);
}
} }
.content-section-field { .content-section-field {
@ -102,10 +112,6 @@
} }
} }
} }
.toggle-label {
color: rgba(255, 255, 255, 0.7);
}
} }
footer { footer {
@ -123,7 +129,6 @@
button { button {
font-size: 14px; font-size: 14px;
margin-bottom: 28px;
} }
button.disabled-button { button.disabled-button {

View File

@ -6,19 +6,43 @@ import { Button } from "@/app/element/button";
import { Toggle } from "@/app/element/toggle"; import { Toggle } from "@/app/element/toggle";
import { WshServer } from "@/app/store/wshserver"; import { WshServer } from "@/app/store/wshserver";
import * as services from "@/store/services"; import * as services from "@/store/services";
import { useEffect, useState } from "react"; import { OverlayScrollbarsComponent } from "overlayscrollbars-react";
import { useEffect, useRef, useState } from "react";
import { FlexiModal } from "./modal"; import { FlexiModal } from "./modal";
import "./tos.less"; import "./tos.less";
const TosModal = () => { const TosModal = () => {
const [telemetryEnabled, setTelemetryEnabled] = useState<boolean>(true); const [telemetryEnabled, setTelemetryEnabled] = useState<boolean>(true);
const modalRef = useRef<HTMLDivElement | null>(null);
const updateModalHeight = () => {
const windowHeight = window.innerHeight;
if (modalRef.current) {
const modalHeight = modalRef.current.offsetHeight;
const maxHeight = windowHeight * 0.9;
if (maxHeight < modalHeight) {
modalRef.current.style.height = `${maxHeight}px`;
} else {
modalRef.current.style.height = "auto";
}
}
};
useEffect(() => {
updateModalHeight(); // Run on initial render
window.addEventListener("resize", updateModalHeight); // Run on window resize
return () => {
window.removeEventListener("resize", updateModalHeight);
};
}, []);
const acceptTos = () => { const acceptTos = () => {
services.ClientService.AgreeTos(); services.ClientService.AgreeTos();
}; };
function setTelemetry(value: boolean) { const setTelemetry = (value: boolean) => {
WshServer.SetConfigCommand({ "telemetry:enabled": value }) WshServer.SetConfigCommand({ "telemetry:enabled": value })
.then(() => { .then(() => {
setTelemetryEnabled(value); setTelemetryEnabled(value);
@ -26,7 +50,7 @@ const TosModal = () => {
.catch((error) => { .catch((error) => {
console.error("failed to set telemetry:", error); console.error("failed to set telemetry:", error);
}); });
} };
useEffect(() => { useEffect(() => {
services.FileService.GetFullConfig() services.FileService.GetFullConfig()
@ -42,16 +66,16 @@ const TosModal = () => {
}); });
}, []); }, []);
const label = telemetryEnabled ? "Telemetry enabled" : "Telemetry disabled"; const label = telemetryEnabled ? "Telemetry Enabled" : "Telemetry Disabled";
return ( return (
<FlexiModal className="tos-modal"> <FlexiModal className="tos-modal" ref={modalRef}>
<div className="modal-inner"> <OverlayScrollbarsComponent className="modal-inner" options={{ scrollbars: { autoHide: "leave" } }}>
<header className="modal-header tos-header unselectable"> <header className="modal-header tos-header unselectable">
<div className="logo"> <div className="logo">
<Logo /> <Logo />
</div> </div>
<div className="modal-title">Welcome to Wave Terminal!</div> <div className="modal-title">Welcome to Wave Terminal</div>
</header> </header>
<div className="modal-content tos-content unselectable"> <div className="modal-content tos-content unselectable">
<div className="content-section"> <div className="content-section">
@ -73,7 +97,7 @@ const TosModal = () => {
</div> </div>
<div className="content-section"> <div className="content-section">
<div className="icon-wrapper"> <div className="icon-wrapper">
<a target="_blank" href="https://github.com/wavetermdev/thenextwave" rel={"noopener"}> <a target="_blank" href="https://discord.gg/XfvZ334gwU" rel={"noopener"}>
<i className="icon fa-solid fa-people-group"></i> <i className="icon fa-solid fa-people-group"></i>
</a> </a>
</div> </div>
@ -96,15 +120,24 @@ const TosModal = () => {
<div className="content-section-inner"> <div className="content-section-inner">
<div className="content-section-title">Telemetry</div> <div className="content-section-title">Telemetry</div>
<div className="content-section-text"> <div className="content-section-text">
We collect minimal anonymous We collect minimal anonymous{" "}
<a <a
target="_blank" target="_blank"
href="https://docs.waveterm.dev/reference/telemetry" href="https://docs.waveterm.dev/reference/telemetry"
rel={"noopener"} rel={"noopener"}
> >
&nbsp;telemetry data&nbsp; telemetry data
</a>{" "}
to help us understand how people are using Wave (
<a
className="plain-link"
target="_blank"
href="https://waveterm.dev/privacy"
rel="noopener"
>
Privacy Policy
</a> </a>
to help us understand how people are using Wave. ).
</div> </div>
<Toggle checked={telemetryEnabled} onChange={setTelemetry} label={label} /> <Toggle checked={telemetryEnabled} onChange={setTelemetry} label={label} />
</div> </div>
@ -116,12 +149,8 @@ const TosModal = () => {
Get Started Get Started
</Button> </Button>
</div> </div>
<div className="content-section-text">
By continuing, I accept the&nbsp;
<a href="https://www.waveterm.dev/tos">Terms of Service</a>
</div>
</footer> </footer>
</div> </OverlayScrollbarsComponent>
</FlexiModal> </FlexiModal>
); );
}; };