feat(style): update fonts & themes in frontend (#14)
Reviewed-on: https://gitea.vodsystem.ddns.net/dylan/vod-system/pulls/14
This commit was merged in pull request #14.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
@@ -6,7 +7,7 @@ type Props = {
|
||||
};
|
||||
|
||||
const Box = ({ children, className }: Props) => (
|
||||
<div className={clsx("bg-white border border-gray-200 shadow-sm rounded-lg", className)}>
|
||||
<div className={clsx("bg-card border-hairline shadow-sm rounded-lg", className)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,7 @@ type DropdownItemProps = {
|
||||
|
||||
const DropdownItem = ({ item, onClick, className }: DropdownItemProps) => (
|
||||
<li
|
||||
className={clsx("cursor-pointer hover:bg-gray-50 px-3 py-2 text-sm text-gray-700", className)}
|
||||
className={clsx("cursor-pointer hover:bg-hover px-3 py-2 text-sm rounded-md", className)}
|
||||
onClick={() => onClick(item)}
|
||||
>
|
||||
{item}
|
||||
@@ -40,12 +40,12 @@ const Dropdown = ({ label, children, className }: DropdownProps) => {
|
||||
<div className={clsx(className, "relative inline-block text-left")} ref={ref}>
|
||||
<button
|
||||
onClick={() => setIsOpen(v => !v)}
|
||||
className="inline-flex items-center gap-1 rounded-lg px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 transition-colors duration-150"
|
||||
className="inline-flex items-center gap-1 px-3 py-2 text-sm transition-colors duration-150"
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
{isOpen && (
|
||||
<ul className="absolute right-0 mt-1 w-36 origin-top-right rounded-lg bg-white border border-gray-200 shadow-md z-50 py-1 overflow-hidden">
|
||||
<ul className="absolute right-0 mt-1 w-36 origin-top-right rounded-lg bg-background border border-hairline shadow-md z-50 py-1 overflow-hidden">
|
||||
{children}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
@@ -7,8 +7,8 @@ type Props = {
|
||||
|
||||
const Selector = ({ children, label }: Props) => (
|
||||
<div className="flex items-center gap-2">
|
||||
<label className="w-full text-sm text-gray-600">{label}</label>
|
||||
<div className="w-px h-5 bg-gray-200 mx-2" />
|
||||
<label className="w-full text-sm text-text-secondary">{label}</label>
|
||||
<div className="w-px h-5 text-text-secondary mx-2" />
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -8,43 +8,43 @@ type props = {
|
||||
className?: string
|
||||
}
|
||||
|
||||
const Divider = () => <hr className="my-1.5 mx-2 border-gray-100" />;
|
||||
const Divider = () => <hr className="w-full my-1.5 mx-2 border-hover" />;
|
||||
|
||||
const Sidebar = ({user, className}: props) => {
|
||||
return (
|
||||
<aside className={clsx("h-screen flex flex-col bg-white border-r border-gray-200", className)}>
|
||||
<div className="px-4 pt-5 pb-3">
|
||||
<img
|
||||
className="w-36 mx-auto"
|
||||
src="../../public/logo.png"
|
||||
alt="VoD System Logo"
|
||||
/>
|
||||
</div>
|
||||
<nav className="flex flex-col flex-1 px-2 pb-4">
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<aside className={clsx("h-screen flex flex-col bg-sidebar border-r border-hairline p-2 items-center gap-1", className)}>
|
||||
<h2 className={"font-heading text-2xl mt-1"}>The VoD System</h2>
|
||||
|
||||
<nav className="flex flex-col flex-1 items-center w-3/4">
|
||||
<Divider />
|
||||
|
||||
<div className="flex flex-col gap-0.5 w-full">
|
||||
<SidebarButton url="/" logo={<Home size={18}/>} label="Home" />
|
||||
<SidebarButton url="/create" logo={<Plus size={18}/>} label="Create Clip" />
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
{user && (
|
||||
<>
|
||||
<Divider />
|
||||
<div className="flex flex-col gap-0.5">
|
||||
<SidebarButton url="/stream" logo={<Radio size={18}/>} label="Stream" />
|
||||
<SidebarButton url="/my-clips" logo={<Film size={18}/>} label="Clips" />
|
||||
<SidebarButton url="/my-vods" logo={<Video size={18}/>} label="VoDs" />
|
||||
<div className="flex flex-col gap-0.5 w-full">
|
||||
<div>
|
||||
<SidebarButton url="/stream" logo={<Radio size={18}/>} label="Stream" />
|
||||
<SidebarButton url="/my-clips" logo={<Film size={18}/>} label="Clips" />
|
||||
<SidebarButton url="/my-vods" logo={<Video size={18}/>} label="VoDs" />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="flex-1" />
|
||||
|
||||
{user && (
|
||||
<>
|
||||
<Divider />
|
||||
<SidebarButton url="/profile" logo={<UserIcon size={18}/>} label="Profile" />
|
||||
</>
|
||||
)}
|
||||
<div className={"w-full"}>
|
||||
{user && (
|
||||
<>
|
||||
<Divider />
|
||||
<SidebarButton url="/profile" logo={<UserIcon size={18}/>} label="Profile" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</nav>
|
||||
</aside>
|
||||
);
|
||||
|
||||
@@ -58,13 +58,13 @@ const Topbar = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={clsx(className, "flex justify-between items-center px-4 py-2 bg-white border-b border-gray-200")}>
|
||||
<div className={clsx(className, "flex justify-between items-center px-4 py-2 bg-sidebar border-b border-hairline")}>
|
||||
<MenuButton onClick={() => setSidebarToggled(!sidebarToggled)}>
|
||||
{sidebarToggled ? <Menu size={24}/> : <X size={24}/>}
|
||||
</MenuButton>
|
||||
|
||||
{ user ? (
|
||||
<div>
|
||||
<div className={"hover:bg-hover rounded-lg p-0.5"}>
|
||||
<img
|
||||
className={"w-8 h-8 rounded-full inline-block"}
|
||||
src={user.profilePictureUrl}
|
||||
@@ -76,8 +76,8 @@ const Topbar = ({
|
||||
{user.name}
|
||||
{isStreaming && (
|
||||
<span className="relative flex h-2 w-2">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-red-500 opacity-75" />
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-red-500" />
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-live opacity-75" />
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-live" />
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
@@ -86,7 +86,7 @@ const Topbar = ({
|
||||
onClick={() => navigate("/profile")} />
|
||||
<DropdownItem item="Logout"
|
||||
onClick={() => handleLogout()}
|
||||
className={"text-red-500 font-medium"} />
|
||||
className={"text-error font-medium"} />
|
||||
</Dropdown>
|
||||
</div>
|
||||
) :
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
type Props = React.ButtonHTMLAttributes<HTMLButtonElement>;
|
||||
|
||||
const BlueButton: React.FC<Props> = ({ className = "", ...props }) => {
|
||||
return (
|
||||
<button
|
||||
className={`bg-primary text-white font-medium rounded-lg hover:bg-primary-pressed transition-colors duration-150 h-10 px-4 ${className}`}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlueButton;
|
||||
@@ -6,7 +6,7 @@ type Props = React.ButtonHTMLAttributes<HTMLButtonElement>;
|
||||
const MenuButton: React.FC<Props> = ({ className = "", ...props }) => {
|
||||
return (
|
||||
<button
|
||||
className={clsx("p-2 rounded-md text-gray-600 hover:bg-gray-100 hover:text-gray-900 transition-colors duration-150", className)}
|
||||
className={clsx("p-2 rounded-md text-text-secondary hover:bg-hover hover:text-text-primary transition-colors duration-150", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
14
frontend/src/components/buttons/PrimaryButton.tsx
Normal file
14
frontend/src/components/buttons/PrimaryButton.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import React from "react";
|
||||
|
||||
type Props = React.ButtonHTMLAttributes<HTMLButtonElement>;
|
||||
|
||||
const PrimaryButton: React.FC<Props> = ({ className = "", ...props }) => {
|
||||
return (
|
||||
<button
|
||||
className={`bg-terracotta text-on-accent font-body text-sm rounded-lg hover:bg-terracotta-hover disabled:bg-terracotta-disabled transition-colors duration-150 h-10 px-4 ${className}`}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default PrimaryButton;
|
||||
@@ -1,5 +1,5 @@
|
||||
import MenuButton from "./MenuButton.tsx";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import React from "react";
|
||||
|
||||
type Props = {
|
||||
url: string;
|
||||
@@ -13,11 +13,17 @@ const SidebarButton = ({url, logo, label}: Props) => {
|
||||
|
||||
return (
|
||||
<Link className="w-full" to={url}>
|
||||
<MenuButton className={`flex items-center gap-2.5 w-full px-3 py-2 text-sm font-medium rounded-md ${isActive ? "bg-gray-100 text-gray-900" : "text-gray-600"}`}>
|
||||
<button className={`flex items-center gap-2.5 w-full text-sm
|
||||
font-body p-2 hover:bg-hover hover:text-text-strong
|
||||
rounded-md
|
||||
${isActive ?
|
||||
"bg-card text-text-strong font-medium shadow-sm"
|
||||
:
|
||||
"text-text-primary"}`}>
|
||||
{logo}{label}
|
||||
</MenuButton>
|
||||
</button>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
export default SidebarButton;
|
||||
export default SidebarButton;
|
||||
@@ -8,7 +8,7 @@ type Props = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const inputClass = "border border-gray-300 bg-white rounded-md w-full p-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-colors";
|
||||
const inputClass = "border border-hairline bg-card rounded-md w-full p-2 text-sm focus:outline-none focus:ring-2 focus:ring-muted transition-colors";
|
||||
|
||||
export default function ConfigBox({ setMetadata, className }: Props) {
|
||||
const updateRes = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
@@ -26,7 +26,7 @@ export default function ConfigBox({ setMetadata, className }: Props) {
|
||||
|
||||
return (
|
||||
<div className={clsx("flex flex-col gap-3 p-6", className)}>
|
||||
<h2 className="text-sm font-semibold text-gray-800 mb-1">Export Settings</h2>
|
||||
<h2 className="text-sm text-text-primary font-semibold mb-1">Export Settings</h2>
|
||||
|
||||
<Selector label="Resolution">
|
||||
<select id="resolution" name="resolution" defaultValue="1280,720"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import BlueButton from "../buttons/BlueButton.tsx";
|
||||
import PrimaryButton from "../buttons/PrimaryButton.tsx";
|
||||
import React from "react";
|
||||
|
||||
type props = {
|
||||
@@ -24,24 +24,24 @@ const Spinner = () => (
|
||||
const ExportWidget = ({dataSend, handleDownload, downloadable, progress, uploading}: props) => {
|
||||
return (
|
||||
<div className={"flex flex-col gap-3"}>
|
||||
<BlueButton
|
||||
<PrimaryButton
|
||||
onClick={dataSend}
|
||||
disabled={uploading}
|
||||
className={uploading ? "opacity-60 cursor-not-allowed flex items-center justify-center gap-2" : ""}
|
||||
>
|
||||
{uploading && <Spinner />}
|
||||
{uploading ? "Uploading..." : "Export"}
|
||||
</BlueButton>
|
||||
</PrimaryButton>
|
||||
|
||||
<div className="h-10 flex items-center">
|
||||
{ downloadable ?
|
||||
(<BlueButton className="w-full" onClick={handleDownload}>
|
||||
(<PrimaryButton className="w-full" onClick={handleDownload}>
|
||||
Download
|
||||
</BlueButton>)
|
||||
</PrimaryButton>)
|
||||
:(
|
||||
<progress
|
||||
value={progress}
|
||||
className="w-full h-1.5 rounded bg-gray-200">
|
||||
className="w-full h-1.5 rounded bg-fields">
|
||||
</progress>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,12 +9,12 @@ type Props = {
|
||||
|
||||
const MetadataBox = ({ setMetadata, className }: Props) => (
|
||||
<div className={clsx("flex flex-col content-between p-6 gap-2", className)}>
|
||||
<p className="w-full font-semibold text-gray-800 text-sm">Title</p>
|
||||
<p className="w-full font-body text-text-strong font-bold text-sm">Title</p>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter title"
|
||||
onChange={(e) => setMetadata(prev => ({ ...prev, title: e.target.value }))}
|
||||
className="border border-gray-300 bg-white rounded-md w-full p-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-colors"
|
||||
className="border border-hairline bg-fields rounded-md w-full p-2 text-sm focus:outline-none focus:ring-2 focus:ring-muted transition-colors"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -62,11 +62,11 @@ export default function Playbar({ video, videoMetadata, className }: Props) {
|
||||
</div>
|
||||
<button
|
||||
onClick={togglePlay}
|
||||
className="hover:bg-gray-100 rounded-full p-1.5 transition-colors duration-150 text-gray-700"
|
||||
className="hover:bg-hover rounded-full p-1.5 transition-colors duration-150 text-text-body"
|
||||
>
|
||||
{isPlaying ? <Pause size={24} /> : <Play size={24} />}
|
||||
</button>
|
||||
<span>
|
||||
<span className="font-data text-sm text-text-secondary tabular-nums">
|
||||
{formatTime(video?.currentTime ?? 0)} / {formatTime(videoMetadata.duration ?? 0)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -70,7 +70,7 @@ const VideoCard = ({ id, title, duration, createdAt, onEdit, onDelete, className
|
||||
className="w-full aspect-video object-cover group-hover:scale-105 transition-transform duration-200"
|
||||
/>
|
||||
|
||||
<span className="absolute top-1.5 left-1.5 bg-black/70 text-white px-1.5 py-0.5 rounded text-xs font-medium pointer-events-none">
|
||||
<span className="absolute top-1.5 left-1.5 bg-text-primary/75 text-on-dark px-1.5 py-0.5 rounded text-xs font-data pointer-events-none">
|
||||
{formatTime(duration)}
|
||||
</span>
|
||||
|
||||
@@ -82,18 +82,18 @@ const VideoCard = ({ id, title, duration, createdAt, onEdit, onDelete, className
|
||||
>
|
||||
<button
|
||||
onClick={(e) => stopAndRun(e, () => { setMenuOpen(v => !v); setConfirmDelete(false); })}
|
||||
className="bg-black/70 hover:bg-black/90 text-white rounded p-0.5 transition-colors"
|
||||
className="bg-text-primary/75 hover:bg-text-primary/90 text-on-dark rounded p-0.5 transition-colors"
|
||||
>
|
||||
<MoreVertical size={14} />
|
||||
</button>
|
||||
|
||||
{menuOpen && (
|
||||
<div className="absolute right-0 top-7 w-32 bg-white rounded-lg border border-gray-200 shadow-md z-50 py-1 overflow-hidden">
|
||||
<div className="absolute right-0 top-7 w-32 bg-card rounded-lg border border-hairline shadow-md z-50 py-1 overflow-hidden">
|
||||
{!confirmDelete && (<>
|
||||
{onEdit && (
|
||||
<button
|
||||
onClick={(e) => stopAndRun(e, onEdit)}
|
||||
className="flex items-center gap-2 w-full px-3 py-2 text-sm text-gray-700 hover:bg-gray-50"
|
||||
className="flex items-center gap-2 w-full px-3 py-2 text-sm text-text-body hover:bg-hover"
|
||||
>
|
||||
<Pencil size={13} /> Edit
|
||||
</button>
|
||||
@@ -101,7 +101,7 @@ const VideoCard = ({ id, title, duration, createdAt, onEdit, onDelete, className
|
||||
{onDelete && (
|
||||
<button
|
||||
onClick={(e) => stopAndRun(e, () => setConfirmDelete(true))}
|
||||
className="flex items-center gap-2 w-full px-3 py-2 text-sm text-red-500 hover:bg-red-50"
|
||||
className="flex items-center gap-2 w-full px-3 py-2 text-sm text-error hover:bg-hover"
|
||||
>
|
||||
<Trash2 size={13} /> Delete
|
||||
</button>
|
||||
@@ -109,17 +109,17 @@ const VideoCard = ({ id, title, duration, createdAt, onEdit, onDelete, className
|
||||
</>)}
|
||||
{confirmDelete && (
|
||||
<div className="px-3 py-2">
|
||||
<p className="text-xs text-gray-600 mb-2">Delete this {itemLabel}?</p>
|
||||
<p className="text-xs text-text-secondary mb-2">Delete this {itemLabel}?</p>
|
||||
<div className="flex gap-1.5">
|
||||
<button
|
||||
onClick={(e) => stopAndRun(e, onDelete!)}
|
||||
className="flex-1 text-xs bg-red-500 text-white rounded px-2 py-1 hover:bg-red-600 transition-colors"
|
||||
className="flex-1 text-xs bg-error text-on-accent rounded px-2 py-1 hover:bg-terracotta-hover transition-colors"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => stopAndRun(e, () => setConfirmDelete(false))}
|
||||
className="flex-1 text-xs bg-gray-100 text-gray-600 rounded px-2 py-1 hover:bg-gray-200 transition-colors"
|
||||
className="flex-1 text-xs bg-fields text-text-secondary rounded px-2 py-1 hover:bg-hover transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
@@ -133,10 +133,10 @@ const VideoCard = ({ id, title, duration, createdAt, onEdit, onDelete, className
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col p-2">
|
||||
<p className="font-medium text-gray-900 text-sm leading-snug line-clamp-2">
|
||||
<p className="font-body text-text-strong text-sm leading-snug line-clamp-2">
|
||||
{title === "" ? "(No Title)" : title}
|
||||
</p>
|
||||
<p className="text-gray-500 text-xs mt-0.5">{timeAgo}</p>
|
||||
<p className="text-muted text-xs mt-0.5">{timeAgo}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
Reference in New Issue
Block a user