From a7f4b59c36bc18c668cd769a370b64d26a1bee7f Mon Sep 17 00:00:00 2001 From: Ajay Ghanwat Date: Sat, 16 Aug 2025 12:37:38 +0530 Subject: [PATCH] Fixxed bugs From form and Fields --- package-lock.json | 14 ++ package.json | 1 + src/components/Field.astro | 12 +- src/components/MultiSelect.astro | 163 +++++++++++-------- src/pages/forms2.astro | 264 ++++++++++++++++++++----------- 5 files changed, 285 insertions(+), 169 deletions(-) diff --git a/package-lock.json b/package-lock.json index 036095a..1204bc5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@iconify/json": "^2.2.373", "@iconify/utils": "^3.0.1", "@tailwindcss/forms": "^0.5.10", + "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "tailwind-merge": "^3.3.1" }, @@ -2686,6 +2687,19 @@ "node": ">=8" } }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, "node_modules/cli-boxes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", diff --git a/package.json b/package.json index 409a968..01b5151 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "@iconify/json": "^2.2.373", "@iconify/utils": "^3.0.1", "@tailwindcss/forms": "^0.5.10", + "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "tailwind-merge": "^3.3.1" } diff --git a/src/components/Field.astro b/src/components/Field.astro index 462dc34..47ce164 100644 --- a/src/components/Field.astro +++ b/src/components/Field.astro @@ -89,7 +89,7 @@ const helpCls = "mt-2 text-sm text-gray-500 dark:text-neutral-500"; const errCls = "text-sm text-red-600 mt-2"; const groupCls = "relative flex items-stretch"; const addonBase = - "px-4 inline-flex items-center min-w-fit rounded-s-md border border-e-0 border-gray-200 bg-gray-50 text-sm text-gray-500 dark:bg-neutral-700 dark:border-neutral-700 dark:text-neutral-400"; + "px-4 inline-flex items-center min-w-fit rounded-md border border-gray-200 bg-gray-50 text-sm text-gray-500 dark:bg-neutral-700 dark:border-neutral-700 dark:text-neutral-400"; const controlWrap = "relative"; --- @@ -144,8 +144,6 @@ const controlWrap = "relative"; }), p.prefixText && "rounded-l-none", p.suffixText && "rounded-r-none", - p.iconLeft && "rounded-l-none", - p.iconRight && "rounded-r-none", p.error && "border-danger focus-visible:ring-danger/40", )} @@ -217,8 +215,6 @@ const controlWrap = "relative"; "block w-full border border-gray-200 shadow-sm rounded-lg text-sm focus:z-10 focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-900 dark:border-neutral-700 dark:text-neutral-400 file:bg-gray-50 file:border-0 file:me-4 file:py-3 file:px-4 dark:file:bg-neutral-700 dark:file:text-neutral-400", p.prefixText && "rounded-l-none", p.suffixText && "rounded-r-none", - p.iconLeft && "rounded-l-none", - p.iconRight && "rounded-r-none", p.error && "border-danger focus-visible:ring-danger/40", )} @@ -281,8 +277,6 @@ const controlWrap = "relative"; : p.size === "lg" ? "text-base p-4" : "text-sm p-3", - p.iconLeft && "rounded-l-none", - p.iconRight && "rounded-r-none", p.error && "border-danger focus-visible:ring-danger/40", )} data-initial={typeof p.value === "string" ? p.value : ""} @@ -309,9 +303,7 @@ const controlWrap = "relative"; } class={cn( selectBase({ size: p.size ?? "md" }), - "appearance-none bg-[length:16px_16px] pr-9", - p.iconLeft && "rounded-l-none", - p.iconRight && "rounded-r-none", + "appearance-none bg-none pr-9", p.error && "border-danger focus-visible:ring-danger/40", )} > diff --git a/src/components/MultiSelect.astro b/src/components/MultiSelect.astro index 330edae..d409e85 100644 --- a/src/components/MultiSelect.astro +++ b/src/components/MultiSelect.astro @@ -18,12 +18,13 @@ interface Props extends HTMLAttributes<"div"> { required?: boolean; disabled?: boolean; options: Option[]; - value?: string[]; // initial selected values - placeholder?: string; // shown when empty - searchable?: boolean; // show search box in dropdown - clearable?: boolean; // show clear (x) button - maxSelections?: number; // optional selection cap + value?: string[]; + placeholder?: string; + searchable?: boolean; + clearable?: boolean; + maxSelections?: number; size?: "sm" | "md" | "lg"; + display?: "text" | "chips"; class?: string; } @@ -32,8 +33,9 @@ const p = Astro.props as Props; const fieldId = p.id ?? p.name; const descId = p.description ? `${fieldId}-desc` : undefined; const errId = `${fieldId}-err`; + const selected = new Set(p.value ?? []); -const summary = selected.size +const summarySSR = selected.size ? Array.from(selected) .map((v) => p.options.find((o) => o.value === v)?.label ?? v) .join(", ") @@ -47,11 +49,15 @@ const sizeCls = : "h-10 px-3 text-sm"; const controlCls = - "py-2.5 sm:py-3 px-4 block w-full border-gray-200 rounded-lg sm:text-sm focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-900 dark:border-neutral-700 dark:text-neutral-400 dark:placeholder-neutral-500 dark:focus:ring-neutral-600"; + "py-2.5 sm:py-3 px-4 block w-full border-gray-200 rounded-lg sm:text-sm " + + "focus:border-blue-500 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none " + + "dark:bg-neutral-900 dark:border-neutral-700 dark:text-neutral-400 dark:placeholder-neutral-500 dark:focus:ring-neutral-600"; const chipBase = "inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs bg-muted text-foreground"; + const containerId = `ms-${Math.random().toString(36).slice(2)}`; +const displayMode = p.display ?? "text"; ---
@@ -67,9 +73,6 @@ const containerId = `ms-${Math.random().toString(36).slice(2)}`; }
- { - /** Visible control is a readonly input so validation styles apply here */ - } - {/** Chevron / Clear */}
{ - p.clearable !== false && selected.size > 0 && !p.disabled && ( + p.clearable !== false && !p.disabled && (
- { - /** Selected chips (optional; comment out if you prefer only summary text) */ - } - { - selected.size > 0 && ( -
- {Array.from(selected) - .slice(0, 3) - .map((v) => { - const lab = - p.options.find((o) => o.value === v)?.label ?? - v; - return {lab}; - })} - {selected.size > 3 && ( - +{selected.size - 3} - )} -
- ) - } +
+
- {/** Dropdown */}
@@ -236,6 +239,7 @@ const containerId = `ms-${Math.random().toString(36).slice(2)}`; fieldId, name: p.name, maxSelections: p.maxSelections ?? null, + displayMode, }} > const root = document.getElementById(containerId); @@ -248,39 +252,72 @@ const containerId = `ms-${Math.random().toString(36).slice(2)}`; const btnCan = root.querySelector("[data-ms-cancel]"); const btnApp = root.querySelector("[data-ms-apply]"); const sch = root.querySelector("[data-ms-search]"); + const chips = root.querySelector("[data-ms-chips]"); + const checks = Array.from( root.querySelectorAll( `input[type="checkbox"][name="${CSS.escape(name)}"]`, ), ); let open = false; + const origPlaceholder = input.getAttribute("placeholder") || "Select..."; - function setExpanded( - v, - { focusSearch = false, refocusInput = false } = {}, - ) { + function setExpanded(v, { focusSearch = false } = {}) { open = v; input.setAttribute("aria-expanded", String(open)); panel.classList.toggle("hidden", !open); if (open && focusSearch) sch?.focus(); - if (!open && refocusInput) input.focus(); + } + + function makeChip(label) { + const span = document.createElement("span"); + span.className = + "inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs bg-muted text-foreground"; + span.textContent = label; + return span; + } + + function toggleClear(show) { + if (!btnClr) return; + btnClr.classList.toggle("hidden", !show); } function updateSummary() { const selected = checks.filter((c) => c.checked); - const labels = selected - .slice(0, 3) - .map( - (c) => - c - .closest("[data-ms-item]") - ?.querySelector("span") - ?.textContent?.trim() || c.value, - ); - const extra = Math.max(0, selected.length - 3); - input.value = selected.length - ? labels.join(", ") + (extra ? ` +${extra}` : "") - : ""; + const labels = selected.map( + (c) => + c + .closest("[data-ms-item]") + ?.querySelector("span") + ?.textContent?.trim() || c.value, + ); + + if (displayMode === "text") { + const preview = labels.slice(0, 3).join(", "); + const extra = Math.max(0, labels.length - 3); + input.value = labels.length + ? preview + (extra ? ` +${extra}` : "") + : ""; + input.placeholder = labels.length ? "" : origPlaceholder; + if (chips) { + chips.innerHTML = ""; + chips.classList.add("hidden"); + } + } else { + input.value = ""; + input.placeholder = labels.length ? "" : origPlaceholder; + if (!chips) return; + chips.innerHTML = ""; + const max = 3; + labels + .slice(0, max) + .forEach((lab) => chips.appendChild(makeChip(lab))); + const extra = labels.length - max; + if (extra > 0) chips.appendChild(makeChip(`+${extra}`)); + chips.classList.toggle("hidden", labels.length === 0); + } + + toggleClear(labels.length > 0); } function enforceMax() { @@ -308,7 +345,6 @@ const containerId = `ms-${Math.random().toString(36).slice(2)}`; updateSummary(); } - // Open/close btnTgl?.addEventListener("click", () => setExpanded(!open, { focusSearch: !open }), ); @@ -322,21 +358,20 @@ const containerId = `ms-${Math.random().toString(36).slice(2)}`; } }); - // Actions btnClr?.addEventListener("click", (e) => { e.stopPropagation(); clearAll(); }); btnAll?.addEventListener("click", selectAll); - btnCan?.addEventListener("click", () => setExpanded(false)); // no refocus - btnApp?.addEventListener("click", () => setExpanded(false)); // no refocus + btnCan?.addEventListener("click", () => setExpanded(false)); + btnApp?.addEventListener("click", () => setExpanded(false)); + list.addEventListener("change", () => { enforceMax(); updateSummary(); input.dispatchEvent(new Event("change", { bubbles: true })); }); - // Search filter sch?.addEventListener("input", () => { const q = sch.value.trim().toLowerCase(); root.querySelectorAll("[data-ms-item]").forEach((li) => { @@ -345,12 +380,10 @@ const containerId = `ms-${Math.random().toString(36).slice(2)}`; }); }); - // Close on outside click without refocusing input document.addEventListener("click", (e) => { - if (!root.contains(e.target)) setExpanded(false); // note: no refocusInput + if (!root.contains(e.target)) setExpanded(false); }); - // Init enforceMax(); updateSummary(); diff --git a/src/pages/forms2.astro b/src/pages/forms2.astro index f5d738d..02b8e95 100644 --- a/src/pages/forms2.astro +++ b/src/pages/forms2.astro @@ -1,105 +1,155 @@ --- -import Form from "../components/Form.astro"; -import Field from "../components/Field.astro"; +import { Form, Field, MultiSelect } from "@wrnexus/astro"; import Base from "../layouts/Base.astro"; -import MultiSelect from "../components/MultiSelect.astro"; ---
+ + + + + + + - - - + + + + + + + + + + @@ -108,46 +158,72 @@ import MultiSelect from "../components/MultiSelect.astro";