mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-20 17:02:53 -04:00
characters modal bug overflowing on very small screens
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user