moox/clipboard
Moox Clipboard adds Filament form fields with a built-in “copy to clipboard” action, making it easy for users to copy values like tokens, IDs, or URLs from your admin panels. Install via Composer and run moox:install to get started.
Installation:
composer require moox/clipboard
php artisan moox:install
moox:install command publishes the package's assets (JS, CSS) and config file to your project.First Use Case:
Clipboard action to a Filament field (e.g., TextInput, Textarea, or MarkdownEditor):
use Moox\Clipboard\Actions\Clipboard;
TextInput::make('api_key')
->extraAttributes(['class' => 'w-full'])
->afterStateUpdated(
Clipboard::make()
->label('Copy to Clipboard')
->icon('heroicon-o-clipboard-document-check')
),
Where to Look First:
config/moox-clipboard.php) for global settings (e.g., default icon, toast messages).Dynamic Clipboard Actions:
afterStateUpdated or afterStateHydrated for fields that update dynamically (e.g., API keys, tokens):
TextInput::make('secret_token')
->afterStateUpdated(
Clipboard::make()
->successMessage('Token copied!')
->errorMessage('Failed to copy.')
),
Conditional Visibility:
TextInput::make('email')
->extraAttributes(['class' => 'w-full'])
->afterStateUpdated(
Clipboard::make()
->visible(fn ($record) => $record->isVerified())
),
Customizing the Copied Value:
Clipboard::make()
->copyValue(fn ($state) => "Bearer {$state}")
Bulk Actions:
Table actions for bulk copying (e.g., copy multiple IDs):
use Moox\Clipboard\Actions\TableClipboard;
Tables\Actions\Action::make('copyIds')
->action(TableClipboard::make('id'))
->label('Copy Selected IDs'),
Integration with Filament Forms/Tables:
create() or edit() methods.TableClipboard for column-specific actions (e.g., copy emails or URLs).Localization:
Clipboard::make()
->label(__('actions.copy'))
->successMessage(__('messages.copied'))
lazy loading for clipboard actions to avoid rendering all rows at once.extraAttributes or Filament’s CSS utilities:
Clipboard::make()
->extraAttributes(['class' => 'bg-blue-500 hover:bg-blue-600'])
Asset Conflicts:
clipboard.js). The package uses Filament’s asset pipeline, so conflicts are rare but possible if multiple clipboard solutions are loaded.resources/js/app.js for duplicate clipboard initializations.Field State Timing:
afterStateUpdated may not work as expected in create() forms if the field is initially empty. Use afterStateHydrated for pre-filled fields:
->afterStateHydrated(
Clipboard::make()
)
Browser Permissions:
Clipboard::make()
->errorMessage('Permission denied. Try again or use Ctrl+C.')
Dynamic Fields:
repeatable or file upload fields, ensure the clipboard action targets the correct state path:
TextInput::make('repeatable.field')
->columnSpanFull()
->afterStateUpdated(
Clipboard::make()
->copyValue(fn ($state, $record) => $record->repeatable->field)
)
Caching Issues:
php artisan filament:cache-reset
Check Console: Open browser dev tools (F12) to verify clipboard events fire. Look for errors like:
Uncaught TypeError: clipboard.copy is not a function
npm run dev or php artisan filament:assets.Log State: Debug the copied value by logging it in the copyValue callback:
->copyValue(fn ($state) => {
\Log::info('Copying:', ['state' => $state]);
return $state;
})
Custom Clipboard Logic:
Clipboard action class to add pre/post-copy logic:
namespace App\Filament\Actions;
use Moox\Clipboard\Actions\Clipboard;
class CustomClipboard extends Clipboard {
public function handle() {
// Pre-copy logic (e.g., validate state)
if (empty($this->state)) {
return $this->fail('Field is empty.');
}
parent::handle();
// Post-copy logic (e.g., log event)
event(new ClipboardCopied($this->state));
}
}
Global Configuration:
config/moox-clipboard.php:
'default_icon' => 'heroicon-o-copy',
'toast_duration' => 2000,
'copy_fallback' => true, // Fallback to text selection if clipboard fails
Testing:
$this->partialMock(Clipboard::class, 'handle')
->shouldReceive('handle')
->once();
Server-Side Fallback:
Clipboard::make()
->fallbackAction(
Tables\Actions\DownloadAction::make()
->filename(fn ($record) => "{$record->id}.txt")
->content(fn ($record) => $record->secret)
)
How can I help you explore Laravel packages today?