[🎨] Bootstrap 걷어내고 Tailwind로 전체 적용

This commit is contained in:
김선규 2025-04-25 15:13:35 +09:00
parent b13ec315a5
commit f15471621a
18 changed files with 64 additions and 684 deletions

View File

@ -25,11 +25,14 @@
<_ContentIncludedByDefault Remove="Pages\Counter.razor" />
<_ContentIncludedByDefault Remove="Pages\Home.razor" />
<_ContentIncludedByDefault Remove="Pages\Weather.razor" />
<_ContentIncludedByDefault Remove="wwwroot\css\bootstrap\bootstrap.min.css" />
<_ContentIncludedByDefault Remove="wwwroot\css\bootstrap\bootstrap.min.css.map" />
</ItemGroup>
<ItemGroup>
<Folder Include="Program\Models\" />
<Folder Include="Program\ViewModels\" />
<Folder Include="Program\Views\" />
</ItemGroup>

View File

@ -1,52 +1,11 @@
@inherits LayoutComponentBase
<!-- index.html (Blazor 프로젝트 기준) -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<div class="page">
<!-- 상단 네비게이션 (떠 있음) -->
@* <div class="top-view"> *@
@* <div class="top-title"> *@
@* <img class="logoIcon" src="logo.png" alt="icon"/> *@
@* <h1 class="prj-title">PROJECT</h1> *@
@* </div> *@
@* </div> *@
<div class="min-h-screen bg-gray-50 text-gray-900">
<header class="bg-purple-600 text-white p-4 shadow">
<h1 class="text-xl font-bold">AcaMate</h1>
</header>
<div class="top-floating-menu">
<div class="menu-toggle">≡</div>
<div class="menu-box">
<div class = "menu-list">
<a href="#">메뉴 1</a>
<a href="#">메뉴 2</a>
<a href="#">메뉴 3</a>
</div>
<button class = "btn-primary">X</button>
</div>
</div>
<!-- 본문 콘텐츠 -->
<main class="content-container">
@* <article class="content"> *@
@* @Body *@
@* $1$ <inpu #1# *@
@* </article> *@
<div class="dummy-scroll-test">
@for (int i = 0; i < 50; i++)
{
<div class="dummy-box">Dummy Box @i</div>
}
</div>
<main class="p-6">
@Body
</main>
<div class="float-left-down">
<button class="fld-btn">🛒 상점</button>
</div>
<div class="float-right-down">
<button class="fld-btn">🛒 상점</button>
</div>
@* <div class="bottom-right-btn"> *@
@* <button class="btn-primary">TEST</button> *@
@* </div> *@
</div>

View File

@ -1,193 +0,0 @@
html, body {
overscroll-behavior: none; /* 바운스/오버스크롤 차단 */
}
/* 전체 페이지 wrapper */
.page {
position: relative;
min-height: 100vh;
overflow-x: hidden;
}
.top-view {
position: fixed;
}
/* TopNav를 화면 위에 떠 있게 설정 */
.top-nav {
position: fixed;
top: 0;
left: 0;
width: 100%;
padding: 1.5rem 2rem;
background-color: rgba(255, 255, 255, 0.95); /* 반투명 배경 */
z-index: 1000;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
}
.logoIcon{
width: 5vw;
min-width: 32px;
max-width: 128px;
height: auto;
aspect-ratio: 1/1;
}
/*.logo-mask img {*/
/* visibility: hidden;*/
/* width: 100%;*/
/* height: 100%;*/
/*}*/
/* 타이틀 텍스트 */
.prj-title {
font-size: 1.5rem;
font-weight: bold;
margin: 0;
color: var(--text-title);
}
/* 메뉴 영역 */
.top-menu {
display: flex;
gap: 1.5rem;
}
/* 테스트 플로팅 */
.top-floating-menu {
position: fixed;
top: 4rem;
right: 2rem;
z-index: 1000;
display: inline-block;
}
/* 원형 버튼 (아래쪽 레이어) */
.menu-toggle {
width: 48px;
height: 48px;
background-color: black;
color: white;
font-size: 1.5rem;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
position: relative;
z-index: 1; /* 🔽 아래 레이어 */
}
/* 펼쳐질 박스 (위에 뜨는 레이어) */
.menu-box {
position: absolute;
top: 0;
right: 0;
width: 85vw;
height: 120px;
background-color: white;
border-radius: 50%;
transform: scale(0);
transform-origin: top right;
opacity: 0;
transition: all 0.3s ease;
padding: 1rem;
pointer-events: none;
z-index: 2; /* 🔼 위쪽 레이어 */
display: flex;
flex-direction: row;
gap: 0.5rem;
}
/* 마우스를 전체에 올리면 박스가 앞에 뜨도록 */
.top-floating-menu:hover .menu-box {
transform: scale(1);
border-radius: 0.75rem;
opacity: 1;
pointer-events: auto;
}
.menu-box {
position: absolute;
top: 0;
right: 0;
/* 나머지 스타일 유지 */
padding-top: 2rem; /* X 버튼이 박스 내부에서 안 겹치도록 여백 추가 */
}
.menu-box button {
position: absolute;
top: 0.5rem;
right: 0.5rem;
background: transparent;
border: none;
font-size: 1.2rem;
color: #333;
cursor: pointer;
z-index: 3;
}
/* 플로팅 뷰 */
.float-left-down {
position: fixed;
left: 2rem;
bottom: 2rem;
width: clamp(48px, 8vw, 128px);
height: clamp(48px, 8vw, 128px);
z-index: 1000;
}
.float-right-down {
position: fixed;
right: 2rem;
bottom: 2rem;
z-index: 1000;
}
.fld-btn {
padding: 0.75rem 1.2rem;
background-color: var(--point-dark);
color: white;
border: none;
border-radius: 10px;
font-size: 1rem;
font-weight: bold;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
cursor: pointer;
transition: all 0.2s ease;
}
.fld-btn :hover {
transform: translateY(-3px);
}
/*더미 체크용*/
.dummy-scroll-test {
overscroll-behavior: none;
}
.dummy-box {
height: 150px;
background-color: var(--normal-normal);
margin: 1rem 0;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
border: 1px solid var(--normal-dark);
}

