frontend: one component = one dir for hive-btn/hive-dialog/hive-toast

Splits the shadow-DOM custom elements out of the flat shared/src layout
into per-component directories:

  hive-btn/hive-btn.{js,css}
  hive-dialog/hive-dialog.{js,css}
  hive-toast/hive-toast.{js,css}

hive-dialog and hive-toast were previously defined inline inside
modal.js alongside the openDialog/themedConfirm/themedPrompt/themedToast
orchestration helpers; modal.js is now a slim entry point that imports
the two component modules for their customElements.define side effect
and keeps only the orchestration functions, which aren't components
themselves. hive-dialog.js now imports hive-btn.js directly (it's the
actual consumer that creates <hive-btn> elements), instead of modal.js
importing it on hive-dialog's behalf.

Pulled the identical shadow-root-plus-adopted-stylesheet boilerplate
(previously duplicated between modal.js's local attachShadow() and
hive-btn.js's inline version) into a shared shadow-css.js helper,
attachShadowCss(host, cssText, shadowInit), used by all three
components. Behaviorally identical — same attachShadow() options per
component, just deduplicated.

No external import paths changed: every consumer only ever imported
the package-level @hive/shared/modal.js entry point, never the
component internals directly, so this is fully internal to the shared
package. Verified with a full frontend build (dashboard + agent
bundles).
This commit is contained in:
iris 2026-07-31 21:29:45 +02:00
commit edf1c5a17f
8 changed files with 172 additions and 169 deletions

View file

@ -0,0 +1,25 @@
/* hive-toast.css scoped stylesheet for the <hive-toast> shadow-DOM
custom element (hive-toast.js). Loaded as raw text at build time
(esbuild's `text` loader) and adopted via `adoptedStyleSheets`. `:host`
styles the element itself, which *is* the visible toast box; `:host(.error)`
etc. switch on a plain class the host element carries (set in JS). */
:host {
display: block;
pointer-events: auto;
cursor: pointer;
background: var(--bg-elev);
border: 1px solid var(--purple-dim);
border-left-width: 3px;
box-shadow: 0 4px 20px -6px var(--crust);
padding: 0.6em 0.9em;
font-size: 0.9em;
color: var(--fg);
white-space: pre-wrap;
opacity: 1;
transition: opacity 0.2s ease, transform 0.2s ease;
}
:host(.out) { opacity: 0; transform: translateX(0.5em); }
:host(.error) { border-left-color: var(--red); }
:host(.info) { border-left-color: var(--purple-dim); }
:host(.ok) { border-left-color: var(--green); }

View file

@ -0,0 +1,37 @@
// hive-toast.js — <hive-toast>, one transient notification entry
// (constructed by `themedToast` in modal.js). Owns its own auto-dismiss
// timer via connectedCallback/disconnectedCallback (cleared on removal so
// a toast dismissed early by click doesn't leave a stray timer), rather
// than a closure-captured timer handle. Callers set `._message`/`._opts`
// before append — same reason as `<hive-dialog>`. The element itself is
// the visible toast box (`:host` carries the styling, `:host(.error)` etc.
// switch on a plain class the host carries) — the message text renders
// directly in the shadow root rather than via a `<slot>`, since there's no
// external light-DOM content to project.
import { attachShadowCss } from '../shadow-css.js';
import toastCss from './hive-toast.css';
class HiveToast extends HTMLElement {
connectedCallback() {
const { type = 'info', duration } = this._opts || {};
const ms = duration != null ? duration : (type === 'error' ? 8000 : 4000);
const root = attachShadowCss(this, toastCss);
this.classList.add(type);
this.setAttribute('role', type === 'error' ? 'alert' : 'status');
root.textContent = this._message || '';
let removed = false;
this._remove = () => {
if (removed) return;
removed = true;
this.classList.add('out');
setTimeout(() => this.remove(), 200);
};
this.addEventListener('click', this._remove);
if (ms > 0) this._timer = setTimeout(this._remove, ms);
}
disconnectedCallback() {
clearTimeout(this._timer);
}
}
customElements.define('hive-toast', HiveToast);