Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Filament Workspace Tabs Laravel Package

wezlo/filament-workspace-tabs

Browser-like tabs for Filament panels: open every page in a new tab, persist via localStorage, drag to reorder, pin tabs, reopen recently closed, and use right-click actions plus keyboard shortcuts. Works with Filament v4/v5, SPA and dark mode.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps to First Use

  1. Install the package:
    composer require wezlo/filament-workspace-tabs
    
  2. Register the plugin in your PanelProvider:
    public function panel(Panel $panel): Panel
    {
        return $panel->plugins([
            WorkspaceTabsPlugin::make(),
        ]);
    }
    
  3. Add Tailwind source to your theme CSS (resources/css/filament/{panel}/theme.css):
    @source '../../../../vendor/wezlo/filament-workspace-tabs/resources/views/**/*';
    
  4. Rebuild assets:
    npm run build
    
  5. Test the tab functionality:
    • Click any sidebar link to open in a new tab.
    • Middle-click or Ctrl+click a link to open in a new tab without navigating away.
    • Use Ctrl+W/Cmd+W to close the active tab.

First Use Case: Multi-Page Workflow

A developer working on a Filament admin panel with multiple pages (e.g., Resources, Pages, Settings) can now:

  • Open all three in separate tabs simultaneously.
  • Reorder tabs via drag-and-drop for workflow efficiency.
  • Pin the Dashboard tab to always keep it accessible.
  • Quickly switch between pages without losing context.

Implementation Patterns

Core Workflows

  1. Tab Management:

    • Opening Tabs:
      • Default behavior: Sidebar links open in new tabs.
      • Middle-click/Ctrl+click: Open links in new tabs without navigating away.
    • Closing Tabs:
      • Click the x button on the tab.
      • Use Ctrl+W/Cmd+W keyboard shortcut.
      • Right-click context menu (Close, Close Others, Close to the Right, Close All).
    • Pinning Tabs:
      • Double-click a tab to pin/unpin.
      • Pinned tabs cannot be closed until unpinned and persist to the left.
  2. Navigation:

    • Clicking an existing tab switches to that page (uses Livewire.navigate() for SPA transitions).
    • Tabs are automatically updated when revisiting a URL (prevents duplicates).
  3. Persistence:

    • Tab state (URLs, order, pinned status) is saved to localStorage under a panel-scoped key (default: filament_workspace_tabs_{panel_id}).
    • Survives page reloads and browser sessions.
  4. Recently Closed Tabs:

    • Closed tabs are stored in a history list (last 50).
    • Access via dropdown arrow on the right side of the tab bar.

Integration Tips

  • Customizing Tab Labels: Override the default tab label (extracted from document.title) by publishing the config and extending the TabLabel class:

    php artisan vendor:publish --tag="filament-workspace-tabs-config"
    

    Then modify config/filament-workspace-tabs.php or create a custom tab label resolver.

  • Excluding URLs: Prevent specific URLs (e.g., login page) from being tracked as tabs:

    WorkspaceTabsPlugin::make()
        ->excludeUrls(['/admin/login', '/admin/register'])
    
  • Disabling Features: Disable drag-to-reorder or context menu for a cleaner UI:

    WorkspaceTabsPlugin::make()
        ->dragReorder(false)
        ->contextMenu(false)
    
  • SPA Mode: Works seamlessly with Filament’s ->spa() mode and Livewire’s wire:navigate. No additional setup required.

  • Dark Mode: Fully compatible with Filament’s dark mode. No manual adjustments needed.

Advanced Patterns

  1. Dynamic Tab Updates: Use the tab-updated Alpine.js event to listen for tab changes (e.g., update UI based on active tab):

    window.Alpine.store('tabs', {
        init() {
            this.$wire.on('tab-updated', (tab) => {
                console.log('Active tab changed:', tab);
            });
        },
    });
    
  2. Custom Tab Actions: Extend the context menu by publishing the config and overriding the ContextMenu component:

    // config/filament-workspace-tabs.php
    'context_menu_items' => [
        'custom-action' => [
            'label' => 'Custom Action',
            'icon' => 'heroicon-o-star',
            'action' => 'customAction',
        ],
    ];
    
  3. Programmatic Tab Control: Use Livewire to manage tabs programmatically (e.g., close all tabs on logout):

    public function logout()
    {
        $this->dispatch('close-all-tabs');
        auth()->logout();
    }
    

Gotchas and Tips

Pitfalls

  1. Cross-Panel Tab Bleed:

    • Tabs are scoped to the panel ID by default. If you have multiple panels, ensure the persistKey is unique per panel to avoid shared tab states.
    • Fix: Override the persistKey in the plugin config:
      WorkspaceTabsPlugin::make()
          ->persistKey('panel_1_tabs') // Unique per panel
      
  2. LocalStorage Conflicts:

    • If multiple Filament panels or applications use the same persistKey, tab states may conflict.
    • Fix: Use a unique persistKey for each panel or application.
  3. SPA Navigation Issues:

    • In SPA mode, some older browsers or misconfigured Livewire setups may not handle Livewire.navigate() correctly, causing tab URLs to desync.
    • Fix: Ensure your Filament panel is configured with ->spa() and Livewire’s wire:navigate is properly set up.
  4. Tab Label Overrides Not Reflecting:

    • If custom tab labels (via TabLabel resolver) aren’t updating, clear the browser’s localStorage or check for JavaScript errors.
    • Fix: Rebuild assets and verify the custom resolver is registered in the config.
  5. Drag-and-Drop Performance:

    • With a large number of tabs (e.g., >50), drag-and-drop reordering may feel laggy.
    • Fix: Reduce the maxTabs limit or optimize the SortableJS configuration.
  6. Recently Closed Tabs Limit:

    • The default limit of 50 recently closed tabs may be too low for power users.
    • Fix: Extend the limit by modifying the recentlyClosedTabsLimit in the config (if exposed) or by overriding the RecentlyClosedTabs component.

Debugging Tips

  1. Inspect Tab State:

    • Open browser dev tools (F12) and check localStorage for the panel-scoped key (e.g., filament_workspace_tabs_{panel_id}) to debug tab persistence issues.
  2. Disable Features for Testing:

    • Temporarily disable drag-to-reorder or the context menu to isolate issues:
      WorkspaceTabsPlugin::make()
          ->dragReorder(false)
          ->contextMenu(false)
      
  3. Check for JavaScript Errors:

    • Look for errors in the browser console related to Alpine.js, SortableJS, or Livewire. Common culprits:
      • Missing Tailwind classes (ensure @source is correctly added to theme.css).
      • Conflicting Alpine.js stores or Livewire hooks.
  4. Verify Plugin Registration:

    • Ensure the plugin is registered after the panel is initialized (e.g., in the panel() method of your PanelProvider).
  5. Clear Cache:

    • If tabs behave unexpectedly, clear Filament’s cache:
      php artisan filament:cache-clear
      
    • Also clear browser cache or test in incognito mode to rule out localStorage issues.

Extension Points

  1. Custom Tab Components:

    • Publish the package assets and override the Tab or TabBar views to customize styling or behavior:
      php artisan vendor:publish --tag="filament-workspace-tabs-views"
      
    • Modify files in resources/views/vendor/filament-workspace-tabs/.
  2. Custom Persistence Backend:

    • Extend the TabPersistence class to use a database or session storage instead of localStorage:
      // config/filament-workspace-tabs.php
      'persistence' => \App\Services\CustomTabPersistence::class,
      
  3. Add Tab Metadata:

    • Attach custom data to tabs (e.g., last accessed time, user-specific notes) by extending the Tab model or using Alpine.js stores:
      document.addEventListener('alpine:init', () => {
          Alpine.store('customTabData', {
              init() {
                  this.data = {};
              },
              set(tabId, key, value) {
                  this.data[tabId] = this.data[tabId] || {};
                  this.data[tabId][key] = value;
              },
          });
      });
      
  4. Tab Events:

    • Listen for tab lifecycle events (e.g., tab-opened, `tab-closed
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony
develia/geo-bundle
dreamzy/livewire-charts
touchestate-sdk/php-sdk
22h/doctrine-garbage-collection-bundle
agtp/agtp-php
agtp/mod-php
splash/sonata-admin
splash/metadata
splash/openapi
splash/scopes
splash/toolkit
testo/output-teamcity
testo/bridge-symfony
spatie/flare-daemon-runtime
canaltp/sam-ecore-application-manager-bundle
canaltp/sam-ecore-security-manager-bundle