mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 22:01:09 +02:00
feat(command-bar): match priority highlight colors to UI
Priority keywords now show their actual UI colors: - Dringend (urgent): red #ef4444 - Wichtig (high): orange #f97316 - Normal (medium): yellow #eab308 - Später (low): green #22c55e 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
d9626a9d8f
commit
aa117c51cd
12 changed files with 551 additions and 92 deletions
|
|
@ -15,6 +15,8 @@ export interface AppRoute {
|
|||
labelKey: string;
|
||||
/** Optional icon name */
|
||||
icon?: string;
|
||||
/** If true, this route cannot be hidden (e.g., Settings, Home) */
|
||||
alwaysVisible?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -199,3 +201,46 @@ export function getAvailableRoutes(appId: string): AppRoute[] {
|
|||
export function getDefaultRoute(appId: string): string {
|
||||
return APP_ROUTES[appId]?.defaultRoute ?? '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter hidden navigation items from a list of nav items
|
||||
* @param appId The app identifier
|
||||
* @param items Array of nav items with href property
|
||||
* @param hiddenNavItems Hidden items config (appId -> hidden paths)
|
||||
* @returns Filtered array with hidden items removed
|
||||
*/
|
||||
export function filterHiddenNavItems<T extends { href: string }>(
|
||||
appId: string,
|
||||
items: T[],
|
||||
hiddenNavItems: Record<string, string[]> = {}
|
||||
): T[] {
|
||||
const hidden = hiddenNavItems[appId] || [];
|
||||
return items.filter((item) => !hidden.includes(item.href));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get routes that can be hidden for a specific app
|
||||
* (excludes routes marked as alwaysVisible)
|
||||
* @param appId The app identifier
|
||||
* @returns Array of routes that can be hidden
|
||||
*/
|
||||
export function getHideableRoutes(appId: string): AppRoute[] {
|
||||
const config = APP_ROUTES[appId];
|
||||
return config?.availableRoutes.filter((r) => !r.alwaysVisible) || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a route is hidden for a specific app
|
||||
* @param appId The app identifier
|
||||
* @param path The route path
|
||||
* @param hiddenNavItems Hidden items config
|
||||
* @returns True if the route is hidden
|
||||
*/
|
||||
export function isRouteHidden(
|
||||
appId: string,
|
||||
path: string,
|
||||
hiddenNavItems: Record<string, string[]> = {}
|
||||
): boolean {
|
||||
const hidden = hiddenNavItems[appId] || [];
|
||||
return hidden.includes(path);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,4 +117,12 @@ export {
|
|||
|
||||
// App Routes
|
||||
export type { AppRoute, AppRouteConfig } from './app-routes';
|
||||
export { APP_ROUTES, getStartPage, getAvailableRoutes, getDefaultRoute } from './app-routes';
|
||||
export {
|
||||
APP_ROUTES,
|
||||
getStartPage,
|
||||
getAvailableRoutes,
|
||||
getDefaultRoute,
|
||||
filterHiddenNavItems,
|
||||
getHideableRoutes,
|
||||
isRouteHidden,
|
||||
} from './app-routes';
|
||||
|
|
|
|||
|
|
@ -240,6 +240,8 @@ export interface NavSettings {
|
|||
desktopPosition: NavPosition;
|
||||
/** Whether sidebar is collapsed */
|
||||
sidebarCollapsed: boolean;
|
||||
/** Hidden navigation items per app (appId -> list of hidden paths) */
|
||||
hiddenNavItems?: Record<string, string[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -323,7 +325,7 @@ export const DEFAULT_GENERAL_SETTINGS: GeneralSettings = {
|
|||
* Default global settings
|
||||
*/
|
||||
export const DEFAULT_GLOBAL_SETTINGS: GlobalSettings = {
|
||||
nav: { desktopPosition: 'top', sidebarCollapsed: false },
|
||||
nav: { desktopPosition: 'top', sidebarCollapsed: false, hiddenNavItems: {} },
|
||||
theme: { mode: 'system', colorScheme: 'ocean', pinnedThemes: [] },
|
||||
locale: 'de',
|
||||
general: DEFAULT_GENERAL_SETTINGS,
|
||||
|
|
@ -364,6 +366,12 @@ export interface UserSettingsStore {
|
|||
setStartPage: (appId: string, path: string) => Promise<void>;
|
||||
/** Update general settings */
|
||||
updateGeneral: (settings: Partial<GeneralSettings>) => Promise<void>;
|
||||
/** Get hidden nav items for a specific app */
|
||||
getHiddenNavItemsForApp: (appId: string) => string[];
|
||||
/** Toggle visibility of a navigation item */
|
||||
toggleNavItemVisibility: (appId: string, href: string) => Promise<void>;
|
||||
/** Set hidden nav items for an app */
|
||||
setHiddenNavItems: (appId: string, hiddenHrefs: string[]) => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -314,6 +314,46 @@ export function createUserSettingsStore(config: UserSettingsStoreConfig): UserSe
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get hidden nav items for a specific app
|
||||
*/
|
||||
function getHiddenNavItemsForApp(targetAppId: string): string[] {
|
||||
return globalSettings.nav.hiddenNavItems?.[targetAppId] || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle visibility of a navigation item for an app
|
||||
*/
|
||||
async function toggleNavItemVisibility(targetAppId: string, href: string): Promise<void> {
|
||||
const currentHidden = getHiddenNavItemsForApp(targetAppId);
|
||||
const isHidden = currentHidden.includes(href);
|
||||
|
||||
const newHidden = isHidden ? currentHidden.filter((h) => h !== href) : [...currentHidden, href];
|
||||
|
||||
await setHiddenNavItems(targetAppId, newHidden);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set hidden nav items for an app
|
||||
*/
|
||||
async function setHiddenNavItems(targetAppId: string, hiddenHrefs: string[]): Promise<void> {
|
||||
const newHiddenNavItems = {
|
||||
...globalSettings.nav.hiddenNavItems,
|
||||
[targetAppId]: hiddenHrefs,
|
||||
};
|
||||
|
||||
// Remove empty arrays
|
||||
if (hiddenHrefs.length === 0) {
|
||||
delete newHiddenNavItems[targetAppId];
|
||||
}
|
||||
|
||||
await updateGlobal({
|
||||
nav: {
|
||||
hiddenNavItems: newHiddenNavItems,
|
||||
},
|
||||
} as Partial<GlobalSettings>);
|
||||
}
|
||||
|
||||
return {
|
||||
get nav() {
|
||||
return nav;
|
||||
|
|
@ -349,5 +389,8 @@ export function createUserSettingsStore(config: UserSettingsStoreConfig): UserSe
|
|||
removeAppOverride,
|
||||
setStartPage,
|
||||
updateGeneral,
|
||||
getHiddenNavItemsForApp,
|
||||
toggleNavItemVisibility,
|
||||
setHiddenNavItems,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@
|
|||
}
|
||||
|
||||
const HIGHLIGHT_PATTERNS: HighlightPattern[] = [
|
||||
// Priority keywords (Todo)
|
||||
{
|
||||
pattern: /(!{1,3}|!?dringend|!?wichtig|!?normal|!?später|!?späer)\b/gi,
|
||||
className: 'hl-priority',
|
||||
},
|
||||
// Priority keywords (Todo) - with specific colors per level
|
||||
{ pattern: /(!{3,}|!?dringend)\b/gi, className: 'hl-priority-urgent' },
|
||||
{ pattern: /(!{2}|!?wichtig)\b/gi, className: 'hl-priority-high' },
|
||||
{ pattern: /!?normal\b/gi, className: 'hl-priority-medium' },
|
||||
{ pattern: /!?sp[aä]ter\b/gi, className: 'hl-priority-low' },
|
||||
// Tags
|
||||
{ pattern: /#\w+/g, className: 'hl-tag' },
|
||||
// Projects/Calendars/Companies (@reference)
|
||||
|
|
@ -593,9 +593,24 @@
|
|||
color: hsl(var(--color-muted-foreground));
|
||||
}
|
||||
|
||||
/* Syntax highlighting colors */
|
||||
.input-highlight-backdrop :global(.hl-priority) {
|
||||
color: hsl(var(--color-warning, 38 92% 50%));
|
||||
/* Syntax highlighting colors - Priority levels with matching UI colors */
|
||||
.input-highlight-backdrop :global(.hl-priority-urgent) {
|
||||
color: #ef4444; /* red - Dringend */
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.input-highlight-backdrop :global(.hl-priority-high) {
|
||||
color: #f97316; /* orange - Wichtig */
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.input-highlight-backdrop :global(.hl-priority-medium) {
|
||||
color: #eab308; /* yellow - Normal */
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.input-highlight-backdrop :global(.hl-priority-low) {
|
||||
color: #22c55e; /* green - Später */
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import { getAvailableRoutes, getDefaultRoute } from '@manacore/shared-theme';
|
||||
import SettingsSection from './SettingsSection.svelte';
|
||||
import SettingsCard from './SettingsCard.svelte';
|
||||
import NavVisibilitySettings from './NavVisibilitySettings.svelte';
|
||||
|
||||
interface Props {
|
||||
/** User settings store instance */
|
||||
|
|
@ -16,6 +17,8 @@
|
|||
appId?: string;
|
||||
/** Whether to show navigation settings */
|
||||
showNavigation?: boolean;
|
||||
/** Whether to show nav visibility settings */
|
||||
showNavVisibility?: boolean;
|
||||
/** Whether to show theme settings */
|
||||
showTheme?: boolean;
|
||||
/** Whether to show language settings */
|
||||
|
|
@ -34,6 +37,7 @@
|
|||
userSettings,
|
||||
appId,
|
||||
showNavigation = true,
|
||||
showNavVisibility = true,
|
||||
showTheme = true,
|
||||
showLanguage = true,
|
||||
showGeneral = true,
|
||||
|
|
@ -205,10 +209,21 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
{#if showNavVisibility && appId}
|
||||
<!-- Navigation Visibility Settings -->
|
||||
<div
|
||||
class="space-y-4 {showNavigation ? 'pt-4 border-t border-[hsl(var(--border))]' : ''}"
|
||||
>
|
||||
<NavVisibilitySettings {userSettings} {appId} {t} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if showTheme}
|
||||
<!-- Theme Settings -->
|
||||
<div
|
||||
class="space-y-4 {showNavigation ? 'pt-4 border-t border-[hsl(var(--border))]' : ''}"
|
||||
class="space-y-4 {showNavigation || (showNavVisibility && appId)
|
||||
? 'pt-4 border-t border-[hsl(var(--border))]'
|
||||
: ''}"
|
||||
>
|
||||
<h3
|
||||
class="text-xs font-semibold text-[hsl(var(--muted-foreground))] uppercase tracking-wider"
|
||||
|
|
@ -266,7 +281,7 @@
|
|||
{#if showLanguage}
|
||||
<!-- Language Settings -->
|
||||
<div
|
||||
class="space-y-4 {showTheme || showNavigation
|
||||
class="space-y-4 {showTheme || showNavigation || (showNavVisibility && appId)
|
||||
? 'pt-4 border-t border-[hsl(var(--border))]'
|
||||
: ''}"
|
||||
>
|
||||
|
|
@ -303,7 +318,10 @@
|
|||
{#if showGeneral}
|
||||
<!-- General Settings -->
|
||||
<div
|
||||
class="space-y-4 {showLanguage || showTheme || showNavigation
|
||||
class="space-y-4 {showLanguage ||
|
||||
showTheme ||
|
||||
showNavigation ||
|
||||
(showNavVisibility && appId)
|
||||
? 'pt-4 border-t border-[hsl(var(--border))]'
|
||||
: ''}"
|
||||
>
|
||||
|
|
|
|||
161
packages/shared-ui/src/settings/NavVisibilitySettings.svelte
Normal file
161
packages/shared-ui/src/settings/NavVisibilitySettings.svelte
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
<script lang="ts">
|
||||
import type { UserSettingsStore, AppRoute } from '@manacore/shared-theme';
|
||||
import { getHideableRoutes, APP_ROUTES } from '@manacore/shared-theme';
|
||||
|
||||
interface Props {
|
||||
/** User settings store instance */
|
||||
userSettings: UserSettingsStore;
|
||||
/** Current app ID */
|
||||
appId: string;
|
||||
/** Translation function (optional, falls back to German) */
|
||||
t?: (key: string) => string;
|
||||
}
|
||||
|
||||
let { userSettings, appId, t = (key: string) => key }: Props = $props();
|
||||
|
||||
// Get all apps that have configurable routes
|
||||
const configurableApps = $derived(
|
||||
Object.entries(APP_ROUTES)
|
||||
.filter(([, config]) => {
|
||||
const hideableRoutes = config.availableRoutes.filter((r) => !r.alwaysVisible);
|
||||
return hideableRoutes.length > 0;
|
||||
})
|
||||
.map(([id, config]) => ({
|
||||
id,
|
||||
label: getAppLabel(id),
|
||||
routes: config.availableRoutes.filter((r) => !r.alwaysVisible),
|
||||
}))
|
||||
);
|
||||
|
||||
// Sort so current app is first
|
||||
const sortedApps = $derived(
|
||||
[...configurableApps].sort((a, b) => {
|
||||
if (a.id === appId) return -1;
|
||||
if (b.id === appId) return 1;
|
||||
return a.label.localeCompare(b.label);
|
||||
})
|
||||
);
|
||||
|
||||
function getAppLabel(id: string): string {
|
||||
const labels: Record<string, string> = {
|
||||
clock: 'Uhr',
|
||||
calendar: 'Kalender',
|
||||
contacts: 'Kontakte',
|
||||
mail: 'Mail',
|
||||
todo: 'Aufgaben',
|
||||
storage: 'Speicher',
|
||||
chat: 'Chat',
|
||||
picture: 'Bilder',
|
||||
manadeck: 'ManaDeck',
|
||||
zitare: 'Zitare',
|
||||
presi: 'Präsentation',
|
||||
manacore: 'ManaCore',
|
||||
};
|
||||
return labels[id] || id;
|
||||
}
|
||||
|
||||
function isRouteHidden(targetAppId: string, path: string): boolean {
|
||||
const hidden = userSettings.getHiddenNavItemsForApp(targetAppId);
|
||||
return hidden.includes(path);
|
||||
}
|
||||
|
||||
async function handleToggle(targetAppId: string, path: string): Promise<void> {
|
||||
await userSettings.toggleNavItemVisibility(targetAppId, path);
|
||||
}
|
||||
|
||||
// Expanded state per app
|
||||
let expandedApps = $state<Record<string, boolean>>({});
|
||||
|
||||
// Initialize with current app expanded
|
||||
$effect(() => {
|
||||
if (appId && expandedApps[appId] === undefined) {
|
||||
expandedApps[appId] = true;
|
||||
}
|
||||
});
|
||||
|
||||
function toggleApp(id: string): void {
|
||||
expandedApps[id] = !expandedApps[id];
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<h3 class="text-xs font-semibold text-[hsl(var(--muted-foreground))] uppercase tracking-wider">
|
||||
Navigation anpassen
|
||||
</h3>
|
||||
<p class="text-sm text-[hsl(var(--muted-foreground))] mt-1">
|
||||
Versteckte Seiten bleiben über die URL erreichbar
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
{#each sortedApps as app (app.id)}
|
||||
<div class="border border-[hsl(var(--border))] rounded-lg overflow-hidden">
|
||||
<!-- App Header (collapsible) -->
|
||||
<button
|
||||
type="button"
|
||||
class="w-full flex items-center justify-between px-4 py-3 bg-[hsl(var(--muted))] hover:bg-[hsl(var(--muted))]/80 transition-colors"
|
||||
onclick={() => toggleApp(app.id)}
|
||||
>
|
||||
<span class="font-medium text-[hsl(var(--foreground))] flex items-center gap-2">
|
||||
{app.label}
|
||||
{#if app.id === appId}
|
||||
<span
|
||||
class="text-xs px-1.5 py-0.5 rounded bg-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))]"
|
||||
>
|
||||
Aktuell
|
||||
</span>
|
||||
{/if}
|
||||
</span>
|
||||
<svg
|
||||
class="w-5 h-5 text-[hsl(var(--muted-foreground))] transition-transform {expandedApps[
|
||||
app.id
|
||||
]
|
||||
? 'rotate-180'
|
||||
: ''}"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Routes List (collapsible) -->
|
||||
{#if expandedApps[app.id]}
|
||||
<div class="p-3 space-y-1 bg-[hsl(var(--background))]">
|
||||
{#each app.routes as route (route.path)}
|
||||
{@const hidden = isRouteHidden(app.id, route.path)}
|
||||
<label
|
||||
class="flex items-center justify-between py-2 px-3 rounded-lg hover:bg-[hsl(var(--muted))]/50 cursor-pointer transition-colors"
|
||||
>
|
||||
<span
|
||||
class="text-sm {hidden
|
||||
? 'text-[hsl(var(--muted-foreground))] line-through'
|
||||
: 'text-[hsl(var(--foreground))]'}"
|
||||
>
|
||||
{t(route.labelKey)}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="relative inline-flex h-5 w-9 items-center rounded-full transition-colors {!hidden
|
||||
? 'bg-[hsl(var(--primary))]'
|
||||
: 'bg-gray-200 dark:bg-gray-700'}"
|
||||
onclick={() => handleToggle(app.id, route.path)}
|
||||
aria-label={hidden ? 'Einblenden' : 'Ausblenden'}
|
||||
>
|
||||
<span
|
||||
class="inline-block h-3.5 w-3.5 transform rounded-full bg-white transition-transform shadow-sm {!hidden
|
||||
? 'translate-x-5'
|
||||
: 'translate-x-0.5'}"
|
||||
></span>
|
||||
</button>
|
||||
</label>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -10,3 +10,4 @@ export { default as SettingsTimeInput } from './SettingsTimeInput.svelte';
|
|||
export { default as SettingsDangerZone } from './SettingsDangerZone.svelte';
|
||||
export { default as SettingsDangerButton } from './SettingsDangerButton.svelte';
|
||||
export { default as GlobalSettingsSection } from './GlobalSettingsSection.svelte';
|
||||
export { default as NavVisibilitySettings } from './NavVisibilitySettings.svelte';
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue