- 보조 글: span · small 에 붙은 .ui-text = inline(줄 안 끊김) · p · div 는 한 줄 상자 그대로 - 단추: 위아래 여백 = 1em 기준 + 줄 높이 1 · 왼쪽 패널 양식(.ui-panel-form) 옛 규칙(글 12 · 줄 높이 normal)이 단을 덮지 않음 — B07 도면 단추 35 → 32 - 숨김: .ui-btn · .ui-icon-btn [hidden] = display none(공용 기본 꼴이 안 실린 곳에서도) - 파일 놓기 칸: 칸 어디든 누름 · 초점 Enter · Space = 파일 고르기(옛 B03 동작) · 단추 누름은 칸으로 올라가 한 번만 - 표: 격자 꼴(grid · 세로선 · 칸 테두리 · 몸 안 이름 칸 th) · 합친 칸(colSpan · rowSpan · head) · columns 비우면 머리 줄 없음 · 기본 꼴 그대로 - 부품 모음 견본: 글 줄 안 보조 글 · 격자표(수량 산출표 꼴) - 시험: test_552_5_part_fixes 새 · test_552_2_button 높이 식 기대 고침(1em · 줄 높이 1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
115 lines
5.0 KiB
TypeScript
115 lines
5.0 KiB
TypeScript
/* =============================================================================
|
|
* ui_template_button.ts
|
|
* 부품 「단추」 · 「아이콘 단추」 · 크기 여덟 단(552-2 · 부품 모음 `#/b01-account?view=ui-kit`)
|
|
*
|
|
* 꼴 = 단추: 주(filled) · 보조(ghost) · 고스트 글(pill) · 위험(danger) · 위험 고스트(danger-ghost) · 유리(glass · 그림 위) · 글 링크(link)
|
|
* 아이콘 단추: 고스트(ghost · 기본) · 선(outline) · 채움(filled) · 켜짐 = 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";
|
|
|
|
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;
|
|
}
|