* ui : open MCP servers in a dialog from the chat form Replace the MCP servers submenu with a single "MCP Servers" item that opens a new DialogMcpServers dialog instead of navigating to the /mcp-servers route. Assisted-by: pi * ui : browse MCP resources from the server card Make the Resources capability badge clickable so it opens the MCP resources browser dialog, and drop the page-only chrome from SettingsMcpServers. Assisted-by: pi * ui : remove mcp-servers route and sidebar entry MCP servers are now managed in a dialog, so drop the dedicated route and the sidebar icon that navigated to it. Assisted-by: pi * ui : remove unused MCP servers submenu component The submenu was replaced by the MCP servers dialog, so delete the component and its export. Assisted-by: pi * feat(ui): add DialogSettingsChat dialog * refactor(ui): switch SettingsChat to in-app section navigation * feat(ui): open settings as dialog from sidebar * refactor(ui): remove settings route and URL-based settings navigation * fix(ui): adjust MCP dialogs for new base sizing * chore: Formatting & linting
62 lines
1.9 KiB
Svelte
62 lines
1.9 KiB
Svelte
<script lang="ts">
|
|
import { ScrollCarousel } from '$lib/components/app';
|
|
import { ICON_CLASS_DEFAULT, UI_DATA_ATTRS } from '$lib/constants';
|
|
import { BooleanString } from '$lib/enums';
|
|
import { useScrollCarousel } from '$lib/hooks/use-scroll-carousel.svelte';
|
|
import type { SettingsSection, SettingsSectionTitle } from '$lib/types';
|
|
import { onMount, tick } from 'svelte';
|
|
|
|
interface Props {
|
|
sections: SettingsSection[];
|
|
isActive: (section: SettingsSection) => boolean;
|
|
onSectionChange?: (section: SettingsSectionTitle) => void;
|
|
}
|
|
|
|
let { isActive, onSectionChange, sections }: Props = $props();
|
|
|
|
const carousel = useScrollCarousel();
|
|
|
|
onMount(async () => {
|
|
await tick();
|
|
|
|
if (carousel.scrollContainer) {
|
|
const activeTab = carousel.scrollContainer.querySelector(
|
|
`[${UI_DATA_ATTRS.ACTIVE}="${BooleanString.TRUE}"]`
|
|
);
|
|
|
|
if (activeTab instanceof HTMLElement) {
|
|
carousel.scrollToCenter(activeTab);
|
|
}
|
|
}
|
|
});
|
|
|
|
export function updateCarousel() {
|
|
setTimeout(carousel.updateScrollButtons, 100);
|
|
}
|
|
</script>
|
|
|
|
<div class="flex flex-col bg-background md:hidden sticky top-13 z-50">
|
|
<div class="border-b border-border/30">
|
|
<ScrollCarousel alwaysShowArrows {carousel} containerClass="py-2" innerClass="gap-2">
|
|
{#each sections as section (section.title)}
|
|
<button
|
|
class="flex cursor-pointer items-center gap-2 rounded-lg px-3 py-2 text-sm whitespace-nowrap transition-colors first:ml-4 last:mr-4 hover:bg-accent {isActive(
|
|
section
|
|
)
|
|
? 'bg-accent text-accent-foreground'
|
|
: 'text-muted-foreground'}"
|
|
{...{ [UI_DATA_ATTRS.ACTIVE]: isActive(section) }}
|
|
onclick={(e: MouseEvent) => {
|
|
onSectionChange?.(section.title);
|
|
carousel.scrollToCenter(e.currentTarget as HTMLElement);
|
|
}}
|
|
>
|
|
<section.icon class="{ICON_CLASS_DEFAULT} flex-shrink-0" />
|
|
|
|
<span>{section.title}</span>
|
|
</button>
|
|
{/each}
|
|
</ScrollCarousel>
|
|
</div>
|
|
</div>
|