feat(responsive): adaptive tablet/fold breakpoints, touch/pen parity, pointer events drag
Add intermediate breakpoints (960px, 1024px, 900px, 640px, 600px) for tablets and fold devices across all 12 rApp components. Add touch-action: manipulation and -webkit-tap-highlight-color to eliminate 300ms tap delay. Fix undersized tap targets (<36px) in rtasks, rfiles, rinbox, and rcart. Replace HTML5 drag API with pointer events in rtasks kanban and rchoices ranking for touch/pen/mouse parity. Replace mouseenter/ mouseleave with pointerenter/pointerleave in rchoices spider chart with click toggle. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
fab970e439
commit
233b7e3689
|
|
@ -2298,8 +2298,9 @@ class FolkCalendarView extends HTMLElement {
|
|||
|
||||
private getStyles(): string {
|
||||
return `
|
||||
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: var(--rs-text-primary); padding: 0.5rem; }
|
||||
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: var(--rs-text-primary); padding: 0.5rem; -webkit-tap-highlight-color: transparent; }
|
||||
* { box-sizing: border-box; }
|
||||
button, a, input, select, textarea, [role="button"] { touch-action: manipulation; }
|
||||
|
||||
.error { color: var(--rs-error); text-align: center; padding: 8px; }
|
||||
|
||||
|
|
@ -2606,6 +2607,16 @@ class FolkCalendarView extends HTMLElement {
|
|||
.kbd-hint { text-align: center; font-size: 10px; color: var(--rs-text-muted); margin-top: 12px; padding-top: 8px; border-top: 1px solid var(--rs-border-subtle); }
|
||||
.kbd-hint kbd { padding: 1px 4px; background: var(--rs-bg-surface); border: 1px solid var(--rs-border); border-radius: 3px; font-family: inherit; font-size: 9px; }
|
||||
|
||||
/* ── Tablet/Adaptive ── */
|
||||
@media (max-width: 1024px) {
|
||||
.main-layout--docked { grid-template-columns: 1fr auto 320px; }
|
||||
}
|
||||
@media (max-width: 900px) and (min-width: 769px) {
|
||||
.main-layout--docked { grid-template-columns: 1fr; }
|
||||
.zoom-bar--middle { display: none; }
|
||||
.ev-label { display: none; }
|
||||
.day { min-height: 64px; }
|
||||
}
|
||||
/* ── Mobile ── */
|
||||
@media (max-width: 768px) {
|
||||
:host { padding: 0.25rem; }
|
||||
|
|
|
|||
|
|
@ -436,8 +436,9 @@ class FolkGroupBuyPage extends HTMLElement {
|
|||
|
||||
private getStyles(): string {
|
||||
return `
|
||||
:host { display: block; padding: 2rem 1.5rem; width: 100%; max-width: 960px; }
|
||||
:host { display: block; padding: 2rem 1.5rem; width: 100%; max-width: 960px; -webkit-tap-highlight-color: transparent; }
|
||||
* { box-sizing: border-box; }
|
||||
button, a, input, select, textarea, [role="button"] { touch-action: manipulation; }
|
||||
|
||||
.loading { text-align: center; padding: 3rem; color: var(--rs-text-secondary); }
|
||||
.error { background: rgba(239,68,68,0.1); border: 1px solid var(--rs-error); border-radius: 8px; padding: 1.5rem; color: #fca5a5; text-align: center; }
|
||||
|
|
@ -585,7 +586,7 @@ class FolkGroupBuyPage extends HTMLElement {
|
|||
}
|
||||
.btn-pledge:hover { background: linear-gradient(135deg, #16a34a, #15803d); box-shadow: 0 4px 12px rgba(34,197,94,0.4); }
|
||||
.btn-pledge:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.btn-sm { padding: 0.375rem 0.75rem; font-size: 0.8125rem; }
|
||||
.btn-sm { padding: 0.375rem 0.75rem; font-size: 0.8125rem; min-height: 36px; min-width: 36px; }
|
||||
.btn-lg { padding: 0.875rem; font-size: 1rem; width: 100%; }
|
||||
|
||||
.pledge-disclaimer { color: var(--rs-text-muted); font-size: 0.6875rem; text-align: center; margin: 0; line-height: 1.4; }
|
||||
|
|
@ -628,6 +629,11 @@ class FolkGroupBuyPage extends HTMLElement {
|
|||
.commons-stat-projected .commons-stat-value { color: #a855f7; }
|
||||
.commons-stat-arrow { color: var(--rs-text-muted); font-size: 1.25rem; }
|
||||
|
||||
@media (max-width: 960px) and (min-width: 769px) {
|
||||
.main-grid { grid-template-columns: 1fr 280px; }
|
||||
.hero-img { width: 160px; height: 160px; }
|
||||
.tier-commons { display: none; }
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
:host { padding: 1.25rem 1rem; }
|
||||
.hero { flex-direction: column; padding: 1.25rem; }
|
||||
|
|
@ -638,6 +644,9 @@ class FolkGroupBuyPage extends HTMLElement {
|
|||
.commons-stats { flex-wrap: wrap; gap: 0.5rem; }
|
||||
.fill-visual { flex-direction: column; align-items: center; }
|
||||
.fill-visual__marker-label { display: none; }
|
||||
.sim-controls { flex-direction: column; align-items: stretch; gap: 0.5rem; }
|
||||
.sim-label { white-space: normal; }
|
||||
.commons-controls { flex-direction: column; align-items: stretch; gap: 0.5rem; }
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
:host { padding: 1rem 0.75rem; }
|
||||
|
|
@ -649,9 +658,6 @@ class FolkGroupBuyPage extends HTMLElement {
|
|||
.tier-row { padding: 0.625rem 0.75rem; gap: 0.5rem; }
|
||||
.tier-qty { font-size: 0.8125rem; min-width: 2.5rem; }
|
||||
.tier-price { font-size: 0.8125rem; }
|
||||
.sim-controls { flex-direction: column; align-items: stretch; gap: 0.5rem; }
|
||||
.sim-label { white-space: normal; }
|
||||
.commons-controls { flex-direction: column; align-items: stretch; gap: 0.5rem; }
|
||||
.pledge-panel { padding: 1rem; }
|
||||
h3 { font-size: 0.9375rem; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -628,8 +628,9 @@ class FolkPaymentPage extends HTMLElement {
|
|||
|
||||
private getStyles(): string {
|
||||
return `
|
||||
:host { display: block; padding: 1.5rem; width: 100%; max-width: 560px; }
|
||||
:host { display: block; padding: 1.5rem; width: 100%; max-width: 560px; -webkit-tap-highlight-color: transparent; }
|
||||
* { box-sizing: border-box; }
|
||||
button, a, input, select, textarea, [role="button"] { touch-action: manipulation; }
|
||||
|
||||
.payment-page { }
|
||||
|
||||
|
|
@ -712,7 +713,7 @@ class FolkPaymentPage extends HTMLElement {
|
|||
.terminal-msg { text-align: center; padding: 2rem; color: var(--rs-text-muted); font-size: 0.875rem; }
|
||||
|
||||
.transak-container { border-radius: 8px; overflow: hidden; border: 1px solid var(--rs-border); }
|
||||
.transak-iframe { width: 100%; height: 600px; border: none; }
|
||||
.transak-iframe { width: 100%; height: clamp(400px, 72vh, 600px); border: none; }
|
||||
|
||||
.footer { margin-top: 2rem; border-top: 1px solid var(--rs-border); padding-top: 1.5rem; }
|
||||
.qr-section { display: flex; flex-direction: column; align-items: center; gap: 1rem; }
|
||||
|
|
@ -723,6 +724,9 @@ class FolkPaymentPage extends HTMLElement {
|
|||
.loading { text-align: center; padding: 3rem; color: var(--rs-text-secondary); }
|
||||
.error { text-align: center; padding: 3rem; color: #f87171; }
|
||||
|
||||
@media (max-width: 600px) {
|
||||
:host { padding: 1.25rem 1rem; }
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
:host { padding: 1rem; }
|
||||
.header { flex-direction: column; align-items: flex-start; gap: 0.5rem; }
|
||||
|
|
|
|||
|
|
@ -561,8 +561,9 @@ class FolkPaymentRequest extends HTMLElement {
|
|||
|
||||
private getStyles(): string {
|
||||
return `
|
||||
:host { display: block; padding: 1.5rem; width: 100%; max-width: 520px; }
|
||||
:host { display: block; padding: 1.5rem; width: 100%; max-width: 520px; -webkit-tap-highlight-color: transparent; }
|
||||
* { box-sizing: border-box; }
|
||||
button, a, input, select, textarea, [role="button"] { touch-action: manipulation; }
|
||||
|
||||
.page-title { color: var(--rs-text-primary); font-size: 1.5rem; font-weight: 700; margin: 0 0 0.25rem; text-align: center; }
|
||||
.page-subtitle { color: var(--rs-text-secondary); font-size: 0.9375rem; text-align: center; margin: 0 0 2rem; }
|
||||
|
|
@ -642,6 +643,10 @@ class FolkPaymentRequest extends HTMLElement {
|
|||
.share-input { flex: 1; padding: 0.5rem 0.75rem; border-radius: 8px; border: 1px solid var(--rs-input-border); background: var(--rs-input-bg); color: var(--rs-input-text); font-size: 0.75rem; font-family: monospace; }
|
||||
.action-row { display: flex; gap: 0.5rem; justify-content: center; flex-wrap: wrap; }
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.toggle-btn { font-size: 0.75rem; }
|
||||
.method-desc { display: none; }
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
:host { padding: 1rem; }
|
||||
.page-title { font-size: 1.25rem; }
|
||||
|
|
|
|||
|
|
@ -94,7 +94,8 @@ class FolkChoicesDashboard extends HTMLElement {
|
|||
|
||||
this.shadow.innerHTML = `
|
||||
<style>
|
||||
:host { display: block; padding: 1.5rem; }
|
||||
:host { display: block; padding: 1.5rem; -webkit-tap-highlight-color: transparent; }
|
||||
button, a, input, select, textarea, [role="button"] { touch-action: manipulation; }
|
||||
.rapp-nav { display: flex; gap: 8px; align-items: center; margin-bottom: 1rem; min-height: 36px; }
|
||||
.rapp-nav__title { font-size: 15px; font-weight: 600; flex: 1; color: var(--rs-text-primary); }
|
||||
.create-btns { display: flex; gap: 0.5rem; }
|
||||
|
|
@ -210,7 +211,8 @@ class FolkChoicesDashboard extends HTMLElement {
|
|||
|
||||
this.shadow.innerHTML = `
|
||||
<style>
|
||||
:host { display: block; padding: 1.5rem; }
|
||||
:host { display: block; padding: 1.5rem; -webkit-tap-highlight-color: transparent; }
|
||||
button, a, input, select, textarea, [role="button"] { touch-action: manipulation; }
|
||||
.rapp-nav { display: flex; gap: 8px; align-items: center; margin-bottom: 1rem; min-height: 36px; }
|
||||
.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; }
|
||||
|
|
@ -435,59 +437,73 @@ class FolkChoicesDashboard extends HTMLElement {
|
|||
|
||||
// Spider legend hover
|
||||
this.shadow.querySelectorAll<HTMLElement>(".spider-legend-item").forEach((el) => {
|
||||
el.addEventListener("mouseenter", () => {
|
||||
el.addEventListener("pointerenter", () => {
|
||||
this.hoveredPerson = el.dataset.person || null;
|
||||
this.renderDemo();
|
||||
});
|
||||
el.addEventListener("mouseleave", () => {
|
||||
el.addEventListener("pointerleave", () => {
|
||||
this.hoveredPerson = null;
|
||||
this.renderDemo();
|
||||
});
|
||||
el.addEventListener("click", () => {
|
||||
this.hoveredPerson = this.hoveredPerson === (el.dataset.person || null) ? null : (el.dataset.person || null);
|
||||
this.renderDemo();
|
||||
});
|
||||
});
|
||||
|
||||
// Ranking drag-and-drop
|
||||
// Ranking drag-and-drop (pointer events — works with touch, pen, mouse)
|
||||
const rankList = this.shadow.querySelector(".rank-list");
|
||||
if (rankList) {
|
||||
const items = rankList.querySelectorAll<HTMLLIElement>(".rank-item");
|
||||
items.forEach((li) => {
|
||||
li.addEventListener("dragstart", (e: DragEvent) => {
|
||||
li.removeAttribute("draggable");
|
||||
li.addEventListener("pointerdown", (e: PointerEvent) => {
|
||||
if (e.button !== 0) return;
|
||||
const id = parseInt(li.dataset.rankId || "0", 10);
|
||||
this.rankDragging = id;
|
||||
li.classList.add("dragging");
|
||||
if (e.dataTransfer) {
|
||||
e.dataTransfer.effectAllowed = "move";
|
||||
e.dataTransfer.setData("text/plain", String(id));
|
||||
}
|
||||
li.setPointerCapture(e.pointerId);
|
||||
li.style.touchAction = "none";
|
||||
});
|
||||
|
||||
li.addEventListener("dragover", (e: DragEvent) => {
|
||||
li.addEventListener("pointermove", (e: PointerEvent) => {
|
||||
if (this.rankDragging === null) return;
|
||||
e.preventDefault();
|
||||
if (e.dataTransfer) e.dataTransfer.dropEffect = "move";
|
||||
li.classList.add("drag-over");
|
||||
// Find target item under pointer
|
||||
const allItems = rankList.querySelectorAll<HTMLLIElement>(".rank-item");
|
||||
allItems.forEach(item => item.classList.remove("drag-over"));
|
||||
const target = this.shadow.elementFromPoint(e.clientX, e.clientY) as HTMLElement | null;
|
||||
const targetLi = target?.closest?.(".rank-item") as HTMLLIElement | null;
|
||||
if (targetLi && targetLi !== li) targetLi.classList.add("drag-over");
|
||||
});
|
||||
|
||||
li.addEventListener("dragleave", () => {
|
||||
li.classList.remove("drag-over");
|
||||
});
|
||||
|
||||
li.addEventListener("drop", (e: DragEvent) => {
|
||||
e.preventDefault();
|
||||
li.classList.remove("drag-over");
|
||||
const targetId = parseInt(li.dataset.rankId || "0", 10);
|
||||
if (this.rankDragging !== null && this.rankDragging !== targetId) {
|
||||
const fromIdx = this.rankItems.findIndex((r) => r.id === this.rankDragging);
|
||||
const toIdx = this.rankItems.findIndex((r) => r.id === targetId);
|
||||
if (fromIdx !== -1 && toIdx !== -1) {
|
||||
const [moved] = this.rankItems.splice(fromIdx, 1);
|
||||
this.rankItems.splice(toIdx, 0, moved);
|
||||
this.renderDemo();
|
||||
li.addEventListener("pointerup", (e: PointerEvent) => {
|
||||
if (this.rankDragging === null) return;
|
||||
const allItems = rankList.querySelectorAll<HTMLLIElement>(".rank-item");
|
||||
allItems.forEach(item => { item.classList.remove("drag-over"); item.classList.remove("dragging"); });
|
||||
li.style.touchAction = "";
|
||||
const target = this.shadow.elementFromPoint(e.clientX, e.clientY) as HTMLElement | null;
|
||||
const targetLi = target?.closest?.(".rank-item") as HTMLLIElement | null;
|
||||
if (targetLi) {
|
||||
const targetId = parseInt(targetLi.dataset.rankId || "0", 10);
|
||||
if (this.rankDragging !== targetId) {
|
||||
const fromIdx = this.rankItems.findIndex((r) => r.id === this.rankDragging);
|
||||
const toIdx = this.rankItems.findIndex((r) => r.id === targetId);
|
||||
if (fromIdx !== -1 && toIdx !== -1) {
|
||||
const [moved] = this.rankItems.splice(fromIdx, 1);
|
||||
this.rankItems.splice(toIdx, 0, moved);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.rankDragging = null;
|
||||
this.renderDemo();
|
||||
});
|
||||
|
||||
li.addEventListener("dragend", () => {
|
||||
li.addEventListener("pointercancel", () => {
|
||||
const allItems = rankList.querySelectorAll<HTMLLIElement>(".rank-item");
|
||||
allItems.forEach(item => { item.classList.remove("drag-over"); item.classList.remove("dragging"); });
|
||||
li.style.touchAction = "";
|
||||
this.rankDragging = null;
|
||||
li.classList.remove("dragging");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -375,8 +375,9 @@ class FolkFileBrowser extends HTMLElement {
|
|||
const filesActive = this.tab === "files";
|
||||
this.shadow.innerHTML = `
|
||||
<style>
|
||||
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: #e0e0e0; }
|
||||
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: #e0e0e0; -webkit-tap-highlight-color: transparent; }
|
||||
* { box-sizing: border-box; }
|
||||
button, a, input, select, textarea, [role="button"] { touch-action: manipulation; }
|
||||
|
||||
.tabs { display: flex; gap: 4px; margin-bottom: 20px; }
|
||||
.tab-btn {
|
||||
|
|
@ -425,7 +426,7 @@ class FolkFileBrowser extends HTMLElement {
|
|||
}
|
||||
.file-meta { font-size: 12px; color: #888; margin-bottom: 8px; }
|
||||
.file-actions { display: flex; gap: 6px; flex-wrap: wrap; }
|
||||
.file-actions button { padding: 3px 8px; font-size: 11px; }
|
||||
.file-actions button { padding: 6px 12px; font-size: 11px; min-height: 36px; }
|
||||
|
||||
.card-grid {
|
||||
display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||
|
|
|
|||
|
|
@ -264,8 +264,9 @@ class FolkForumDashboard extends HTMLElement {
|
|||
private render() {
|
||||
this.shadow.innerHTML = `
|
||||
<style>
|
||||
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: var(--rs-text-primary); }
|
||||
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: var(--rs-text-primary); -webkit-tap-highlight-color: transparent; }
|
||||
* { box-sizing: border-box; }
|
||||
button, a, input, select, textarea, [role="button"] { touch-action: manipulation; }
|
||||
|
||||
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
|
||||
|
||||
|
|
|
|||
|
|
@ -310,7 +310,8 @@ class FolkInboxClient extends HTMLElement {
|
|||
private render() {
|
||||
this.shadow.innerHTML = `
|
||||
<style>
|
||||
:host { display: block; min-height: 60vh; font-family: system-ui, sans-serif; color: var(--rs-text-primary); }
|
||||
:host { display: block; min-height: 60vh; font-family: system-ui, sans-serif; color: var(--rs-text-primary); -webkit-tap-highlight-color: transparent; }
|
||||
button, a, input, select, textarea, [role="button"] { touch-action: manipulation; }
|
||||
.container { max-width: 1000px; margin: 0 auto; }
|
||||
|
||||
/* Nav */
|
||||
|
|
@ -348,8 +349,8 @@ class FolkInboxClient extends HTMLElement {
|
|||
|
||||
/* Filter bar */
|
||||
.filter-bar { display: flex; gap: 0.25rem; padding: 0.75rem 1rem; border-bottom: 1px solid var(--rs-border); }
|
||||
.filter-btn { padding: 0.25rem 0.75rem; border-radius: 6px; border: none; background: transparent; color: var(--rs-text-muted); cursor: pointer; font-size: 0.75rem; transition: all 0.15s; }
|
||||
.filter-btn:hover { color: var(--rs-text-secondary); }
|
||||
.filter-btn { padding: 6px 12px; min-height: 36px; border-radius: 6px; border: none; background: transparent; color: var(--rs-text-muted); cursor: pointer; font-size: 0.75rem; transition: all 0.15s; touch-action: manipulation; }
|
||||
.filter-btn:hover, .filter-btn:active { color: var(--rs-text-secondary); }
|
||||
.filter-btn.active { background: rgba(99,102,241,0.15); color: #818cf8; }
|
||||
|
||||
/* Thread rows */
|
||||
|
|
@ -509,10 +510,9 @@ class FolkInboxClient extends HTMLElement {
|
|||
.sample-banner { padding: 8px 16px; background: rgba(99,102,241,0.12); border: 1px solid rgba(99,102,241,0.25); border-radius: 8px; color: #a5b4fc; font-size: 13px; text-align: center; margin-bottom: 12px; }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.mailbox-grid { grid-template-columns: 1fr; }
|
||||
.mailbox-grid { grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); }
|
||||
.help-grid { grid-template-columns: 1fr; }
|
||||
.thread-row { flex-wrap: wrap; gap: 4px; }
|
||||
.thread-from { max-width: 100px; }
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
.rapp-nav { gap: 0.25rem; }
|
||||
|
|
|
|||
|
|
@ -2287,8 +2287,9 @@ Gear: EUR 400 (10%)</code></pre><p><em>Maya is tracking expenses in rF
|
|||
|
||||
private getStyles(): string {
|
||||
return `
|
||||
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: var(--rs-text-primary); }
|
||||
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: var(--rs-text-primary); -webkit-tap-highlight-color: transparent; }
|
||||
* { box-sizing: border-box; }
|
||||
button, a, input, select, textarea, [role="button"] { touch-action: manipulation; }
|
||||
|
||||
/* ── Navigation ── */
|
||||
.rapp-nav { display: flex; gap: 8px; margin-bottom: 16px; align-items: center; min-height: 36px; }
|
||||
|
|
@ -2659,6 +2660,9 @@ Gear: EUR 400 (10%)</code></pre><p><em>Maya is tracking expenses in rF
|
|||
.tiptap-container .tiptap { padding: 14px 16px; }
|
||||
.note-item { padding: 10px 12px; gap: 8px; }
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
.grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
.grid { grid-template-columns: 1fr; }
|
||||
.rapp-nav { gap: 4px; }
|
||||
|
|
|
|||
|
|
@ -257,8 +257,9 @@ class FolkTasksBoard extends HTMLElement {
|
|||
private render() {
|
||||
this.shadow.innerHTML = `
|
||||
<style>
|
||||
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: var(--rs-text-primary); }
|
||||
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: var(--rs-text-primary); -webkit-tap-highlight-color: transparent; }
|
||||
* { box-sizing: border-box; }
|
||||
button, a, input, select, textarea, [role="button"] { touch-action: manipulation; }
|
||||
|
||||
.rapp-nav { display: flex; gap: 8px; margin-bottom: 16px; align-items: center; min-height: 36px; }
|
||||
.rapp-nav__back { padding: 4px 10px; border-radius: 6px; border: 1px solid var(--rs-border-subtle); background: transparent; color: var(--rs-text-secondary); cursor: pointer; font-size: 13px; text-decoration: none; }
|
||||
|
|
@ -302,8 +303,8 @@ class FolkTasksBoard extends HTMLElement {
|
|||
.badge-low { background: #112a3b; color: #60a5fa; }
|
||||
|
||||
.move-btns { display: flex; gap: 4px; margin-top: 6px; }
|
||||
.move-btn { font-size: 10px; padding: 2px 6px; border-radius: 4px; border: 1px solid var(--rs-border-strong); background: var(--rs-bg-surface-sunken); color: var(--rs-text-muted); cursor: pointer; }
|
||||
.move-btn:hover { border-color: var(--rs-border-strong); color: var(--rs-text-secondary); }
|
||||
.move-btn { font-size: 11px; padding: 6px 10px; min-height: 36px; border-radius: 4px; border: 1px solid var(--rs-border-strong); background: var(--rs-bg-surface-sunken); color: var(--rs-text-muted); cursor: pointer; }
|
||||
.move-btn:hover, .move-btn:active { border-color: var(--rs-border-strong); color: var(--rs-text-secondary); }
|
||||
|
||||
.create-form { background: var(--rs-bg-surface); border: 1px solid var(--rs-primary); border-radius: 8px; padding: 10px; margin-bottom: 10px; }
|
||||
.create-form input, .create-form select, .create-form textarea {
|
||||
|
|
@ -321,8 +322,8 @@ class FolkTasksBoard extends HTMLElement {
|
|||
width: 100%; padding: 4px 6px; border-radius: 4px; border: 1px solid var(--rs-primary-hover);
|
||||
background: var(--rs-bg-surface-sunken); color: var(--rs-text-primary); font-size: 13px; font-weight: 500; outline: none; font-family: inherit;
|
||||
}
|
||||
.badge.clickable { cursor: pointer; transition: all 0.15s; }
|
||||
.badge.clickable:hover { filter: brightness(1.3); transform: scale(1.1); }
|
||||
.badge.clickable { cursor: pointer; transition: all 0.15s; padding: 4px 10px; min-height: 28px; }
|
||||
.badge.clickable:hover, .badge.clickable:active { filter: brightness(1.3); transform: scale(1.1); }
|
||||
|
||||
.empty { text-align: center; color: var(--rs-text-muted); padding: 40px; }
|
||||
|
||||
|
|
@ -524,47 +525,48 @@ class FolkTasksBoard extends HTMLElement {
|
|||
});
|
||||
});
|
||||
|
||||
// HTML5 drag-and-drop on task cards
|
||||
// Pointer events drag-and-drop on task cards (works with touch, pen, mouse)
|
||||
this.shadow.querySelectorAll(".task-card[draggable]").forEach(card => {
|
||||
card.addEventListener("dragstart", (e) => {
|
||||
const el = card as HTMLElement;
|
||||
const el = card as HTMLElement;
|
||||
el.addEventListener("pointerdown", (e) => {
|
||||
const pe = e as PointerEvent;
|
||||
if (pe.button !== 0) return;
|
||||
// Only start drag if not editing
|
||||
if (el.getAttribute("draggable") === "false") return;
|
||||
this.dragTaskId = el.dataset.taskId || null;
|
||||
el.classList.add("dragging");
|
||||
const dt = (e as DragEvent).dataTransfer;
|
||||
if (dt && this.dragTaskId) {
|
||||
const task = this.tasks.find(t => t.id === this.dragTaskId);
|
||||
dt.setData("text/plain", task?.title || this.dragTaskId);
|
||||
dt.setData("application/rspace-item", JSON.stringify({
|
||||
module: "rtasks",
|
||||
entityId: this.dragTaskId,
|
||||
title: task?.title || "",
|
||||
label: "rTasks",
|
||||
color: "#f97316",
|
||||
}));
|
||||
}
|
||||
el.setPointerCapture(pe.pointerId);
|
||||
el.style.touchAction = "none";
|
||||
});
|
||||
card.addEventListener("dragend", () => {
|
||||
(card as HTMLElement).classList.remove("dragging");
|
||||
el.addEventListener("pointermove", (e) => {
|
||||
if (!this.dragTaskId) return;
|
||||
(e as PointerEvent).preventDefault();
|
||||
// Highlight target column under pointer
|
||||
const pe = e as PointerEvent;
|
||||
this.shadow.querySelectorAll(".column[data-status]").forEach(c => (c as HTMLElement).classList.remove("drag-over"));
|
||||
const target = this.shadow.elementFromPoint(pe.clientX, pe.clientY) as HTMLElement | null;
|
||||
const targetCol = target?.closest?.(".column[data-status]") as HTMLElement | null;
|
||||
if (targetCol) targetCol.classList.add("drag-over");
|
||||
});
|
||||
el.addEventListener("pointerup", (e) => {
|
||||
if (!this.dragTaskId) return;
|
||||
const pe = e as PointerEvent;
|
||||
el.classList.remove("dragging");
|
||||
el.style.touchAction = "";
|
||||
this.shadow.querySelectorAll(".column[data-status]").forEach(c => (c as HTMLElement).classList.remove("drag-over"));
|
||||
const target = this.shadow.elementFromPoint(pe.clientX, pe.clientY) as HTMLElement | null;
|
||||
const targetCol = target?.closest?.(".column[data-status]") as HTMLElement | null;
|
||||
if (targetCol) {
|
||||
const status = targetCol.dataset.status!;
|
||||
this.moveTask(this.dragTaskId!, status);
|
||||
}
|
||||
this.dragTaskId = null;
|
||||
});
|
||||
});
|
||||
|
||||
this.shadow.querySelectorAll(".column[data-status]").forEach(col => {
|
||||
col.addEventListener("dragover", (e) => {
|
||||
e.preventDefault();
|
||||
(col as HTMLElement).classList.add("drag-over");
|
||||
});
|
||||
col.addEventListener("dragleave", () => {
|
||||
(col as HTMLElement).classList.remove("drag-over");
|
||||
});
|
||||
col.addEventListener("drop", (e) => {
|
||||
e.preventDefault();
|
||||
(col as HTMLElement).classList.remove("drag-over");
|
||||
const status = (col as HTMLElement).dataset.status!;
|
||||
if (this.dragTaskId) {
|
||||
this.moveTask(this.dragTaskId, status);
|
||||
this.dragTaskId = null;
|
||||
}
|
||||
el.addEventListener("pointercancel", () => {
|
||||
el.classList.remove("dragging");
|
||||
el.style.touchAction = "";
|
||||
this.shadow.querySelectorAll(".column[data-status]").forEach(c => (c as HTMLElement).classList.remove("drag-over"));
|
||||
this.dragTaskId = null;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -407,8 +407,9 @@ class FolkVoteDashboard extends HTMLElement {
|
|||
private render() {
|
||||
this.shadow.innerHTML = `
|
||||
<style>
|
||||
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: var(--rs-text-primary); }
|
||||
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: var(--rs-text-primary); -webkit-tap-highlight-color: transparent; }
|
||||
* { box-sizing: border-box; }
|
||||
button, a, input, select, textarea, [role="button"] { touch-action: manipulation; }
|
||||
|
||||
.header { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; flex-wrap: wrap; }
|
||||
.header-back { padding: 6px 12px; border-radius: 8px; border: 1px solid var(--rs-border); background: transparent; color: var(--rs-text-secondary); cursor: pointer; font-size: 13px; transition: all 0.15s; }
|
||||
|
|
@ -615,6 +616,10 @@ class FolkVoteDashboard extends HTMLElement {
|
|||
.detail-title { font-size: 17px; }
|
||||
.trend-section { padding: 12px 14px; }
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.tally { gap: 12px; }
|
||||
.tally-value { font-size: 20px; }
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
.header-title { font-size: 15px; }
|
||||
.space-card { padding: 14px; }
|
||||
|
|
|
|||
|
|
@ -928,8 +928,9 @@ class FolkWalletViewer extends HTMLElement {
|
|||
private renderStyles(): string {
|
||||
return `
|
||||
<style>
|
||||
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: var(--rs-text-primary); }
|
||||
:host { display: block; font-family: system-ui, -apple-system, sans-serif; color: var(--rs-text-primary); -webkit-tap-highlight-color: transparent; }
|
||||
* { box-sizing: border-box; }
|
||||
button, a, input, select, textarea, [role="button"] { touch-action: manipulation; }
|
||||
|
||||
/* ── Hero ── */
|
||||
.hero {
|
||||
|
|
@ -1277,6 +1278,10 @@ class FolkWalletViewer extends HTMLElement {
|
|||
.top-tabs { max-width: 100%; }
|
||||
.wallet-card-header { flex-direction: column; align-items: flex-start; }
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.view-tab { padding: 6px 10px; font-size: 12px; }
|
||||
.top-tab { padding: 6px 10px; font-size: 12px; }
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
.features { grid-template-columns: 1fr; }
|
||||
.hero-title { font-size: 18px; }
|
||||
|
|
|
|||
Loading…
Reference in New Issue