refactor(form): replace inline error text with tooltip icon
Instead of rendering error messages as `<small class="form-error">` below inputs, errors now display as a circle-x icon inside the input area. Hovering the icon shows the error text in a danger-styled tooltip. - Wrap children in `.form-field-control` when error is present - Use existing tooltip + icon components for the error indicator - Add CSS for positioning, padding-right offset, and danger color overrides - Update tests to match new structure - Add pre-commit tmux server check instructions to AGENTS.md
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
(ns ui.form
|
||||
(:require [clojure.string :as str]))
|
||||
(:require [clojure.string :as str]
|
||||
[ui.icon :as icon]
|
||||
[ui.tooltip :as tooltip]))
|
||||
|
||||
;; In squint, keywords are strings — name is identity
|
||||
#?(:squint (defn- kw-name [s] s)
|
||||
@@ -19,6 +21,12 @@
|
||||
[opts]
|
||||
(str/join " " (form-field-class-list opts)))
|
||||
|
||||
(defn- error-icon
|
||||
"Render a circle-x icon wrapped in a tooltip showing the error text."
|
||||
[error]
|
||||
(tooltip/tooltip {:text error :class "form-error-icon"}
|
||||
(icon/icon {:icon-name :circle-x :size :sm})))
|
||||
|
||||
(defn form-field
|
||||
"Render a form field wrapper with label, hint, and error support.
|
||||
|
||||
@@ -37,9 +45,11 @@
|
||||
(cond-> (if label
|
||||
[[:label {:class "form-label"} label]]
|
||||
[])
|
||||
true (into children)
|
||||
hint (conj [:small {:class "form-hint"} hint])
|
||||
error (conj [:small {:class "form-error"} error]))))
|
||||
true (into (if error
|
||||
[(into [:div {:class "form-field-control"}]
|
||||
(conj (vec children) (error-icon error)))]
|
||||
children))
|
||||
hint (conj [:small {:class "form-hint"} hint]))))
|
||||
|
||||
:cljs
|
||||
(let [cls (form-field-class-list {:error error})
|
||||
@@ -49,9 +59,11 @@
|
||||
(cond-> (if label
|
||||
[[:label {:class ["form-label"]} label]]
|
||||
[])
|
||||
true (into children)
|
||||
hint (conj [:small {:class ["form-hint"]} hint])
|
||||
error (conj [:small {:class ["form-error"]} error]))))
|
||||
true (into (if error
|
||||
[(into [:div {:class ["form-field-control"]}]
|
||||
(conj (vec children) (error-icon error)))]
|
||||
children))
|
||||
hint (conj [:small {:class ["form-hint"]} hint]))))
|
||||
|
||||
:clj
|
||||
(let [classes (cond-> (form-field-classes {:error error})
|
||||
@@ -61,9 +73,11 @@
|
||||
(cond-> (if label
|
||||
[[:label {:class "form-label"} label]]
|
||||
[])
|
||||
true (into children)
|
||||
hint (conj [:small {:class "form-hint"} hint])
|
||||
error (conj [:small {:class "form-error"} error]))))))
|
||||
true (into (if error
|
||||
[(into [:div {:class "form-field-control"}]
|
||||
(conj (vec children) (error-icon error)))]
|
||||
children))
|
||||
hint (conj [:small {:class "form-hint"} hint]))))))
|
||||
|
||||
;; ── Text input ──────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -240,16 +240,63 @@
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* ── Hint & Error text ─────────────────────────────────────────── */
|
||||
/* ── Hint text ─────────────────────────────────────────────────── */
|
||||
|
||||
.form-hint {
|
||||
font-size: var(--font-xs);
|
||||
color: var(--fg-2);
|
||||
}
|
||||
|
||||
.form-error {
|
||||
font-size: var(--font-xs);
|
||||
/* ── Error icon with tooltip ───────────────────────────────────── */
|
||||
|
||||
.form-field-control {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.form-field-control > .form-input,
|
||||
.form-field-control > .form-textarea,
|
||||
.form-field-control > .form-select {
|
||||
padding-right: var(--size-8);
|
||||
}
|
||||
|
||||
.tooltip.form-error-icon {
|
||||
position: absolute;
|
||||
right: var(--size-2);
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--danger);
|
||||
cursor: pointer;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.tooltip.form-error-icon::after {
|
||||
background: color-mix(in srgb, var(--danger) 18%, var(--bg-1));
|
||||
color: var(--fg-0);
|
||||
border: 1px solid color-mix(in srgb, var(--danger) 25%, transparent);
|
||||
white-space: normal;
|
||||
min-width: 12rem;
|
||||
max-width: 20rem;
|
||||
line-height: 1.4;
|
||||
left: auto;
|
||||
right: 0;
|
||||
transform: translateY(4px);
|
||||
}
|
||||
|
||||
.tooltip.form-error-icon:hover::after {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.tooltip.form-error-icon::before {
|
||||
border-top-color: color-mix(in srgb, var(--danger) 18%, var(--bg-1));
|
||||
left: auto;
|
||||
right: var(--size-1);
|
||||
transform: translateY(4px);
|
||||
}
|
||||
|
||||
.tooltip.form-error-icon:hover::before {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* ── Fieldset & Legend ──────────────────────────────────────────── */
|
||||
|
||||
Reference in New Issue
Block a user