feat: add create and message page UI

Co-authored-by: 이지은 <ijieun@ijieun-ui-MacBookPro.local>
This commit is contained in:
Chaewon Lee
2026-06-09 16:11:36 +09:00
committed by GitHub
parent e9dcd22b53
commit d0ba482451
8 changed files with 285 additions and 1 deletions

3
.gitignore vendored
View File

@@ -21,3 +21,6 @@ Thumbs.db
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
# Local notes (not pushed)
docs/

View File

@@ -0,0 +1,71 @@
<script>
let { budget = $bindable(50_000) } = $props();
const min = 10_000;
const max = 150_000;
const step = 5_000;
const fillPercent = $derived(((budget - min) / (max - min)) * 100);
</script>
<div class="space-y-4">
<div>
<p class="text-xs tracking-[0.2em] text-muted uppercase">Budget</p>
<p class="mt-2 text-3xl font-semibold tracking-tight">
{budget.toLocaleString('ko-KR')}
</p>
</div>
<div class="space-y-2">
<div class="relative h-6">
<div class="absolute top-1/2 right-0 left-0 h-px -translate-y-1/2 bg-placeholder"></div>
<div
class="absolute top-1/2 left-0 h-px -translate-y-1/2 bg-subtle"
style="width: {fillPercent}%"
></div>
<input
type="range"
{min}
{max}
{step}
bind:value={budget}
aria-label="Budget"
class="budget-slider absolute inset-0 w-full cursor-pointer appearance-none bg-transparent"
/>
</div>
<div class="flex justify-between text-xs text-muted">
<span>₩10,000</span>
<span>₩150,000+</span>
</div>
</div>
</div>
<style>
.budget-slider::-webkit-slider-thumb {
appearance: none;
width: 1rem;
height: 1rem;
border-radius: 9999px;
background: var(--color-pill);
border: none;
margin-top: -0.4375rem;
}
.budget-slider::-moz-range-thumb {
width: 1rem;
height: 1rem;
border-radius: 9999px;
background: var(--color-pill);
border: none;
}
.budget-slider::-webkit-slider-runnable-track {
height: 1px;
background: transparent;
}
.budget-slider::-moz-range-track {
height: 1px;
background: transparent;
}
</style>

View File

