- 교각점 고름 = 창 없이 근처(40 px) 아이콘 · 멀면 0.7 s 뒤 숨김 · 누름 = 창 열림(멀어져도 유지) - 창 열린 채 다른 교각점 = 열린 채 새 정보 · 닫힌 채 = 닫힌 채 · [닫기] = 창 닫고 고름 풂 - 부품 상자 아이콘 단추 「유리」 꼴 더함(견본 · 시험 552-2) - 새 지킴 시험 test_445_curve_icon · 398-2 · 408 도우미에 가짜 아이콘 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
115 lines
5.1 KiB
TypeScript
115 lines
5.1 KiB
TypeScript
/* =============================================================================
|
|
* ui_template_button.ts
|
|
* 부품 「단추」 · 「아이콘 단추」 · 크기 여덟 단(552-2 · 부품 모음 `#/b01-account?view=ui-kit`)
|
|
*
|
|
* 꼴 = 단추: 주(filled) · 보조(ghost) · 고스트 글(pill) · 위험(danger) · 위험 고스트(danger-ghost) · 유리(glass · 그림 위) · 글 링크(link)
|
|
* 아이콘 단추: 고스트(ghost · 기본) · 선(outline) · 채움(filled) · 유리(glass · 그림 위) · 켜짐 = aria-pressed
|
|
* 크기 = `data-size="18|20|22|24|28|32|36|42"`(높이 px) — 단추 · 아이콘 단추 · 입력 · 고르기 · 분할 · 탭 같은 값
|
|
* 옛 `size: "sm"` = 18 · 크기를 안 주면 data-size 없이 옛 꼴(높이 32 · 아이콘 붙으면 34) 그대로
|
|
* 실측 높이 = 단(꼴 · 페이지 글 크기 · 왼쪽 패널 양식과 상관없이) · `hidden` 이면 늘 숨음
|
|
* 상태 = 마우스 `:hover` · 눌림 `:active` · 막힘 `disabled` — CSS 는 `[data-state]` 로도 받아 부품 모음이 굳혀 보임
|
|
* 쓰기 = `createButton({ label: "저장", size: 24, onClick })` · `createIconButton({ icon: "settings", title: "설정", size: 24 })`
|
|
* 글자 기호 단추(▲ ▼ ✕ ?)는 `glyph: "▲"` — 시험이 글자로 잡음
|
|
* 대신 = 페이지마다 만든 16 ~ 26 짜리 작은 단추 · `b01-shell__icon-btn` · `b01-pg-menu__btn` · `b05-profile__zoom-btn` …(목록 4장)
|
|
* 클래스 = `ui-btn ui-btn--<꼴>`(+ 옛 `ui-btn--sm`) · `ui-btn__icon` · `ui-btn__label` · `ui-icon-btn ui-icon-btn--<꼴>` — 시험 · 가이드 촬영이 잡음
|
|
* ========================================================================== */
|
|
|
|
import { el } from "./ui_template_elements_base";
|
|
import { icon, type IconName } from "./ui_template_icons";
|
|
import "./ui_template_button.css";
|
|
|
|
/** 크기 여덟 단(높이 px · 요구 377 · 378) */
|
|
export const UI_SIZES = [18, 20, 22, 24, 28, 32, 36, 42] as const;
|
|
export type UiSize = (typeof UI_SIZES)[number];
|
|
|
|
/** 단마다 아이콘 크기(`ui_template_button.css` 의 `--ui-size-icon` 과 같은 값) */
|
|
const ICON_PX: Record<UiSize, number> = {
|
|
18: 12,
|
|
20: 14,
|
|
22: 14,
|
|
24: 14,
|
|
28: 16,
|
|
32: 16,
|
|
36: 18,
|
|
42: 20,
|
|
};
|
|
|
|
/** 요소에 크기 단을 붙임 */
|
|
export function setUiSize<T extends HTMLElement>(node: T, size: UiSize): T {
|
|
node.dataset.size = String(size);
|
|
return node;
|
|
}
|
|
|
|
export type ButtonVariant =
|
|
"filled" | "ghost" | "pill" | "danger" | "danger-ghost" | "glass" | "link";
|
|
/** 크기 — 단(18 ~ 42) · 옛 "sm" = 18 · 옛 "md" = 단 없이 옛 꼴 */
|
|
export type ButtonSize = "md" | "sm" | UiSize;
|
|
|
|
export interface ButtonOptions {
|
|
/** 표시 텍스트 (i18n 결과 문자열을 전달) */
|
|
label: string;
|
|
variant?: ButtonVariant;
|
|
size?: ButtonSize;
|
|
/** 이벤트 핸들러 (frontend.md §4: on[페이지]_[기능]_[액션] 명명) */
|
|
onClick?: (ev: MouseEvent) => void;
|
|
disabled?: boolean;
|
|
type?: "button" | "submit" | "reset";
|
|
/** 앞쪽 아이콘 요소 (선택) — 이름이면 단 크기 아이콘 */
|
|
iconStart?: HTMLElement | IconName;
|
|
}
|
|
|
|
export function createButton(opts: ButtonOptions): HTMLButtonElement {
|
|
const variant = opts.variant ?? "filled";
|
|
const size: UiSize | null =
|
|
opts.size === "sm" ? 18 : opts.size === "md" || opts.size === undefined ? null : opts.size;
|
|
const btn = el("button", {
|
|
className: `ui-btn ui-btn--${variant}${opts.size === "sm" ? " ui-btn--sm" : ""}`,
|
|
attrs: { type: opts.type ?? "button" },
|
|
});
|
|
if (size) setUiSize(btn, size);
|
|
if (opts.iconStart) {
|
|
const mark =
|
|
typeof opts.iconStart === "string"
|
|
? icon(opts.iconStart, size ? ICON_PX[size] : 16)
|
|
: opts.iconStart;
|
|
mark.classList.add("ui-btn__icon");
|
|
btn.append(mark);
|
|
}
|
|
btn.append(el("span", { className: "ui-btn__label", text: opts.label }));
|
|
if (opts.disabled) btn.disabled = true;
|
|
if (opts.onClick) btn.addEventListener("click", opts.onClick);
|
|
return btn;
|
|
}
|
|
|
|
export type IconButtonVariant = "ghost" | "outline" | "filled" | "glass";
|
|
|
|
export interface IconButtonOptions {
|
|
/** 아이콘 이름 — 없으면 glyph */
|
|
icon?: IconName;
|
|
/** 글자 기호(▲ ▼ ✕ ? …) — 시험이 글자로 잡는 단추 */
|
|
glyph?: string;
|
|
/** 마우스 글 · 보조기기 이름(필수) */
|
|
title: string;
|
|
variant?: IconButtonVariant;
|
|
/** 기본 32 */
|
|
size?: UiSize;
|
|
/** 켜고 끄는 단추면 지금 값(aria-pressed) */
|
|
pressed?: boolean;
|
|
disabled?: boolean;
|
|
onClick?: (ev: MouseEvent) => void;
|
|
}
|
|
|
|
export function createIconButton(opts: IconButtonOptions): HTMLButtonElement {
|
|
const size = opts.size ?? 32;
|
|
const btn = el("button", {
|
|
className: `ui-icon-btn ui-icon-btn--${opts.variant ?? "ghost"}`,
|
|
attrs: { type: "button", title: opts.title, "aria-label": opts.title },
|
|
});
|
|
setUiSize(btn, size);
|
|
btn.append(opts.icon ? icon(opts.icon, ICON_PX[size]) : (opts.glyph ?? ""));
|
|
if (opts.pressed !== undefined) btn.setAttribute("aria-pressed", String(opts.pressed));
|
|
if (opts.disabled) btn.disabled = true;
|
|
if (opts.onClick) btn.addEventListener("click", opts.onClick);
|
|
return btn;
|
|
}
|