Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
venv/
.codegraph/
190 changes: 190 additions & 0 deletions components/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>KRDS Component Library ยท ๋งฅ๋ฝ์ง€ํ˜œ ์—ฐ๊ตฌ์‹ค</title>
<meta name="description" content="๋งฅ๋ฝ์ง€ํ˜œ ์—ฐ๊ตฌ์‹ค KRDS ์ปดํฌ๋„ŒํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ โ€” ๋ธŒ๋žœ๋“œ ํ† ํฐ ๊ธฐ๋ฐ˜ ๋ฐ”๋‹๋ผ ๊ตฌํ˜„" />
<link rel="stylesheet" href="../styles.css" />
<link rel="stylesheet" href="krds-components.css" />
<style>
/* gallery chrome only โ€” components themselves live in krds-components.css */
.gallery { max-width: 960px; margin: 0 auto; padding: 48px clamp(20px, 5vw, 48px); }
.gallery h1 { font-size: clamp(32px, 5vw, 48px); margin: 0 0 8px; }
.gallery > p.lead { color: var(--muted); font-size: 18px; margin: 0 0 40px; }
.story { margin: 40px 0; padding: 28px; background: var(--white); border: 1px solid var(--line); border-radius: 12px; }
.story > h2 { font-size: 22px; margin: 0 0 4px; }
.story > .src { font-size: 13px; color: var(--muted); margin: 0 0 20px; }
.row { display: flex; flex-wrap: wrap; gap: 16px; align-items: flex-start; }
.row.col { flex-direction: column; }
.stack { display: grid; gap: 16px; max-width: 420px; }
</style>
</head>
<body class="krds-scope">
<main class="gallery">
<h1>KRDS Component Library</h1>
<p class="lead">๋งฅ๋ฝ์ง€ํ˜œ ์—ฐ๊ตฌ์‹ค ยท ๋ธŒ๋žœ๋“œ ํ† ํฐ(<code>:root</code>) ๊ธฐ๋ฐ˜ ๋ฐ”๋‹๋ผ ๊ตฌํ˜„ ยท Figma <code>6Kx2gzcduyIyLGN3Ck2rtm</code> ๋Œ€์‘</p>

<section class="story" id="s-button">
<h2>Button</h2>
<p class="src">.krds-btn ยท Figma Action/Button 36:67</p>
<div class="row">
<button class="krds-btn krds-btn--primary">Primary</button>
<button class="krds-btn krds-btn--secondary">Secondary</button>
<button class="krds-btn krds-btn--tertiary">Tertiary</button>
<button class="krds-btn krds-btn--danger">Danger</button>
<button class="krds-btn krds-btn--primary" disabled>Disabled</button>
<button class="krds-btn krds-btn--primary krds-btn--loading">Loading</button>
</div>
<div class="row" style="margin-top:16px">
<button class="krds-btn krds-btn--primary krds-btn--sm">Small</button>
<button class="krds-btn krds-btn--primary">Medium</button>
<button class="krds-btn krds-btn--primary krds-btn--lg">Large</button>
</div>
</section>

<section class="story" id="s-input">
<h2>Text Input</h2>
<p class="src">.krds-field ยท Figma Input/Text Input 62:22</p>
<div class="stack">
<label class="krds-field">
<span class="krds-field__label">์ด๋ฆ„<span class="krds-req" aria-hidden="true">*</span></span>
<input class="krds-field__control" type="text" placeholder="์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”" required />
<span class="krds-field__help">๊ณต๋ฐฑ ์—†์ด ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค.</span>
</label>
<div class="krds-field krds-field--error">
<label class="krds-field__label" for="email-err">์ด๋ฉ”์ผ</label>
<input class="krds-field__control" id="email-err" type="email" value="not-an-email" aria-invalid="true" aria-describedby="email-err-msg" />
<span class="krds-field__error" id="email-err-msg">์˜ฌ๋ฐ”๋ฅธ ์ด๋ฉ”์ผ ํ˜•์‹์ด ์•„๋‹™๋‹ˆ๋‹ค.</span>
</div>
<label class="krds-field">
<span class="krds-field__label">๋น„ํ™œ์„ฑ</span>
<input class="krds-field__control" type="text" value="์ˆ˜์ • ๋ถˆ๊ฐ€" disabled />
</label>
</div>
</section>