@@ -0,0 +1,66 @@
<script>
import OptionGroup from './OptionGroup.svelte';
import BudgetSlider from './BudgetSlider.svelte';
let {
who = $bindable(null),
whatFor = $bindable(null),
style = $bindable(null),
budget = $bindable(50_000)
} = $props();
const hasAnySelection = $derived(who !== null || whatFor !== null || style !== null);
const whoOptions = ['Friend', 'Family', 'Partner', 'Teacher', 'Others'];
const whatForOptions = ['Birthday', 'Anniversary', 'Thanks', 'Daily'];
const styleOptions = ['Feminine', 'Masculine', 'Neutral'];
</script>
<div class="flex flex-1 flex-col justify-center px-6 py-10 md:px-12 lg:px-16 lg:py-16">
<header class="mb-10 space-y-3 lg:mb-14">
{#if !hasAnySelection}
<h1 class="text-3xl leading-relaxed font-light text-muted md:text-4xl lg:text-[2.75rem]">
Who are we making flowers for?
</h1>
<p class="text-sm text-muted">Pick a few details below</p>
{:else}
<h1 class="text-3xl leading-tight font-light text-muted md:text-4xl lg:text-[2.75rem]">
{#if whatFor}
A <span class="font-semibold text-ink underline decoration-ink/30 underline-offset-4"
>{whatFor}</span
>
bouquet for
{:else}
A bouquet for
{/if}
{#if who}
<span class="font-semibold text-ink underline decoration-ink/30 underline-offset-4"
>{who}</span
>
{:else}
<span class="text-muted">...</span>
{/if}
</h1>
<p class="text-sm text-muted">
{style ?? '—'} | ₩{budget.toLocaleString('ko-KR')}
</p>
{/if}
</header>
<div class="space-y-8 lg:space-y-10">
<OptionGroup label="Who" options={whoOptions} selected={who} onchange={(v) => (who = v)} />
<OptionGroup
label="What for"
options={whatForOptions}
selected={whatFor}
onchange={(v) => (whatFor = v)}
/>
<OptionGroup
label="Style"
options={styleOptions}
selected={style}
onchange={(v) => (style = v)}
/>
<BudgetSlider bind:budget />
</div>
</div>

View File

@@ -0,0 +1,21 @@
<script>
let { label, options, selected, onchange } = $props();
</script>
<div class="space-y-3">
<p class="text-sm tracking-[0.2em] text-muted uppercase">{label}</p>
<div class="flex flex-wrap gap-x-5 gap-y-2">
{#each options as option (option)}
<button
type="button"
onclick={() => onchange(option)}
class={[
'text-xl tracking-wide transition-colors',
selected === option ? 'text-ink' : 'text-muted hover:text-ink'
]}
>
{option}
</button>
{/each}
</div>
</div>

View File

@@ -0,0 +1,34 @@
<script>
import MessagePresetList from './MessagePresetList.svelte';
let { message = $bindable('') } = $props();
const presets = [
'Happy Birthday!',
'Thank you for always being there',
'I love you',
"I'm proud of you",
'Congratulations!',
];
const selectedPreset = $derived(presets.find((preset) => preset === message) ?? null);
</script>
<div class="flex flex-1 flex-col justify-center px-6 py-10 md:px-12 lg:px-16 lg:py-16">
<header class="mb-8 lg:mb-10">
<textarea
bind:value={message}
placeholder="Write something from your heart"
rows="1"
aria-label="Write your message"
class="w-full resize-none border-none bg-transparent text-3xl leading-relaxed font-light text-ink placeholder:text-muted focus:outline-none md:text-4xl lg:text-[2.75rem]"
></textarea>
<div class="mt-6 border-b border-pill lg:mt-3"></div>
</header>
<MessagePresetList
options={presets}
selected={selectedPreset}
onchange={(preset) => (message = preset)}
/>
</div>

View File

@@ -0,0 +1,19 @@
<script>
// OptionGroup과 같은 선택 버튼 스타일 — message는 세로 목록
let { options, selected, onchange } = $props();
</script>
<div class="flex max-w-md flex-col gap-y-8">
{#each options as option (option)}
<button
type="button"
onclick={() => onchange(option)}
class={[
'text-left text-xl tracking-wide transition-colors',
selected === option ? 'text-ink' : 'text-muted hover:text-ink'
]}
>
{option}
</button>
{/each}
</div>

View File

@@ -1 +1,42 @@
<h1>/create page</h1>
<script>
import Header from '$lib/components/ui/Header.svelte';
import Artwork from '$lib/components/ui/Artwork/Artwork.svelte';
import ContextForm from '$lib/components/ui/create/ContextForm.svelte';
let who = $state(null);
let whatFor = $state(null);
let style = $state(null);
let budget = $state(50_000);
const hasAnySelection = $derived(who !== null || whatFor !== null || style !== null);
const artworkTitle = $derived.by(() => {
if (!hasAnySelection) return 'Title';
const occasion = whatFor ? `A ${whatFor} bouquet for` : 'A bouquet for';
return `${occasion} ${who ?? '...'}`;
});
const artworkDescription = $derived(
hasAnySelection
? `${style ?? '—'} style · ₩${budget.toLocaleString('ko-KR')} budget`
: 'Description Description Description'
);
</script>
<!--
upload와 같은 2열 레이아웃: 좌측 Artwork 고정, 우측 ContextForm.
선택값이 바뀌면 create1 → create2 헤드라인·요약이 반응형으로 전환됩니다.
-->
<div
class="flex h-dvh flex-col overflow-x-hidden bg-surface text-ink lg:h-screen lg:overflow-hidden"
>
<Header step={1} total={6} />
<main class="flex min-h-0 flex-1 flex-col lg:flex-row">
<Artwork title={artworkTitle} description={artworkDescription} />
<section class="relative flex min-h-0 flex-1 flex-col lg:overflow-y-auto">
<ContextForm bind:who bind:whatFor bind:style bind:budget />
</section>
</main>
</div>

View File

@@ -0,0 +1,29 @@
<script>
import Header from '$lib/components/ui/Header.svelte';
import Artwork from '$lib/components/ui/Artwork/Artwork.svelte';
import MessageForm from '$lib/components/ui/message/MessageForm.svelte';
let message = $state('');
const artworkTitle = $derived(message ? 'Your message' : 'Title');
const artworkDescription = $derived(message || 'Description Description Description');
</script>
<!--
create / upload와 같은 2열 레이아웃.
우측 MessageForm에서 프리셋 메시지를 pill 버튼으로 선택합니다.
-->
<div
class="flex h-dvh flex-col overflow-x-hidden bg-surface text-ink lg:h-screen lg:overflow-hidden"
>
<Header step={4} total={6} />
<main class="flex min-h-0 flex-1 flex-col lg:flex-row">
<Artwork title={artworkTitle} description={artworkDescription} />
<section class="relative flex min-h-0 flex-1 flex-col lg:overflow-y-auto">
<MessageForm bind:message />
</section>
</main>
</div>