* ui improved * prompt:realisic+noHuman * prompt:editRefinement * fix: map DescriptionCard truncation and truncateAt typo Prevent result/map card overflow with character limits and line-clamp; fix buildMapOrderDescription calling undefined truncateAt. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: 이지은 <ijieun@ijieun-ui-MacBookPro.local> Co-authored-by: Cursor <cursoragent@cursor.com>
82 lines
2.2 KiB
Svelte
82 lines
2.2 KiB
Svelte
<script>
|
|
// A single click-to-upload slot: a light bordered placeholder when empty,
|
|
// the chosen image (cover) when filled. Layout (size / grid placement) is
|
|
// supplied by the parent via `class` and `style` so the same tile works in
|
|
// both the moodboard and the SNS feed.
|
|
let {
|
|
label = null,
|
|
/** 빈 타일 중앙에 표시할 안내 문장 */
|
|
prompt = null,
|
|
showLabel = true,
|
|
class: klass = '',
|
|
style = '',
|
|
file = $bindable(null)
|
|
} = $props();
|
|
|
|
let preview = $state(null);
|
|
|
|
function pick(event) {
|
|
const picked = event.currentTarget.files?.[0];
|
|
if (!picked) return;
|
|
file = picked;
|
|
}
|
|
|
|
// 부모에서 File을 주입할 때(Dev seed 등) 미리보기 동기화
|
|
$effect(() => {
|
|
if (!file) {
|
|
preview = null;
|
|
return;
|
|
}
|
|
|
|
const url = URL.createObjectURL(file);
|
|
preview = url;
|
|
return () => URL.revokeObjectURL(url);
|
|
});
|
|
</script>
|
|
|
|
<label
|
|
class={[
|
|
'group relative flex cursor-pointer items-center justify-center overflow-hidden bg-track transition-colors',
|
|
!preview && 'border border-line hover:border-line-strong',
|
|
klass
|
|
]}
|
|
{style}
|
|
>
|
|
<input
|
|
type="file"
|
|
accept="image/*"
|
|
class="sr-only"
|
|
aria-label={prompt ?? (label ? `Add a ${label} image` : 'Add an image')}
|
|
onchange={pick}
|
|
/>
|
|
|
|
{#if preview}
|
|
<img src={preview} alt={label ?? ''} class="h-full w-full object-cover" />
|
|
<div class="absolute inset-0 bg-linear-to-t from-ink/45 to-transparent"></div>
|
|
{#if label && showLabel}
|
|
<span class="absolute bottom-3 left-4 text-sm tracking-[0.15em] text-surface uppercase"
|
|
>{label}</span
|
|
>
|
|
{/if}
|
|
<span
|
|
class="absolute top-3 right-3 rounded-full bg-ink/40 px-2.5 py-1 text-xs text-surface opacity-0 backdrop-blur-sm transition-opacity group-hover:opacity-100"
|
|
>
|
|
Change
|
|
</span>
|
|
{:else}
|
|
<div
|
|
class="flex flex-col items-center gap-3 px-4 text-center text-subtle transition-transform group-hover:scale-105"
|
|
>
|
|
<span
|
|
class="flex size-10 shrink-0 items-center justify-center rounded-full border border-current text-xl leading-none"
|
|
aria-hidden="true">+</span
|
|
>
|
|
{#if prompt}
|
|
<span class="max-w-[14rem] text-sm leading-snug text-muted">{prompt}</span>
|
|
{:else if label && showLabel}
|
|
<span class="text-sm tracking-[0.15em] uppercase">{label}</span>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</label>
|