fix(rchoices): move CrowdSurf under rChoices sub-nav, fix header overlap
- Hide CrowdSurf from app switcher (hidden: true) since it's now a sub-tab of rChoices - Replace dead outputPaths (Polls/Results with no routes) with actual tabs: Spider Chart, Ranking, Voting, CrowdSurf - Add /:tab route handler so sub-nav pills link to working URLs - Component reads tab attribute for initial tab selection - Remove internal .demo-tabs (shell sub-nav replaces them) - Bump JS cache version to v=6 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e0d976ac92
commit
362bdd5857
|
|
@ -131,6 +131,7 @@ export const crowdsurfModule: RSpaceModule = {
|
||||||
description: "Swipe-based community activity coordination",
|
description: "Swipe-based community activity coordination",
|
||||||
scoping: { defaultScope: 'space', userConfigurable: false },
|
scoping: { defaultScope: 'space', userConfigurable: false },
|
||||||
routes,
|
routes,
|
||||||
|
hidden: true, // CrowdSurf is now a sub-tab of rChoices
|
||||||
standaloneDomain: "crowdsurf.online",
|
standaloneDomain: "crowdsurf.online",
|
||||||
landingPage: renderLanding,
|
landingPage: renderLanding,
|
||||||
seedTemplate: seedTemplateCrowdSurf,
|
seedTemplate: seedTemplateCrowdSurf,
|
||||||
|
|
|
||||||
|
|
@ -70,15 +70,17 @@ class FolkChoicesDashboard extends HTMLElement {
|
||||||
// Guided tour
|
// Guided tour
|
||||||
private _tour!: TourEngine;
|
private _tour!: TourEngine;
|
||||||
private static readonly TOUR_STEPS = [
|
private static readonly TOUR_STEPS = [
|
||||||
{ target: '[data-tab="spider"]', title: "Spider Charts", message: "Compare multiple criteria on a radar chart. Each participant's scores overlay in real time.", advanceOnClick: true },
|
{ target: '.demo-content', title: "rChoices", message: "Explore spider charts, rankings, live voting, and CrowdSurf swipe cards. Use the sub-nav above to switch between modes.", advanceOnClick: true },
|
||||||
{ target: '[data-tab="ranking"]', title: "Rankings", message: "Drag items to rank them. Rankings aggregate across all participants for a collective order.", advanceOnClick: true },
|
|
||||||
{ target: '[data-tab="voting"]', title: "Voting", message: "Cast your vote and watch results update live with animated bars and totals.", advanceOnClick: true },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.shadow = this.attachShadow({ mode: "open" });
|
this.shadow = this.attachShadow({ mode: "open" });
|
||||||
this.space = this.getAttribute("space") || "demo";
|
this.space = this.getAttribute("space") || "demo";
|
||||||
|
const tabAttr = this.getAttribute("tab") as "spider" | "ranking" | "voting" | "crowdsurf" | null;
|
||||||
|
if (tabAttr && ["spider", "ranking", "voting", "crowdsurf"].includes(tabAttr)) {
|
||||||
|
this.demoTab = tabAttr;
|
||||||
|
}
|
||||||
this._tour = new TourEngine(
|
this._tour = new TourEngine(
|
||||||
this.shadow,
|
this.shadow,
|
||||||
FolkChoicesDashboard.TOUR_STEPS,
|
FolkChoicesDashboard.TOUR_STEPS,
|
||||||
|
|
@ -527,13 +529,6 @@ class FolkChoicesDashboard extends HTMLElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderDemo() {
|
private renderDemo() {
|
||||||
const tabs: { key: "spider" | "ranking" | "voting" | "crowdsurf"; label: string; icon: string }[] = [
|
|
||||||
{ key: "spider", label: "Spider Chart", icon: "🕸" },
|
|
||||||
{ key: "ranking", label: "Ranking", icon: "📊" },
|
|
||||||
{ key: "voting", label: "Live Voting", icon: "☑" },
|
|
||||||
{ key: "crowdsurf", label: "CrowdSurf", icon: "🏄" },
|
|
||||||
];
|
|
||||||
|
|
||||||
let content = "";
|
let content = "";
|
||||||
if (this.demoTab === "spider") content = this.renderSpider();
|
if (this.demoTab === "spider") content = this.renderSpider();
|
||||||
else if (this.demoTab === "ranking") content = this.renderRanking();
|
else if (this.demoTab === "ranking") content = this.renderRanking();
|
||||||
|
|
@ -548,15 +543,6 @@ class FolkChoicesDashboard extends HTMLElement {
|
||||||
.rapp-nav__title { font-size: 15px; font-weight: 600; flex: 1; color: var(--rs-text-primary); }
|
.rapp-nav__title { font-size: 15px; font-weight: 600; flex: 1; color: var(--rs-text-primary); }
|
||||||
.demo-badge { font-size: 0.7rem; padding: 2px 8px; border-radius: 999px; background: var(--rs-primary); color: #fff; font-weight: 500; }
|
.demo-badge { font-size: 0.7rem; padding: 2px 8px; border-radius: 999px; background: var(--rs-primary); color: #fff; font-weight: 500; }
|
||||||
|
|
||||||
/* Tabs */
|
|
||||||
.demo-tabs { display: flex; gap: 4px; margin-bottom: 1.5rem; margin-top: 4px; background: var(--rs-bg-page); border-radius: 10px; padding: 4px; overflow-x: auto; -webkit-overflow-scrolling: touch; }
|
|
||||||
.demo-tab { flex: 1 1 0; min-width: 0; text-align: center; padding: 0.5rem 0.5rem; border-radius: 8px; border: none; background: transparent; color: var(--rs-text-secondary); cursor: pointer; font-size: 0.8rem; font-family: inherit; transition: all 0.15s; white-space: nowrap; }
|
|
||||||
.demo-tab:hover { color: var(--rs-text-primary); background: var(--rs-bg-surface); }
|
|
||||||
.demo-tab.active { background: var(--rs-bg-surface); color: var(--rs-text-primary); box-shadow: var(--rs-shadow-sm); }
|
|
||||||
.demo-tab-icon { margin-right: 4px; }
|
|
||||||
.demo-tab-label { }
|
|
||||||
@media (max-width: 480px) { .demo-tab-label { display: none; } .demo-tab-icon { margin-right: 0; } .demo-tab { flex: 0 0 auto; padding: 0.5rem 0.75rem; } }
|
|
||||||
|
|
||||||
/* Spider chart */
|
/* Spider chart */
|
||||||
.spider-wrap { display: flex; flex-direction: column; align-items: center; }
|
.spider-wrap { display: flex; flex-direction: column; align-items: center; }
|
||||||
.spider-svg { width: 100%; max-width: 420px; }
|
.spider-svg { width: 100%; max-width: 420px; }
|
||||||
|
|
@ -628,10 +614,6 @@ class FolkChoicesDashboard extends HTMLElement {
|
||||||
:host { padding: 1rem; }
|
:host { padding: 1rem; }
|
||||||
.rapp-nav { gap: 4px; }
|
.rapp-nav { gap: 4px; }
|
||||||
.create-btn { padding: 0.375rem 0.75rem; font-size: 0.8125rem; }
|
.create-btn { padding: 0.375rem 0.75rem; font-size: 0.8125rem; }
|
||||||
.demo-tabs { gap: 2px; padding: 3px; }
|
|
||||||
.demo-tab { padding: 0.5rem; font-size: 0.8125rem; }
|
|
||||||
.demo-tab-label { display: none; }
|
|
||||||
.demo-tab-icon { margin-right: 0; font-size: 1.1rem; }
|
|
||||||
.rank-item { padding: 0.625rem 0.75rem; gap: 8px; }
|
.rank-item { padding: 0.625rem 0.75rem; gap: 8px; }
|
||||||
.rank-name { font-size: 0.875rem; }
|
.rank-name { font-size: 0.875rem; }
|
||||||
.vote-option { padding: 0.625rem 0.75rem; }
|
.vote-option { padding: 0.625rem 0.75rem; }
|
||||||
|
|
@ -645,11 +627,7 @@ class FolkChoicesDashboard extends HTMLElement {
|
||||||
<div class="rapp-nav">
|
<div class="rapp-nav">
|
||||||
<span class="rapp-nav__title">Choices</span>
|
<span class="rapp-nav__title">Choices</span>
|
||||||
<span class="demo-badge">DEMO</span>
|
<span class="demo-badge">DEMO</span>
|
||||||
<button class="demo-tab" id="btn-tour" style="margin-left:auto;font-size:0.78rem">Tour</button>
|
<button style="margin-left:auto;padding:4px 10px;border-radius:6px;border:1px solid var(--rs-border);background:var(--rs-bg-surface);color:var(--rs-text-secondary);cursor:pointer;font-size:0.78rem;font-family:inherit;" id="btn-tour">Tour</button>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="demo-tabs">
|
|
||||||
${tabs.map((t) => `<button class="demo-tab${this.demoTab === t.key ? " active" : ""}" data-tab="${t.key}"><span class="demo-tab-icon">${t.icon}</span><span class="demo-tab-label">${this.esc(t.label)}</span></button>`).join("")}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="demo-content">
|
<div class="demo-content">
|
||||||
|
|
@ -1104,16 +1082,6 @@ class FolkChoicesDashboard extends HTMLElement {
|
||||||
|
|
||||||
private bindDemoEvents() {
|
private bindDemoEvents() {
|
||||||
this.shadow.getElementById("btn-tour")?.addEventListener("click", () => this.startTour());
|
this.shadow.getElementById("btn-tour")?.addEventListener("click", () => this.startTour());
|
||||||
// Tab switching
|
|
||||||
this.shadow.querySelectorAll<HTMLButtonElement>(".demo-tab").forEach((btn) => {
|
|
||||||
btn.addEventListener("click", () => {
|
|
||||||
const tab = btn.dataset.tab as "spider" | "ranking" | "voting" | "crowdsurf";
|
|
||||||
if (tab && tab !== this.demoTab) {
|
|
||||||
this.demoTab = tab;
|
|
||||||
this.renderDemo();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Spider legend hover
|
// Spider legend hover
|
||||||
this.shadow.querySelectorAll<HTMLElement>(".spider-legend-item").forEach((el) => {
|
this.shadow.querySelectorAll<HTMLElement>(".spider-legend-item").forEach((el) => {
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ routes.get("/api/choices", async (c) => {
|
||||||
return c.json({ choices, total: choices.length });
|
return c.json({ choices, total: choices.length });
|
||||||
});
|
});
|
||||||
|
|
||||||
// GET / — choices page
|
// GET / — choices page (default tab: spider)
|
||||||
routes.get("/", (c) => {
|
routes.get("/", (c) => {
|
||||||
const spaceSlug = c.req.param("space") || "demo";
|
const spaceSlug = c.req.param("space") || "demo";
|
||||||
return c.html(renderShell({
|
return c.html(renderShell({
|
||||||
|
|
@ -55,8 +55,27 @@ routes.get("/", (c) => {
|
||||||
spaceSlug,
|
spaceSlug,
|
||||||
modules: getModuleInfoList(),
|
modules: getModuleInfoList(),
|
||||||
theme: "dark",
|
theme: "dark",
|
||||||
body: `<folk-choices-dashboard space="${spaceSlug}"></folk-choices-dashboard>`,
|
body: `<folk-choices-dashboard space="${spaceSlug}" tab="spider"></folk-choices-dashboard>`,
|
||||||
scripts: `<script type="module" src="/modules/rchoices/folk-choices-dashboard.js?v=5"></script>`,
|
scripts: `<script type="module" src="/modules/rchoices/folk-choices-dashboard.js?v=6"></script>`,
|
||||||
|
styles: `<link rel="stylesheet" href="/modules/rchoices/choices.css">`,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
// GET /:tab — choices page with active sub-tab
|
||||||
|
routes.get("/:tab", (c) => {
|
||||||
|
const spaceSlug = c.req.param("space") || "demo";
|
||||||
|
const tab = c.req.param("tab");
|
||||||
|
const validTabs = ["spider", "ranking", "voting", "crowdsurf"];
|
||||||
|
if (!validTabs.includes(tab)) return c.notFound();
|
||||||
|
const tabLabel = tab === "crowdsurf" ? "CrowdSurf" : tab.charAt(0).toUpperCase() + tab.slice(1);
|
||||||
|
return c.html(renderShell({
|
||||||
|
title: `${spaceSlug} — ${tabLabel} | rChoices`,
|
||||||
|
moduleId: "rchoices",
|
||||||
|
spaceSlug,
|
||||||
|
modules: getModuleInfoList(),
|
||||||
|
theme: "dark",
|
||||||
|
body: `<folk-choices-dashboard space="${spaceSlug}" tab="${tab}"></folk-choices-dashboard>`,
|
||||||
|
scripts: `<script type="module" src="/modules/rchoices/folk-choices-dashboard.js?v=6"></script>`,
|
||||||
styles: `<link rel="stylesheet" href="/modules/rchoices/choices.css">`,
|
styles: `<link rel="stylesheet" href="/modules/rchoices/choices.css">`,
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
@ -139,7 +158,9 @@ export const choicesModule: RSpaceModule = {
|
||||||
],
|
],
|
||||||
acceptsFeeds: ["data", "governance"],
|
acceptsFeeds: ["data", "governance"],
|
||||||
outputPaths: [
|
outputPaths: [
|
||||||
{ path: "polls", name: "Polls", icon: "☑️", description: "Active and closed polls" },
|
{ path: "spider", name: "Spider Chart", icon: "🕸", description: "Multi-criteria radar charts" },
|
||||||
{ path: "results", name: "Results", icon: "📊", description: "Poll and scoring results" },
|
{ path: "ranking", name: "Ranking", icon: "📊", description: "Drag-and-drop rankings" },
|
||||||
|
{ path: "voting", name: "Voting", icon: "☑", description: "Live polls and voting" },
|
||||||
|
{ path: "crowdsurf", name: "CrowdSurf", icon: "🏄", description: "Swipe-based option surfacing" },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue