feat: polish send button — hidden until content, icon-circle, pop-in animation
- index.html: btnSend hidden by default (display:none), icon-only (upward arrow SVG, no text label), title attribute for accessibility - style.css: new send-btn design — 34px circle, blue fill (#7cb9ff), subtle glow box-shadow, scale() hover/active for tactile feel, .send-btn.visible with @keyframes send-pop-in (scale+opacity spring using cubic-bezier(.34,1.56,.64,1) for a satisfying pop). Mobile override updated to preserve circle dimensions. - ui.js: updateSendBtn() — shows button with pop-in animation when textarea has content OR files are attached and agent is not busy; hides instantly when content is cleared. Hooked into setBusy() and renderTray() so button state tracks all content sources correctly. - boot.js: input event listener calls updateSendBtn() on every keystroke. - messages.js: autoResize() calls updateSendBtn() so button disappears immediately after send clears the textarea. - tests/test_sprint21.py: 33 tests covering HTML structure, CSS design (circle shape, colors, animations, keyframes), JS logic (updateSendBtn, setBusy, renderTray, autoResize integration), and regressions (363 total, all pass).
This commit is contained in:
19
static/ui.js
19
static/ui.js
@@ -187,9 +187,25 @@ function setStatus(t){
|
||||
if(dismiss)dismiss.style.display=(!transient && !S.busy)?'inline':'none';
|
||||
}
|
||||
}
|
||||
function updateSendBtn(){
|
||||
const btn=$('btnSend');
|
||||
if(!btn) return;
|
||||
const hasContent=$('msg').value.trim().length>0||S.pendingFiles.length>0;
|
||||
const shouldShow=hasContent&&!S.busy;
|
||||
if(shouldShow&&btn.style.display==='none'){
|
||||
btn.style.display='';
|
||||
// Remove then re-add class to retrigger animation each time
|
||||
btn.classList.remove('visible');
|
||||
requestAnimationFrame(()=>btn.classList.add('visible'));
|
||||
} else if(!shouldShow&&btn.style.display!=='none'){
|
||||
btn.style.display='none';
|
||||
btn.classList.remove('visible');
|
||||
}
|
||||
}
|
||||
function setBusy(v){
|
||||
S.busy=v;
|
||||
$('btnSend').disabled=v;
|
||||
updateSendBtn();
|
||||
const dots=$('activityDots');
|
||||
if(dots) dots.style.display=v?'flex':'none';
|
||||
if(!v){
|
||||
@@ -920,8 +936,9 @@ async function promptNewFolder(){
|
||||
|
||||
function renderTray(){
|
||||
const tray=$('attachTray');tray.innerHTML='';
|
||||
if(!S.pendingFiles.length){tray.classList.remove('has-files');return;}
|
||||
if(!S.pendingFiles.length){tray.classList.remove('has-files');updateSendBtn();return;}
|
||||
tray.classList.add('has-files');
|
||||
updateSendBtn();
|
||||
S.pendingFiles.forEach((f,i)=>{
|
||||
const chip=document.createElement('div');chip.className='attach-chip';
|
||||
chip.innerHTML=`📎 ${esc(f.name)} <button title="Remove">✕</button>`;
|
||||
|
||||
Reference in New Issue
Block a user