fix(rflows): theme toggle in shadow DOM + icon-only canvas toolbar

- Fix rFlows light/dark theme: change CSS selectors from :root /
  [data-theme] to :host / :host([data-theme]) so they work inside
  shadow DOM. Mirror data-theme attribute from <html> onto the
  folk-flows-app host element via MutationObserver.
- Canvas toolbar: icons only (no text labels), hover opens group
  name header + submenu flyout. Minimize button moved to top with
  chevron icon, collapses to wrench icon. Mobile gets emoji + text
  via ::after pseudo-element for touch accessibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-09 19:39:29 -07:00
parent 0b58ff364b
commit d45aaabea7
3 changed files with 85 additions and 33 deletions

View File

@ -1,7 +1,8 @@
/* ── Flows module theme ───────────────────────────────── */
/* ── rFlows color tokens (dark defaults) ─────────────── */
:root {
/* Use :host so vars apply inside shadow DOM (flows.css is loaded via <link> in shadow root) */
:host {
/* Source node */
--rflows-source-bg: #064e3b;
--rflows-source-border: #10b981;
@ -872,7 +873,7 @@
.edge-splash { pointer-events: none; }
/* ── Light theme overrides ──────────────────────────── */
[data-theme="light"] {
:host([data-theme="light"]) {
--rflows-source-text: #059669;
--rflows-source-hover-bg: #d1fae5;
--rflows-funnel-text: #2563eb;
@ -926,7 +927,7 @@
--rflows-modal-border: #e2e8f0;
}
@media (prefers-color-scheme: light) {
:root:not([data-theme]) {
:host:not([data-theme]) {
--rflows-source-text: #059669;
--rflows-source-hover-bg: #d1fae5;
--rflows-funnel-text: #2563eb;

View File

@ -161,6 +161,12 @@ class FolkFlowsApp extends HTMLElement {
this.flowId = this.getAttribute("flow-id") || "";
this.isDemo = this.getAttribute("mode") === "demo" || this.space === "demo";
// Mirror document theme to host for shadow DOM CSS selectors
this._syncTheme();
document.addEventListener("theme-change", () => this._syncTheme());
new MutationObserver(() => this._syncTheme())
.observe(document.documentElement, { attributes: true, attributeFilter: ["data-theme"] });
// Canvas-first: always open in detail (canvas) view
this.view = "detail";
@ -176,6 +182,11 @@ class FolkFlowsApp extends HTMLElement {
}
}
private _syncTheme() {
const theme = document.documentElement.getAttribute("data-theme") || "dark";
this.setAttribute("data-theme", theme);
}
private loadDemoOrLocalFlow() {
const activeId = localStorage.getItem('rflows:local:active') || '';
if (activeId) {

View File

@ -56,17 +56,23 @@
}
.toolbar-group-toggle {
padding: 7px 10px;
padding: 6px;
border: none;
border-radius: 8px;
background: var(--rs-toolbar-btn-bg);
color: var(--rs-toolbar-btn-text);
cursor: pointer;
font-size: 13px;
font-size: 18px;
line-height: 1;
transition: background 0.2s;
white-space: nowrap;
text-align: left;
text-align: center;
touch-action: manipulation;
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
}
.toolbar-group-toggle:hover {
@ -91,6 +97,17 @@
z-index: 1001;
}
.toolbar-dropdown-header {
padding: 6px 12px 4px;
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--rs-text-muted);
border-bottom: 1px solid var(--rs-toolbar-panel-border, rgba(255,255,255,0.08));
margin-bottom: 4px;
}
.toolbar-group.open > .toolbar-dropdown {
display: flex;
flex-direction: column;
@ -245,30 +262,36 @@
text-align: left;
}
/* Collapse/expand toggle — chevron at bottom of toolbar */
/* Collapse/expand toggle — at top of toolbar */
#toolbar-collapse {
padding: 4px 0 !important;
background: transparent !important;
font-size: 16px !important;
padding: 6px;
background: transparent;
border: none;
border-radius: 8px;
line-height: 1;
opacity: 0.4;
transition: opacity 0.2s;
opacity: 0.5;
transition: opacity 0.2s, background 0.15s;
text-align: center;
letter-spacing: 0;
color: var(--rs-text-muted);
order: 999; /* always last */
margin-top: auto;
cursor: pointer;
order: -1; /* always first */
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
}
#toolbar-collapse:hover {
opacity: 1;
background: var(--rs-toolbar-btn-bg) !important;
background: var(--rs-toolbar-btn-bg);
color: var(--rs-text-primary);
}
#toolbar-collapse svg { pointer-events: none; }
#toolbar.collapsed .toolbar-group,
#toolbar.collapsed .toolbar-sep,
#toolbar.collapsed > button:not(#toolbar-collapse) {
#toolbar.collapsed .toolbar-sep {
display: none;
}
@ -278,9 +301,7 @@
}
#toolbar.collapsed #toolbar-collapse {
opacity: 0.7;
font-size: 14px !important;
padding: 4px 6px !important;
opacity: 0.8;
}
/* ── Bottom toolbar — basic canvas tools ── */
@ -1536,7 +1557,15 @@
padding: 14px 16px;
font-size: 16px;
min-height: 48px;
height: auto;
overflow: visible;
justify-content: flex-start;
}
/* Show group name on mobile via title attr */
#toolbar .toolbar-group-toggle::after {
content: attr(title);
margin-left: 8px;
font-size: 15px;
}
#toolbar .toolbar-dropdown {
@ -1838,10 +1867,13 @@
</div>
<div id="toolbar">
<button id="toolbar-collapse" title="Minimize toolbar"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg></button>
<!-- 1. Note -->
<div class="toolbar-group">
<button class="toolbar-group-toggle">📝 Note</button>
<button class="toolbar-group-toggle" title="Note">📝</button>
<div class="toolbar-dropdown">
<div class="toolbar-dropdown-header">Note</div>
<button id="new-markdown" title="New Note">📝 Note</button>
<button id="new-slide" title="New Slide">🎞️ Slide</button>
</div>
@ -1849,8 +1881,9 @@
<!-- 2. Connect -->
<div class="toolbar-group">
<button class="toolbar-group-toggle">🤝 Connect</button>
<button class="toolbar-group-toggle" title="Connect">🤝</button>
<div class="toolbar-dropdown">
<div class="toolbar-dropdown-header">Connect</div>
<button id="new-chat" title="New Chat">💬 Chat</button>
<button id="new-video-chat" title="Video Meet">📹 Video Meet</button>
<button id="new-record" title="Record" class="toolbar-disabled">🔴 Record</button>
@ -1859,8 +1892,9 @@
<!-- 3. Media -->
<div class="toolbar-group">
<button class="toolbar-group-toggle">🎵 Media</button>
<button class="toolbar-group-toggle" title="Media">🎵</button>
<div class="toolbar-dropdown">
<div class="toolbar-dropdown-header">Media</div>
<button id="new-transcription" title="Transcribe">🎤 Transcribe</button>
<button id="new-stream" title="Stream" class="toolbar-disabled">📡 Stream</button>
<button id="new-piano" title="Piano">🎹 Piano</button>
@ -1871,8 +1905,9 @@
<!-- 4. Embed -->
<div class="toolbar-group">
<button class="toolbar-group-toggle">🔗 Embed</button>
<button class="toolbar-group-toggle" title="Embed">🔗</button>
<div class="toolbar-dropdown">
<div class="toolbar-dropdown-header">Embed</div>
<button id="new-embed" title="Web Embed">🔗 Web Embed</button>
<button id="new-google-item" title="Google">📎 Google</button>
<button id="new-calendar" title="Calendar">📅 Calendar</button>
@ -1892,8 +1927,9 @@
<!-- 5. AI -->
<div class="toolbar-group">
<button class="toolbar-group-toggle">🤖 AI</button>
<button class="toolbar-group-toggle" title="AI">🤖</button>
<div class="toolbar-dropdown">
<div class="toolbar-dropdown-header">AI</div>
<button id="new-prompt" title="AI Chat">🤖 AI Chat</button>
<button id="new-workflow" title="Workflow">⚙️ Workflow</button>
<button id="new-image-gen" title="AI Image">🎨 AI Image</button>
@ -1904,8 +1940,9 @@
<!-- 6. Create -->
<div class="toolbar-group">
<button class="toolbar-group-toggle">🛠️ Create</button>
<button class="toolbar-group-toggle" title="Create">🛠️</button>
<div class="toolbar-dropdown">
<div class="toolbar-dropdown-header">Create</div>
<button id="new-splat" title="3D Splat">🔮 3D Splat</button>
<button id="new-blender" title="3D Blender">🧊 Blender</button>
<button id="new-drawfast" title="Drawfast">✏️ Drawfast</button>
@ -1918,8 +1955,9 @@
<!-- 7. Decide -->
<div class="toolbar-group">
<button class="toolbar-group-toggle">📊 Decide</button>
<button class="toolbar-group-toggle" title="Decide">📊</button>
<div class="toolbar-dropdown">
<div class="toolbar-dropdown-header">Decide</div>
<button id="new-choice-vote" title="Poll">☑ Poll</button>
<button id="new-choice-rank" title="Ranking">📊 Ranking</button>
<button id="new-choice-spider" title="Scoring">🕸 Scoring</button>
@ -1932,8 +1970,9 @@
<!-- 8. Spend -->
<div class="toolbar-group">
<button class="toolbar-group-toggle">💰 Spend</button>
<button class="toolbar-group-toggle" title="Spend">💰</button>
<div class="toolbar-dropdown">
<div class="toolbar-dropdown-header">Spend</div>
<button id="embed-wallet" title="rWallet">💰 rWallet</button>
<button id="embed-flows" title="rFlows">🌊 rFlows</button>
</div>
@ -1941,8 +1980,9 @@
<!-- 9. Travel -->
<div class="toolbar-group">
<button class="toolbar-group-toggle">✈️ Travel</button>
<button class="toolbar-group-toggle" title="Travel">✈️</button>
<div class="toolbar-dropdown">
<div class="toolbar-dropdown-header">Travel</div>
<button id="new-itinerary" title="Itinerary">🗓️ Itinerary</button>
<button id="new-destination" title="Destination">📍 Destination</button>
<button id="new-budget" title="Budget">💰 Budget</button>
@ -1950,8 +1990,6 @@
<button id="new-booking" title="Booking">✈️ Booking</button>
</div>
</div>
<button id="toolbar-collapse" title="Minimize toolbar"></button>
</div>
<div id="toolbar-panel">
@ -5090,9 +5128,11 @@
// Collapse/expand toolbar
const collapseBtn = document.getElementById("toolbar-collapse");
const chevronSVG = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg>';
const wrenchSVG = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg>';
collapseBtn.addEventListener("click", () => {
const isCollapsed = toolbarEl.classList.toggle("collapsed");
collapseBtn.textContent = isCollapsed ? "" : "";
collapseBtn.innerHTML = isCollapsed ? wrenchSVG : chevronSVG;
collapseBtn.title = isCollapsed ? "Expand toolbar" : "Minimize toolbar";
});