From 863f2967331a2f01b1ad037bf3e1e0a5982500c9 Mon Sep 17 00:00:00 2001 From: Till-JS <101404291+Till-JS@users.noreply.github.com> Date: Sun, 14 Dec 2025 16:14:30 +0100 Subject: [PATCH] feat(ui): add shared ExpandableToolbar and unify toolbar dropdowns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create ExpandableToolbar shared component in shared-ui - Migrate CalendarToolbar to use shared component (253 → 90 LOC) - Migrate ContactsToolbar to use shared component (177 → 31 LOC) - Add portal pattern to FilterBar dropdown for proper positioning - Unify FilterBar dropdown styling with ContextMenu design - Add fly transition with offset for smooth dropdown appearance - Add ContactsToolbarContent for toolbar content separation - Add filter store for contacts filter state management - Add NewContactModal component - Various contacts view improvements 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../calendar/CalendarToolbar.svelte | 240 +---- .../backend/src/contact/contact.controller.ts | 66 ++ .../backend/src/db/schema/contacts.schema.ts | 15 + .../contacts/apps/web/src/lib/api/contacts.ts | 14 + .../lib/components/ContactDetailModal.svelte | 16 + .../web/src/lib/components/ContactList.svelte | 121 +-- .../src/lib/components/ContactsToolbar.svelte | 280 +---- .../components/ContactsToolbarContent.svelte | 161 +++ .../web/src/lib/components/FilterBar.svelte | 159 ++- .../src/lib/components/NewContactModal.svelte | 961 ++++++++++++++++++ .../views/ContactAlphabetView.svelte | 386 ++++--- .../components/views/ContactGridView.svelte | 50 + .../components/views/ContactListView.svelte | 53 + .../apps/web/src/lib/stores/filter.svelte.ts | 146 +++ .../lib/stores/new-contact-modal.svelte.ts | 41 + .../apps/web/src/routes/(app)/+layout.svelte | 113 +- .../web/src/routes/(app)/network/+page.svelte | 19 +- packages/shared-ui/src/index.ts | 2 + .../ExpandableToolbar.svelte | 223 ++++ .../navigation/expandable-toolbar/index.ts | 2 + .../navigation/expandable-toolbar/types.ts | 28 + packages/shared-ui/src/navigation/index.ts | 2 + .../organisms/network/NetworkControls.svelte | 36 +- 23 files changed, 2347 insertions(+), 787 deletions(-) create mode 100644 apps/contacts/apps/web/src/lib/components/ContactsToolbarContent.svelte create mode 100644 apps/contacts/apps/web/src/lib/components/NewContactModal.svelte create mode 100644 apps/contacts/apps/web/src/lib/stores/filter.svelte.ts create mode 100644 apps/contacts/apps/web/src/lib/stores/new-contact-modal.svelte.ts create mode 100644 packages/shared-ui/src/navigation/expandable-toolbar/ExpandableToolbar.svelte create mode 100644 packages/shared-ui/src/navigation/expandable-toolbar/index.ts create mode 100644 packages/shared-ui/src/navigation/expandable-toolbar/types.ts diff --git a/apps/calendar/apps/web/src/lib/components/calendar/CalendarToolbar.svelte b/apps/calendar/apps/web/src/lib/components/calendar/CalendarToolbar.svelte index 4799fa658..a997b1683 100644 --- a/apps/calendar/apps/web/src/lib/components/calendar/CalendarToolbar.svelte +++ b/apps/calendar/apps/web/src/lib/components/calendar/CalendarToolbar.svelte @@ -1,5 +1,5 @@ - -
- -
+ + - -{#if !isCollapsed} -
-
- - -
- - - -
-
-{/if} + {#snippet rightActions()} + + {/snippet} +
+ + + diff --git a/apps/contacts/apps/web/src/lib/components/ContactsToolbarContent.svelte b/apps/contacts/apps/web/src/lib/components/ContactsToolbarContent.svelte new file mode 100644 index 000000000..454d92623 --- /dev/null +++ b/apps/contacts/apps/web/src/lib/components/ContactsToolbarContent.svelte @@ -0,0 +1,161 @@ + + +
+ + contactsFilterStore.setSelectedTagId(id)} + contactFilter={contactsFilterStore.contactFilter} + onContactFilterChange={(f) => contactsFilterStore.setContactFilter(f)} + birthdayFilter={contactsFilterStore.birthdayFilter} + onBirthdayFilterChange={(f) => contactsFilterStore.setBirthdayFilter(f)} + selectedCompany={contactsFilterStore.selectedCompany} + onCompanyChange={(c) => contactsFilterStore.setSelectedCompany(c)} + embedded={true} + /> + +
+ + + + +
+ + +
+ + + +
+
+ + diff --git a/apps/contacts/apps/web/src/lib/components/FilterBar.svelte b/apps/contacts/apps/web/src/lib/components/FilterBar.svelte index 43f0f5c2b..ccae142c5 100644 --- a/apps/contacts/apps/web/src/lib/components/FilterBar.svelte +++ b/apps/contacts/apps/web/src/lib/components/FilterBar.svelte @@ -1,6 +1,7 @@ + + + + + + + + + diff --git a/apps/contacts/apps/web/src/lib/components/views/ContactAlphabetView.svelte b/apps/contacts/apps/web/src/lib/components/views/ContactAlphabetView.svelte index 6fdc07007..777a9898b 100644 --- a/apps/contacts/apps/web/src/lib/components/views/ContactAlphabetView.svelte +++ b/apps/contacts/apps/web/src/lib/components/views/ContactAlphabetView.svelte @@ -2,6 +2,7 @@ import { _ } from 'svelte-i18n'; import type { Contact } from '$lib/api/contacts'; import type { SortField } from '$lib/components/SortToggle.svelte'; + import { newContactModalStore } from '$lib/stores/new-contact-modal.svelte'; interface Props { contacts: Contact[]; @@ -11,6 +12,7 @@ selectedIds?: Set; onToggleSelection?: (id: string) => void; sortField?: SortField; + showNewContactCard?: boolean; } let { @@ -21,6 +23,7 @@ selectedIds = new Set(), onToggleSelection, sortField = 'lastName', + showNewContactCard = true, }: Props = $props(); function handleCheckboxClick(e: MouseEvent, id: string) { @@ -87,6 +90,39 @@
+ + {#if showNewContactCard && !selectionMode} +
+
newContactModalStore.open()} + onkeydown={(e) => e.key === 'Enter' && newContactModalStore.open()} + class="alphabet-contact-card new-contact-card" + > + +
+ + + +
+ + +
+
+ {$_('contacts.new')} + {$_('contacts.addFirst')} +
+
+
+
+ {/if} +
{#each availableLetters as letter} @@ -142,30 +178,46 @@
-
- {getDisplayName(contact)} -
-
- {#if contact.jobTitle && contact.company} - {contact.jobTitle} @ {contact.company} - {:else if contact.company} - {contact.company} - {:else if contact.email} - {contact.email} +
+ {getDisplayName(contact)} + {#if contact.isFavorite} + + + + {/if} + {#if contact.company} + @ {contact.company} {/if}
+ {#if contact.tags && contact.tags.length > 0} +
+ {#each contact.tags.slice(0, 3) as tag} + + {tag.name} + + {/each} + {#if contact.tags.length > 3} + +{contact.tags.length - 3} + {/if} +
+ {/if}
- -
+ +
{#if contact.phone || contact.mobile} e.stopPropagation()} - class="quick-action-btn" - title={$_('contacts.call')} + class="action-chip" + title={contact.mobile || contact.phone} > - + e.stopPropagation()} - class="quick-action-btn" - title={$_('contacts.email')} + class="action-chip" + title={contact.email} > - + {/if} -
{/each} @@ -222,37 +252,38 @@ {/each}
- +
- {#each alphabet as letter} - - {/each} - {#if availableLetters.includes('#')} - - {/if} +
+ {#each alphabet as letter} + + {/each} + {#if availableLetters.includes('#')} + + {/if} +
diff --git a/apps/contacts/apps/web/src/lib/components/views/ContactGridView.svelte b/apps/contacts/apps/web/src/lib/components/views/ContactGridView.svelte index 8fb5e20fa..325b7de53 100644 --- a/apps/contacts/apps/web/src/lib/components/views/ContactGridView.svelte +++ b/apps/contacts/apps/web/src/lib/components/views/ContactGridView.svelte @@ -1,6 +1,7 @@
+ + {#if showNewContactCard && !selectionMode} +
newContactModalStore.open()} + onkeydown={(e) => e.key === 'Enter' && newContactModalStore.open()} + class="grid-card new-contact-card" + > + +
+ + + +
+ + +
+

{$_('contacts.new')}

+

{$_('contacts.addFirst')}

+
+
+ {/if} + {#each contacts as contact (contact.id)}
diff --git a/apps/contacts/apps/web/src/lib/components/views/ContactListView.svelte b/apps/contacts/apps/web/src/lib/components/views/ContactListView.svelte index 279dde6c6..6ae4e5e8d 100644 --- a/apps/contacts/apps/web/src/lib/components/views/ContactListView.svelte +++ b/apps/contacts/apps/web/src/lib/components/views/ContactListView.svelte @@ -1,6 +1,7 @@
+ + {#if showNewContactCard && !selectionMode} +
newContactModalStore.open()} + onkeydown={(e) => e.key === 'Enter' && newContactModalStore.open()} + class="contact-card new-contact-card w-full text-left cursor-pointer" + > + +
+ + + +
+ + +
+
+ {$_('contacts.new')} +
+
+ {$_('contacts.addFirst')} +
+
+
+ {/if} + {#each contacts as contact (contact.id)}
diff --git a/apps/contacts/apps/web/src/lib/stores/filter.svelte.ts b/apps/contacts/apps/web/src/lib/stores/filter.svelte.ts new file mode 100644 index 000000000..eba2b00d5 --- /dev/null +++ b/apps/contacts/apps/web/src/lib/stores/filter.svelte.ts @@ -0,0 +1,146 @@ +/** + * Filter Store - Manages filter state for the Contacts app toolbar + * Uses Svelte 5 runes for reactivity + */ + +import { browser } from '$app/environment'; + +export type SortField = 'firstName' | 'lastName'; +export type ContactFilter = 'all' | 'favorites' | 'hasPhone' | 'hasEmail' | 'incomplete'; +export type BirthdayFilter = 'all' | 'today' | 'thisWeek' | 'thisMonth'; + +export interface ContactsFilterState { + sortField: SortField; + contactFilter: ContactFilter; + birthdayFilter: BirthdayFilter; + selectedTagId: string | null; + selectedCompany: string | null; + isToolbarCollapsed: boolean; + searchQuery: string; +} + +const DEFAULT_STATE: ContactsFilterState = { + sortField: 'lastName', + contactFilter: 'all', + birthdayFilter: 'all', + selectedTagId: null, + selectedCompany: null, + isToolbarCollapsed: true, + searchQuery: '', +}; + +const STORAGE_KEY = 'contacts-filter-state'; + +function loadState(): ContactsFilterState { + if (!browser) return DEFAULT_STATE; + + try { + const stored = localStorage.getItem(STORAGE_KEY); + if (stored) { + const parsed = JSON.parse(stored); + return { ...DEFAULT_STATE, ...parsed }; + } + } catch (e) { + console.error('Failed to load contacts filter state:', e); + } + + return DEFAULT_STATE; +} + +function saveState(state: ContactsFilterState) { + if (!browser) return; + + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); + } catch (e) { + console.error('Failed to save contacts filter state:', e); + } +} + +// Reactive state +let state = $state(DEFAULT_STATE); + +export const contactsFilterStore = { + // Getters + get sortField() { + return state.sortField; + }, + get contactFilter() { + return state.contactFilter; + }, + get birthdayFilter() { + return state.birthdayFilter; + }, + get selectedTagId() { + return state.selectedTagId; + }, + get selectedCompany() { + return state.selectedCompany; + }, + get isToolbarCollapsed() { + return state.isToolbarCollapsed; + }, + get searchQuery() { + return state.searchQuery; + }, + + // Setters + setSortField(value: SortField) { + state = { ...state, sortField: value }; + saveState(state); + }, + + setContactFilter(value: ContactFilter) { + state = { ...state, contactFilter: value }; + saveState(state); + }, + + setBirthdayFilter(value: BirthdayFilter) { + state = { ...state, birthdayFilter: value }; + saveState(state); + }, + + setSelectedTagId(value: string | null) { + state = { ...state, selectedTagId: value }; + saveState(state); + }, + + setSelectedCompany(value: string | null) { + state = { ...state, selectedCompany: value }; + saveState(state); + }, + + setToolbarCollapsed(value: boolean) { + state = { ...state, isToolbarCollapsed: value }; + saveState(state); + }, + + toggleToolbar() { + state = { ...state, isToolbarCollapsed: !state.isToolbarCollapsed }; + saveState(state); + }, + + setSearchQuery(value: string) { + state = { ...state, searchQuery: value }; + // Don't persist search query to localStorage + }, + + // Reset filters (but not toolbar state) + resetFilters() { + state = { + ...state, + contactFilter: 'all', + birthdayFilter: 'all', + selectedTagId: null, + selectedCompany: null, + searchQuery: '', + }; + saveState(state); + }, + + // Initialize from localStorage + initialize() { + if (!browser) return; + state = loadState(); + }, +}; diff --git a/apps/contacts/apps/web/src/lib/stores/new-contact-modal.svelte.ts b/apps/contacts/apps/web/src/lib/stores/new-contact-modal.svelte.ts new file mode 100644 index 000000000..b565ef051 --- /dev/null +++ b/apps/contacts/apps/web/src/lib/stores/new-contact-modal.svelte.ts @@ -0,0 +1,41 @@ +/** + * Store for controlling the New Contact Modal + */ + +interface NewContactData { + firstName?: string; + lastName?: string; + displayName?: string; + email?: string; + phone?: string; + company?: string; +} + +let isOpen = $state(false); +let prefillData = $state(null); + +export const newContactModalStore = { + get isOpen() { + return isOpen; + }, + + get prefillData() { + return prefillData; + }, + + /** + * Open the modal, optionally with pre-filled data + */ + open(data?: NewContactData) { + prefillData = data || null; + isOpen = true; + }, + + /** + * Close the modal and reset data + */ + close() { + isOpen = false; + prefillData = null; + }, +}; diff --git a/apps/contacts/apps/web/src/routes/(app)/+layout.svelte b/apps/contacts/apps/web/src/routes/(app)/+layout.svelte index e06d03bd3..68c279715 100644 --- a/apps/contacts/apps/web/src/routes/(app)/+layout.svelte +++ b/apps/contacts/apps/web/src/routes/(app)/+layout.svelte @@ -34,15 +34,19 @@ import { getPillAppItems } from '@manacore/shared-branding'; import { setLocale, supportedLocales } from '$lib/i18n'; import ContactDetailModal from '$lib/components/ContactDetailModal.svelte'; + import NewContactModal from '$lib/components/NewContactModal.svelte'; import { contactsStore } from '$lib/stores/contacts.svelte'; + import { newContactModalStore } from '$lib/stores/new-contact-modal.svelte'; import { contactsApi, tagsApi } from '$lib/api/contacts'; import { viewModeStore } from '$lib/stores/view-mode.svelte'; import { contactsSettings } from '$lib/stores/settings.svelte'; + import { contactsFilterStore } from '$lib/stores/filter.svelte'; import { parseContactInput, resolveContactIds, formatParsedContactPreview, } from '$lib/utils/contact-parser'; + import ContactsToolbar from '$lib/components/ContactsToolbar.svelte'; // Tags state for Quick-Create let availableTags = $state<{ id: string; name: string }[]>([]); @@ -68,6 +72,18 @@ let isSidebarMode = $state(false); let isCollapsed = $state(false); + // Show toolbar only on main contacts page + const showContactsToolbar = $derived($page.url.pathname === '/' && !isSidebarMode); + + // Dynamic bottom offset based on toolbar state + const inputBarBottomOffset = $derived( + isSidebarMode + ? '0px' + : showContactsToolbar && !contactsFilterStore.isToolbarCollapsed + ? '140px' + : '70px' + ); + // Use theme store's isDark directly let isDark = $derived(theme.isDark); @@ -227,30 +243,21 @@ async function handleCreate(query: string): Promise { const parsed = parseContactInput(query); - if (!parsed.displayName) return; - - // Resolve tag names to IDs - const resolved = resolveContactIds(parsed, availableTags); - - try { - const contact = await contactsStore.createContact({ - displayName: resolved.displayName, - firstName: resolved.firstName, - lastName: resolved.lastName, - company: resolved.company, - email: resolved.email, - phone: resolved.phone, - }); - - // Add tags to the created contact - if (resolved.tagIds.length > 0 && contact) { - for (const tagId of resolved.tagIds) { - await tagsApi.addToContact(tagId, contact.id); - } - } - } catch (e) { - console.error('Failed to create contact:', e); + if (!parsed.displayName) { + // If no query, just open empty modal + newContactModalStore.open(); + return; } + + // Open modal with prefilled data + newContactModalStore.open({ + displayName: parsed.displayName, + firstName: parsed.firstName || undefined, + lastName: parsed.lastName || undefined, + email: parsed.email || undefined, + phone: parsed.phone || undefined, + company: parsed.company || undefined, + }); } // QuickInputBar quick actions @@ -281,9 +288,10 @@ console.error('Failed to load tags:', e); } - // Initialize contacts settings and view mode + // Initialize contacts settings, view mode, and filter store contactsSettings.initialize(); viewModeStore.initialize(); + contactsFilterStore.initialize(); // Initialize sidebar mode from localStorage const savedSidebar = localStorage.getItem('contacts-nav-sidebar'); @@ -306,10 +314,7 @@
- - - - + {/if} + + {#if newContactModalStore.isOpen} + newContactModalStore.close()} /> + {/if} + contactsFilterStore.setSearchQuery(query)} {quickActions} placeholder="Neuer Kontakt oder suchen..." emptyText="Keine Kontakte gefunden" @@ -374,8 +385,15 @@ createText="Erstellen" appIcon="contacts" primaryColor="#3b82f6" - autoFocus={false} + autoFocus={true} + bottomOffset={inputBarBottomOffset} + hasFabRight={showContactsToolbar} /> + + + {#if showContactsToolbar} + + {/if}
@@ -389,17 +407,19 @@ .main-content { flex: 1; transition: all 300ms ease; + /* Space for QuickInputBar + PillNav at bottom */ + padding-bottom: calc(150px + env(safe-area-inset-bottom)); } - /* Floating nav mode - add top padding for fixed nav */ + /* Floating nav mode - nav is at bottom, no top padding needed */ .main-content.floating-mode { - padding-top: 80px; + padding-top: 0; } - /* Extra padding on mobile for larger nav */ + /* Extra bottom padding on mobile */ @media (max-width: 768px) { - .main-content.floating-mode { - padding-top: 90px; + .main-content { + padding-bottom: calc(160px + env(safe-area-inset-bottom)); } } @@ -426,27 +446,4 @@ padding: 2rem; } } - - /* Shadow gradient above pill navigation */ - .nav-shadow-gradient { - position: fixed; - top: 0; - left: 0; - right: 0; - height: 80px; - background: linear-gradient( - to bottom, - hsl(var(--background)) 0%, - hsl(var(--background)) 50%, - hsl(var(--background) / 0) 100% - ); - pointer-events: none; - z-index: 40; - } - - @media (max-width: 768px) { - .nav-shadow-gradient { - height: 90px; - } - } diff --git a/apps/contacts/apps/web/src/routes/(app)/network/+page.svelte b/apps/contacts/apps/web/src/routes/(app)/network/+page.svelte index 0508d3e8f..132141d3d 100644 --- a/apps/contacts/apps/web/src/routes/(app)/network/+page.svelte +++ b/apps/contacts/apps/web/src/routes/(app)/network/+page.svelte @@ -2,11 +2,17 @@ import { onMount, onDestroy } from 'svelte'; import { goto } from '$app/navigation'; import { networkStore, type SimulationNode } from '$lib/stores/network.svelte'; + import { contactsFilterStore } from '$lib/stores/filter.svelte'; import { NetworkGraph, NetworkControls } from '@manacore/shared-ui'; import ContactDetailModal from '$lib/components/ContactDetailModal.svelte'; import { NetworkGraphSkeleton } from '$lib/components/skeletons'; import '$lib/i18n'; + // Sync global search to network store + $effect(() => { + networkStore.setSearch(contactsFilterStore.searchQuery); + }); + let graphComponent: NetworkGraph; let controlsComponent: NetworkControls; let graphContainer: HTMLDivElement; @@ -110,7 +116,7 @@
+ import { slide } from 'svelte/transition'; + import type { Snippet } from 'svelte'; + + interface Props { + /** Whether the toolbar is collapsed */ + isCollapsed?: boolean; + /** Called when collapsed state changes */ + onCollapsedChange?: (isCollapsed: boolean) => void; + /** Whether in sidebar mode (affects positioning) */ + isSidebarMode?: boolean; + /** Bottom offset from viewport bottom (default: '70px') */ + bottomOffset?: string; + /** Sidebar mode bottom offset (default: '0px') */ + sidebarBottomOffset?: string; + /** Panel height when expanded (default: '70px') */ + panelHeight?: string; + /** FAB tooltip when collapsed */ + collapsedTitle?: string; + /** FAB tooltip when expanded */ + expandedTitle?: string; + /** Custom collapsed icon snippet */ + collapsedIcon?: Snippet; + /** Custom expanded icon snippet */ + expandedIcon?: Snippet; + /** Panel content (required) */ + children: Snippet; + /** Optional right-side content (e.g., layout toggle) */ + rightActions?: Snippet; + } + + let { + isCollapsed = true, + onCollapsedChange, + isSidebarMode = false, + bottomOffset = '70px', + sidebarBottomOffset = '0px', + panelHeight = '70px', + collapsedTitle = 'Optionen', + expandedTitle = 'Schließen', + collapsedIcon, + expandedIcon, + children, + rightActions, + }: Props = $props(); + + function toggleToolbar() { + onCollapsedChange?.(!isCollapsed); + } + + + +
+ +
+ + +{#if !isCollapsed} +
+
+ {@render children()} + + {#if rightActions} +
+ {@render rightActions()} + {/if} +
+
+{/if} + + diff --git a/packages/shared-ui/src/navigation/expandable-toolbar/index.ts b/packages/shared-ui/src/navigation/expandable-toolbar/index.ts new file mode 100644 index 000000000..db710aed6 --- /dev/null +++ b/packages/shared-ui/src/navigation/expandable-toolbar/index.ts @@ -0,0 +1,2 @@ +export { default as ExpandableToolbar } from './ExpandableToolbar.svelte'; +export type { ExpandableToolbarProps } from './types'; diff --git a/packages/shared-ui/src/navigation/expandable-toolbar/types.ts b/packages/shared-ui/src/navigation/expandable-toolbar/types.ts new file mode 100644 index 000000000..fec554943 --- /dev/null +++ b/packages/shared-ui/src/navigation/expandable-toolbar/types.ts @@ -0,0 +1,28 @@ +import type { Snippet } from 'svelte'; + +export interface ExpandableToolbarProps { + /** Whether the toolbar is collapsed */ + isCollapsed?: boolean; + /** Called when collapsed state changes */ + onCollapsedChange?: (isCollapsed: boolean) => void; + /** Whether in sidebar mode (affects positioning) */ + isSidebarMode?: boolean; + /** Bottom offset from viewport bottom (default: '70px') */ + bottomOffset?: string; + /** Sidebar mode bottom offset (default: '0px') */ + sidebarBottomOffset?: string; + /** Panel height when expanded (default: '70px') */ + panelHeight?: string; + /** FAB tooltip when collapsed */ + collapsedTitle?: string; + /** FAB tooltip when expanded */ + expandedTitle?: string; + /** Custom collapsed icon snippet */ + collapsedIcon?: Snippet; + /** Custom expanded icon snippet */ + expandedIcon?: Snippet; + /** Panel content (required) */ + children: Snippet; + /** Optional right-side content (e.g., layout toggle) */ + rightActions?: Snippet; +} diff --git a/packages/shared-ui/src/navigation/index.ts b/packages/shared-ui/src/navigation/index.ts index 500d8fe2f..e5a8947b8 100644 --- a/packages/shared-ui/src/navigation/index.ts +++ b/packages/shared-ui/src/navigation/index.ts @@ -10,6 +10,8 @@ export { default as PillViewSwitcher } from './PillViewSwitcher.svelte'; export { default as PillToolbar } from './PillToolbar.svelte'; export { default as PillToolbarButton } from './PillToolbarButton.svelte'; export { default as PillToolbarDivider } from './PillToolbarDivider.svelte'; +export { ExpandableToolbar } from './expandable-toolbar'; +export type { ExpandableToolbarProps } from './expandable-toolbar'; export type { NavItem, NavbarProps, diff --git a/packages/shared-ui/src/organisms/network/NetworkControls.svelte b/packages/shared-ui/src/organisms/network/NetworkControls.svelte index 3056e3167..47f0df22f 100644 --- a/packages/shared-ui/src/organisms/network/NetworkControls.svelte +++ b/packages/shared-ui/src/organisms/network/NetworkControls.svelte @@ -15,6 +15,7 @@ linkLabel?: string; searchPlaceholder?: string; minStrength?: number; + showSearch?: boolean; onSearch?: (query: string) => void; onTagFilter?: (tagId: string | null) => void; onSubtitleFilter?: (subtitle: string | null) => void; @@ -39,6 +40,7 @@ linkLabel = 'Verbindungen', searchPlaceholder = 'Suchen...', minStrength = 0, + showSearch = true, onSearch, onTagFilter, onSubtitleFilter, @@ -122,22 +124,24 @@
-
- - - {#if searchInput} - - {/if} -
+ {#if showSearch} +
+ + + {#if searchInput} + + {/if} +
+ {/if} {#if tags.length > 0 || subtitles.length > 0}