characters modal bug overflowing on very small screens

This commit is contained in:
bflatastic
2026-04-05 06:40:55 +07:00
parent 03ed457cb5
commit 60c97ff312
@@ -99,9 +99,17 @@ export const CharactersModal = ({
isOpen={isOpen}
onClose={handleClose}
title={view === "list" ? "Characters" : undefined}
className="max-w-[800px] h-[60vh] max-h-[600px] flex flex-col overflow-hidden"
className="max-w-[800px] h-[600px] max-h-[80vh] overflow-hidden"
>
<div className="min-h-0 flex-1 overflow-y-auto">
<div
className="overflow-y-auto"
style={{
maxHeight:
view === "list"
? "min(calc(600px - 5rem), calc(80vh - 5rem))"
: "min(calc(600px - 2.5rem), calc(80vh - 2.5rem))",
}}
>
{view === "list" ? (
<CharacterListView
key={refreshKey}
@@ -297,7 +305,7 @@ const CharacterListView = ({
};
return (
<div className="flex max-h-[50vh] flex-col overflow-y-auto">
<div className="flex flex-col">
{loading && characters.length === 0 ? (
<div className="grid grid-cols-4 gap-3">
{Array.from({ length: 8 }).map((_, i) => (
@@ -517,61 +525,64 @@ const EditCharacterView = ({
};
return (
<div className="flex flex-col gap-4">
{/* Header */}
<div className="flex items-center gap-3 pb-0">
<button
onClick={onBack}
className="flex items-center justify-center text-base-fg/60 transition-colors hover:text-base-fg"
>
<FontAwesomeIcon icon={faArrowLeft} />
</button>
<h2 className="text-xl font-bold text-base-fg">Edit Character</h2>
</div>
<div className="flex flex-col">
{/* Scrollable content */}
<div className="flex flex-col gap-4">
{/* Header */}
<div className="flex items-center gap-3 pb-0">
<button
onClick={onBack}
className="flex items-center justify-center text-base-fg/60 transition-colors hover:text-base-fg"
>
<FontAwesomeIcon icon={faArrowLeft} />
</button>
<h2 className="text-xl font-bold text-base-fg">Edit Character</h2>
</div>
{/* Avatar preview */}
{character.maybe_avatar?.cdn_url && (
<div className="flex h-56 max-h-56 items-center justify-center overflow-hidden rounded-lg bg-base-fg/[0.05]">
<img
src={character.maybe_avatar.cdn_url}
alt={character.name}
className="max-h-full max-w-full object-contain"
{/* Avatar preview */}
{character.maybe_avatar?.cdn_url && (
<div className="flex h-56 max-h-56 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-base-fg/[0.05]">
<img
src={character.maybe_avatar.cdn_url}
alt={character.name}
className="max-h-full max-w-full object-contain"
/>
</div>
)}
{/* Name input */}
<div className="flex flex-col">
<Label htmlFor="edit-character-name">Name</Label>
<Input
id="edit-character-name"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Character name"
autoComplete="off"
inputClassName="bg-base-fg/[0.07] hover:border-ui-panel-border"
/>
</div>
)}
{/* Name input */}
<div className="flex flex-col">
<Label htmlFor="edit-character-name">Name</Label>
<Input
id="edit-character-name"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Character name"
autoComplete="off"
inputClassName="bg-base-fg/[0.07] hover:border-ui-panel-border"
/>
</div>
{/* Description input */}
<div className="flex flex-col">
<Label htmlFor="edit-character-description">
Description{" "}
<span className="font-normal text-base-fg/40">(optional)</span>
</Label>
<textarea
id="edit-character-description"
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Describe this character..."
rows={3}
autoComplete="off"
className="w-full resize-none rounded-lg px-3 py-2 outline-none bg-base-fg/[0.07] text-base-fg placeholder-base-fg/50 border border-ui-panel-border transition-all duration-150 ease-in-out focus:border-primary focus:!outline-none"
/>
{/* Description input */}
<div className="flex flex-col">
<Label htmlFor="edit-character-description">
Description{" "}
<span className="font-normal text-base-fg/40">(optional)</span>
</Label>
<textarea
id="edit-character-description"
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Description..."
rows={3}
autoComplete="off"
className="w-full resize-none rounded-lg px-3 py-2 outline-none bg-base-fg/[0.07] text-base-fg placeholder-base-fg/50 border border-ui-panel-border transition-all duration-150 ease-in-out focus:border-primary focus:!outline-none"
/>
</div>
</div>
{/* Action buttons */}
<div className="flex justify-end gap-2">
<div className="flex justify-end gap-2 pt-4">
<Button variant="secondary" className="border-none" onClick={onBack}>
Cancel
</Button>
@@ -778,132 +789,135 @@ const NewCharacterView = ({
}, []);
return (
<div className="flex flex-col gap-4">
{/* Header with back button */}
<div className="flex items-center gap-3 pb-0">
<button
onClick={onBack}
className="flex items-center justify-center text-base-fg/60 transition-colors hover:text-base-fg"
>
<FontAwesomeIcon icon={faArrowLeft} />
</button>
<h2 className="text-xl font-bold text-base-fg">New Character</h2>
</div>
{/* Image upload area */}
<div
ref={dropZoneRef}
className={twMerge(
"flex h-56 max-h-56 cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed border-base-fg/20 bg-base-fg/[0.05] transition-colors overflow-hidden",
isDragging && "border-blue-400 bg-blue-500/10",
)}
onDragEnter={handleDragEnter}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
onClick={() => fileInputRef.current?.click()}
>
{images.length > 0 ? (
<div
className="group relative flex h-full w-full items-center justify-center"
onClick={(e) => e.stopPropagation()}
<div className="flex flex-col">
{/* Scrollable content */}
<div className="flex flex-col gap-4">
{/* Header with back button */}
<div className="flex items-center gap-3 pb-0">
<button
onClick={onBack}
className="flex items-center justify-center text-base-fg/60 transition-colors hover:text-base-fg"
>
<img
src={images[0]!.url}
alt="Reference"
className="max-h-full max-w-full object-contain"
/>
{!images[0]!.mediaToken && (
<div className="absolute inset-0 flex items-center justify-center bg-black/50">
<FontAwesomeIcon
icon={faSpinnerThird}
className="text-white animate-spin"
/>
</div>
)}
<button
onClick={(e) => {
e.stopPropagation();
removeImage(0);
}}
className="absolute right-2 top-2 flex h-7 w-7 items-center justify-center rounded-full bg-black/60 text-white/80 opacity-0 transition-all group-hover:opacity-100 hover:bg-red-500"
>
<FontAwesomeIcon icon={faXmark} className="text-sm" />
</button>
</div>
) : (
<div className="flex h-full w-full flex-col items-center justify-center text-base-fg/60">
<FontAwesomeIcon
icon={faUpload}
className="mb-2 text-xl text-base-fg/40"
/>
<p className="text-sm">Upload reference image</p>
<p className="mb-3 text-xs text-base-fg/40">
Click or drag an image here
</p>
<FontAwesomeIcon icon={faArrowLeft} />
</button>
<h2 className="text-xl font-bold text-base-fg">New Character</h2>
</div>
{/* Image upload area */}
<div
ref={dropZoneRef}
className={twMerge(
"flex h-56 max-h-56 shrink-0 cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed border-base-fg/20 bg-base-fg/[0.05] transition-colors overflow-hidden",
isDragging && "border-blue-400 bg-blue-500/10",
)}
onDragEnter={handleDragEnter}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
onClick={() => fileInputRef.current?.click()}
>
{images.length > 0 ? (
<div
className="flex items-center gap-3"
className="group relative flex h-full w-full items-center justify-center"
onClick={(e) => e.stopPropagation()}
>
<img
src={images[0]!.url}
alt="Reference"
className="max-h-full max-w-full object-contain"
/>
{!images[0]!.mediaToken && (
<div className="absolute inset-0 flex items-center justify-center bg-black/50">
<FontAwesomeIcon
icon={faSpinnerThird}
className="text-white animate-spin"
/>
</div>
)}
<button
onClick={() => setIsGalleryOpen(true)}
className="flex items-center gap-2 rounded-lg bg-base-fg/10 px-3 py-1.5 text-sm text-base-fg/80 transition-colors hover:bg-base-fg/20"
onClick={(e) => {
e.stopPropagation();
removeImage(0);
}}
className="absolute right-2 top-2 flex h-7 w-7 items-center justify-center rounded-full bg-black/60 text-white/80 opacity-0 transition-all group-hover:opacity-100 hover:bg-red-500"
>
<FontAwesomeIcon icon={faImages} className="text-xs" />
Choose from Library
</button>
<button
onClick={() => fileInputRef.current?.click()}
className="flex items-center gap-2 rounded-lg bg-base-fg/10 px-3 py-1.5 text-sm text-base-fg/80 transition-colors hover:bg-base-fg/20"
>
<FontAwesomeIcon icon={faUpload} className="text-xs" />
Upload Image
<FontAwesomeIcon icon={faXmark} className="text-sm" />
</button>
</div>
</div>
)}
</div>
) : (
<div className="flex h-full w-full flex-col items-center justify-center text-base-fg/60">
<FontAwesomeIcon
icon={faUpload}
className="mb-2 text-xl text-base-fg/40"
/>
<p className="text-sm">Upload reference image</p>
<p className="mb-3 text-xs text-base-fg/40">
Click or drag an image here
</p>
<div
className="flex items-center gap-3"
onClick={(e) => e.stopPropagation()}
>
<button
onClick={() => setIsGalleryOpen(true)}
className="flex items-center gap-2 rounded-lg bg-base-fg/10 px-3 py-1.5 text-sm text-base-fg/80 transition-colors hover:bg-base-fg/20"
>
<FontAwesomeIcon icon={faImages} className="text-xs" />
Choose from Library
</button>
<button
onClick={() => fileInputRef.current?.click()}
className="flex items-center gap-2 rounded-lg bg-base-fg/10 px-3 py-1.5 text-sm text-base-fg/80 transition-colors hover:bg-base-fg/20"
>
<FontAwesomeIcon icon={faUpload} className="text-xs" />
Upload Image
</button>
</div>
</div>
)}
</div>
<input
ref={fileInputRef}
type="file"
accept="image/*"
onChange={handleFileSelect}
className="hidden"
/>
{/* Name input */}
<div className="flex flex-col">
<Label htmlFor="character-name">Name</Label>
<Input
id="character-name"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Character name"
autoComplete="off"
inputClassName="bg-base-fg/[0.07] hover:border-ui-panel-border"
<input
ref={fileInputRef}
type="file"
accept="image/*"
onChange={handleFileSelect}
className="hidden"
/>
{/* Name input */}
<div className="flex flex-col">
<Label htmlFor="character-name">Name</Label>
<Input
id="character-name"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Character name"
autoComplete="off"
inputClassName="bg-base-fg/[0.07] hover:border-ui-panel-border"
/>
</div>
{/* Description input */}
<div className="flex flex-col">
<Label htmlFor="character-description">
Description{" "}
<span className="font-normal text-base-fg/40">(optional)</span>
</Label>
<textarea
id="character-description"
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Description..."
rows={3}
autoComplete="off"
className="w-full resize-none rounded-lg px-3 py-2 outline-none bg-base-fg/[0.07] text-base-fg placeholder-base-fg/50 border border-ui-panel-border transition-all duration-150 ease-in-out focus:border-primary focus:!outline-none"
/>
</div>
</div>
{/* Description input */}
<div className="flex flex-col">
<Label htmlFor="character-description">
Description{" "}
<span className="font-normal text-base-fg/40">(optional)</span>
</Label>
<textarea
id="character-description"
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Describe this character..."
rows={3}
autoComplete="off"
className="w-full resize-none rounded-lg px-3 py-2 outline-none bg-base-fg/[0.07] text-base-fg placeholder-base-fg/50 border border-ui-panel-border transition-all duration-150 ease-in-out focus:border-primary focus:!outline-none"
/>
</div>
{/* Action buttons */}
<div className="flex justify-end gap-2">
{/* Action buttons — sticky at the bottom */}
<div className="sticky bottom-0 flex justify-end gap-2 pt-4 pb-1 bg-ui-modal">
<Button variant="secondary" className="border-none" onClick={onBack}>
Cancel
</Button>