Improve drag/swipe handling and Features page UX
- SwipeTabs: Delay pointer capture until drag starts for better tap detection - Features: Only allow drag via handle dots, not entire card - Features: Smart hasOrderChanges - hide buttons when order returns to initial - Features: Don't apply dragging style until movement exceeds threshold - ThemeSettings: Add role="button" for accessibility on all option cards - Sidebar: Light theme active menu item styling improvements - Layout: Tab bar translucency and blur effects 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -95,8 +95,9 @@ export function SwipeTabs<T extends string | number>({
|
|||||||
const shouldIgnoreSwipe = useCallback((target: EventTarget | null) => {
|
const shouldIgnoreSwipe = useCallback((target: EventTarget | null) => {
|
||||||
const element = resolveSwipeTarget(target);
|
const element = resolveSwipeTarget(target);
|
||||||
if (!element) return false;
|
if (!element) return false;
|
||||||
|
// Don't ignore role="button" - we handle tap vs swipe via delayed pointer capture
|
||||||
if (element.closest(
|
if (element.closest(
|
||||||
'[data-swipe-ignore="true"], button, a, input, select, textarea, [role="button"], [role="link"], [role="switch"], [contenteditable="true"]'
|
'[data-swipe-ignore="true"], button, a, input, select, textarea, [role="link"], [role="switch"], [contenteditable="true"]'
|
||||||
)) {
|
)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -287,6 +288,14 @@ export function SwipeTabs<T extends string | number>({
|
|||||||
state.isDragging = true;
|
state.isDragging = true;
|
||||||
isDraggingRef.current = true;
|
isDraggingRef.current = true;
|
||||||
setDraggingClass(true);
|
setDraggingClass(true);
|
||||||
|
// Capture pointer now that drag has started
|
||||||
|
if (state.pointerId !== null && containerRef.current?.setPointerCapture) {
|
||||||
|
try {
|
||||||
|
containerRef.current.setPointerCapture(state.pointerId);
|
||||||
|
} catch {
|
||||||
|
// Ignore capture failures
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!state.isDragging) return;
|
if (!state.isDragging) return;
|
||||||
@@ -371,13 +380,8 @@ export function SwipeTabs<T extends string | number>({
|
|||||||
if (event.pointerType === 'mouse' && event.button !== 0) return;
|
if (event.pointerType === 'mouse' && event.button !== 0) return;
|
||||||
if (dragRef.current.isActive) return;
|
if (dragRef.current.isActive) return;
|
||||||
startDrag(event.clientX, event.clientY, event.pointerId, null);
|
startDrag(event.clientX, event.clientY, event.pointerId, null);
|
||||||
if (containerRef.current?.setPointerCapture) {
|
// Don't capture pointer immediately - let taps work normally
|
||||||
try {
|
// Capture will happen in updateDrag when drag actually starts
|
||||||
containerRef.current.setPointerCapture(event.pointerId);
|
|
||||||
} catch {
|
|
||||||
// Ignore pointer capture failures.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePointerMove = (event: PointerEvent<HTMLDivElement>) => {
|
const handlePointerMove = (event: PointerEvent<HTMLDivElement>) => {
|
||||||
|
|||||||
@@ -88,6 +88,17 @@ export default function Features() {
|
|||||||
return positions;
|
return positions;
|
||||||
}, [moduleStates]);
|
}, [moduleStates]);
|
||||||
|
|
||||||
|
const hasChangesFromInitial = useCallback((nextOrder: string[], nextPositions: Record<string, 'top' | 'bottom'>) => {
|
||||||
|
const initialOrder = initialOrderRef.current;
|
||||||
|
const initialPositions = initialPositionsRef.current;
|
||||||
|
if (nextOrder.length !== initialOrder.length) return true;
|
||||||
|
if (nextOrder.some((id, index) => id !== initialOrder[index])) return true;
|
||||||
|
for (const id of nextOrder) {
|
||||||
|
if ((nextPositions[id] || 'top') !== (initialPositions[id] || 'top')) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}, []);
|
||||||
|
|
||||||
const beginOrderEdit = useCallback(() => {
|
const beginOrderEdit = useCallback(() => {
|
||||||
if (!isUserEditing.current) {
|
if (!isUserEditing.current) {
|
||||||
const snapshotOrder = localOrder.length ? [...localOrder] : buildFullOrder(moduleOrder);
|
const snapshotOrder = localOrder.length ? [...localOrder] : buildFullOrder(moduleOrder);
|
||||||
@@ -294,9 +305,15 @@ export default function Features() {
|
|||||||
localPositionsRef.current = nextPositions;
|
localPositionsRef.current = nextPositions;
|
||||||
setLocalPositions(nextPositions);
|
setLocalPositions(nextPositions);
|
||||||
setLocalOrder(nextOrder);
|
setLocalOrder(nextOrder);
|
||||||
setHasOrderChanges(true);
|
// Only show buttons if different from initial saved state
|
||||||
|
const hasChanges = hasChangesFromInitial(nextOrder, nextPositions);
|
||||||
|
setHasOrderChanges(hasChanges);
|
||||||
|
// Reset editing state when order returns to initial
|
||||||
|
if (!hasChanges) {
|
||||||
|
isUserEditing.current = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [buildFullOrder, buildPositionsFromStates, getInsertIndex, getSectionForY, moduleOrder]);
|
}, [buildFullOrder, buildPositionsFromStates, getInsertIndex, getSectionForY, hasChangesFromInitial, moduleOrder]);
|
||||||
|
|
||||||
const handleOrderPointerMove = useCallback((event: React.PointerEvent<HTMLDivElement>) => {
|
const handleOrderPointerMove = useCallback((event: React.PointerEvent<HTMLDivElement>) => {
|
||||||
const dragState = dragPointerRef.current;
|
const dragState = dragPointerRef.current;
|
||||||
@@ -363,7 +380,7 @@ export default function Features() {
|
|||||||
const handleOrderPointerDown = useCallback((event: React.PointerEvent<HTMLDivElement>, moduleId: string) => {
|
const handleOrderPointerDown = useCallback((event: React.PointerEvent<HTMLDivElement>, moduleId: string) => {
|
||||||
if (event.pointerType === 'mouse' && event.button !== 0) return;
|
if (event.pointerType === 'mouse' && event.button !== 0) return;
|
||||||
beginOrderEdit();
|
beginOrderEdit();
|
||||||
setDraggedItem(moduleId);
|
// Don't set draggedItem here - only set it when drag actually starts (in handleOrderPointerMove)
|
||||||
dragPointerRef.current.pointerId = event.pointerId;
|
dragPointerRef.current.pointerId = event.pointerId;
|
||||||
dragPointerRef.current.touchId = null;
|
dragPointerRef.current.touchId = null;
|
||||||
dragPointerRef.current.activeId = moduleId;
|
dragPointerRef.current.activeId = moduleId;
|
||||||
@@ -432,7 +449,7 @@ export default function Features() {
|
|||||||
const handleOrderTouchStart = useCallback((event: React.TouchEvent<HTMLDivElement>, moduleId: string) => {
|
const handleOrderTouchStart = useCallback((event: React.TouchEvent<HTMLDivElement>, moduleId: string) => {
|
||||||
if (event.changedTouches.length === 0) return;
|
if (event.changedTouches.length === 0) return;
|
||||||
beginOrderEdit();
|
beginOrderEdit();
|
||||||
setDraggedItem(moduleId);
|
// Don't set draggedItem here - only set it when drag actually starts (in handleGlobalTouchMove)
|
||||||
const touch = event.changedTouches[0];
|
const touch = event.changedTouches[0];
|
||||||
dragPointerRef.current.pointerId = null;
|
dragPointerRef.current.pointerId = null;
|
||||||
dragPointerRef.current.touchId = touch.identifier;
|
dragPointerRef.current.touchId = touch.identifier;
|
||||||
@@ -565,7 +582,6 @@ export default function Features() {
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="order-cards"
|
className="order-cards"
|
||||||
data-swipe-ignore="true"
|
|
||||||
ref={topOrderRef}
|
ref={topOrderRef}
|
||||||
>
|
>
|
||||||
{topOrderModules.map((moduleId) => {
|
{topOrderModules.map((moduleId) => {
|
||||||
@@ -576,11 +592,6 @@ export default function Features() {
|
|||||||
key={moduleId}
|
key={moduleId}
|
||||||
className={`order-card ${draggedItem === moduleId ? 'dragging' : ''}`}
|
className={`order-card ${draggedItem === moduleId ? 'dragging' : ''}`}
|
||||||
data-module-id={moduleId}
|
data-module-id={moduleId}
|
||||||
onPointerDown={supportsPointerEvents ? (e) => handleOrderPointerDown(e, moduleId) : undefined}
|
|
||||||
onPointerMove={supportsPointerEvents ? handleOrderPointerMove : undefined}
|
|
||||||
onPointerUp={supportsPointerEvents ? handleOrderPointerUp : undefined}
|
|
||||||
onPointerCancel={supportsPointerEvents ? handleOrderPointerCancel : undefined}
|
|
||||||
onTouchStart={!supportsPointerEvents ? (e) => handleOrderTouchStart(e, moduleId) : undefined}
|
|
||||||
>
|
>
|
||||||
<div className="order-card-preview">
|
<div className="order-card-preview">
|
||||||
<span className="material-symbols-outlined">{moduleInfo.icon}</span>
|
<span className="material-symbols-outlined">{moduleInfo.icon}</span>
|
||||||
@@ -588,7 +599,15 @@ export default function Features() {
|
|||||||
<div className="order-card-info">
|
<div className="order-card-info">
|
||||||
<span className="order-card-name">{moduleName}</span>
|
<span className="order-card-name">{moduleName}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="order-card-handle">
|
<div
|
||||||
|
className="order-card-handle"
|
||||||
|
data-swipe-ignore="true"
|
||||||
|
onPointerDown={supportsPointerEvents ? (e) => handleOrderPointerDown(e, moduleId) : undefined}
|
||||||
|
onPointerMove={supportsPointerEvents ? handleOrderPointerMove : undefined}
|
||||||
|
onPointerUp={supportsPointerEvents ? handleOrderPointerUp : undefined}
|
||||||
|
onPointerCancel={supportsPointerEvents ? handleOrderPointerCancel : undefined}
|
||||||
|
onTouchStart={!supportsPointerEvents ? (e) => handleOrderTouchStart(e, moduleId) : undefined}
|
||||||
|
>
|
||||||
<span className="material-symbols-outlined">drag_indicator</span>
|
<span className="material-symbols-outlined">drag_indicator</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -606,7 +625,6 @@ export default function Features() {
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="order-cards"
|
className="order-cards"
|
||||||
data-swipe-ignore="true"
|
|
||||||
ref={bottomOrderRef}
|
ref={bottomOrderRef}
|
||||||
>
|
>
|
||||||
{bottomOrderModules.map((moduleId) => {
|
{bottomOrderModules.map((moduleId) => {
|
||||||
@@ -617,11 +635,6 @@ export default function Features() {
|
|||||||
key={moduleId}
|
key={moduleId}
|
||||||
className={`order-card ${draggedItem === moduleId ? 'dragging' : ''}`}
|
className={`order-card ${draggedItem === moduleId ? 'dragging' : ''}`}
|
||||||
data-module-id={moduleId}
|
data-module-id={moduleId}
|
||||||
onPointerDown={supportsPointerEvents ? (e) => handleOrderPointerDown(e, moduleId) : undefined}
|
|
||||||
onPointerMove={supportsPointerEvents ? handleOrderPointerMove : undefined}
|
|
||||||
onPointerUp={supportsPointerEvents ? handleOrderPointerUp : undefined}
|
|
||||||
onPointerCancel={supportsPointerEvents ? handleOrderPointerCancel : undefined}
|
|
||||||
onTouchStart={!supportsPointerEvents ? (e) => handleOrderTouchStart(e, moduleId) : undefined}
|
|
||||||
>
|
>
|
||||||
<div className="order-card-preview">
|
<div className="order-card-preview">
|
||||||
<span className="material-symbols-outlined">{moduleInfo.icon}</span>
|
<span className="material-symbols-outlined">{moduleInfo.icon}</span>
|
||||||
@@ -629,7 +642,15 @@ export default function Features() {
|
|||||||
<div className="order-card-info">
|
<div className="order-card-info">
|
||||||
<span className="order-card-name">{moduleName}</span>
|
<span className="order-card-name">{moduleName}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="order-card-handle">
|
<div
|
||||||
|
className="order-card-handle"
|
||||||
|
data-swipe-ignore="true"
|
||||||
|
onPointerDown={supportsPointerEvents ? (e) => handleOrderPointerDown(e, moduleId) : undefined}
|
||||||
|
onPointerMove={supportsPointerEvents ? handleOrderPointerMove : undefined}
|
||||||
|
onPointerUp={supportsPointerEvents ? handleOrderPointerUp : undefined}
|
||||||
|
onPointerCancel={supportsPointerEvents ? handleOrderPointerCancel : undefined}
|
||||||
|
onTouchStart={!supportsPointerEvents ? (e) => handleOrderTouchStart(e, moduleId) : undefined}
|
||||||
|
>
|
||||||
<span className="material-symbols-outlined">drag_indicator</span>
|
<span className="material-symbols-outlined">drag_indicator</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -291,8 +291,16 @@ export default function ThemeSettings() {
|
|||||||
{colors.map((color) => (
|
{colors.map((color) => (
|
||||||
<div
|
<div
|
||||||
key={color.id}
|
key={color.id}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
className={`color-card ${accentColor === color.id ? 'active' : ''}`}
|
className={`color-card ${accentColor === color.id ? 'active' : ''}`}
|
||||||
onClick={() => handleAccentColorChange(color.id)}
|
onClick={() => handleAccentColorChange(color.id)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
handleAccentColorChange(color.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="color-swatch-large" style={{ backgroundColor: color.value }}>
|
<div className="color-swatch-large" style={{ backgroundColor: color.value }}>
|
||||||
{accentColor === color.id && (
|
{accentColor === color.id && (
|
||||||
@@ -318,8 +326,16 @@ export default function ThemeSettings() {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={palette.id}
|
key={palette.id}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
className={`palette-card ${colorPalette === palette.id ? 'active' : ''}`}
|
className={`palette-card ${colorPalette === palette.id ? 'active' : ''}`}
|
||||||
onClick={() => handleColorPaletteChange(palette.id)}
|
onClick={() => handleColorPaletteChange(palette.id)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
handleColorPaletteChange(palette.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="palette-preview">
|
<div className="palette-preview">
|
||||||
<div className="palette-swatch-row">
|
<div className="palette-swatch-row">
|
||||||
@@ -363,8 +379,16 @@ export default function ThemeSettings() {
|
|||||||
{radii.map((radius) => (
|
{radii.map((radius) => (
|
||||||
<div
|
<div
|
||||||
key={radius.id}
|
key={radius.id}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
className={`option-card ${borderRadius === radius.id ? 'active' : ''}`}
|
className={`option-card ${borderRadius === radius.id ? 'active' : ''}`}
|
||||||
onClick={() => handleBorderRadiusChange(radius.id)}
|
onClick={() => handleBorderRadiusChange(radius.id)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
handleBorderRadiusChange(radius.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="option-preview">
|
<div className="option-preview">
|
||||||
<div
|
<div
|
||||||
@@ -389,8 +413,16 @@ export default function ThemeSettings() {
|
|||||||
{sidebarStyles.map((style) => (
|
{sidebarStyles.map((style) => (
|
||||||
<div
|
<div
|
||||||
key={style.id}
|
key={style.id}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
className={`option-card ${sidebarStyle === style.id ? 'active' : ''}`}
|
className={`option-card ${sidebarStyle === style.id ? 'active' : ''}`}
|
||||||
onClick={() => handleSidebarStyleChange(style.id)}
|
onClick={() => handleSidebarStyleChange(style.id)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
handleSidebarStyleChange(style.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="option-preview">
|
<div className="option-preview">
|
||||||
<div className={`sidebar-preview sidebar-preview-${style.id}`}>
|
<div className={`sidebar-preview sidebar-preview-${style.id}`}>
|
||||||
@@ -415,8 +447,16 @@ export default function ThemeSettings() {
|
|||||||
{sidebarModes.map((mode) => (
|
{sidebarModes.map((mode) => (
|
||||||
<div
|
<div
|
||||||
key={mode.id}
|
key={mode.id}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
className={`option-card ${sidebarMode === mode.id ? 'active' : ''}`}
|
className={`option-card ${sidebarMode === mode.id ? 'active' : ''}`}
|
||||||
onClick={() => setSidebarMode(mode.id)}
|
onClick={() => setSidebarMode(mode.id)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
setSidebarMode(mode.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="option-preview">
|
<div className="option-preview">
|
||||||
<div className={`sidebar-mode-preview sidebar-mode-${mode.id}`}>
|
<div className={`sidebar-mode-preview sidebar-mode-${mode.id}`}>
|
||||||
@@ -441,8 +481,16 @@ export default function ThemeSettings() {
|
|||||||
{densities.map((d) => (
|
{densities.map((d) => (
|
||||||
<div
|
<div
|
||||||
key={d.id}
|
key={d.id}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
className={`option-card ${density === d.id ? 'active' : ''}`}
|
className={`option-card ${density === d.id ? 'active' : ''}`}
|
||||||
onClick={() => handleDensityChange(d.id)}
|
onClick={() => handleDensityChange(d.id)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
handleDensityChange(d.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="option-preview">
|
<div className="option-preview">
|
||||||
<div className={`density-preview density-preview-${d.id}`}>
|
<div className={`density-preview density-preview-${d.id}`}>
|
||||||
@@ -468,8 +516,16 @@ export default function ThemeSettings() {
|
|||||||
{fonts.map((f) => (
|
{fonts.map((f) => (
|
||||||
<div
|
<div
|
||||||
key={f.id}
|
key={f.id}
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
className={`option-card ${fontFamily === f.id ? 'active' : ''}`}
|
className={`option-card ${fontFamily === f.id ? 'active' : ''}`}
|
||||||
onClick={() => handleFontFamilyChange(f.id)}
|
onClick={() => handleFontFamilyChange(f.id)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
handleFontFamilyChange(f.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="option-preview">
|
<div className="option-preview">
|
||||||
<div className="font-preview" style={{ fontFamily: f.fontStyle }}>
|
<div className="font-preview" style={{ fontFamily: f.fontStyle }}>
|
||||||
|
|||||||
@@ -2350,22 +2350,9 @@
|
|||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Admin Tab Tooltip - Mobile Only */
|
/* Admin Tab Tooltip - Disabled on mobile */
|
||||||
.admin-tab-tooltip {
|
.admin-tab-tooltip {
|
||||||
display: block;
|
display: none;
|
||||||
position: fixed;
|
|
||||||
top: calc(var(--tabs-height, 56px) + 8px);
|
|
||||||
transform: translateX(-50%);
|
|
||||||
background: var(--color-bg-elevated);
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
padding: 6px 12px;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
font-size: 0.8rem;
|
|
||||||
white-space: nowrap;
|
|
||||||
z-index: 1000;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,14 @@ label,
|
|||||||
-ms-overflow-style: none;
|
-ms-overflow-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@supports (color: color-mix(in srgb, black, transparent)) {
|
||||||
|
.page-tabs-slider,
|
||||||
|
.admin-tabs-slider {
|
||||||
|
background: color-mix(in srgb, var(--color-bg-elevated) 75%, transparent);
|
||||||
|
backdrop-filter: blur(20px) saturate(1.15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.page-tabs-slider::-webkit-scrollbar,
|
.page-tabs-slider::-webkit-scrollbar,
|
||||||
.admin-tabs-slider::-webkit-scrollbar {
|
.admin-tabs-slider::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -791,7 +799,8 @@ label,
|
|||||||
@supports (color: color-mix(in srgb, black, white)) {
|
@supports (color: color-mix(in srgb, black, white)) {
|
||||||
[data-theme='dark'] .page-tabs-slider,
|
[data-theme='dark'] .page-tabs-slider,
|
||||||
[data-theme='dark'] .admin-tabs-slider {
|
[data-theme='dark'] .admin-tabs-slider {
|
||||||
background: color-mix(in srgb, var(--color-bg-sidebar) 95%, transparent);
|
background: color-mix(in srgb, var(--color-bg-sidebar) 72%, transparent);
|
||||||
|
backdrop-filter: blur(24px) saturate(1.2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,14 +27,14 @@
|
|||||||
|
|
||||||
@supports (color: color-mix(in srgb, black, transparent)) {
|
@supports (color: color-mix(in srgb, black, transparent)) {
|
||||||
.sidebar {
|
.sidebar {
|
||||||
background: color-mix(in srgb, var(--color-bg-sidebar) 88%, transparent);
|
background: color-mix(in srgb, var(--color-bg-sidebar) 72%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Avoid backdrop-filter on collapsed sidebar: it can break fixed-position dropdowns */
|
/* Avoid backdrop-filter on collapsed sidebar: it breaks fixed-position dropdowns */
|
||||||
.sidebar:not(.collapsed),
|
.sidebar:not(.collapsed),
|
||||||
.sidebar.dynamic.collapsed.expanded-force,
|
.sidebar.dynamic.collapsed.expanded-force,
|
||||||
.sidebar.open {
|
.sidebar.open {
|
||||||
backdrop-filter: blur(18px) saturate(1.15);
|
backdrop-filter: blur(24px) saturate(1.2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -666,10 +666,6 @@ button.nav-item {
|
|||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
/* Rounded on mobile */
|
/* Rounded on mobile */
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background: var(--color-bg-sidebar);
|
|
||||||
/* Use theme color */
|
|
||||||
backdrop-filter: blur(12px);
|
|
||||||
/* Blur effect */
|
|
||||||
border: 1px solid var(--color-sidebar-border);
|
border: 1px solid var(--color-sidebar-border);
|
||||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
@@ -1027,7 +1023,7 @@ button.nav-item {
|
|||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: 1px solid transparent;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
@@ -1059,10 +1055,17 @@ button.nav-item {
|
|||||||
color: var(--color-accent);
|
color: var(--color-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Light theme: make active menu item more visible */
|
||||||
|
[data-theme='light'] .user-menu-item.active {
|
||||||
|
background: rgba(var(--color-accent-rgb), 0.15);
|
||||||
|
border: 1px solid rgba(var(--color-accent-rgb), 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
/* Auto accent override for active menu item */
|
/* Auto accent override for active menu item */
|
||||||
[data-accent='auto'] .user-menu-item.active {
|
[data-accent='auto'] .user-menu-item.active {
|
||||||
background: #f3f4f6;
|
background: #f3f4f6;
|
||||||
color: #111827;
|
color: #111827;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-accent='auto'] .user-menu-item.active .material-symbols-outlined {
|
[data-accent='auto'] .user-menu-item.active .material-symbols-outlined {
|
||||||
|
|||||||
Reference in New Issue
Block a user