Commit d58472a2 authored by timel's avatar timel

feat: waitroom improvement

parent a1e99135
Pipeline #22911 canceled with stages
in 1 minute and 13 seconds
// 全局修改 antd 样式
.ant-btn {
display: flex;
align-items: center;
}
// 点击按钮产生的波浪扩散效果
.ant-wave {
color: hsla(0, 0%, 100%, 0.3);
}
@charset "utf-8"; @charset "utf-8";
@import url("https://fonts.googleapis.com/css2?family=Electrolize&display=swap"); @import url("https://fonts.googleapis.com/css2?family=Electrolize&display=swap");
@import "./antd.scss";
body { body {
color-scheme: dark; color-scheme: dark;
......
.container { .container {
// height: calc(100% - var(--header-height));
height: 100%; height: 100%;
display: flex; display: flex;
--side-box-width: 400px; --side-box-width: 400px;
...@@ -57,6 +56,7 @@ ...@@ -57,6 +56,7 @@
.inner { .inner {
background: linear-gradient(to right, blue, rgb(0, 149, 255)); background: linear-gradient(to right, blue, rgb(0, 149, 255));
} }
z-index: 1;
} }
&.op { &.op {
box-shadow: 5px 0 20px 0 rgba(255, 0, 81, 0.1); box-shadow: 5px 0 20px 0 rgba(255, 0, 81, 0.1);
...@@ -85,30 +85,8 @@ ...@@ -85,30 +85,8 @@
margin-left: auto; margin-left: auto;
} }
.btn-large { .avatars-watch {
min-width: 100px; padding-left: 8px;
height: 40px;
background: hsla(0, 0%, 100%, 0.1);
border-radius: 6px;
display: flex;
align-items: center;
padding: 0 16px 0 16px;
gap: 8px;
cursor: pointer;
transition: 0.2s;
.avatars-watch {
height: 100%;
place-items: center;
}
& > * {
transition: 0.2s;
}
&:hover > * {
opacity: 0.8;
}
&:active > * {
opacity: 0.6;
}
} }
.btn-sider { .btn-sider {
...@@ -135,7 +113,7 @@ ...@@ -135,7 +113,7 @@
} }
} }
.room-name { .status-bar {
position: absolute; position: absolute;
top: 40px; top: 40px;
left: 0; left: 0;
...@@ -147,4 +125,30 @@ ...@@ -147,4 +125,30 @@
font-size: 13px; font-size: 13px;
line-height: 28px; line-height: 28px;
border-radius: 14px; border-radius: 14px;
display: flex;
gap: 8px;
}
.fake-btn-large {
min-width: 88px;
height: 40px;
background: hsla(0, 0%, 100%, 0.1);
border-radius: 6px;
display: flex;
align-items: center;
padding: 0 16px 0 16px;
gap: 8px;
cursor: not-allowed;
transition: 0.2s;
position: relative;
& > * {
transition: 0.2s;
}
&:hover > * {
opacity: 0.8;
}
&:active > * {
opacity: 0.6;
}
overflow: hidden;
} }
import { CheckCircleFilled } from "@ant-design/icons"; import { CheckCircleFilled } from "@ant-design/icons";
import { Avatar, Button, ConfigProvider, Skeleton, Space } from "antd"; import { Avatar, Button, ConfigProvider, Popover, Skeleton, Space } from "antd";
import classNames from "classnames"; import classNames from "classnames";
import { useState } from "react"; import React, { useState } from "react";
import { type LoaderFunction, useLoaderData } from "react-router-dom"; import { type LoaderFunction, useLoaderData } from "react-router-dom";
import { useSnapshot } from "valtio"; import { useSnapshot } from "valtio";
import { accountStore } from "@/stores"; import { accountStore, type User } from "@/stores";
import { Background, IconFont, Select } from "@/ui/Shared"; import { Background, IconFont, Select } from "@/ui/Shared";
import { Chat } from "./Chat"; import { Chat } from "./Chat";
...@@ -19,13 +19,7 @@ interface Params { ...@@ -19,13 +19,7 @@ interface Params {
export const loader: LoaderFunction = async ({ params }) => params; export const loader: LoaderFunction = async ({ params }) => params;
export const Component: React.FC = () => { const theme = {
const _params = useLoaderData<Params>();
const { user } = useSnapshot(accountStore);
const [collapsed, setCollapsed] = useState(false);
return (
<ConfigProvider
theme={{
components: { components: {
Button: { Button: {
lineWidth: 0, lineWidth: 0,
...@@ -35,9 +29,18 @@ export const Component: React.FC = () => { ...@@ -35,9 +29,18 @@ export const Component: React.FC = () => {
colorPrimaryHover: "#ccc", colorPrimaryHover: "#ccc",
colorPrimaryActive: "#aaa", colorPrimaryActive: "#aaa",
}, },
Popover: {
colorBgElevated: "hsla(0, 0%, 100%, 0.1)",
}, },
}} },
> };
export const Component: React.FC = () => {
const _params = useLoaderData<Params>();
const { user } = useSnapshot(accountStore);
const [collapsed, setCollapsed] = useState(false);
return (
<ConfigProvider theme={theme}>
<div <div
className={classNames(styles.container, { className={classNames(styles.container, {
[styles.collapsed]: collapsed, [styles.collapsed]: collapsed,
...@@ -54,7 +57,159 @@ export const Component: React.FC = () => { ...@@ -54,7 +57,159 @@ export const Component: React.FC = () => {
<Chat /> <Chat />
</div> </div>
<div className={styles.main}> <div className={styles.main}>
{/* <div className={styles["room-name"]}>Timel 的房间</div> */} {/* <div className={styles["status-bar"]}>
<LoadingOutlined />
对方正在选择先后
</div> */}
<Controller />
<div className={styles["both-side-container"]}>
<PlayerZone
isReady
who={Who.Me}
user={user}
btn={
<>
{/* <Button size="large" className={styles["btn-join"]}>
决斗准备
</Button> */}
<MoraButton />
</>
}
/>
<PlayerZone
isReady
who={Who.Op}
btn={
<Button size="large" className={styles["btn-join"]}>
加入决斗
</Button>
}
/>
</div>
</div>
</div>
</ConfigProvider>
);
};
enum Mora {
Rock = "rock",
Scissors = "scissors",
Paper = "paper",
}
const MoraButton: React.FC<{
onClick?: () => Promise<Mora>;
}> = () => {
// TODO: 实现这个onclick
// 防抖
return (
<Popover
overlayStyle={{ backdropFilter: "blur(10px)" }}
content={
<Space>
<Button
size="large"
type="text"
icon={<IconFont type="icon-hand-rock" size={16} />}
>
石头
</Button>
<Button
size="large"
type="text"
icon={<IconFont type="icon-hand-scissors" size={16} />}
>
剪刀
</Button>
<Button
size="large"
type="text"
icon={<IconFont type="icon-hand-paper" size={16} />}
>
</Button>
</Space>
}
trigger="focus"
placement="bottom"
>
<Button
size="large"
className={styles["btn-join"]}
icon={<IconFont type="icon-mora" size={20} />}
>
猜拳
</Button>
</Popover>
);
};
const OrderPopup: React.FC<React.PropsWithChildren<{ open: boolean }>> = ({
children,
open,
}) => {
return (
<Popover
overlayStyle={{ backdropFilter: "blur(10px)" }}
open={open}
content={
<Space>
<Button
size="large"
type="text"
icon={<IconFont type="icon-one" size={18} />}
>
先手
</Button>
<Button
size="large"
type="text"
icon={<IconFont type="icon-two" size={18} />}
>
后手
</Button>
</Space>
}
trigger="focus"
placement="bottom"
>
{children}
</Popover>
);
};
enum Who {
Me = "me",
Op = "op",
}
// 玩家区域: 两侧各有一个
const PlayerZone: React.FC<{
btn?: React.ReactNode; // 在内部右侧可以放一个按钮
isReady: boolean;
who?: Who;
user?: User;
}> = ({ btn, isReady, who, user }) => {
return (
<div className={classNames(styles["side-box"], who && styles[who])}>
<div className={styles.inner}></div>
<OrderPopup open={false}>
<div style={{ position: "relative" }}>
<Avatar src={user?.avatar_url} size={48} />
{isReady && <CheckCircleFilled className={styles.check} />}
</div>
</OrderPopup>
<div className={styles.name}>
{user ? user.username : <Skeleton.Input size="small" />}
</div>
{btn}
</div>
);
};
const Controller: React.FC = () => {
return (
<Space> <Space>
<Select <Select
title="卡组" title="卡组"
...@@ -68,8 +223,7 @@ export const Component: React.FC = () => { ...@@ -68,8 +223,7 @@ export const Component: React.FC = () => {
{ value: "disabled", label: "Disabled", disabled: true }, { value: "disabled", label: "Disabled", disabled: true },
]} ]}
/> />
<div className={styles["btn-large"]}> <Button size="large" icon={<IconFont type="icon-record" size={18} />}>
<IconFont type="icon-record" size={18} />
加入观战 加入观战
<Avatar.Group className={styles["avatars-watch"]}> <Avatar.Group className={styles["avatars-watch"]}>
<Avatar <Avatar
...@@ -81,40 +235,10 @@ export const Component: React.FC = () => { ...@@ -81,40 +235,10 @@ export const Component: React.FC = () => {
</Avatar> </Avatar>
<Avatar style={{ backgroundColor: "#87d068" }} size="small" /> <Avatar style={{ backgroundColor: "#87d068" }} size="small" />
</Avatar.Group> </Avatar.Group>
</div>
<div className={styles["btn-large"]}>
<IconFont type="icon-play" size={12} />
开始游戏
</div>
</Space>
<div className={styles["both-side-container"]}>
<div className={classNames(styles["side-box"], styles.me)}>
<div className={styles.inner}></div>
<div style={{ position: "relative" }}>
<Avatar src={user?.avatar_url} size={48} />
<CheckCircleFilled className={styles.check} />
</div>
<div className={styles.name}>Timel#6675</div>
<Button size="large" className={styles["btn-join"]}>
决斗准备
</Button> </Button>
</div> <Button size="large" icon={<IconFont type="icon-play" size={12} />}>
{/* <div className={styles.battle}> 开始游戏
<IconFont type="icon-battle" size={32} />
</div> */}
<div className={classNames(styles["side-box"], styles.op)}>
<div className={styles.inner}></div>
<Avatar size={48} />
<div className={styles.name}>
<Skeleton.Input size="small" />
</div>
<Button size="large" className={styles["btn-join"]}>
加入决斗
</Button> </Button>
</div> </Space>
</div>
</div>
</div>
</ConfigProvider>
); );
}; };
import { createFromIconfontCN } from "@ant-design/icons"; import { createFromIconfontCN } from "@ant-design/icons";
const _IconFont = createFromIconfontCN({ const _IconFont = createFromIconfontCN({
scriptUrl: ["//at.alicdn.com/t/c/font_4188978_25l7e0zb6xo.js"], scriptUrl: ["//at.alicdn.com/t/c/font_4188978_6q59nqrc9ix.js"],
}); });
export const IconFont: React.FC<{ export const IconFont: React.FC<{
...@@ -10,5 +10,5 @@ export const IconFont: React.FC<{ ...@@ -10,5 +10,5 @@ export const IconFont: React.FC<{
color?: string; color?: string;
style?: React.CSSProperties; style?: React.CSSProperties;
}> = ({ type, size = "inherit", style, color = "inherit" }) => ( }> = ({ type, size = "inherit", style, color = "inherit" }) => (
<_IconFont type={type} style={{ ...style, fontSize: size, color }} /> <_IconFont type={type} style={{ fontSize: size, color, ...style }} />
); );
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment