Skip to content

Commit d2f8d6c

Browse files
authored
feat: Modernize UI with Tailwind CSS (#25)
1 parent 18c0167 commit d2f8d6c

15 files changed

Lines changed: 2336 additions & 719 deletions

src/DotNetDevLottery/Components/BallDesignSettingsDialog.razor

Lines changed: 116 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3,66 +3,122 @@
33
@using DotNetDevLottery.Models
44
@using Microsoft.AspNetCore.Components.Forms
55

6-
<FluentDialog @bind-Hidden="@isHidden" Modal="true" TrapFocus="true" @ondialogdismiss="OnDialogDismiss">
7-
<DialogHeader>
8-
<FluentStack Orientation="Orientation.Horizontal">
9-
<FluentLabel Typo="Typography.PaneHeader">볼 디자인 설정</FluentLabel>
10-
</FluentStack>
11-
</DialogHeader>
12-
<DialogBody>
13-
<FluentStack Orientation="Orientation.Vertical" Style="gap: 1.5rem;">
14-
<FluentStack Orientation="Orientation.Vertical" Style="gap: 0.5rem;">
15-
<FluentLabel Style="font-weight: 600;">일반 볼 이미지 업로드</FluentLabel>
16-
<InputFile OnChange="@(e => OnBallImageSelected(e, false))" accept="image/*" class="file-input" />
17-
@if (!string.IsNullOrEmpty(settings.BallImageUrl))
18-
{
19-
<FluentStack Orientation="Orientation.Horizontal" Style="align-items: center; gap: 1rem; padding: 0.5rem; background: var(--neutral-layer-2); border-radius: 4px;">
20-
<img src="@settings.BallImageUrl" alt="볼 이미지 미리보기" style="width: 60px; height: 60px; object-fit: contain; border-radius: 4px;" />
21-
<FluentButton Appearance="Appearance.Lightweight" OnClick="@(() => ClearImage(false))">삭제</FluentButton>
22-
</FluentStack>
23-
}
24-
</FluentStack>
25-
26-
<FluentStack Orientation="Orientation.Vertical" Style="gap: 0.5rem;">
27-
<FluentLabel Style="font-weight: 600;">당첨 볼 이미지 업로드</FluentLabel>
28-
<InputFile OnChange="@(e => OnBallImageSelected(e, true))" accept="image/*" class="file-input" />
29-
@if (!string.IsNullOrEmpty(settings.DrawedBallImageUrl))
30-
{
31-
<FluentStack Orientation="Orientation.Horizontal" Style="align-items: center; gap: 1rem; padding: 0.5rem; background: var(--neutral-layer-2); border-radius: 4px;">
32-
<img src="@settings.DrawedBallImageUrl" alt="당첨 볼 이미지 미리보기" style="width: 60px; height: 60px; object-fit: contain; border-radius: 4px;" />
33-
<FluentButton Appearance="Appearance.Lightweight" OnClick="@(() => ClearImage(true))">삭제</FluentButton>
34-
</FluentStack>
35-
}
36-
</FluentStack>
37-
38-
<FluentDivider Style="width: 100%;" />
39-
<FluentLabel Style="text-align: center; color: var(--neutral-foreground-hint);">또는 색상 사용</FluentLabel>
40-
41-
<FluentStack Orientation="Orientation.Vertical" Style="gap: 0.5rem;">
42-
<FluentLabel Style="font-weight: 600;">일반 볼 색상</FluentLabel>
43-
<input type="color" @bind="@settings.BallColor" style="width: 100%; height: 40px; border: 1px solid var(--neutral-stroke-rest); border-radius: 4px; cursor: pointer;" />
44-
</FluentStack>
45-
46-
<FluentStack Orientation="Orientation.Vertical" Style="gap: 0.5rem;">
47-
<FluentLabel Style="font-weight: 600;">일반 볼 테두리 색상</FluentLabel>
48-
<input type="color" @bind="@settings.BallBorderColor" style="width: 100%; height: 40px; border: 1px solid var(--neutral-stroke-rest); border-radius: 4px; cursor: pointer;" />
49-
</FluentStack>
50-
51-
<FluentStack Orientation="Orientation.Vertical" Style="gap: 0.5rem;">
52-
<FluentLabel Style="font-weight: 600;">당첨 볼 색상</FluentLabel>
53-
<input type="color" @bind="@settings.DrawedBallColor" style="width: 100%; height: 40px; border: 1px solid var(--neutral-stroke-rest); border-radius: 4px; cursor: pointer;" />
54-
</FluentStack>
55-
56-
<FluentStack Orientation="Orientation.Vertical" Style="gap: 0.5rem;">
57-
<FluentLabel Style="font-weight: 600;">당첨 볼 텍스트 색상</FluentLabel>
58-
<input type="color" @bind="@settings.DrawedBallTextColor" style="width: 100%; height: 40px; border: 1px solid var(--neutral-stroke-rest); border-radius: 4px; cursor: pointer;" />
59-
</FluentStack>
60-
</FluentStack>
61-
</DialogBody>
62-
<DialogFooter>
63-
<FluentButton Appearance="Appearance.Neutral" OnClick="OnCancel">취소</FluentButton>
64-
<FluentButton Appearance="Appearance.Accent" OnClick="OnSave">저장</FluentButton>
65-
</DialogFooter>
6+
<FluentDialog @bind-Hidden="@isHidden" Modal="true" TrapFocus="true" @ondialogdismiss="OnDialogDismiss" class="!p-0 !rounded-3xl !border-0 !shadow-2xl overflow-hidden">
7+
<div class="flex flex-col h-full bg-white w-full">
8+
<!-- Header -->
9+
<div class="px-6 py-5 border-b border-gray-100 flex items-center justify-between">
10+
<h2 class="text-xl font-bold text-gray-800 m-0">볼 디자인 설정</h2>
11+
<button @onclick="OnCancel" class="text-gray-400 hover:text-gray-600 transition-colors p-1 rounded-full hover:bg-gray-100">
12+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
13+
</button>
14+
</div>
15+
16+
<!-- Body -->
17+
<div class="p-6 overflow-y-auto max-h-[70vh]">
18+
<div class="flex flex-col gap-8">
19+
<!-- Image Upload Section -->
20+
<div class="flex flex-col gap-6">
21+
<div class="flex flex-col gap-3">
22+
<label class="text-sm font-bold text-gray-700">일반 볼 이미지</label>
23+
<div class="relative group">
24+
<InputFile OnChange="@(e => OnBallImageSelected(e, false))" accept="image/*" class="absolute inset-0 w-full h-full opacity-0 cursor-pointer z-10" />
25+
<div class="flex items-center justify-center p-4 border-2 border-dashed border-gray-200 rounded-xl bg-gray-50 transition-all group-hover:border-primary/50 group-hover:bg-primary/5">
26+
<span class="text-sm text-gray-500 group-hover:text-primary font-medium">Click to upload image</span>
27+
</div>
28+
</div>
29+
@if (!string.IsNullOrEmpty(settings.BallImageUrl))
30+
{
31+
<div class="flex items-center gap-4 p-3 bg-gray-50 rounded-xl border border-gray-100">
32+
<img src="@settings.BallImageUrl" alt="Preview" class="w-12 h-12 object-contain rounded-lg bg-white shadow-sm" />
33+
<div class="flex-1 min-w-0">
34+
<p class="text-xs text-gray-400 font-mono truncate">Selected Image</p>
35+
</div>
36+
<button @onclick="@(() => ClearImage(false))" class="text-xs font-bold text-red-500 hover:bg-red-50 px-3 py-1.5 rounded-lg transition-colors">
37+
삭제
38+
</button>
39+
</div>
40+
}
41+
</div>
42+
43+
<div class="flex flex-col gap-3">
44+
<label class="text-sm font-bold text-gray-700">당첨 볼 이미지</label>
45+
<div class="relative group">
46+
<InputFile OnChange="@(e => OnBallImageSelected(e, true))" accept="image/*" class="absolute inset-0 w-full h-full opacity-0 cursor-pointer z-10" />
47+
<div class="flex items-center justify-center p-4 border-2 border-dashed border-gray-200 rounded-xl bg-gray-50 transition-all group-hover:border-primary/50 group-hover:bg-primary/5">
48+
<span class="text-sm text-gray-500 group-hover:text-primary font-medium">Click to upload image</span>
49+
</div>
50+
</div>
51+
@if (!string.IsNullOrEmpty(settings.DrawedBallImageUrl))
52+
{
53+
<div class="flex items-center gap-4 p-3 bg-gray-50 rounded-xl border border-gray-100">
54+
<img src="@settings.DrawedBallImageUrl" alt="Preview" class="w-12 h-12 object-contain rounded-lg bg-white shadow-sm" />
55+
<div class="flex-1 min-w-0">
56+
<p class="text-xs text-gray-400 font-mono truncate">Selected Image</p>
57+
</div>
58+
<button @onclick="@(() => ClearImage(true))" class="text-xs font-bold text-red-500 hover:bg-red-50 px-3 py-1.5 rounded-lg transition-colors">
59+
삭제
60+
</button>
61+
</div>
62+
}
63+
</div>
64+
</div>
65+
66+
<div class="relative py-2">
67+
<div class="absolute inset-0 flex items-center">
68+
<div class="w-full border-t border-gray-200"></div>
69+
</div>
70+
<div class="relative flex justify-center text-sm">
71+
<span class="px-4 bg-white text-gray-500 font-medium">또는 색상 직접 지정</span>
72+
</div>
73+
</div>
74+
75+
<!-- Color Config Section -->
76+
<div class="grid grid-cols-2 gap-4">
77+
<div class="flex flex-col gap-2">
78+
<label class="text-xs font-bold text-gray-600 uppercase tracking-wide">일반 볼 색상</label>
79+
<div class="flex items-center gap-2 p-1.5 border border-gray-200 rounded-xl bg-white focus-within:ring-2 focus-within:ring-primary/20 transition-shadow">
80+
<input type="color" @bind="@settings.BallColor" class="w-8 h-8 rounded cursor-pointer border-0 p-0" />
81+
<span class="text-xs font-mono text-gray-500">@settings.BallColor</span>
82+
</div>
83+
</div>
84+
85+
<div class="flex flex-col gap-2">
86+
<label class="text-xs font-bold text-gray-600 uppercase tracking-wide">테두리 색상</label>
87+
<div class="flex items-center gap-2 p-1.5 border border-gray-200 rounded-xl bg-white focus-within:ring-2 focus-within:ring-primary/20 transition-shadow">
88+
<input type="color" @bind="@settings.BallBorderColor" class="w-8 h-8 rounded cursor-pointer border-0 p-0" />
89+
<span class="text-xs font-mono text-gray-500">@settings.BallBorderColor</span>
90+
</div>
91+
</div>
92+
93+
<div class="flex flex-col gap-2">
94+
<label class="text-xs font-bold text-gray-600 uppercase tracking-wide">당첨 볼 색상</label>
95+
<div class="flex items-center gap-2 p-1.5 border border-gray-200 rounded-xl bg-white focus-within:ring-2 focus-within:ring-primary/20 transition-shadow">
96+
<input type="color" @bind="@settings.DrawedBallColor" class="w-8 h-8 rounded cursor-pointer border-0 p-0" />
97+
<span class="text-xs font-mono text-gray-500">@settings.DrawedBallColor</span>
98+
</div>
99+
</div>
100+
101+
<div class="flex flex-col gap-2">
102+
<label class="text-xs font-bold text-gray-600 uppercase tracking-wide">텍스트 색상</label>
103+
<div class="flex items-center gap-2 p-1.5 border border-gray-200 rounded-xl bg-white focus-within:ring-2 focus-within:ring-primary/20 transition-shadow">
104+
<input type="color" @bind="@settings.DrawedBallTextColor" class="w-8 h-8 rounded cursor-pointer border-0 p-0" />
105+
<span class="text-xs font-mono text-gray-500">@settings.DrawedBallTextColor</span>
106+
</div>
107+
</div>
108+
</div>
109+
</div>
110+
</div>
111+
112+
<!-- Footer -->
113+
<div class="px-6 py-4 bg-gray-50 border-t border-gray-100 flex justify-end gap-3 rounded-b-3xl">
114+
<button @onclick="OnCancel" class="px-5 py-2.5 text-gray-600 font-bold bg-white border border-gray-200 rounded-xl hover:bg-gray-50 hover:border-gray-300 transition-all shadow-sm">
115+
취소
116+
</button>
117+
<button @onclick="OnSave" class="px-5 py-2.5 bg-gradient-to-r from-[var(--color-primary)] to-[var(--color-secondary)] text-white font-bold rounded-xl shadow-md hover:shadow-lg hover:-translate-y-0.5 transition-all">
118+
저장하기
119+
</button>
120+
</div>
121+
</div>
66122
</FluentDialog>
67123

68124
@code {

src/DotNetDevLottery/Components/BallDesignSettingsDialog.razor.css

Lines changed: 0 additions & 86 deletions
This file was deleted.

src/DotNetDevLottery/Components/Random/MachineAnimation.razor

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,40 @@
33
@inject Blazored.LocalStorage.ILocalStorageService LocalStorage
44
@using DotNetDevLottery.Models
55

6-
<div class="container">
7-
<div class="button-row">
8-
<FluentButton Appearance="Appearance.Accent" OnClick="@(() => TargetPersonCount--)" IconOnly="true">
9-
<span>-</span>
10-
</FluentButton>
11-
<span>
12-
@TargetPersonCount
13-
</span>
14-
<FluentButton Appearance="Appearance.Accent" OnClick="@(() => TargetPersonCount++)" IconOnly="true">
15-
<span>+</span>
16-
</FluentButton>
17-
<FluentButton
18-
OnClick="OnClickButton"
19-
Appearance="@(Status == DrawMachineStatus.Pending ? Appearance.Neutral : Appearance.Accent)"
20-
Disabled="@(Status == DrawMachineStatus.Pending && PendingDrawCount == 0)"
21-
class="@TriggerButtonClass(Status == DrawMachineStatus.Pending)">
22-
추첨 진행
23-
</FluentButton>
6+
<div class="w-full h-full flex flex-col items-center justify-center gap-6">
7+
<!-- Control Bar -->
8+
<div class="flex items-center gap-6 px-10 py-5 bg-white/70 backdrop-blur-2xl rounded-full shadow-[0_8px_40px_-12px_rgba(0,0,0,0.1)] border border-white/60 transition-all hover:scale-[1.02] hover:shadow-[0_20px_40px_-12px_rgba(0,0,0,0.15)] duration-500 z-50">
9+
<button @onclick="@(() => { if (TargetPersonCount > 1) TargetPersonCount--; })" class="w-14 h-14 flex items-center justify-center rounded-full bg-white text-gray-400 font-bold text-2xl shadow-sm border border-gray-100/50 hover:bg-gray-50 hover:text-primary hover:border-indigo-100 hover:-translate-y-0.5 transition-all active:scale-95 active:bg-gray-100 disabled:opacity-30 disabled:cursor-not-allowed group">
10+
<span class="group-hover:scale-110 transition-transform">-</span>
11+
</button>
12+
13+
<div class="flex flex-col items-center min-w-[80px] gap-1">
14+
<span class="text-4xl font-black text-transparent bg-clip-text bg-gradient-to-br from-primary via-indigo-500 to-secondary leading-none filter drop-shadow-sm">
15+
@TargetPersonCount
16+
</span>
17+
<span class="text-[11px] uppercase tracking-[0.2em] font-bold text-gray-400/80">Winners</span>
18+
</div>
19+
20+
<button @onclick="@(() => TargetPersonCount++)" class="w-14 h-14 flex items-center justify-center rounded-full bg-white text-gray-400 font-bold text-2xl shadow-sm border border-gray-100/50 hover:bg-gray-50 hover:text-primary hover:border-indigo-100 hover:-translate-y-0.5 transition-all active:scale-95 active:bg-gray-100 group">
21+
<span class="group-hover:scale-110 transition-transform">+</span>
22+
</button>
23+
24+
<div class="w-px h-10 bg-gradient-to-b from-transparent via-gray-200 to-transparent mx-2"></div>
25+
26+
<button @onclick="OnClickButton"
27+
disabled="@(Status == DrawMachineStatus.Pending && PendingDrawCount == 0)"
28+
class="@(Status == DrawMachineStatus.Pending
29+
? "px-8 py-3.5 rounded-full font-bold text-gray-400 bg-gray-100/80 border border-gray-200/50 cursor-not-allowed transition-colors"
30+
: "px-8 py-3.5 rounded-full font-bold text-white bg-gradient-to-r from-[var(--color-primary)] to-[var(--color-secondary)] shadow-[0_4px_20px_-4px_rgba(99,102,241,0.4)] hover:shadow-[0_8px_25px_-4px_rgba(99,102,241,0.5)] hover:-translate-y-0.5 hover:scale-105 active:scale-95 active:translate-y-0 transition-all border border-white/20")">
31+
<span class="flex items-center gap-2">
32+
<span>추첨 진행</span>
33+
<span class="@(Status != DrawMachineStatus.Pending ? "animate-bounce" : "")" style="font-family: 'Fluent Emoji Flat'">🎲</span>
34+
</span>
35+
</button>
2436
</div>
25-
<div class="machine-wrapper">
26-
<div class="machine" @ref="machineElement" style="@BallStyleVariables" />
37+
38+
<!-- Machine Canvas -->
39+
<div class="relative w-full flex-1 flex items-center justify-center min-h-0">
40+
<div class="machine relative transition-all duration-500" @ref="machineElement" style="@BallStyleVariables" />
2741
</div>
2842
</div>

src/DotNetDevLottery/Components/Random/MachineAnimation.razor.css

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,3 @@
1-
.container {
2-
width: 70%;
3-
height: 100vh;
4-
display: flex;
5-
flex-direction: column;
6-
}
7-
81
/* Fluent UI 버튼 커스텀 스타일 */
92
.success {
103
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
@@ -19,42 +12,13 @@
1912
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
2013
}
2114

22-
.button-row {
23-
flex: 0 0 auto;
24-
padding: 2rem;
25-
gap: 1rem;
26-
display: flex;
27-
justify-content: center;
28-
align-items: center;
29-
box-sizing: border-box;
30-
background: white;
31-
border-radius: 20px;
32-
margin: 1rem;
33-
box-shadow: 0 4px 20px rgba(102, 126, 234, 0.1);
34-
35-
& span {
36-
font-size: 1.5rem;
37-
font-weight: 700;
38-
min-width: 3rem;
39-
text-align: center;
40-
color: #667eea;
41-
}
42-
}
43-
4415
.button-row::deep fluent-button {
4516
min-width: 48px;
4617
height: 48px;
4718
border-radius: 12px;
4819
font-weight: 600;
4920
}
5021

51-
.machine-wrapper {
52-
flex: 1 1 auto;
53-
display: flex;
54-
justify-content: center;
55-
align-items: center;
56-
}
57-
5822
.machine {
5923
position: relative;
6024
}

0 commit comments

Comments
 (0)