24b44033cfda

smooth collapse toggle and enter animation on identity suggestion lists

time
2026-05-10 20:50 UTC · 5 months ago
machine
koh2
parent
bb3cc8fd3352
6 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/content/inject.ts+36 −4src/popup/styles/popup.css+44 −0src/popup/views/Add.tsx+27 −9
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach",4-  "version": "2.38.6",4+  "version": "2.38.7",55   "description": "Zero-knowledge password manager",66   "browser_specific_settings": {77     "gecko": {

view file

manifest.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach",4-  "version": "2.38.6",4+  "version": "2.38.7",55   "description": "Zero-knowledge password manager",66   "action": {77     "default_popup": "src/popup/index.html",

view file

package.json+1 −1
@@ -1,6 +1,6 @@11 {22   "name": "peach",3-  "version": "2.38.6",3+  "version": "2.38.7",44   "description": "Zero-knowledge password manager",55   "type": "module",66   "scripts": {

view file

src/content/inject.ts+36 −4
@@ -1088,8 +1088,25 @@10881088     background: transparent; border: none; border-bottom: 1px solid var(--peach-accent);10891089     outline: none; padding: 0; min-width: 22px;10901090   }1091-  .identity-suggest-list { display: flex; flex-direction: column; gap: 1px; margin-top: 3px; }1092-  .identity-filter-row { display: flex; gap: 4px; padding: 2px 0 4px; }1091+  @keyframes identity-suggest-in {1092+    from { opacity: 0; transform: translateY(-4px); }1093+    to   { opacity: 1; transform: none; }1094+  }1095+  .identity-suggest-list { display: flex; flex-direction: column; gap: 1px; margin-top: 3px;1096+    animation: identity-suggest-in 180ms cubic-bezier(0.16,1,0.3,1) both; }1097+  .identity-filter-row { display: flex; align-items: center; gap: 4px; padding: 2px 0 4px; }1098+  .identity-collapse-btn {1099+    margin-left: auto; background: transparent; border: none; cursor: pointer;1100+    color: var(--peach-text-muted); padding: 2px 4px; display: flex; align-items: center;1101+    line-height: 0; border-radius: 3px;1102+    transition: color 100ms ease, transform 220ms cubic-bezier(0.16,1,0.3,1);1103+  }1104+  .identity-collapse-btn:hover { color: var(--peach-text); }1105+  .identity-collapse-btn.is-collapsed { transform: rotate(-90deg); }1106+  .identity-rows { display: grid; grid-template-rows: 1fr;1107+    transition: grid-template-rows 220ms cubic-bezier(0.16,1,0.3,1); }1108+  .identity-rows.is-collapsed { grid-template-rows: 0fr; }1109+  .identity-rows-inner { overflow: hidden; min-height: 0; }10931110   .identity-filter-pill {10941111     padding: 2px 7px; border-radius: 999px;10951112     border: 1px solid var(--peach-input-border);@@ -1592,12 +1609,13 @@15921609   function attachIdentitySuggestions(g: HTMLDivElement, inp: HTMLInputElement, identities: SavedIdentity[]): void {15931610     if (!identities.length) return;15941611     let activeFilter: "all" | "username" | "email" = "all";1612+    let collapsed = false;15951613 15961614     const list = g.appendChild(document.createElement("div"));15971615     list.className = "identity-suggest-list";15981616     list.style.display = "none";15991617 1600-    // Filter pill row1618+    // Filter pill row + collapse toggle16011619     const filterRow = list.appendChild(document.createElement("div"));16021620     filterRow.className = "identity-filter-row";16031621     function makePill(label: string, value: "all" | "username" | "email") {@@ -1617,7 +1635,21 @@16171635     makePill("user", "username");16181636     makePill("email", "email");16191637 1620-    const rows = list.appendChild(document.createElement("div"));1638+    const collapseBtn = filterRow.appendChild(document.createElement("button"));1639+    collapseBtn.type = "button";1640+    collapseBtn.className = "identity-collapse-btn";1641+    collapseBtn.innerHTML = `<svg width="10" height="6" viewBox="0 0 10 6" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 1L5 5L9 1"/></svg>`;1642+    collapseBtn.addEventListener("mousedown", (e) => {1643+      e.preventDefault();1644+      collapsed = !collapsed;1645+      collapseBtn.classList.toggle("is-collapsed", collapsed);1646+      rowsWrapper.classList.toggle("is-collapsed", collapsed);1647+    });1648+1649+    const rowsWrapper = list.appendChild(document.createElement("div"));1650+    rowsWrapper.className = "identity-rows";1651+    const rows = rowsWrapper.appendChild(document.createElement("div"));1652+    rows.className = "identity-rows-inner";16211653 16221654     function render() {16231655       const q = inp.value.toLowerCase();

view file

src/popup/styles/popup.css+44 −0
@@ -5128,17 +5128,61 @@51285128 }51295129 51305130 /* Identity suggestions — shown below username/email inputs in new-entry form */5131+@keyframes peach-suggest-in {5132+  from { opacity: 0; transform: translateY(-4px); }5133+  to   { opacity: 1; transform: none; }5134+}5135+51315136 .peach-identity-suggestions {51325137   display: flex;51335138   flex-direction: column;51345139   gap: 1px;51355140   margin-top: 4px;5141+  animation: peach-suggest-in 180ms cubic-bezier(0.16, 1, 0.3, 1) both;51365142 }51375143 51385144 .peach-identity-filter-row {51395145   display: flex;5146+  align-items: center;51405147   gap: 4px;51415148   padding: 2px 0 5px;5149+}5150+5151+.peach-identity-collapse-btn {5152+  margin-left: auto;5153+  background: transparent;5154+  border: none;5155+  cursor: pointer;5156+  color: var(--peach-t3);5157+  padding: 2px 4px;5158+  display: flex;5159+  align-items: center;5160+  line-height: 0;5161+  border-radius: 3px;5162+  transition: color 100ms ease, transform 220ms cubic-bezier(0.16, 1, 0.3, 1);5163+}5164+5165+.peach-identity-collapse-btn:hover {5166+  color: var(--peach-t1);5167+}5168+5169+.peach-identity-collapse-btn.is-collapsed {5170+  transform: rotate(-90deg);5171+}5172+5173+.peach-identity-rows {5174+  display: grid;5175+  grid-template-rows: 1fr;5176+  transition: grid-template-rows 220ms cubic-bezier(0.16, 1, 0.3, 1);5177+}5178+5179+.peach-identity-rows.is-collapsed {5180+  grid-template-rows: 0fr;5181+}5182+5183+.peach-identity-rows-inner {5184+  overflow: hidden;5185+  min-height: 0;51425186 }51435187 51445188 .peach-identity-filter-pill {

view file

src/popup/views/Add.tsx+27 −9
@@ -631,22 +631,40 @@631631 }632632 633633 function IdentitySuggestList(props: IdentitySuggestListProps) {634+  const [collapsed, setCollapsed] = createSignal(false);635+  const ChevronIcon = () => (636+    <svg width="10" height="6" viewBox="0 0 10 6" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round">637+      <path d="M1 1L5 5L9 1" />638+    </svg>639+  );634640   return (635641     <div class="peach-identity-suggestions">636642       <div class="peach-identity-filter-row">637643         <button class={`peach-identity-filter-pill ${props.filter === "all" ? "is-on" : ""}`} type="button" onMouseDown={() => props.onFilter("all")}>all</button>638644         <button class={`peach-identity-filter-pill ${props.filter === "username" ? "is-on" : ""}`} type="button" onMouseDown={() => props.onFilter("username")}>user</button>639645         <button class={`peach-identity-filter-pill ${props.filter === "email" ? "is-on" : ""}`} type="button" onMouseDown={() => props.onFilter("email")}>email</button>646+        <button647+          class={`peach-identity-collapse-btn${collapsed() ? " is-collapsed" : ""}`}648+          type="button"649+          onMouseDown={(e) => { e.preventDefault(); setCollapsed(c => !c); }}650+          aria-label={collapsed() ? "Show suggestions" : "Hide suggestions"}651+        >652+          <ChevronIcon />653+        </button>640654       </div>641-      <For each={props.suggestions}>642-        {(s) => (643-          <button class="peach-identity-suggestion" type="button" onMouseDown={() => props.onPick(s.value)}>644-            <span class="peach-identity-suggestion-value">{s.value}</span>645-            <span class={`peach-identity-suggestion-type peach-identity-suggestion-type--${s.type}`}>{s.type === "email" ? "@" : "u"}</span>646-            <span class="peach-identity-suggestion-meta">{s.count}×</span>647-          </button>648-        )}649-      </For>655+      <div class={`peach-identity-rows${collapsed() ? " is-collapsed" : ""}`}>656+        <div class="peach-identity-rows-inner">657+          <For each={props.suggestions}>658+            {(s) => (659+              <button class="peach-identity-suggestion" type="button" onMouseDown={() => props.onPick(s.value)}>660+                <span class="peach-identity-suggestion-value">{s.value}</span>661+                <span class={`peach-identity-suggestion-type peach-identity-suggestion-type--${s.type}`}>{s.type === "email" ? "@" : "u"}</span>662+                <span class="peach-identity-suggestion-meta">{s.count}×</span>663+              </button>664+            )}665+          </For>666+        </div>667+      </div>650668     </div>651669   );652670 }

view file