mirror of
https://github.com/spacebarchat/client.git
synced 2024-11-21 18:02:32 +01:00
implement more modals, and user context menu kick/ban
This commit is contained in:
parent
c598875a75
commit
c048563b53
@ -10,6 +10,7 @@
|
||||
"@fontsource/roboto": "^4.5.8",
|
||||
"@fontsource/roboto-mono": "^5.0.16",
|
||||
"@hcaptcha/react-hcaptcha": "^1.9.2",
|
||||
"@hookform/resolvers": "^3.3.2",
|
||||
"@mattjennings/react-modal-stack": "^1.0.4",
|
||||
"@mdi/js": "^7.3.67",
|
||||
"@mdi/react": "^1.6.1",
|
||||
@ -65,7 +66,8 @@
|
||||
"remark-gfm": "^3.0.1",
|
||||
"reoverlay": "^1.0.3",
|
||||
"styled-components": "^5.3.11",
|
||||
"use-resize-observer": "^9.1.0"
|
||||
"use-resize-observer": "^9.1.0",
|
||||
"yup": "^1.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@craco/craco": "^7.1.0",
|
||||
|
@ -23,6 +23,9 @@ dependencies:
|
||||
'@hcaptcha/react-hcaptcha':
|
||||
specifier: ^1.9.2
|
||||
version: 1.9.2(react-dom@18.2.0)(react@18.2.0)
|
||||
'@hookform/resolvers':
|
||||
specifier: ^3.3.2
|
||||
version: 3.3.2(react-hook-form@7.49.0)
|
||||
'@mattjennings/react-modal-stack':
|
||||
specifier: ^1.0.4
|
||||
version: 1.0.4(react@18.2.0)
|
||||
@ -191,6 +194,9 @@ dependencies:
|
||||
use-resize-observer:
|
||||
specifier: ^9.1.0
|
||||
version: 9.1.0(react-dom@18.2.0)(react@18.2.0)
|
||||
yup:
|
||||
specifier: ^1.3.3
|
||||
version: 1.3.3
|
||||
|
||||
devDependencies:
|
||||
'@craco/craco':
|
||||
@ -2551,6 +2557,14 @@ packages:
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@hookform/resolvers@3.3.2(react-hook-form@7.49.0):
|
||||
resolution: {integrity: sha512-Tw+GGPnBp+5DOsSg4ek3LCPgkBOuOgS5DsDV7qsWNH9LZc433kgsWICjlsh2J9p04H2K66hsXPPb9qn9ILdUtA==}
|
||||
peerDependencies:
|
||||
react-hook-form: ^7.0.0
|
||||
dependencies:
|
||||
react-hook-form: 7.49.0(react@18.2.0)
|
||||
dev: false
|
||||
|
||||
/@humanwhocodes/config-array@0.11.11:
|
||||
resolution: {integrity: sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==}
|
||||
engines: {node: '>=10.10.0'}
|
||||
@ -11202,6 +11216,10 @@ packages:
|
||||
object-assign: 4.1.1
|
||||
react-is: 16.13.1
|
||||
|
||||
/property-expr@2.0.6:
|
||||
resolution: {integrity: sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==}
|
||||
dev: false
|
||||
|
||||
/property-information@5.6.0:
|
||||
resolution: {integrity: sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==}
|
||||
dependencies:
|
||||
@ -12893,6 +12911,10 @@ packages:
|
||||
resolution: {integrity: sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==}
|
||||
dev: true
|
||||
|
||||
/tiny-case@1.0.3:
|
||||
resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==}
|
||||
dev: false
|
||||
|
||||
/tmpl@1.0.5:
|
||||
resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==}
|
||||
dev: true
|
||||
@ -12913,6 +12935,10 @@ packages:
|
||||
engines: {node: '>=0.6'}
|
||||
dev: true
|
||||
|
||||
/toposort@2.0.2:
|
||||
resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==}
|
||||
dev: false
|
||||
|
||||
/tough-cookie@4.1.3:
|
||||
resolution: {integrity: sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==}
|
||||
engines: {node: '>=6'}
|
||||
@ -13052,6 +13078,11 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/type-fest@2.19.0:
|
||||
resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
|
||||
engines: {node: '>=12.20'}
|
||||
dev: false
|
||||
|
||||
/type-is@1.6.18:
|
||||
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@ -14071,6 +14102,15 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/yup@1.3.3:
|
||||
resolution: {integrity: sha512-v8QwZSsHH2K3/G9WSkp6mZKO+hugKT1EmnMqLNUcfu51HU9MDyhlETT/JgtzprnrnQHPWsjc6MUDMBp/l9fNnw==}
|
||||
dependencies:
|
||||
property-expr: 2.0.6
|
||||
tiny-case: 1.0.3
|
||||
toposort: 2.0.2
|
||||
type-fest: 2.19.0
|
||||
dev: false
|
||||
|
||||
/zwitch@2.0.4:
|
||||
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
||||
dev: false
|
||||
|
@ -79,7 +79,7 @@ function MemberListItem({ item }: Props) {
|
||||
<ListItem
|
||||
key={item.user?.id}
|
||||
ref={contextMenu.setReferenceElement}
|
||||
onContextMenu={(e) => contextMenu.onContextMenu(e, { user: item.user! })}
|
||||
onContextMenu={(e) => contextMenu.onContextMenu(e, { user: item.user!, member: item })}
|
||||
>
|
||||
<Container>
|
||||
<Wrapper offline={presence?.status === PresenceUpdateStatus.Offline}>
|
||||
|
@ -21,16 +21,23 @@ export const ContextMenu = styled.div`
|
||||
export const ContextMenuDivider = styled.div`
|
||||
height: 1px;
|
||||
margin: 4px;
|
||||
background: var(--text);
|
||||
background: var(--text-disabled);
|
||||
`;
|
||||
|
||||
export const ContextMenuItem = styled("a")`
|
||||
export const ContextMenuItem = styled("button")`
|
||||
display: block;
|
||||
padding: 6px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
margin: 2px 0;
|
||||
cursor: pointer;
|
||||
cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")};
|
||||
opacity: ${(props) => (props.disabled ? 0.5 : 1)};
|
||||
|
||||
// remove default button styles
|
||||
border: none;
|
||||
background: none;
|
||||
color: inherit;
|
||||
outline: none;
|
||||
`;
|
||||
|
||||
const ButtonBase = styled(ContextMenuItem)<{ destructive?: boolean }>`
|
||||
@ -53,14 +60,15 @@ const ButtonBase = styled(ContextMenuItem)<{ destructive?: boolean }>`
|
||||
|
||||
type ButtonProps = ComponentProps<typeof ContextMenuItem> & {
|
||||
icon?: IconProps["icon"];
|
||||
iconProps?: Omit<IconProps, "icon" | "size">;
|
||||
destructive?: boolean;
|
||||
};
|
||||
|
||||
export function ContextMenuButton(props: ButtonProps) {
|
||||
export function ContextMenuButton({ icon, children, iconProps, ...props }: ButtonProps) {
|
||||
return (
|
||||
<ButtonBase {...props}>
|
||||
<span>{props.children}</span>
|
||||
{props.icon && <Icon icon={props.icon} size="18px" />}
|
||||
<span>{children}</span>
|
||||
{icon && <Icon icon={icon} {...iconProps} size="18px" />}
|
||||
</ButtonBase>
|
||||
);
|
||||
}
|
||||
|
@ -1,13 +1,16 @@
|
||||
// loosely based on https://github.com/revoltchat/frontend/blob/master/components/app/menus/UserContextMenu.tsx
|
||||
|
||||
import { modalController } from "../../controllers/modals";
|
||||
import GuildMember from "../../stores/objects/GuildMember";
|
||||
import User from "../../stores/objects/User";
|
||||
import { ContextMenu, ContextMenuButton, ContextMenuDivider } from "./ContextMenu";
|
||||
|
||||
interface MenuProps {
|
||||
user: User;
|
||||
member?: GuildMember;
|
||||
}
|
||||
|
||||
function UserContextMenu({ user }: MenuProps) {
|
||||
function UserContextMenu({ user, member }: MenuProps) {
|
||||
/**
|
||||
* Copy user id to clipboard
|
||||
*/
|
||||
@ -15,14 +18,61 @@ function UserContextMenu({ user }: MenuProps) {
|
||||
navigator.clipboard.writeText(user.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open kick modal
|
||||
*/
|
||||
function kick() {
|
||||
if (!member) return;
|
||||
modalController.push({
|
||||
type: "kick_member",
|
||||
target: member,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Open ban modal
|
||||
*/
|
||||
function ban() {
|
||||
if (!member) return;
|
||||
modalController.push({
|
||||
type: "ban_member",
|
||||
target: member,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<ContextMenu>
|
||||
<ContextMenuButton icon="mdiAt" destructive>
|
||||
Mention
|
||||
<ContextMenuButton disabled>Profile</ContextMenuButton>
|
||||
<ContextMenuButton disabled>Mention</ContextMenuButton>
|
||||
<ContextMenuButton disabled>Message</ContextMenuButton>
|
||||
<ContextMenuDivider />
|
||||
<ContextMenuButton disabled>Change Nickname</ContextMenuButton>
|
||||
<ContextMenuButton disabled>Add Friend</ContextMenuButton>
|
||||
<ContextMenuButton disabled>Block</ContextMenuButton>
|
||||
<ContextMenuDivider />
|
||||
<ContextMenuButton destructive onClick={kick}>
|
||||
Kick {member?.nick ?? user.username}
|
||||
</ContextMenuButton>
|
||||
<ContextMenuButton destructive onClick={ban}>
|
||||
Ban {member?.nick ?? user.username}
|
||||
</ContextMenuButton>
|
||||
<ContextMenuDivider />
|
||||
<ContextMenuButton disabled icon="mdiChevronRight">
|
||||
Roles
|
||||
</ContextMenuButton>
|
||||
<ContextMenuDivider />
|
||||
|
||||
<ContextMenuButton icon="mdiIdentifier" onClick={copyId}>
|
||||
<ContextMenuButton
|
||||
icon="mdiIdentifier"
|
||||
iconProps={{
|
||||
style: {
|
||||
filter: "invert(100%)",
|
||||
background: "black",
|
||||
borderRadius: "4px",
|
||||
},
|
||||
}}
|
||||
onClick={copyId}
|
||||
>
|
||||
Copy user ID
|
||||
</ContextMenuButton>
|
||||
</ContextMenu>
|
||||
|
@ -3,12 +3,12 @@ import { ModalProps, modalController } from "../../controllers/modals";
|
||||
import Button from "../Button";
|
||||
import { Modal } from "./ModalComponents";
|
||||
|
||||
export const ActionWrapper = styled.div`
|
||||
const ActionWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
`;
|
||||
function AddServerModal({ ...props }: ModalProps<"add_server">) {
|
||||
export function AddServerModal({ ...props }: ModalProps<"add_server">) {
|
||||
return (
|
||||
<Modal {...props} title="Add a Guild" description="Lorem ipsum dolor sit amet, consectetur adipiscing elit.">
|
||||
<ActionWrapper>
|
||||
@ -37,5 +37,3 @@ function AddServerModal({ ...props }: ModalProps<"add_server">) {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default AddServerModal;
|
||||
|
@ -1,5 +1,3 @@
|
||||
function AttachmentPreviewModal() {
|
||||
export function AttachmentPreviewModal() {
|
||||
return null;
|
||||
}
|
||||
|
||||
export default AttachmentPreviewModal;
|
||||
|
72
src/components/modals/BanMemberModal.tsx
Normal file
72
src/components/modals/BanMemberModal.tsx
Normal file
@ -0,0 +1,72 @@
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { useForm } from "react-hook-form";
|
||||
import styled from "styled-components";
|
||||
import * as yup from "yup";
|
||||
import { ModalProps, modalController } from "../../controllers/modals";
|
||||
import { Modal } from "./ModalComponents";
|
||||
|
||||
const DescriptionText = styled.p`
|
||||
font-size: 16px;
|
||||
font-weight: var(--font-weight-regular);
|
||||
color: var(--text-header-secondary);
|
||||
margin-top: 8px;
|
||||
`;
|
||||
|
||||
const schema = yup
|
||||
.object({
|
||||
reason: yup.string(),
|
||||
})
|
||||
.required();
|
||||
|
||||
export function BanMemberModal({ target, ...props }: ModalProps<"ban_member">) {
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
setError,
|
||||
handleSubmit,
|
||||
formState: { errors, disabled, isLoading, isSubmitting },
|
||||
} = useForm({
|
||||
resolver: yupResolver(schema),
|
||||
});
|
||||
|
||||
const isDisabled = disabled || isLoading || isSubmitting;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
{...props}
|
||||
title={`Ban '${target.user?.username}'`}
|
||||
description={
|
||||
<DescriptionText>
|
||||
Are you sure you want to ban <b>@{target.user?.username}</b>? They won't be able to rejoin unless
|
||||
they are unbanned.
|
||||
</DescriptionText>
|
||||
}
|
||||
actions={[
|
||||
{
|
||||
onClick: () => console.log("kick"),
|
||||
children: <span>Kick</span>,
|
||||
palette: "danger",
|
||||
confirmation: true,
|
||||
disabled: isDisabled,
|
||||
},
|
||||
{
|
||||
onClick: () => modalController.pop("close"),
|
||||
children: <span>Cancel</span>,
|
||||
palette: "link",
|
||||
disabled: isDisabled,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<img
|
||||
src="https://media1.tenor.com/m/TG5OF7UkLasAAAAd/thanos-infinity.gif"
|
||||
loading="lazy"
|
||||
alt="Thanos Snap GIF"
|
||||
height={300}
|
||||
style={{
|
||||
objectFit: "contain",
|
||||
}}
|
||||
/>
|
||||
<span>reason form</span>
|
||||
</Modal>
|
||||
);
|
||||
}
|
@ -85,7 +85,7 @@ interface FormValues extends APICreateInvite {
|
||||
code: string;
|
||||
}
|
||||
|
||||
function CreateInviteModal({ target, ...props }: ModalProps<"create_invite">) {
|
||||
export function CreateInviteModal({ target, ...props }: ModalProps<"create_invite">) {
|
||||
const logger = useLogger("CreateInviteModal");
|
||||
const app = useAppStore();
|
||||
const [maxAge, setMaxAge] = React.useState(ExpiryOptions.DAY_7);
|
||||
@ -290,5 +290,3 @@ function CreateInviteModal({ target, ...props }: ModalProps<"create_invite">) {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreateInviteModal;
|
||||
|
@ -11,11 +11,6 @@ import { Input, InputErrorText, InputLabel, InputWrapper, LabelWrapper } from ".
|
||||
import { TextDivider } from "../Divider";
|
||||
import { InputContainer, Modal } from "./ModalComponents";
|
||||
|
||||
export const ModalHeader = styled.div`
|
||||
margin-bottom: 30px;
|
||||
padding: 24px 24px 0;
|
||||
`;
|
||||
|
||||
const UploadIcon = styled.div`
|
||||
padding-top: 4;
|
||||
display: flex;
|
||||
@ -49,7 +44,7 @@ type FormValues = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
function CreateServerModal({ ...props }: ModalProps<"create_server">) {
|
||||
export function CreateServerModal({ ...props }: ModalProps<"create_server">) {
|
||||
const app = useAppStore();
|
||||
const logger = useLogger("CreateServerModal");
|
||||
const [selectedFile, setSelectedFile] = React.useState<File>();
|
||||
@ -210,5 +205,3 @@ function CreateServerModal({ ...props }: ModalProps<"create_server">) {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default CreateServerModal;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ModalProps } from "../../controllers/modals/types";
|
||||
import { Modal } from "./ModalComponents";
|
||||
|
||||
function ErrorModal({ error, ...props }: ModalProps<"error">) {
|
||||
export function ErrorModal({ error, ...props }: ModalProps<"error">) {
|
||||
return (
|
||||
<Modal
|
||||
{...props}
|
||||
@ -20,5 +20,3 @@ function ErrorModal({ error, ...props }: ModalProps<"error">) {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default ErrorModal;
|
||||
|
@ -1,5 +1,3 @@
|
||||
function ForgotPasswordModal() {
|
||||
export function ForgotPasswordModal() {
|
||||
return null;
|
||||
}
|
||||
|
||||
export default ForgotPasswordModal;
|
||||
|
@ -10,10 +10,6 @@ import { Input, InputErrorText, InputLabel, LabelWrapper } from "../AuthComponen
|
||||
import { TextDivider } from "../Divider";
|
||||
import { Modal } from "./ModalComponents";
|
||||
|
||||
export const ModalHeader = styled.div`
|
||||
padding: 16px;
|
||||
`;
|
||||
|
||||
const InviteInputContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -23,7 +19,7 @@ type FormValues = {
|
||||
code: string;
|
||||
};
|
||||
|
||||
function JoinServerModal({ ...props }: ModalProps<"join_server">) {
|
||||
export function JoinServerModal({ ...props }: ModalProps<"join_server">) {
|
||||
const logger = useLogger("JoinServerModal");
|
||||
const app = useAppStore();
|
||||
const navigate = useNavigate();
|
||||
@ -137,5 +133,3 @@ function JoinServerModal({ ...props }: ModalProps<"join_server">) {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default JoinServerModal;
|
||||
|
63
src/components/modals/KickMemberModal.tsx
Normal file
63
src/components/modals/KickMemberModal.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import { useForm } from "react-hook-form";
|
||||
import styled from "styled-components";
|
||||
import * as yup from "yup";
|
||||
import { ModalProps, modalController } from "../../controllers/modals";
|
||||
import { Modal } from "./ModalComponents";
|
||||
|
||||
const DescriptionText = styled.p`
|
||||
font-size: 16px;
|
||||
font-weight: var(--font-weight-regular);
|
||||
color: var(--text-header-secondary);
|
||||
margin-top: 8px;
|
||||
`;
|
||||
|
||||
const schema = yup
|
||||
.object({
|
||||
reason: yup.string(),
|
||||
})
|
||||
.required();
|
||||
|
||||
export function KickMemberModal({ target, ...props }: ModalProps<"kick_member">) {
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
setError,
|
||||
handleSubmit,
|
||||
formState: { errors, disabled, isLoading, isSubmitting },
|
||||
} = useForm({
|
||||
resolver: yupResolver(schema),
|
||||
});
|
||||
|
||||
const isDisabled = disabled || isLoading || isSubmitting;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
{...props}
|
||||
title={`Kick ${target.user?.username} from Guild`}
|
||||
description={
|
||||
<DescriptionText>
|
||||
Are you sure you want to kick <b>@{target.user?.username}</b> from the guild? They will be able to
|
||||
rejoin again with a new invite.
|
||||
</DescriptionText>
|
||||
}
|
||||
actions={[
|
||||
{
|
||||
onClick: () => console.log("kick"),
|
||||
children: <span>Kick</span>,
|
||||
palette: "danger",
|
||||
confirmation: true,
|
||||
disabled: isDisabled,
|
||||
},
|
||||
{
|
||||
onClick: () => modalController.pop("close"),
|
||||
children: <span>Cancel</span>,
|
||||
palette: "link",
|
||||
disabled: isDisabled,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<span>reason form</span>
|
||||
</Modal>
|
||||
);
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
function KickModal() {
|
||||
return null;
|
||||
}
|
||||
|
||||
export default KickModal;
|
@ -1,5 +1,3 @@
|
||||
function LeaveServerModal() {
|
||||
export function LeaveServerModal() {
|
||||
return null;
|
||||
}
|
||||
|
||||
export default LeaveServerModal;
|
||||
|
@ -17,7 +17,7 @@ interface ModalProps {
|
||||
onClose?: (force: boolean) => void;
|
||||
signal?: "close" | "confirm" | "cancel";
|
||||
title?: string;
|
||||
description?: string;
|
||||
description?: React.ReactNode;
|
||||
transparent?: boolean;
|
||||
nonDismissable?: boolean;
|
||||
maxWidth?: string;
|
||||
@ -214,8 +214,16 @@ export function Modal(props: ModalProps) {
|
||||
</div>
|
||||
{(props.title || props.description) && (
|
||||
<ModalHeader>
|
||||
{props.title && <ModalHeaderText>{props.title}</ModalHeaderText>}
|
||||
{props.description && <ModalSubHeaderText>{props.description}</ModalSubHeaderText>}
|
||||
{props.title && typeof props.title === "string" ? (
|
||||
<ModalHeaderText>{props.title}</ModalHeaderText>
|
||||
) : (
|
||||
props.title
|
||||
)}
|
||||
{props.description && typeof props.description === "string" ? (
|
||||
<ModalSubHeaderText>{props.description}</ModalSubHeaderText>
|
||||
) : (
|
||||
props.description
|
||||
)}
|
||||
</ModalHeader>
|
||||
)}
|
||||
<ModalContentContainer {...props}>{props.children}</ModalContentContainer>
|
||||
|
@ -1,7 +1,3 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
function SettingsModal() {
|
||||
export function SettingsModal() {
|
||||
return null;
|
||||
}
|
||||
|
||||
export default observer(SettingsModal);
|
||||
|
11
src/components/modals/index.ts
Normal file
11
src/components/modals/index.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export * from "./AddServerModal";
|
||||
export * from "./AttachmentPreviewModal";
|
||||
export * from "./BanMemberModal";
|
||||
export * from "./CreateInviteModal";
|
||||
export * from "./CreateServerModal";
|
||||
export * from "./ErrorModal";
|
||||
export * from "./ForgotPasswordModal";
|
||||
export * from "./JoinServerModal";
|
||||
export * from "./KickMemberModal";
|
||||
export * from "./LeaveServerModal";
|
||||
export * from "./SettingsModal";
|
@ -3,10 +3,12 @@ import { FloatingPortal, useFloating } from "@floating-ui/react";
|
||||
import React from "react";
|
||||
import UserContextMenu from "../components/contextMenus/UserContextMenu";
|
||||
import useContextMenu from "../hooks/useContextMenu";
|
||||
import GuildMember from "../stores/objects/GuildMember";
|
||||
import User from "../stores/objects/User";
|
||||
|
||||
interface MenuProps {
|
||||
user: User;
|
||||
member?: GuildMember;
|
||||
}
|
||||
|
||||
export type ContextMenuContextType = {
|
||||
|
@ -6,10 +6,13 @@ import { rgbToHsl } from "../utils/Utils";
|
||||
const font: ThemeFont["font"] = {
|
||||
weight: {
|
||||
thin: 100,
|
||||
// extraLight: 200,
|
||||
light: 300,
|
||||
regular: 400,
|
||||
medium: 500,
|
||||
// semiBold: 600,
|
||||
bold: 700,
|
||||
// extraBold: 800,
|
||||
black: 900,
|
||||
},
|
||||
family: "Roboto, Arial, Helvetica, sans-serif",
|
||||
@ -69,10 +72,13 @@ export type ThemeFont = {
|
||||
font: {
|
||||
weight: {
|
||||
thin?: number;
|
||||
extraLight?: number;
|
||||
light?: number;
|
||||
regular?: number;
|
||||
medium?: number;
|
||||
semiBold?: number;
|
||||
bold?: number;
|
||||
extraBold?: number;
|
||||
black?: number;
|
||||
};
|
||||
family: string;
|
||||
|
@ -1,11 +1,15 @@
|
||||
// adapted from https://github.com/revoltchat/revite/blob/master/src/controllers/modals/ModalController.tsx
|
||||
|
||||
import { action, computed, makeObservable, observable } from "mobx";
|
||||
import AddServerModal from "../../components/modals/AddServerModal";
|
||||
import CreateInviteModal from "../../components/modals/CreateInviteModal";
|
||||
import CreateServerModal from "../../components/modals/CreateServerModal";
|
||||
import ErrorModal from "../../components/modals/ErrorModal";
|
||||
import JoinServerModal from "../../components/modals/JoinServerModal";
|
||||
import {
|
||||
AddServerModal,
|
||||
BanMemberModal,
|
||||
CreateInviteModal,
|
||||
CreateServerModal,
|
||||
ErrorModal,
|
||||
JoinServerModal,
|
||||
KickMemberModal,
|
||||
} from "../../components/modals";
|
||||
import { Modal } from "./types";
|
||||
|
||||
function randomUUID() {
|
||||
@ -95,6 +99,7 @@ class ModalController<T extends Modal> {
|
||||
<>
|
||||
{this.stack.map((modal) => {
|
||||
const Component = this.components[modal.type];
|
||||
if (!Component) return null;
|
||||
return <Component {...modal} onClose={() => this.remove(modal.key!)} />;
|
||||
})}
|
||||
</>
|
||||
@ -132,7 +137,7 @@ class ModalControllerExtended extends ModalController<Modal> {
|
||||
export const modalController = new ModalControllerExtended({
|
||||
add_server: AddServerModal,
|
||||
// add_friend: AddFriend,
|
||||
// ban_member: BanMember,
|
||||
ban_member: BanMemberModal,
|
||||
// changelog: Changelog,
|
||||
// channel_info: ChannelInfo,
|
||||
// clipboard: Clipboard,
|
||||
@ -156,7 +161,7 @@ export const modalController = new ModalControllerExtended({
|
||||
// delete_message: DeleteMessage,
|
||||
error: ErrorModal,
|
||||
// image_viewer: ImageViewer,
|
||||
// kick_member: KickMember,
|
||||
kick_member: KickMemberModal,
|
||||
// link_warning: LinkWarning,
|
||||
// mfa_flow: MFAFlow,
|
||||
// mfa_recovery: MFARecovery,
|
||||
|
@ -1,6 +1,7 @@
|
||||
// adapted from https://github.com/revoltchat/revite/blob/master/src/controllers/modals/types.ts
|
||||
|
||||
import Channel from "../../stores/objects/Channel";
|
||||
import GuildMember from "../../stores/objects/GuildMember";
|
||||
|
||||
export type Modal = {
|
||||
key?: string;
|
||||
@ -23,6 +24,14 @@ export type Modal = {
|
||||
type: "create_invite";
|
||||
target: Channel;
|
||||
}
|
||||
| {
|
||||
type: "kick_member";
|
||||
target: GuildMember;
|
||||
}
|
||||
| {
|
||||
type: "ban_member";
|
||||
target: GuildMember;
|
||||
}
|
||||
);
|
||||
|
||||
export type ModalProps<T extends Modal["type"]> = Modal & { type: T } & {
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { autoUpdate, flip, offset, shift, useDismiss, useFloating, useInteractions, useRole } from "@floating-ui/react";
|
||||
import { useMemo, useState } from "react";
|
||||
import GuildMember from "../stores/objects/GuildMember";
|
||||
import User from "../stores/objects/User";
|
||||
|
||||
interface MenuProps {
|
||||
user: User;
|
||||
member?: GuildMember;
|
||||
}
|
||||
|
||||
export default function (type: "user") {
|
||||
|
@ -18,9 +18,12 @@ textarea {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html *:not(code) {
|
||||
font-family: var(--font-family);
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--font-family);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
overflow: hidden;
|
||||
|
@ -11,6 +11,7 @@ import "@fontsource/roboto/400.css";
|
||||
import "@fontsource/roboto/500.css";
|
||||
import "@fontsource/roboto/700.css";
|
||||
import "@fontsource/roboto/900.css";
|
||||
|
||||
import dayjs from "dayjs";
|
||||
import calendar from "dayjs/plugin/calendar";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
|
Loading…
Reference in New Issue
Block a user