<section class="story" id="s-choice">
<h2>Checkbox / Radio</h2>
<p class="src">.krds-choice ยท Figma Selection 38:16 / 38:27</p>
<div class="row col">
<label class="krds-choice"><input type="checkbox" checked /> ๋™์˜ํ•ฉ๋‹ˆ๋‹ค</label>
<label class="krds-choice"><input type="checkbox" /> ์„ ํƒ ์•ˆ ํ•จ</label>
<label class="krds-choice"><input type="checkbox" disabled /> ๋น„ํ™œ์„ฑ</label>
<label class="krds-choice"><input type="radio" name="r" checked /> ์˜ต์…˜ A</label>
<label class="krds-choice"><input type="radio" name="r" /> ์˜ต์…˜ B</label>
</div>
</section>

<section class="story" id="s-select">
<h2>Select</h2>
<p class="src">.krds-select ยท Figma Selection/Select 56:29</p>
<label class="krds-field" style="max-width:320px">
<span class="krds-field__label">์ •๋ ฌ</span>
<select class="krds-select">
<option>์ตœ์‹ ์ˆœ</option>
<option>์ด๋ฆ„์ˆœ</option>
<option>๊ด€๋ จ๋„์ˆœ</option>
</select>
</label>
</section>

<section class="story" id="s-badge-tag">
<h2>Badge &amp; Tag</h2>
<p class="src">.krds-badge / .krds-tag ยท Figma 58:48 / 58:54</p>
<div class="row">
<span class="krds-badge">Default</span>
<span class="krds-badge krds-badge--info">Info</span>
<span class="krds-badge krds-badge--success">Success</span>
<span class="krds-badge krds-badge--warning">Warning</span>
<span class="krds-badge krds-badge--danger">Danger</span>
</div>
<div class="row" style="margin-top:16px">
<span class="krds-tag">ํ•„ํ„ฐ A <button class="krds-tag__remove" aria-label="ํ•„ํ„ฐ A ์ œ๊ฑฐ">ร—</button></span>
<span class="krds-tag">ํ•„ํ„ฐ B <button class="krds-tag__remove" aria-label="ํ•„ํ„ฐ B ์ œ๊ฑฐ">ร—</button></span>
</div>
</section>

<section class="story" id="s-tabs">
<h2>Tabs</h2>
<p class="src">.krds-tabs ยท Figma Layout/Tabs 59:11</p>
<div class="krds-tabs">
<div class="krds-tabs__list" role="tablist">
<button class="krds-tab" role="tab" aria-selected="true" aria-controls="tp1" id="t1">๊ฐœ์š”</button>
<button class="krds-tab" role="tab" aria-selected="false" aria-controls="tp2" id="t2">๊ทผ๊ฑฐ</button>
<button class="krds-tab" role="tab" aria-selected="false" aria-controls="tp3" id="t3">์ฐธ๊ณ </button>
</div>
<div class="krds-tabpanel" role="tabpanel" id="tp1" aria-labelledby="t1">๊ฐœ์š” ํŒจ๋„ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.</div>
<div class="krds-tabpanel" role="tabpanel" id="tp2" aria-labelledby="t2" hidden>๊ทผ๊ฑฐ ํŒจ๋„ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.</div>
<div class="krds-tabpanel" role="tabpanel" id="tp3" aria-labelledby="t3" hidden>์ฐธ๊ณ  ํŒจ๋„ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.</div>
</div>
</section>

<section class="story" id="s-accordion">
<h2>Accordion</h2>
<p class="src">.krds-accordion (native details) ยท Figma Layout/Accordion 59:21</p>
<details class="krds-accordion" open>
<summary>DIKW๋ž€ ๋ฌด์—‡์ธ๊ฐ€</summary>
<div class="krds-accordion__body">์ž๋ฃŒยท์ •๋ณดยท์ง€์‹ยท์ง€ํ˜œ๋ฅผ ์ ๊ฒ€ํ•˜๋Š” ์งˆ๋ฌธ ์ฒด๊ณ„์ž…๋‹ˆ๋‹ค.</div>
</details>
<details class="krds-accordion" style="margin-top:8px">
<summary>์™œ ์œ„๊ณ„๊ฐ€ ์•„๋‹Œ๊ฐ€</summary>
<div class="krds-accordion__body">๋งฅ๋ฝ์— ๋”ฐ๋ผ ์ˆœํ™˜ยท์žฌ๊ตฌ์„ฑ๋˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.</div>
</details>
</section>

<section class="story" id="s-pagination">
<h2>Pagination</h2>
<p class="src">.krds-pagination ยท Figma Navigation/Pagination 58:25</p>
<nav aria-label="ํŽ˜์ด์ง€ ์ด๋™">
<ul class="krds-pagination">
<li><span aria-disabled="true">์ด์ „</span></li>
<li><a href="#" aria-current="page">1</a></li>
<li><a href="#">2</a></li>
<li><a href="#">3</a></li>
<li><span>โ€ฆ</span></li>
<li><a href="#">9</a></li>
<li><a href="#">๋‹ค์Œ</a></li>
</ul>
</nav>
</section>

<section class="story" id="s-alert">
<h2>Alert</h2>
<p class="src">.krds-alert ยท Figma Layout/Critical Alert</p>
<div class="stack" style="max-width:640px">
<div class="krds-alert krds-alert--info"><span><strong class="krds-alert__title">์•ˆ๋‚ด</strong> ์ •๋ณด ๋ฉ”์‹œ์ง€์ž…๋‹ˆ๋‹ค.</span></div>
<div class="krds-alert krds-alert--success"><span><strong class="krds-alert__title">์™„๋ฃŒ</strong> ์„ฑ๊ณต์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.</span></div>
<div class="krds-alert krds-alert--warning"><span><strong class="krds-alert__title">์ฃผ์˜</strong> ํ™•์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.</span></div>
<div class="krds-alert krds-alert--danger"><span><strong class="krds-alert__title">์˜ค๋ฅ˜</strong> ์ฒ˜๋ฆฌ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.</span></div>
</div>
</section>

<section class="story" id="s-toast">
<h2>Toast</h2>
<p class="src">.krds-toast ยท Figma Feedback/Toast 56:46</p>
<div class="krds-toast">์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. <button class="krds-toast__action">์‹คํ–‰ ์ทจ์†Œ</button></div>
</section>
</main>

<script>
// Tabs: minimal roving behavior. ponytail: native buttons + aria, no framework.
document.querySelectorAll(".krds-tabs").forEach((tabs) => {
const tabList = [...tabs.querySelectorAll('[role="tab"]')];
tabList.forEach((tab) => {
tab.addEventListener("click", () => {
tabList.forEach((t) => {
const sel = t === tab;
t.setAttribute("aria-selected", sel);
document.getElementById(t.getAttribute("aria-controls")).hidden = !sel;
});
});
});
});
// Tag remove
document.querySelectorAll(".krds-tag__remove").forEach((btn) =>
btn.addEventListener("click", () => btn.closest(".krds-tag").remove())
);
</script>
</body>
</html>
198 changes: 198 additions & 0 deletions components/krds-components.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
/*
* KRDS Component Library โ€” vanilla, token-bound implementation
* Mirrors the Figma design system (file 6Kx2gzcduyIyLGN3Ck2rtm).
* Consumes the site's brand tokens from styles.css (:root) and adds the
* KRDS status/semantic tokens the components need. No build step, no framework.
*/

:root {
/* status tokens โ€” KRDS-standard, not present in brand palette */
--krds-info: #256ef4;
--krds-success: #2e7d32;
--krds-warning: #b45309;
--krds-danger: #c0392b;
--krds-info-bg: #eef4ff;
--krds-success-bg: #eaf5eb;
--krds-warning-bg: #fdf3e7;
--krds-danger-bg: #fdecea;
/* component tokens derived from brand palette */
--krds-radius: 4px;
--krds-radius-lg: 12px;
--krds-gap: 8px;
--krds-disabled: color-mix(in srgb, var(--muted) 40%, var(--white));
}

.krds-scope {
color: var(--ink);
font-family: "Pretendard CWL", "Apple SD Gothic Neo", "Noto Sans KR", system-ui, sans-serif;
line-break: keep-all;
word-break: keep-all;
}

/* Shared focus ring โ€” matches styles.css convention (teal, 2px, offset 2px) */
.krds-scope :is(button, a, input, select, textarea, summary, [tabindex]):focus-visible {
outline: 2px solid var(--teal);
outline-offset: 2px;
}

/* ---------- Button ---------- */
.krds-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--krds-gap);
min-height: 48px;
padding: 12px 20px;
border: 1px solid var(--ink);
border-radius: var(--krds-radius);
background: var(--white);
color: var(--ink);
font: inherit;
font-size: 15px;
font-weight: 800;
cursor: pointer;
transition: opacity 0.2s, background 0.2s;
}
.krds-btn:hover { opacity: 0.85; }
.krds-btn:active { opacity: 0.7; }
.krds-btn--primary { background: var(--ink); color: var(--white); }
.krds-btn--secondary { background: var(--white); color: var(--ink); }
.krds-btn--tertiary { border-color: transparent; background: transparent; text-decoration: underline; }
.krds-btn--danger { background: var(--krds-danger); border-color: var(--krds-danger); color: var(--white); }
.krds-btn--sm { min-height: 36px; padding: 8px 14px; font-size: 14px; }
.krds-btn--lg { min-height: 56px; padding: 16px 28px; font-size: 16px; }
.krds-btn[disabled], .krds-btn[aria-disabled="true"] {
opacity: 1;
cursor: not-allowed;
background: var(--krds-disabled);
border-color: var(--krds-disabled);
color: var(--muted);
}
.krds-btn--loading { position: relative; color: transparent; pointer-events: none; }
.krds-btn--loading::after {
content: ""; position: absolute; width: 16px; height: 16px;
border: 2px solid currentColor; border-top-color: transparent; border-radius: 50%;
color: var(--white); animation: krds-spin 0.7s linear infinite;
}

/* ---------- Text field (input / textarea) ---------- */
.krds-field { display: grid; gap: 6px; }
.krds-field__label { font-size: 14px; font-weight: 700; }
.krds-field__label .krds-req { color: var(--krds-danger); margin-left: 2px; }
.krds-field__control {
min-height: 48px;
padding: 12px 14px;
border: 1px solid var(--line);
border-radius: var(--krds-radius);
background: var(--white);
font: inherit;
font-size: 15px;
color: var(--ink);
}
.krds-field__control::placeholder { color: var(--muted); }
.krds-field__control:hover { border-color: var(--muted); }
.krds-field__help { font-size: 13px; color: var(--muted); }
.krds-field__error { font-size: 13px; color: var(--krds-danger); font-weight: 700; }
.krds-field--error .krds-field__control { border-color: var(--krds-danger); }
.krds-field__control[disabled] { background: var(--paper); color: var(--muted); cursor: not-allowed; }

/* ---------- Checkbox / Radio (native inputs, accent-color) ---------- */
.krds-choice { display: inline-flex; align-items: center; gap: 10px; font-size: 15px; cursor: pointer; }
.krds-choice input { width: 20px; height: 20px; accent-color: var(--teal); cursor: pointer; }
.krds-choice input[disabled] { cursor: not-allowed; }
.krds-choice:has(input[disabled]) { color: var(--muted); cursor: not-allowed; }

/* ---------- Select ---------- */
.krds-select {
min-height: 48px;
padding: 12px 40px 12px 14px;
border: 1px solid var(--line);
border-radius: var(--krds-radius);
background: var(--white)
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpath d='M4 6l4 4 4-4' fill='none' stroke='%234f5968' stroke-width='2'/%3E%3C/svg%3E")
no-repeat right 14px center;
font: inherit; font-size: 15px; color: var(--ink); appearance: none; cursor: pointer;
}

