Plugin Studio: Rudder is a small mic circle in its corner, with no label
The corner widget's bubble was a pill that read "Rudder · voice to text" and widened to 300 px to show the words while recording. It is now a 44 px circle with the mic (a chevron while its panel is open): the label stays its accessible name and tooltip, and the words show in the panel. Recording still shows on the collapsed circle: a red ring that pulses, a red dot, and the mic lit while speech is heard.
This commit is contained in:
parent
3ca828421f
commit
414a1929ae
1 changed files with 9 additions and 21 deletions
|
|
@ -450,17 +450,14 @@ const WIDGET_CSS = `
|
|||
[data-studio-widgets^="top"] [data-widget]{flex-direction:column-reverse}
|
||||
[data-widget-panel]{display:none;position:relative;width:360px;max-width:calc(100vw - 32px);height:min(480px,calc(100vh - 110px));height:min(480px,calc(100dvh - 110px - env(safe-area-inset-top) - env(safe-area-inset-bottom)));border-radius:16px;overflow:hidden;background:#0b0f14;box-shadow:0 12px 40px rgba(0,0,0,.35)}
|
||||
[data-widget-panel][data-open]{display:block}
|
||||
[data-widget-bubble]{position:relative;display:inline-flex;align-items:center;gap:8px;height:48px;max-width:min(360px,calc(100vw - 32px));padding:0 18px 0 14px;border:1px solid #334155;border-radius:999px;background:#0f172a;color:#e5e7eb;font:600 14px/1 system-ui,-apple-system,"Segoe UI",sans-serif;cursor:pointer;box-shadow:0 4px 16px rgba(0,0,0,.3)}
|
||||
[data-widget-bubble]{position:relative;display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;padding:0;border:1px solid #334155;border-radius:999px;background:#0f172a;color:#e5e7eb;font:600 14px/1 system-ui,-apple-system,"Segoe UI",sans-serif;cursor:pointer;box-shadow:0 4px 16px rgba(0,0,0,.3)}
|
||||
[data-widget-bubble]:focus-visible{outline:3px solid #7dd3fc;outline-offset:3px}
|
||||
[data-wb-icon]{display:grid;place-items:center;flex:none;color:#38bdf8}
|
||||
[data-wb-icon] svg,[data-wb-chev] svg{width:20px;height:20px;display:block}
|
||||
[data-wb-text]{min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
|
||||
/* ★ WHILE RECORDING THE PILL DOES NOT BREATHE. A recording pill that changes width on every word reads as instability. width alone was not enough: the bubble is inline-flex, so it still sizes to content wherever width loses — and it carries a max-width that the <=480px query overrides, which is exactly the phone case he was looking at. Truncating the text cannot fix it either: 33 CHARACTERS is 133px of "illi" and 344px of "MMM WOW", so a character count is not a width. min-width and max-width are pinned to the same value as width so no later rule and no viewport can let it grow or shrink; the text ellipsises inside it. */
|
||||
[data-widget-bubble][data-listening="true"]{width:min(300px,calc(100vw - 32px));min-width:min(300px,calc(100vw - 32px));max-width:min(300px,calc(100vw - 32px))}
|
||||
[data-widget-bubble][data-listening="true"] [data-wb-text]{flex:1 1 auto}
|
||||
[data-wb-dot],[data-wb-chev]{display:none;flex:none}
|
||||
[data-wb-dot]{width:10px;height:10px;border-radius:50%;background:#ef4444}
|
||||
[data-wb-dot]{position:absolute;top:1px;right:1px;width:10px;height:10px;border-radius:50%;background:#ef4444;box-shadow:0 0 0 2px #0f172a}
|
||||
[data-widget-bubble][aria-expanded="true"] [data-wb-chev]{display:block}
|
||||
[data-widget-bubble][aria-expanded="true"] [data-wb-icon]{display:none}
|
||||
[data-widget-bubble][data-listening="true"]{border-color:#ef4444;background:#1f0f12;animation:studio-widget-pulse 1.4s ease-out infinite}
|
||||
[data-widget-bubble][data-listening="true"] [data-wb-dot]{display:block}
|
||||
[data-widget-bubble][data-listening="true"] [data-wb-icon]{color:#6b7280}
|
||||
|
|
@ -473,14 +470,12 @@ const WIDGET_CSS = `
|
|||
[data-studio-widgets$="right"]{right:max(8px,env(safe-area-inset-right))}
|
||||
[data-studio-widgets$="left"]{left:max(8px,env(safe-area-inset-left))}
|
||||
[data-widget-panel]{width:calc(100vw - 16px);max-width:none;height:60vh;height:60dvh}
|
||||
[data-widget-bubble]{max-width:calc(100vw - 16px)}
|
||||
/* ...but a RECORDING pill keeps its fixed width on a phone too, or it grows to the viewport. */
|
||||
[data-widget-bubble][data-listening="true"]{width:min(300px,calc(100vw - 16px));min-width:min(300px,calc(100vw - 16px));max-width:min(300px,calc(100vw - 16px))}
|
||||
}`
|
||||
const WIDGET_ICON_OPEN = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="2" width="6" height="12" rx="3"/><path d="M5 10v1a7 7 0 0 0 14 0v-1"/><path d="M12 18v4"/></svg>'
|
||||
const WIDGET_ICON_CLOSE = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>'
|
||||
// The pill: [recording dot] [mic] [label, or the last words while recording] [chevron when open]
|
||||
const WIDGET_PILL = '<span data-wb-dot aria-hidden="true"></span><span data-wb-icon>' + WIDGET_ICON_OPEN + '</span><span data-wb-text></span><span data-wb-chev>' + WIDGET_ICON_CLOSE + '</span>'
|
||||
// The bubble: a mic in a small circle (a chevron while its panel is open), a red dot while it records. It shows
|
||||
// no text: its name is its accessible label and tooltip, so it takes the corner and nothing more.
|
||||
const WIDGET_PILL = '<span data-wb-dot aria-hidden="true"></span><span data-wb-icon>' + WIDGET_ICON_OPEN + '</span><span data-wb-chev>' + WIDGET_ICON_CLOSE + '</span>'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PluginStudio',
|
||||
|
|
@ -1130,7 +1125,6 @@ export default defineComponent({
|
|||
btn.title = btn.getAttribute('aria-label')
|
||||
btn.setAttribute('aria-expanded', 'false')
|
||||
btn.innerHTML = WIDGET_PILL
|
||||
btn.querySelector('[data-wb-text]').textContent = btn.getAttribute('aria-label')
|
||||
btn.addEventListener('click', () => this.openWidget(row, !panel.hasAttribute('data-open')))
|
||||
box.appendChild(panel); box.appendChild(btn); host.appendChild(box)
|
||||
}
|
||||
|
|
@ -1163,24 +1157,18 @@ export default defineComponent({
|
|||
el.style.cssText = 'display:block;position:absolute;inset:0;width:100%;height:100%;overflow-y:auto;overscroll-behavior:contain;'
|
||||
// LIVE MIC, VISIBLE WHEN COLLAPSED: a widget that emits `recording` (Rudder does) turns its bubble
|
||||
// red, pulsing, with a dot while it listens, so closing the panel never hides that the mic is on.
|
||||
// While recording the pill drops its label for the last words heard, and its mic lights only while
|
||||
// speech is detected (`activity`), so a collapsed Rudder still shows it is hearing the reader.
|
||||
// Its mic lights only while speech is detected (`activity`), so a collapsed Rudder still shows it is
|
||||
// hearing the reader; the words themselves show in the panel.
|
||||
const pill = box.querySelector('[data-widget-bubble]')
|
||||
const text = pill.querySelector('[data-wb-text]')
|
||||
el.addEventListener('recording', (e) => {
|
||||
const on = !!(Array.isArray(e.detail) ? e.detail[0] : e.detail)
|
||||
pill.setAttribute('data-listening', on ? 'true' : 'false')
|
||||
if (!on) { pill.setAttribute('data-speaking', 'false'); text.textContent = pill.getAttribute('aria-label') }
|
||||
else text.textContent = ''
|
||||
if (!on) pill.setAttribute('data-speaking', 'false')
|
||||
})
|
||||
el.addEventListener('activity', (e) => {
|
||||
const a = (Array.isArray(e.detail) ? e.detail[0] : e.detail) || {}
|
||||
if (pill.getAttribute('data-listening') !== 'true') return
|
||||
pill.setAttribute('data-speaking', a.speaking ? 'true' : 'false')
|
||||
// A FIXED-width pill while recording (it resized with every word): show the newest
|
||||
// words, cut from the front, never let the pill grow.
|
||||
const w = a.words || ''
|
||||
text.textContent = w.length > 34 ? '…' + w.slice(-33) : w
|
||||
})
|
||||
W.els[row.key] = el
|
||||
panel.appendChild(el)
|
||||
|
|
|
|||
Loading…
Reference in a new issue