View File

@ -1,39 +0,0 @@
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">Front</a>
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
</div>
<div class="@NavMenuCssClass nav-scrollable" @onclick="ToggleNavMenu">
<nav class="flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Counter
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="weather">
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Weather
</NavLink>
</div>
</nav>
</div>
@code {
private bool collapseNavMenu = true;
private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
}

View File

@ -1,83 +0,0 @@
.navbar-toggler {
background-color: rgba(255, 255, 255, 0.1);
}
.top-row {
height: 3.5rem;
background-color: rgba(0,0,0,0.4);
}
.navbar-brand {
font-size: 1.1rem;
}
.bi {
display: inline-block;
position: relative;
width: 1.25rem;
height: 1.25rem;
margin-right: 0.75rem;
top: -1px;
background-size: cover;
}
.bi-house-door-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
}
.bi-plus-square-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
}
.bi-list-nested-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
}
.nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
}
.nav-item:first-of-type {
padding-top: 1rem;
}
.nav-item:last-of-type {
padding-bottom: 1rem;
}
.nav-item ::deep a {
color: #d7d7d7;
border-radius: 4px;
height: 3rem;
display: flex;
align-items: center;
line-height: 3rem;
}
.nav-item ::deep a.active {
background-color: rgba(255,255,255,0.37);
color: white;
}
.nav-item ::deep a:hover {
background-color: rgba(255,255,255,0.1);
color: white;
}
@media (min-width: 641px) {
.navbar-toggler {
display: none;
}
.collapse {
/* Never collapse the sidebar for wide screens */
display: block;
}
.nav-scrollable {
/* Allow sidebar to scroll for tall menus */
height: calc(100vh - 3.5rem);
overflow-y: auto;
}
}

View File

@ -1,18 +0,0 @@
@page "/counter"
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}

View File

@ -1,9 +0,0 @@
@page "/"
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
15

View File

@ -1,57 +0,0 @@
@page "/weather"
@inject HttpClient Http
<PageTitle>Weather</PageTitle>
<h1>Weather</h1>
<p>This component demonstrates fetching data from the server.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json");
}
public class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public string? Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
}

13
package.json Normal file
View File

@ -0,0 +1,13 @@
{
"name": "tailwind-blazor-template",
"version": "1.0.0",
"scripts": {
"build:css": "tailwindcss -i ./wwwroot/css/app.css -o ./wwwroot/css/tailwind.css --minify",
"watch:css": "tailwindcss -i ./wwwroot/css/app.css -o ./wwwroot/css/tailwind.css --watch"
},
"devDependencies": {
"tailwindcss": "^3.4.1",
"postcss": "^8.4.21",
"autoprefixer": "^10.4.14"
}
}

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

10
tailwind.config.js Normal file
View File

@ -0,0 +1,10 @@
module.exports = {
content: [
"./**/*.razor",
"./wwwroot/index.html"
],
theme: {
extend: {},
},
plugins: [],
}

View File

@ -1,171 +1,19 @@
html, body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
h1:focus {
outline: none;
}
a, .btn-link {
color: #0071c1;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}
.content {
padding-top: 1.1rem;
}
.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}
.invalid {
outline: 1px solid red;
}
.validation-message {
color: red;
}
#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
.blazor-error-boundary {
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
padding: 1rem 1rem 1rem 3.7rem;
color: white;
}
.blazor-error-boundary::after {
content: "An error has occurred."
}
/* 전체 화면을 덮는 로딩 오버레이 */
.loading-overlay {
position: fixed;
z-index: 9999;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
}
/* 기존 코드 유지 */
.custom-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
/* 이후 동일한 스타일 유지 */
/*.loading-progress {*/
/* position: relative;*/
/* display: block;*/
/* width: 8rem;*/
/* height: 8rem;*/
/* margin: 20vh auto 1rem auto;*/
/*}*/
/* .loading-progress circle {*/
/* fill: none;*/
/* stroke: #e0e0e0;*/
/* stroke-width: 0.6rem;*/
/* transform-origin: 50% 50%;*/
/* transform: rotate(-90deg);*/
/* }*/
/* .loading-progress circle:last-child {*/
/* stroke: #1b6ec2;*/
/* stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;*/
/* transition: stroke-dasharray 0.05s ease-in-out;*/
/* }*/
/*.loading-progress-text {*/
/* position: absolute;*/
/* text-align: center;*/
/* font-weight: bold;*/
/* inset: calc(20vh + 3.25rem) 0 auto 0.2rem;*/
/*}*/
/* .loading-progress-text:after {*/
/* content: var(--blazor-load-percentage-text, "Loading");*/
/* }*/
/*code {*/
/* color: #c02d76;*/
/*}*/
@tailwind base;
@tailwind components;
@tailwind utilities;
/* 색상 변수 정의 */
:root {
/* Disable */
--disable-light: #F8F8F8;
--disable-normal: #B8B9B4;
--disable-dark: #8A8B87;
--disable-darker: #40413F;
/* Information */
--information-light: #F7FBF8;
--information-normal: #B2DBBB;
--information-dark: #86A48C;
--information-darker: #3E4D41;
/* Point */
--point-light: #F9FBFF;
--point-normal: #C2D6FA;
--point-dark: #92A1BC;
--point-darker: #444B58;
/* Second */
--second-light: #F2F0ED;
--second-normal: #79654E;
--second-dark: #5D4C3B;
--second-darker: #2A231B;
/* Normal */
--normal-light: #FDFCFB;
--normal-normal: #EBDFD2;
--normal-dark: #B0A79E;
--normal-darker: #524E4A;
/* Danger */
--danger-light: #FFF9F9;
--danger-normal: #FDC6C3;
--danger-dark: #BE9592;
--danger-darker: #594544;
/* 기타 텍스트 및 상태 */
--text-title: #1D1D1D;
--text-detail: #545454;
--text-disabled: #8E8E8E;
@ -174,60 +22,20 @@ a, .btn-link {
--text-border: #C6C6C6;
}
/* Progress Icon */
.custom-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
background: #f0f0f0;
}
.logo-mask {
position: relative;
width: 192px;
height: 192px;
mask-image: url('/logo.png');
mask-size: cover;
-webkit-mask-image: url('/logo.png');
-webkit-mask-size: cover;
}
.logo-mask img {
visibility: hidden;
width: 100%;
height: 100%;
}
.logo-progress {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 0%;
background-color: black;
animation: progressFill 3s linear forwards;
}
@keyframes progressFill {
/* 클리핑 애니메이션 (왼쪽부터 점점 보임) */
@keyframes clipReveal {
from {
width: 0%;
clip-path: inset(0 100% 0 0); /* 왼쪽만 남기고 나머지 잘라냄 */
}
to {
width: 100%;
clip-path: inset(0 0 0 0); /* 전체 다 보임 */
}
}
.loading-text {
margin-top: 20px;
font-size: 1.2em;
color: #333;
animation: blink 1.2s infinite;
.clip-reveal {
animation: clipReveal 2500ms ease forwards;
}
/* 텍스트 블링크 */
@keyframes blink {
0%, 100% {
opacity: 1;
@ -236,3 +44,17 @@ a, .btn-link {
opacity: 0;
}
}
.animate-blink {
animation: blink 1.2s infinite;
}
/* 로고 마스크 설정 */
.mask-logo {
-webkit-mask-image: url('/logo.png');
-webkit-mask-size: cover;
-webkit-mask-repeat: no-repeat;
mask-image: url('/logo.png');
mask-size: cover;
mask-repeat: no-repeat;
mask-position: center;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
wwwroot/css/tailwind.css Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -1,27 +0,0 @@
[
{
"date": "2022-01-06",
"temperatureC": 1,
"summary": "Freezing"
},
{
"date": "2022-01-07",
"temperatureC": 14,
"summary": "Bracing"
},
{
"date": "2022-01-08",
"temperatureC": -13,
"summary": "Freezing"
},
{
"date": "2022-01-09",
"temperatureC": -16,
"summary": "Balmy"
},
{
"date": "2022-01-10",
"temperatureC": -2,
"summary": "Chilly"
}
]