/* ---------- Badge ---------- */
.krds-badge {
display: inline-flex; align-items: center; gap: 4px;
padding: 2px 8px; border-radius: 999px;
font-size: 12px; font-weight: 800; line-height: 1.6;
background: var(--line); color: var(--ink);
}
.krds-badge--info { background: var(--krds-info-bg); color: var(--krds-info); }
.krds-badge--success { background: var(--krds-success-bg); color: var(--krds-success); }
.krds-badge--warning { background: var(--krds-warning-bg); color: var(--krds-warning); }
.krds-badge--danger { background: var(--krds-danger-bg); color: var(--krds-danger); }

/* ---------- Tag (removable) ---------- */
.krds-tag {
display: inline-flex; align-items: center; gap: 6px;
padding: 4px 10px; border: 1px solid var(--line); border-radius: 999px;
font-size: 13px; background: var(--white);
}
.krds-tag__remove {
border: 0; background: none; padding: 0; cursor: pointer;
font-size: 14px; line-height: 1; color: var(--muted);
}
.krds-tag__remove:hover { color: var(--ink); }

/* ---------- Tabs ---------- */
.krds-tabs__list { display: flex; gap: 4px; border-bottom: 2px solid var(--line); }
.krds-tab {
border: 0; background: none; padding: 12px 16px; cursor: pointer;
font: inherit; font-size: 15px; font-weight: 700; color: var(--muted);
border-bottom: 2px solid transparent; margin-bottom: -2px;
}
.krds-tab[aria-selected="true"] { color: var(--ink); border-bottom-color: var(--teal); }
.krds-tabpanel { padding: 20px 4px; }
.krds-tabpanel[hidden] { display: none; }

/* ---------- Accordion (native details/summary) ---------- */
.krds-accordion { border: 1px solid var(--line); border-radius: var(--krds-radius); }
.krds-accordion > summary {
list-style: none; cursor: pointer; padding: 16px 18px;
font-weight: 700; display: flex; justify-content: space-between; align-items: center;
}
.krds-accordion > summary::-webkit-details-marker { display: none; }
.krds-accordion > summary::after { content: "+"; font-size: 20px; color: var(--muted); }
.krds-accordion[open] > summary::after { content: "\2212"; }
.krds-accordion__body { padding: 0 18px 18px; color: var(--muted); }

/* ---------- Pagination ---------- */
.krds-pagination { display: flex; gap: 4px; align-items: center; list-style: none; padding: 0; }
.krds-pagination a, .krds-pagination span {
display: inline-flex; align-items: center; justify-content: center;
min-width: 40px; min-height: 40px; padding: 0 8px;
border-radius: var(--krds-radius); text-decoration: none; color: var(--ink); font-weight: 700;
}
.krds-pagination a:hover { background: var(--paper); }
.krds-pagination [aria-current="page"] { background: var(--ink); color: var(--white); }
.krds-pagination [aria-disabled="true"] { color: var(--krds-disabled); pointer-events: none; }

/* ---------- Alert ---------- */
.krds-alert {
display: flex; gap: 12px; padding: 14px 16px;
border-radius: var(--krds-radius); border-left: 4px solid var(--muted);
background: var(--paper); font-size: 15px;
}
.krds-alert--info { border-left-color: var(--krds-info); background: var(--krds-info-bg); }
.krds-alert--success { border-left-color: var(--krds-success); background: var(--krds-success-bg); }
.krds-alert--warning { border-left-color: var(--krds-warning); background: var(--krds-warning-bg); }
.krds-alert--danger { border-left-color: var(--krds-danger); background: var(--krds-danger-bg); }
.krds-alert__title { font-weight: 800; }

/* ---------- Toast ---------- */
.krds-toast {
display: inline-flex; align-items: center; gap: 12px;
padding: 14px 18px; border-radius: var(--krds-radius-lg);
background: var(--ink); color: var(--white); box-shadow: var(--shadow); font-size: 15px;
}
.krds-toast__action { border: 0; background: none; color: var(--gold); font-weight: 800; cursor: pointer; }

@keyframes krds-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
.krds-btn--loading::after { animation: none; }
}
Loading