v3: inline edit forms and the narrow-viewport search row ease open instead of popping in
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 41s
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 41s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -30,6 +30,7 @@ import { useAuth } from "../hooks/useAuth.ts";
|
||||
import { useCategories } from "../hooks/useCategories.ts";
|
||||
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
|
||||
import { relativeTime } from "../utils/relativeTime.ts";
|
||||
import { useHeightTransition } from "../hooks/useHeightTransition.ts";
|
||||
import { useWS } from "../hooks/useWS.ts";
|
||||
import { Avatar } from "../components/Avatar.tsx";
|
||||
import RichContentCard from "../components/RichContentCard.tsx";
|
||||
@@ -74,6 +75,7 @@ export function Dump() {
|
||||
const [collections, setCollections] = useState<Playlist[]>([]);
|
||||
|
||||
const [titleEditing, setTitleEditing] = useState(false);
|
||||
const headerRightRef = useHeightTransition<HTMLDivElement>(titleEditing);
|
||||
const [titleDraft, setTitleDraft] = useState("");
|
||||
const [titleSaving, setTitleSaving] = useState(false);
|
||||
const [titleError, setTitleError] = useState<string | null>(null);
|
||||
@@ -364,10 +366,10 @@ export function Dump() {
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="dump-header-right">
|
||||
<div className="dump-header-right" ref={headerRightRef}>
|
||||
{titleEditing
|
||||
? (
|
||||
<div className="dump-title-editor">
|
||||
<div className="dump-title-editor animate-in">
|
||||
<input
|
||||
type="text"
|
||||
className="dump-title-input"
|
||||
|
||||
@@ -28,6 +28,7 @@ import { playlistUrl } from "../utils/urls.ts";
|
||||
import { can } from "../utils/permissions.ts";
|
||||
import { useAuth } from "../hooks/useAuth.ts";
|
||||
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
|
||||
import { useHeightTransition } from "../hooks/useHeightTransition.ts";
|
||||
import { useWS } from "../hooks/useWS.ts";
|
||||
import { relativeTime } from "../utils/relativeTime.ts";
|
||||
import { DumpCard } from "../components/DumpCard.tsx";
|
||||
@@ -105,6 +106,7 @@ export function PlaylistDetail() {
|
||||
const [dragOverIndex, setDragOverIndex] = useState<number | null>(null);
|
||||
|
||||
const [editOpen, setEditOpen] = useState(false);
|
||||
const headerRef = useHeightTransition<HTMLDivElement>(editOpen);
|
||||
const [confirmDelete, setConfirmDelete] = useState(false);
|
||||
|
||||
// Mirrors activeDumpIds for use in effects without adding it as a dep.
|
||||
@@ -587,7 +589,7 @@ export function PlaylistDetail() {
|
||||
|
||||
return (
|
||||
<PageShell>
|
||||
<div className="playlist-detail-header">
|
||||
<div className="playlist-detail-header" ref={headerRef}>
|
||||
<div className="playlist-detail-header-top">
|
||||
{editOpen
|
||||
? (
|
||||
@@ -842,6 +844,7 @@ function PlaylistEditForm(
|
||||
: null)}
|
||||
alt="Cover"
|
||||
size={72}
|
||||
className="animate-in"
|
||||
onChange={(file) => {
|
||||
field.onChange(file);
|
||||
setImagePreview(URL.createObjectURL(file));
|
||||
@@ -850,7 +853,7 @@ function PlaylistEditForm(
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="playlist-detail-content playlist-edit-content">
|
||||
<div className="playlist-detail-content playlist-edit-content animate-in">
|
||||
<Controller
|
||||
name="title"
|
||||
control={form.control}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "../model.ts";
|
||||
import { useAuth } from "../hooks/useAuth.ts";
|
||||
import { useDocumentTitle } from "../hooks/useDocumentTitle.ts";
|
||||
import { useHeightTransition } from "../hooks/useHeightTransition.ts";
|
||||
import { PageShell } from "../components/PageShell.tsx";
|
||||
import {
|
||||
expectOk,
|
||||
@@ -35,6 +36,7 @@ export function UserLogin() {
|
||||
const navigate = useNavigate();
|
||||
const { login } = useAuth();
|
||||
const [showReset, setShowReset] = useState(false);
|
||||
const cardRef = useHeightTransition<HTMLDivElement>(showReset);
|
||||
|
||||
const form = useApiForm<LoginValues>({
|
||||
defaultValues: { username: "", password: "" },
|
||||
@@ -54,7 +56,7 @@ export function UserLogin() {
|
||||
|
||||
return (
|
||||
<PageShell centered>
|
||||
<div className="auth-card">
|
||||
<div className="auth-card" ref={cardRef}>
|
||||
<h1 className="auth-card-title">
|
||||
<Trans>Log in</Trans>
|
||||
</h1>
|
||||
@@ -115,7 +117,7 @@ function ResetRequestPanel() {
|
||||
|
||||
if (sent) {
|
||||
return (
|
||||
<div className="auth-reset-panel">
|
||||
<div className="auth-reset-panel animate-in">
|
||||
<p className="auth-reset-sent">
|
||||
<Trans>
|
||||
If that address is registered you'll receive a reset link shortly.
|
||||
@@ -126,7 +128,7 @@ function ResetRequestPanel() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="auth-reset-panel">
|
||||
<div className="auth-reset-panel animate-in">
|
||||
<FormProvider {...form}>
|
||||
<form onSubmit={onSubmit} className="form auth-reset-form">
|
||||
<FormError title={t`Request failed`} />
|
||||
|
||||
@@ -45,6 +45,7 @@ import { useDefaultFeedTab } from "../hooks/useDefaultFeedTab.ts";
|
||||
import { useWS } from "../hooks/useWS.ts";
|
||||
import { useDumpListSync } from "../hooks/useDumpListSync.ts";
|
||||
import { useFading } from "../hooks/useFading.ts";
|
||||
import { useHeightTransition } from "../hooks/useHeightTransition.ts";
|
||||
import { usePlaylistListSync } from "../hooks/usePlaylistListSync.ts";
|
||||
import { usePositionAwareSync } from "../hooks/usePositionAwareSync.ts";
|
||||
import type { Playlist, RawPlaylist } from "../model.ts";
|
||||
@@ -327,6 +328,8 @@ export function UserPublicProfile() {
|
||||
|
||||
const [descEditing, setDescEditing] = useState(false);
|
||||
const [emailEditing, setEmailEditing] = useState(false);
|
||||
const infoRef = useHeightTransition<HTMLDivElement>(emailEditing);
|
||||
const descRef = useHeightTransition<HTMLDivElement>(descEditing);
|
||||
const prevMyVotesRef = useRef<Set<string> | null>(null);
|
||||
|
||||
const [changePasswordOpen, setChangePasswordOpen] = useState(false);
|
||||
@@ -786,7 +789,7 @@ export function UserPublicProfile() {
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
<div className="profile-info">
|
||||
<div className="profile-info" ref={infoRef}>
|
||||
<div className="profile-info-scroll">
|
||||
<div className="profile-username-row">
|
||||
<h1 className="profile-username">{profileUser.username}</h1>
|
||||
@@ -856,7 +859,7 @@ export function UserPublicProfile() {
|
||||
</div>
|
||||
|
||||
{(profileUser.description || isOwnProfile) && (
|
||||
<div className="profile-description">
|
||||
<div className="profile-description" ref={descRef}>
|
||||
{descEditing
|
||||
? (
|
||||
<DescriptionEditor
|
||||
@@ -1646,7 +1649,7 @@ function EmailEditor({ initialEmail, onCancel, onSaved }: EmailEditorProps) {
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<form className="profile-email-editor" onSubmit={onSubmit}>
|
||||
<form className="profile-email-editor animate-in" onSubmit={onSubmit}>
|
||||
<input
|
||||
type="email"
|
||||
className="profile-email-input"
|
||||
@@ -1712,7 +1715,10 @@ function DescriptionEditor(
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<form className="profile-description-editor" onSubmit={onSubmit}>
|
||||
<form
|
||||
className="profile-description-editor animate-in"
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<Controller
|
||||
name="description"
|
||||
control={form.control}
|
||||
|
||||
Reference in New Issue
Block a user