Skip to content

Commit df993fc

Browse files
committed
Category-based homepage layout
1 parent c4a9eab commit df993fc

14 files changed

Lines changed: 1020 additions & 155 deletions

src/lib/components/furniture/SettingsMenu.svelte

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import { browser } from '$app/environment';
88
import { theme, themes, type ThemeOption, type Theme } from '$lib/stores/theme';
99
import { navbarDisplay, navbarDisplayOptions, type NavbarDisplayMode } from '$lib/stores/navbarDisplay';
10+
import { homepageLayout, homepageLayoutOptions, type HomepageLayoutMode } from '$lib/stores/homepageLayout';
1011
import { site } from '$lib/constants/site';
1112
import { resolve } from '$app/paths';
1213
@@ -24,6 +25,7 @@
2425
let accessibilitySettings = $state(accessibility);
2526
let currentTheme = $state(theme);
2627
let currentNavbarDisplay = $state(navbarDisplay);
28+
let currentHomepageLayout = $state(homepageLayout);
2729
2830
// Shortcut key detection
2931
const isMac = browser && navigator.platform.toUpperCase().indexOf('MAC') >= 0;
@@ -54,6 +56,12 @@
5456
navbarDisplay.setMode(target.value as NavbarDisplayMode);
5557
}
5658
59+
// Handle homepage layout mode change
60+
function handleHomepageLayoutChange(event: Event) {
61+
const target = event.currentTarget as HTMLSelectElement;
62+
homepageLayout.setMode(target.value as HomepageLayoutMode);
63+
}
64+
5765
// Primary accessibility options (always visible)
5866
const primaryA11yOptions = ['reduce-motion'];
5967
// const primaryA11yOptions: string[] = [];
@@ -108,6 +116,7 @@
108116
accessibility.init();
109117
theme.init();
110118
navbarDisplay.init();
119+
homepageLayout.init();
111120
112121
return () => {
113122
document.removeEventListener('click', handleClickOutside);
@@ -236,6 +245,24 @@
236245
</small>
237246
</div>
238247

248+
<!-- Homepage Layout -->
249+
<div class="settings-section">
250+
<h3>Homepage Layout</h3>
251+
<div class="navbar-select-wrapper">
252+
<select class="navbar-select" value={$currentHomepageLayout} onchange={handleHomepageLayoutChange}>
253+
{#each homepageLayoutOptions as option (option.id)}
254+
<option value={option.id}>{option.name}</option>
255+
{/each}
256+
</select>
257+
<div class="dropdown-icon">
258+
<Icon name="chevron-down" size="xs" />
259+
</div>
260+
</div>
261+
<small class="navbar-description">
262+
{homepageLayoutOptions.find((opt) => opt.id === $currentHomepageLayout)?.description}
263+
</small>
264+
</div>
265+
239266
<!-- Accessibility Options -->
240267
<div class="settings-section">
241268
<h3>Accessibility</h3>
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
<script lang="ts">
2+
import { onMount } from 'svelte';
3+
import Icon from './Icon.svelte';
4+
5+
export let x: number = 0;
6+
export let y: number = 0;
7+
export let items: Array<{
8+
label: string;
9+
icon: string;
10+
action: () => void;
11+
condition?: boolean;
12+
}> = [];
13+
export let onClose: () => void;
14+
15+
let menuElement: HTMLDivElement;
16+
17+
onMount(() => {
18+
// Position the menu, ensuring it stays within viewport
19+
if (menuElement) {
20+
const rect = menuElement.getBoundingClientRect();
21+
const viewportWidth = window.innerWidth;
22+
const viewportHeight = window.innerHeight;
23+
24+
// Adjust horizontal position if menu would overflow
25+
if (x + rect.width > viewportWidth) {
26+
x = viewportWidth - rect.width - 10;
27+
}
28+
29+
// Adjust vertical position if menu would overflow
30+
if (y + rect.height > viewportHeight) {
31+
y = viewportHeight - rect.height - 10;
32+
}
33+
}
34+
35+
// Close on click outside
36+
const handleClickOutside = (e: MouseEvent) => {
37+
if (menuElement && !menuElement.contains(e.target as Node)) {
38+
onClose();
39+
}
40+
};
41+
42+
// Close on Escape key
43+
const handleKeyDown = (e: KeyboardEvent) => {
44+
if (e.key === 'Escape') {
45+
onClose();
46+
}
47+
};
48+
49+
document.addEventListener('click', handleClickOutside);
50+
document.addEventListener('keydown', handleKeyDown);
51+
52+
return () => {
53+
document.removeEventListener('click', handleClickOutside);
54+
document.removeEventListener('keydown', handleKeyDown);
55+
};
56+
});
57+
58+
function handleItemClick(action: () => void) {
59+
action();
60+
onClose();
61+
}
62+
63+
// Filter items based on condition
64+
$: visibleItems = items.filter((item) => item.condition !== false);
65+
</script>
66+
67+
<div class="context-menu" bind:this={menuElement} style="left: {x}px; top: {y}px;">
68+
{#each visibleItems as item (item.label)}
69+
<button class="menu-item" onclick={() => handleItemClick(item.action)}>
70+
<Icon name={item.icon} size="sm" />
71+
<span>{item.label}</span>
72+
</button>
73+
{/each}
74+
</div>
75+
76+
<style lang="scss">
77+
.context-menu {
78+
position: fixed;
79+
z-index: 1000;
80+
background: var(--bg-secondary);
81+
border: 1px solid var(--border-primary);
82+
border-radius: var(--radius-md);
83+
box-shadow: var(--shadow-lg);
84+
padding: var(--spacing-xs);
85+
min-width: 12rem;
86+
animation: menuFadeIn 0.15s ease-out;
87+
}
88+
89+
.menu-item {
90+
width: 100%;
91+
display: flex;
92+
align-items: center;
93+
gap: var(--spacing-sm);
94+
padding: var(--spacing-sm) var(--spacing-md);
95+
background: none;
96+
border: none;
97+
border-radius: var(--radius-sm);
98+
color: var(--text-primary);
99+
font-size: var(--font-size-sm);
100+
text-align: left;
101+
cursor: pointer;
102+
transition: all var(--transition-fast);
103+
104+
&:hover {
105+
background: var(--surface-hover);
106+
color: var(--color-primary);
107+
}
108+
109+
&:active {
110+
transform: scale(0.98);
111+
}
112+
113+
:global(svg) {
114+
flex-shrink: 0;
115+
color: var(--text-secondary);
116+
transition: color var(--transition-fast);
117+
}
118+
119+
&:hover :global(svg) {
120+
color: var(--color-primary);
121+
}
122+
123+
span {
124+
flex: 1;
125+
}
126+
}
127+
128+
@keyframes menuFadeIn {
129+
from {
130+
opacity: 0;
131+
transform: translateY(-4px);
132+
}
133+
to {
134+
opacity: 1;
135+
transform: translateY(0);
136+
}
137+
}
138+
</style>

0 commit comments

Comments
 (0)