feat: Implement validation error message display for required fields
- Added a new CSS class for displaying validation error messages below required input fields without affecting layout. - Enhanced the `useDialogAutoValidation` hook to insert error messages dynamically when required fields are empty. - Implemented logic to remove error messages when the input is cleared, improving user feedback during form validation. Made-with: Cursor
This commit is contained in:
parent
35dfe5bd79
commit
cfd49020a0
|
|
@ -441,4 +441,21 @@ select {
|
|||
border-color: hsl(var(--destructive)) !important;
|
||||
}
|
||||
|
||||
/* 필수 입력 경고 문구 (입력 필드 아래, 레이아웃 영향 없음) */
|
||||
.validation-error-msg-wrapper {
|
||||
height: 0;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.validation-error-msg-wrapper > p {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 0;
|
||||
font-size: 11px;
|
||||
color: hsl(var(--destructive));
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* ===== End of Global Styles ===== */
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ const DRAG_THRESHOLD = 5;
|
|||
const SETTLE_MS = 70;
|
||||
const DROP_SETTLE_MS = 180;
|
||||
const BAR_PAD_X = 8;
|
||||
const ACTIVE_TAB_BORDER_OPACITY = 0.3;
|
||||
|
||||
interface DragState {
|
||||
tabId: string;
|
||||
|
|
@ -502,7 +501,7 @@ export function TabBar() {
|
|||
touchAction: "none",
|
||||
...animStyle,
|
||||
...(hiddenByGhost ? { opacity: 0 } : {}),
|
||||
...(isActive ? { borderColor: `rgba(0,0,0,${ACTIVE_TAB_BORDER_OPACITY})` } : {}),
|
||||
...(isActive ? { boxShadow: "0 -1px 28px rgba(0,0,0,0.9)" } : {}),
|
||||
}}
|
||||
title={tab.title}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { toast } from "sonner";
|
|||
|
||||
const HIGHLIGHT_ATTR = "data-validation-highlight";
|
||||
const ERROR_ATTR = "data-validation-error";
|
||||
const MSG_WRAPPER_CLASS = "validation-error-msg-wrapper";
|
||||
|
||||
type TargetEl = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement | HTMLButtonElement;
|
||||
|
||||
|
|
@ -121,11 +122,32 @@ export function useDialogAutoValidation(contentEl: HTMLElement | null) {
|
|||
function markError(input: TargetEl) {
|
||||
input.setAttribute(ERROR_ATTR, "true");
|
||||
errorFields.add(input);
|
||||
showErrorMsg(input);
|
||||
}
|
||||
|
||||
function clearError(input: TargetEl) {
|
||||
input.removeAttribute(ERROR_ATTR);
|
||||
errorFields.delete(input);
|
||||
removeErrorMsg(input);
|
||||
}
|
||||
|
||||
// 빈 필수 필드 아래에 경고 문구 삽입 (레이아웃 영향 없는 zero-height wrapper)
|
||||
function showErrorMsg(input: TargetEl) {
|
||||
if (input.parentElement?.querySelector(`.${MSG_WRAPPER_CLASS}`)) return;
|
||||
|
||||
const wrapper = document.createElement("div");
|
||||
wrapper.className = MSG_WRAPPER_CLASS;
|
||||
|
||||
const msg = document.createElement("p");
|
||||
msg.textContent = "필수 입력 항목입니다";
|
||||
wrapper.appendChild(msg);
|
||||
|
||||
input.insertAdjacentElement("afterend", wrapper);
|
||||
}
|
||||
|
||||
function removeErrorMsg(input: TargetEl) {
|
||||
const wrapper = input.parentElement?.querySelector(`.${MSG_WRAPPER_CLASS}`);
|
||||
if (wrapper) wrapper.remove();
|
||||
}
|
||||
|
||||
function highlightField(input: TargetEl) {
|
||||
|
|
@ -200,6 +222,7 @@ export function useDialogAutoValidation(contentEl: HTMLElement | null) {
|
|||
|
||||
el.querySelectorAll(`[${HIGHLIGHT_ATTR}]`).forEach((node) => node.removeAttribute(HIGHLIGHT_ATTR));
|
||||
el.querySelectorAll(`[${ERROR_ATTR}]`).forEach((node) => node.removeAttribute(ERROR_ATTR));
|
||||
el.querySelectorAll(`.${MSG_WRAPPER_CLASS}`).forEach((node) => node.remove());
|
||||
};
|
||||
}, [mode, contentEl]);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue