260717_0
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import "./ui_template_workflow_layout.css";
|
||||
import { t } from "./ui_template_locale";
|
||||
import { createWorkflowOverlays } from "./ui_template_overlay";
|
||||
|
||||
export interface WorkflowStage {
|
||||
stage_no: number;
|
||||
@@ -11,20 +12,18 @@ export interface WorkflowLayoutOptions {
|
||||
title: string;
|
||||
steps: string[];
|
||||
activeStep: number;
|
||||
leftPanel: HTMLElement;
|
||||
leftPanel?: HTMLElement;
|
||||
mainContent: HTMLElement;
|
||||
stages?: WorkflowStage[];
|
||||
currentStage?: number;
|
||||
routes?: readonly string[];
|
||||
onStepClick?: (stepIndex: number, route: string) => void;
|
||||
onMenuToggle?: (isOpen: boolean) => void;
|
||||
onHeaderToggle?: (isVisible: boolean) => void;
|
||||
}
|
||||
|
||||
export interface WorkflowLayoutHandle {
|
||||
root: HTMLElement;
|
||||
setMenuOpen: (isOpen: boolean) => void;
|
||||
setHeaderVisible: (isVisible: boolean) => void;
|
||||
setOptionsOpen: (isOpen: boolean) => void;
|
||||
setProgressOpen: (isOpen: boolean) => void;
|
||||
}
|
||||
|
||||
export interface StepBarOptions {
|
||||
@@ -32,9 +31,13 @@ export interface StepBarOptions {
|
||||
currentStage?: number;
|
||||
routes?: readonly string[];
|
||||
compact?: boolean;
|
||||
orientation?: "horizontal" | "vertical";
|
||||
icons?: readonly string[];
|
||||
onStepClick?: (stepIndex: number, route: string) => void;
|
||||
}
|
||||
|
||||
export const WORKFLOW_STEP_ICONS = ["📁", "🗺️", "🛣️", "📐", "🏗️", "∑", "📄"] as const;
|
||||
|
||||
export function createStepBar(
|
||||
steps: readonly string[],
|
||||
activeStep: number,
|
||||
@@ -43,18 +46,31 @@ export function createStepBar(
|
||||
const bar = document.createElement("div");
|
||||
bar.className = "ui-workflow-layout__steps";
|
||||
if (options?.compact) bar.classList.add("is-compact");
|
||||
if (options?.orientation === "vertical") bar.classList.add("is-vertical");
|
||||
steps.forEach((step, index) => {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.className = "ui-workflow-layout__step";
|
||||
if (index === activeStep) button.classList.add("is-active");
|
||||
button.textContent = step;
|
||||
const iconText = options?.icons?.[index];
|
||||
if (iconText) {
|
||||
const icon = document.createElement("span");
|
||||
icon.className = "ui-workflow-layout__step-icon";
|
||||
icon.setAttribute("aria-hidden", "true");
|
||||
icon.textContent = iconText;
|
||||
const label = document.createElement("span");
|
||||
label.className = "ui-workflow-layout__step-label";
|
||||
label.textContent = step;
|
||||
button.append(icon, label);
|
||||
} else {
|
||||
button.textContent = step;
|
||||
}
|
||||
|
||||
const stage =
|
||||
options?.stages?.find((item) => item.stage_no === index) ?? options?.stages?.[index];
|
||||
const hasStages = Boolean(options?.stages?.length);
|
||||
const isEnabled =
|
||||
!hasStages || stage?.state !== "NOT_STARTED" || stage?.stage_no === options?.currentStage;
|
||||
const isBlockedState = stage?.state === "NOT_STARTED" || stage?.state === "STALE";
|
||||
const isEnabled = !hasStages || !isBlockedState || stage?.stage_no === options?.currentStage;
|
||||
button.classList.toggle("is-enabled", isEnabled);
|
||||
button.disabled = !isEnabled;
|
||||
|
||||
@@ -90,95 +106,33 @@ export function createWorkflowLayout(options: WorkflowLayoutOptions): WorkflowLa
|
||||
const root = document.createElement("div");
|
||||
root.className = "ui-workflow-layout";
|
||||
|
||||
const header = document.createElement("header");
|
||||
header.className = "ui-workflow-layout__header";
|
||||
|
||||
const menuButton = document.createElement("button");
|
||||
menuButton.type = "button";
|
||||
menuButton.className = "ui-workflow-layout__menu-button";
|
||||
menuButton.setAttribute("aria-label", "Toggle settings panel");
|
||||
menuButton.innerHTML = "🛠️ <span>Option ▾</span>";
|
||||
|
||||
const titleWrap = document.createElement("div");
|
||||
titleWrap.className = "ui-workflow-layout__title-wrap";
|
||||
const title = document.createElement("h2");
|
||||
title.className = "ui-workflow-layout__title";
|
||||
title.textContent = options.title;
|
||||
titleWrap.append(
|
||||
title,
|
||||
createStepBar(options.steps, options.activeStep, {
|
||||
stages: options.stages,
|
||||
currentStage: options.currentStage,
|
||||
routes: options.routes,
|
||||
onStepClick: options.onStepClick,
|
||||
}),
|
||||
);
|
||||
|
||||
const headerHideButton = document.createElement("button");
|
||||
headerHideButton.type = "button";
|
||||
headerHideButton.className = "ui-workflow-layout__header-hide";
|
||||
headerHideButton.setAttribute("aria-label", "Hide header");
|
||||
headerHideButton.textContent = "▲";
|
||||
|
||||
header.append(titleWrap, headerHideButton);
|
||||
|
||||
// 헤더 숨김 시 표시되는 복원 화살표
|
||||
const headerRevealButton = document.createElement("button");
|
||||
headerRevealButton.type = "button";
|
||||
headerRevealButton.className = "ui-workflow-layout__header-reveal";
|
||||
headerRevealButton.setAttribute("aria-label", "Show header");
|
||||
headerRevealButton.textContent = "▼";
|
||||
|
||||
const body = document.createElement("div");
|
||||
body.className = "ui-workflow-layout__body";
|
||||
|
||||
const dropdownPanel = document.createElement("div");
|
||||
dropdownPanel.className = "ui-workflow-layout__dropdown-panel";
|
||||
dropdownPanel.append(options.leftPanel);
|
||||
|
||||
const main = document.createElement("main");
|
||||
main.className = "ui-workflow-layout__main";
|
||||
main.style.position = "relative";
|
||||
main.append(menuButton, options.mainContent);
|
||||
main.append(options.mainContent);
|
||||
body.append(main);
|
||||
|
||||
body.append(dropdownPanel, main);
|
||||
root.append(header, headerRevealButton, body);
|
||||
|
||||
function setMenuOpen(isOpen: boolean): void {
|
||||
root.classList.toggle("is-menu-open", isOpen);
|
||||
menuButton.setAttribute("aria-expanded", String(isOpen));
|
||||
const icon = menuButton.querySelector("span");
|
||||
if (icon) {
|
||||
icon.textContent = isOpen ? "Option ▴" : "Option ▾";
|
||||
}
|
||||
options.onMenuToggle?.(isOpen);
|
||||
}
|
||||
|
||||
function setHeaderVisible(isVisible: boolean): void {
|
||||
root.classList.toggle("is-header-hidden", !isVisible);
|
||||
headerHideButton.setAttribute("aria-expanded", String(isVisible));
|
||||
options.onHeaderToggle?.(isVisible);
|
||||
}
|
||||
|
||||
menuButton.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
setMenuOpen(!root.classList.contains("is-menu-open"));
|
||||
const progressContent = createStepBar(options.steps, options.activeStep, {
|
||||
stages: options.stages,
|
||||
currentStage: options.currentStage,
|
||||
routes: options.routes,
|
||||
orientation: "vertical",
|
||||
icons: WORKFLOW_STEP_ICONS,
|
||||
onStepClick: options.onStepClick,
|
||||
});
|
||||
const overlays = createWorkflowOverlays({
|
||||
title: options.title,
|
||||
optionsContent: options.leftPanel,
|
||||
progressContent,
|
||||
onOptionsOpenChange: (isOpen) => root.classList.toggle("is-options-open", isOpen),
|
||||
});
|
||||
|
||||
document.addEventListener("click", (e) => {
|
||||
if (root.classList.contains("is-menu-open")) {
|
||||
const target = e.target as HTMLElement;
|
||||
if (!dropdownPanel.contains(target) && !menuButton.contains(target)) {
|
||||
setMenuOpen(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
headerHideButton.addEventListener("click", () => setHeaderVisible(false));
|
||||
headerRevealButton.addEventListener("click", () => setHeaderVisible(true));
|
||||
|
||||
setMenuOpen(false);
|
||||
setHeaderVisible(true);
|
||||
|
||||
return { root, setMenuOpen, setHeaderVisible };
|
||||
root.append(body, overlays.root);
|
||||
return {
|
||||
root,
|
||||
setOptionsOpen: overlays.setTitleOpen,
|
||||
setProgressOpen: overlays.setProgressOpen,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user