feat: upload image on click, tooltip

This commit is contained in:
2026-10-05 14:54:39 +05:00
parent 0753c95583
commit a0bc72ef5d
5 changed files with 89 additions and 4 deletions
+1
View File
@@ -122,6 +122,7 @@
--color-main-tint: hct(from var(--brand-main) h 65 55);
--color-main-soft: color-mix(in srgb, var(--color-main) 12%, transparent);
--color-scrim: color-mix(in srgb, var(--color-background) 65%, transparent);
--color-accent-tint: hct(from var(--brand-alt) h 65 55);
--color-success-tint: hct(160 65 55);
--color-warning-tint: hct(80 65 55);
@@ -29,6 +29,27 @@
const sourceUrl = $derived(source ? toDataUrl(source) : null);
let dragging = $state(false);
let input = $state<HTMLInputElement | null>(null);
// Подсказка замены: показываем на ховер и гасим через 10с до нового захода.
let hintVisible = $state(false);
let hintTimer: ReturnType<typeof setTimeout> | null = null;
function showHint() {
hintVisible = true;
if (hintTimer) clearTimeout(hintTimer);
hintTimer = setTimeout(() => {
hintVisible = false;
hintTimer = null;
}, 3_000);
}
function hideHint() {
hintVisible = false;
if (hintTimer) {
clearTimeout(hintTimer);
hintTimer = null;
}
}
function onDrop(e: DragEvent) {
e.preventDefault();
@@ -36,6 +57,10 @@
const file = e.dataTransfer?.files?.[0];
if (file) onupload?.(file);
}
function open() {
input?.click();
}
</script>
<PreviewTile label={t("sourceCard.source")} viewMode={mode}>
@@ -52,15 +77,38 @@
<div
class="tile-drop"
class:dragging
role="region"
aria-label={t("dropZone.overlayDefault")}
class:hint={hintVisible}
role="button"
tabindex="0"
aria-label={t("sourceCard.replaceImage")}
onclick={open}
onkeydown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
open();
}
}}
ondragover={(e) => {
e.preventDefault();
dragging = true;
}}
ondragleave={() => (dragging = false)}
ondrop={onDrop}
onmouseenter={showHint}
onmouseleave={hideHint}
onfocus={showHint}
onblur={hideHint}
>
<input
bind:this={input}
type="file"
accept="image/*"
hidden
onchange={(e) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (file) onupload?.(file);
}}
/>
<img
data-testid="source-image"
src={sourceUrl}
@@ -68,6 +116,8 @@
/>
{#if dragging}
<span class="drop-hint">{t("dropZone.overlayDefault")}</span>
{:else}
<span class="hover-hint">{t("sourceCard.replaceHint")}</span>
{/if}
</div>
{:else if mode === "image"}
@@ -114,4 +164,38 @@
text-align: center;
pointer-events: none;
}
.tile-drop {
cursor: pointer;
}
.tile-drop::before {
content: "";
position: absolute;
inset: 0;
background: var(--color-scrim);
opacity: 0;
transition: opacity var(--duration-s) ease;
pointer-events: none;
}
.tile-drop.hint::before {
opacity: 1;
}
.tile-drop .hover-hint {
position: absolute;
inset: 25%;
display: grid;
place-items: center;
padding: var(--space-l);
border: var(--size-border-thick) dashed var(--color-main);
border-radius: var(--radius-m);
background: var(--color-panel);
color: var(--color-main);
font: 600 var(--font-size-l) var(--font-mono);
text-align: center;
opacity: 0;
transition: opacity var(--duration-s) ease;
pointer-events: none;
}
.tile-drop.hint .hover-hint {
opacity: 1;
}
</style>
+1
View File
@@ -59,6 +59,7 @@ export const en: Dict = {
sourceCard: {
source: "Source",
replaceImage: "Replace image",
replaceHint: "Click to upload an image",
chooseImage: "choose an image",
noSource: "no source — configure the parameters",
alt: "Source image",
+1
View File
@@ -60,6 +60,7 @@ export const ru: Dict = {
sourceCard: {
source: "Источник",
replaceImage: "Заменить изображение",
replaceHint: "Кликните, чтобы загрузить изображение",
chooseImage: "выберите изображение",
noSource: "нет источника — настройте параметры",
alt: "Исходное изображение",