From a709f803f684b93edbb5289257643398fd2bc5f9 Mon Sep 17 00:00:00 2001 From: RJ Date: Sun, 31 Aug 2025 01:33:14 -0700 Subject: [PATCH 1/2] Added themes for the uploader, tailwindcs and boostrap. Customer themes can now be created. --- CHANGELOG.md | 21 +- config/media-uploader.php | 120 ++++ .../views/livewire/media-uploader.blade.php | 564 +----------------- .../themes/bootstrap/media-uploader.blade.php | 506 ++++++++++++++++ .../themes/tailwind/media-uploader.blade.php | 555 +++++++++++++++++ src/Livewire/MediaUploader.php | 23 +- 6 files changed, 1216 insertions(+), 573 deletions(-) create mode 100644 resources/views/themes/bootstrap/media-uploader.blade.php create mode 100644 resources/views/themes/tailwind/media-uploader.blade.php diff --git a/CHANGELOG.md b/CHANGELOG.md index d336f84..fb8829f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,17 +9,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- ## [Unreleased] + ### Added -- Docs: environment variable examples for presets (`MEDIA_TYPES_*`, `MEDIA_MIMES_*`, `MEDIA_MAXKB_*`). -- Tests: deterministic duplicate-detection test helper (`TestableMediaUploader`) and event-based assertions. -- Troubleshooting guidance for Testbench/SQLite and Livewire temp upload disk. +- Configuration documentation for each option in `config/media-uploader.php`, including environment variable overrides, presets (types, mimes, max_kb), and collection → preset mapping. +- Guidance for creating and registering custom themes: + - Create a new folder under `themes`, e.g. `custom`. + - Copy the existing `media-uploader` theme file from `tailwind` or `bootstrap` into the new `custom` folder (do not change the file name). + - Edit the copied file as desired. + - Register it in the `themes` config array, for example: `'custom' => 'media-uploader::themes.custom.media-uploader'`. + - Use it globally via `.env` (`MEDIA_UPLOADER_THEME=custom`) or per instance: + ### Changed -- Test suite favors Pest; PHPUnit example retained only if desired by consumers. -- Assertions updated to reflect Spatie filename sanitization (spaces → dashes on rename). +- Clarified the behavior of `accept_from_config` and how the `accept` attribute is derived from the active preset. ### Fixed -- Intermittent test failures: ensured `media` table migration loads under Testbench and configured fake disks (`public`, `local`, `tmp-for-tests`). + --- diff --git a/config/media-uploader.php b/config/media-uploader.php index b5d851c..8d545f8 100644 --- a/config/media-uploader.php +++ b/config/media-uploader.php @@ -2,33 +2,153 @@ return [ + /* + |-------------------------------------------------------------------------- + | Active UI Theme + |-------------------------------------------------------------------------- + | Controls which Blade view is used to render the uploader UI. + | Accepts a key from the "themes" map below. + | + | Set globally via .env: + | MEDIA_UPLOADER_THEME=tailwind + | + | You can override per-instance in your Livewire component usage: + | + | + | Default: 'tailwind' + */ + 'theme' => env('MEDIA_UPLOADER_THEME', 'tailwind'), + + /* + |-------------------------------------------------------------------------- + | Theme View Map + |-------------------------------------------------------------------------- + | A list of available uploader themes. Keys are theme identifiers, values + | are the fully qualified Blade view names to render the component. + | + | Custom themes: + | - Create a new folder under the resources/views/vendor/media-uploader/themes directory, e.g. "custom". + | - Copy one of the supplied theme files (media-uploader.blade.php) from "tailwind" or "bootstrap" + | into the new "custom" folder. Keep the file name unchanged + | (e.g. media-uploader.blade.php). + | - Edit the copied file as needed. + | - Register it here using the folder name as the key and the view path as the value: + | 'custom' => 'media-uploader::themes.custom.media-uploader', + | - Make sure to clear your view and config cache after implementing the new theme + | + | Usage: + | - Globally via .env (see MEDIA_UPLOADER_THEME above), or + | - Per instance: + | + */ + 'themes' => [ + 'tailwind' => 'media-uploader::themes.tailwind.media-uploader', + 'bootstrap' => 'media-uploader::themes.bootstrap.media-uploader', + // 'custom' => 'media-uploader::themes.custom.media-uploader', + ], + + /* + |-------------------------------------------------------------------------- + | Accept Attribute Source + |-------------------------------------------------------------------------- + | When true, the uploader's HTML "accept" attribute is computed from the + | selected preset's "types" or "mimes" defined below. When false, the + | component won't auto-generate the "accept" attribute from config. + */ 'accept_from_config' => true, + /* + |-------------------------------------------------------------------------- + | Collection → Preset Mapping + |-------------------------------------------------------------------------- + | Define logical collections (used by your forms/models) and map each one + | to a preset name from the "presets" section below. + | Example: uploading to the "avatars" collection will apply the "images" + | preset's validation constraints. + */ 'collections' => [ 'avatars' => 'images', 'images' => 'images', 'attachments' => 'docs', ], + /* + |-------------------------------------------------------------------------- + | Presets + |-------------------------------------------------------------------------- + | Each preset defines: + | - types: Comma-separated file extensions (used for UI accept lists). + | - mimes: Comma-separated MIME types (useful for strict validation). + | - max_kb: Maximum file size in kilobytes. + | + | All values can be overridden via env variables for environment-specific + | behavior. If an env var is missing, the default value is used. + */ 'presets' => [ + /* + |---------------------------------------------------------------------- + | Images Preset + |---------------------------------------------------------------------- + | Env: + | - MEDIA_TYPES_IMAGES (e.g. "jpg,jpeg,png,webp,avif,gif") + | - MEDIA_MIMES_IMAGES (e.g. "image/jpeg,image/png,...") + | - MEDIA_MAXKB_IMAGES (integer KB, e.g. 10240 for 10 MB) + */ 'images' => [ 'types' => env('MEDIA_TYPES_IMAGES', 'jpg,jpeg,png,webp,avif,gif'), 'mimes' => env('MEDIA_MIMES_IMAGES', 'image/jpeg,image/png,image/webp,image/avif,image/gif'), 'max_kb' => (int) env('MEDIA_MAXKB_IMAGES', 10240), ], + // ... existing code ... + /* + |---------------------------------------------------------------------- + | Documents Preset + |---------------------------------------------------------------------- + | Env: + | - MEDIA_TYPES_DOCS (e.g. "pdf,doc,docx,xls,xlsx,ppt,pptx,txt") + | - MEDIA_MIMES_DOCS (e.g. "application/pdf,application/msword,...") + | - MEDIA_MAXKB_DOCS (integer KB, e.g. 20480 for 20 MB) + */ 'docs' => [ 'types' => env('MEDIA_TYPES_DOCS', 'pdf,doc,docx,xls,xlsx,ppt,pptx,txt'), 'mimes' => env('MEDIA_MIMES_DOCS', 'application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation,text/plain'), 'max_kb' => (int) env('MEDIA_MAXKB_DOCS', 20480), ], + /* + |---------------------------------------------------------------------- + | Videos Preset + |---------------------------------------------------------------------- + | Env: + | - MEDIA_TYPES_VIDEOS (e.g. "mp4,mov,webm") + | - MEDIA_MIMES_VIDEOS (e.g. "video/mp4,video/quicktime,video/webm") + | - MEDIA_MAXKB_VIDEOS (integer KB, e.g. 102400 for 100 MB) + */ 'videos' => [ 'types' => env('MEDIA_TYPES_VIDEOS', 'mp4,mov,webm'), 'mimes' => env('MEDIA_MIMES_VIDEOS', 'video/mp4,video/quicktime,video/webm'), 'max_kb' => (int) env('MEDIA_MAXKB_VIDEOS', 102400), ], + /* + |---------------------------------------------------------------------- + | Default Preset + |---------------------------------------------------------------------- + | A catch-all preset combining common image and document formats. + | Env: + | - MEDIA_TYPES_DEFAULT + | - MEDIA_MIMES_DEFAULT + | - MEDIA_MAXKB_DEFAULT + */ 'default' => [ 'types' => env('MEDIA_TYPES_DEFAULT', 'jpg,jpeg,png,webp,avif,gif,pdf,doc,docx,xls,xlsx,ppt,pptx,txt'), 'mimes' => env('MEDIA_MIMES_DEFAULT', 'image/jpeg,image/png,image/webp,image/avif,image/gif,application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation,text/plain'), diff --git a/resources/views/livewire/media-uploader.blade.php b/resources/views/livewire/media-uploader.blade.php index e7062a8..39e1365 100644 --- a/resources/views/livewire/media-uploader.blade.php +++ b/resources/views/livewire/media-uploader.blade.php @@ -1,556 +1,8 @@ -{{-- resources/views/livewire/media-uploader.blade.php --}} -
- @if (session('media_uploader_notice')) - - @endif - -
Manage gallery
- - -
- -
-
- -
-
Drag & drop files here
-
- or click to choose from your computer - @if(!empty($allowedLabel)) - - Allowed file types: {{ $allowedLabel }} - @endif -
-
-
- - -
- - - - - - @if(!empty($selected) && count($selected) > 0) -
-
Ready to upload:
-
- @foreach ($selected as $sel) - @php - $temp = $uploads[$sel['queue_key']] ?? null; - $canPreview = ($sel['is_image'] ?? false) && $temp && method_exists($temp, 'temporaryUrl'); - @endphp - -
- -
- {{ $sel['name'] }} - {{ number_format(($sel['size'] ?? 0)/1024, 1) }} KB -
- - -
-
- -
- @if($canPreview) - - @else -
- - - - -
- @endif -
- - -
- - - @error('pendingMeta.'.$sel['queue_key'].'.caption') -

{{ $message }}

- @enderror -
- - -
- - - @error('pendingMeta.'.$sel['queue_key'].'.description') -

{{ $message }}

- @enderror -
- - -
- - - @error('pendingMeta.'.$sel['queue_key'].'.order') -

{{ $message }}

- @enderror -
- - -
- -
-
-
-
- @endforeach -
- -
- - - -
-
- @endif -
- - @if($showList) -
-
-
{{ $attachedFilesTitle ?? 'Current gallery' }}
- -
- - @if (count($items) === 0) -
-

No gallery images yet.

-
- @else -
    - @foreach ($items as $m) - @php - $id = (int) $m['id']; - $isEditing = isset($editing[$id]); - $linkText = $m['caption'] ?: ($m['name'] ?: ($m['original_name'] ?? $m['file_name'])); - @endphp - -
  • - {{-- Thumbnail / icon --}} - @if(!empty($m['thumb'])) - - @else -
    - - - - -
    - @endif - - {{-- Content --}} -
    - @if (! $isEditing) - @php - $isImage = \Illuminate\Support\Str::startsWith($m['mime'] ?? '', 'image/'); - @endphp - -
    - @if ($isImage) - - @else - - {{ $linkText }} - - @endif -
    - @if(!empty($m['description'])) -
    {{ $m['description'] }}
    - @endif -
    {{ number_format(($m['size'] ?? 0)/1024, 1) }} KB
    - @else -
    -
    -
    Caption
    - - @error('editing.'.$id.'.caption') -

    {{ $message }}

    - @enderror -
    - -
    -
    Description
    - - @error('editing.'.$id.'.description') -

    {{ $message }}

    - @enderror -
    - -
    -
    Order
    -
    - -
    - @error('editing.'.$id.'.order') -

    {{ $message }}

    - @enderror -
    -
    - @endif -
    - -
    - @if (! $isEditing) - - - @else -
    - - -
    - @endif -
    -
  • - @endforeach -
- - - - - {{-- Delete confirmation modal --}} - - @endif -
- @endif -
+{{-- resources/views/vendor/media-uploader/livewire/media-uploader.blade.php --}} +@php + $theme = $theme ?? config('media-uploader.theme', 'tailwind'); + $map = (array) config('media-uploader.themes', []); + $view = $map[$theme] ?? $map['tailwind'] ?? 'media-uploader::themes.tailwind.media-uploader'; +@endphp + +@include($view) diff --git a/resources/views/themes/bootstrap/media-uploader.blade.php b/resources/views/themes/bootstrap/media-uploader.blade.php new file mode 100644 index 0000000..b8aa591 --- /dev/null +++ b/resources/views/themes/bootstrap/media-uploader.blade.php @@ -0,0 +1,506 @@ +
+ @if (session('media_uploader_notice')) + + @endif + +
Manage gallery
+ + +
+ +
+
+ +
+
Drag & drop files here
+
+ or click to choose from your computer + @if(!empty($allowedLabel)) + - Allowed file types: {{ $allowedLabel }} + @endif +
+
+
+ + +
+ + + + + + @if(!empty($selected) && count($selected) > 0) +
+
Ready to upload:
+
+ @foreach ($selected as $sel) + @php + $temp = $uploads[$sel['queue_key']] ?? null; + $canPreview = ($sel['is_image'] ?? false) && $temp && method_exists($temp, 'temporaryUrl'); + @endphp + +
+ +
+ {{ $sel['name'] }} + {{ number_format(($sel['size'] ?? 0)/1024, 1) }} KB +
+ + +
+
+ +
+ @if($canPreview) + + @else +
+ + + + +
+ @endif +
+ + +
+ + + @error('pendingMeta.'.$sel['queue_key'].'.caption') +
{{ $message }}
+ @enderror +
+ + +
+ + + @error('pendingMeta.'.$sel['queue_key'].'.description') +
{{ $message }}
+ @enderror +
+ + +
+ + + @error('pendingMeta.'.$sel['queue_key'].'.order') +
{{ $message }}
+ @enderror +
+ + +
+ +
+
+
+
+ @endforeach +
+ +
+ + + +
+
+ @endif +
+ + @if($showList) +
+
+
{{ $attachedFilesTitle ?? 'Current gallery' }}
+ +
+ + @if (count($items) === 0) +
+

No gallery images yet.

+
+ @else +
    + @foreach ($items as $m) + @php + $id = (int) $m['id']; + $isEditing = isset($editing[$id]); + $linkText = $m['caption'] ?: ($m['name'] ?: ($m['original_name'] ?? $m['file_name'])); + @endphp + +
  • + {{-- Thumbnail / icon --}} + @if(!empty($m['thumb'])) + + @else +
    + + + + +
    + @endif + + {{-- Content --}} +
    + @if (! $isEditing) + @php + $isImage = \Illuminate\Support\Str::startsWith($m['mime'] ?? '', 'image/'); + @endphp + +
    + @if ($isImage) + + @else + + {{ $linkText }} + + @endif +
    + @if(!empty($m['description'])) +
    {{ $m['description'] }}
    + @endif +
    {{ number_format(($m['size'] ?? 0)/1024, 1) }} KB
    + @else +
    +
    +
    Caption
    + + @error('editing.'.$id.'.caption') +
    {{ $message }}
    + @enderror +
    + +
    +
    Description
    + + @error('editing.'.$id.'.description') +
    {{ $message }}
    + @enderror +
    + +
    +
    Order
    +
    + +
    + @error('editing.'.$id.'.order') +
    {{ $message }}
    + @enderror +
    +
    + @endif +
    + +
    + @if (! $isEditing) + + + @else +
    + + +
    + @endif +
    +
  • + @endforeach +
+ + + + + {{-- Delete confirmation modal --}} + + @endif +
+ @endif +
diff --git a/resources/views/themes/tailwind/media-uploader.blade.php b/resources/views/themes/tailwind/media-uploader.blade.php new file mode 100644 index 0000000..098a7aa --- /dev/null +++ b/resources/views/themes/tailwind/media-uploader.blade.php @@ -0,0 +1,555 @@ +
+ @if (session('media_uploader_notice')) + + @endif + +
Manage gallery
+ + +
+ +
+
+ +
+
Drag & drop files here
+
+ or click to choose from your computer + @if(!empty($allowedLabel)) + - Allowed file types: {{ $allowedLabel }} + @endif +
+
+
+ + +
+ + + + + + @if(!empty($selected) && count($selected) > 0) +
+
Ready to upload:
+
+ @foreach ($selected as $sel) + @php + $temp = $uploads[$sel['queue_key']] ?? null; + $canPreview = ($sel['is_image'] ?? false) && $temp && method_exists($temp, 'temporaryUrl'); + @endphp + +
+ +
+ {{ $sel['name'] }} + {{ number_format(($sel['size'] ?? 0)/1024, 1) }} KB +
+ + +
+
+ +
+ @if($canPreview) + + @else +
+ + + + +
+ @endif +
+ + +
+ + + @error('pendingMeta.'.$sel['queue_key'].'.caption') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('pendingMeta.'.$sel['queue_key'].'.description') +

{{ $message }}

+ @enderror +
+ + +
+ + + @error('pendingMeta.'.$sel['queue_key'].'.order') +

{{ $message }}

+ @enderror +
+ + +
+ +
+
+
+
+ @endforeach +
+ +
+ + + +
+
+ @endif +
+ + @if($showList) +
+
+
{{ $attachedFilesTitle ?? 'Current gallery' }}
+ +
+ + @if (count($items) === 0) +
+

No gallery images yet.

+
+ @else +
    + @foreach ($items as $m) + @php + $id = (int) $m['id']; + $isEditing = isset($editing[$id]); + $linkText = $m['caption'] ?: ($m['name'] ?: ($m['original_name'] ?? $m['file_name'])); + @endphp + +
  • + {{-- Thumbnail / icon --}} + @if(!empty($m['thumb'])) + + @else +
    + + + + +
    + @endif + + {{-- Content --}} +
    + @if (! $isEditing) + @php + $isImage = \Illuminate\Support\Str::startsWith($m['mime'] ?? '', 'image/'); + @endphp + +
    + @if ($isImage) + + @else + + {{ $linkText }} + + @endif +
    + @if(!empty($m['description'])) +
    {{ $m['description'] }}
    + @endif +
    {{ number_format(($m['size'] ?? 0)/1024, 1) }} KB
    + @else +
    +
    +
    Caption
    + + @error('editing.'.$id.'.caption') +

    {{ $message }}

    + @enderror +
    + +
    +
    Description
    + + @error('editing.'.$id.'.description') +

    {{ $message }}

    + @enderror +
    + +
    +
    Order
    +
    + +
    + @error('editing.'.$id.'.order') +

    {{ $message }}

    + @enderror +
    +
    + @endif +
    + +
    + @if (! $isEditing) + + + @else +
    + + +
    + @endif +
    +
  • + @endforeach +
+ + + + + {{-- Delete confirmation modal --}} + + @endif +
+ @endif +
diff --git a/src/Livewire/MediaUploader.php b/src/Livewire/MediaUploader.php index ee08738..4a945c5 100644 --- a/src/Livewire/MediaUploader.php +++ b/src/Livewire/MediaUploader.php @@ -18,17 +18,17 @@ class MediaUploader extends Component { use WithFileUploads; - public array $namespaces = ['App\\Models']; - public array $aliases = []; - public ?string $collection = 'images'; - public ?string $disk = null; - public bool $multiple = true; - public ?string $accept = null; - public bool $showList = true; - public int $maxSizeKb = 500; - public array $uploads = []; - public array $selected = []; - public array $items = []; + public array $namespaces = ['App\\Models']; + public array $aliases = []; + public ?string $collection = 'images'; + public ?string $disk = null; + public bool $multiple = true; + public ?string $accept = null; + public bool $showList = true; + public int $maxSizeKb = 500; + public array $uploads = []; + public array $selected = []; + public array $items = []; public string $onNameConflict = 'rename'; public bool $skipExactDuplicates = false; public ?string $preset = null; @@ -39,6 +39,7 @@ class MediaUploader extends Component public array $pendingMeta = []; public ?int $confirmingDeleteId = null; public string $allowedLabel = ''; + public ?string $theme = null; #[Locked] public string $resolvedModelClass; #[Locked] public int|string $resolvedModelId; From 8e868666ae5ee76dc760214cde4b815a38e9a262 Mon Sep 17 00:00:00 2001 From: RJ Date: Mon, 1 Sep 2025 12:26:31 -0700 Subject: [PATCH 2/2] Added theme system (Tailwind default + Bootstrap; configurable via config) --- CHANGELOG.md | 55 +++++++++++++++++++++++++++++++++++++++------------- README.md | 40 +++++++++++++++++++++++++++++++++----- 2 files changed, 77 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb8829f..3c22916 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,26 +11,55 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- Configuration documentation for each option in `config/media-uploader.php`, including environment variable overrides, presets (types, mimes, max_kb), and collection → preset mapping. -- Guidance for creating and registering custom themes: - - Create a new folder under `themes`, e.g. `custom`. - - Copy the existing `media-uploader` theme file from `tailwind` or `bootstrap` into the new `custom` folder (do not change the file name). - - Edit the copied file as desired. - - Register it in the `themes` config array, for example: `'custom' => 'media-uploader::themes.custom.media-uploader'`. - - Use it globally via `.env` (`MEDIA_UPLOADER_THEME=custom`) or per instance: - + ### Changed -- Clarified the behavior of `accept_from_config` and how the `accept` attribute is derived from the active preset. ### Fixed --- +## [v0.2.0] — 2025-09-01 + +### Added +- **Theme system** with **Tailwind (default)** and **Bootstrap** themes. +- **Custom themes** support: + 1. Create a new folder under `resources/views/vendor/media-uploader/themes`, e.g. `custom/`. + 2. Copy `media-uploader.blade.php` from `tailwind/` or `bootstrap/` into `custom/` (keep the filename). + 3. Register in config: + ```php + 'themes' => [ + 'tailwind' => 'media-uploader::themes.tailwind.media-uploader', + 'bootstrap' => 'media-uploader::themes.bootstrap.media-uploader', + 'custom' => 'media-uploader::themes.custom.media-uploader', + ], + 'theme' => 'custom', // to make it default + ``` + 4. Or set per-instance: + ```html + + ``` +- Configuration docs for each option in `config/media-uploader.php`, including **ENV overrides**, presets (`types`, `mimes`, `max_kb`), and **collection → preset** mapping. + +### Changed +- Default view now resolves via the **theme map** (Tailwind by default). + Existing installs continue to render with Tailwind unless you switch themes. + +### Compatibility +- **No breaking changes.** Defaults preserve prior behavior. +- If you previously published the old (pre-theme) Blade, it will keep working if you’ve retained the legacy alias. If you want to use the new theme system, publish/move your override to `themes//media-uploader.blade.php`. + +### Migration Notes (only if you customized the old path) +- Minor migration required for users who published the old view (move file to the themed path). +- Move your customized Blade from: + ```html + resources/views/vendor/media-uploader/livewire/media-uploader.blade.php + ``` + to: + ```html + resources/views/vendor/media-uploader/themes/tailwind/media-uploader.blade.php + ``` + (or into your custom theme folder), and register that theme in the config. ## [v0.1.0] — 2025-08-30 ### Added diff --git a/README.md b/README.md index c261656..2e0e7ac 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Livewire Media Uploader -Livewire Media Uploader is a reusable Livewire v3 component that integrates seamlessly with Spatie Laravel Media Library. It ships a clean Tailwind Blade view (fully publishable), Alpine overlays for previews and confirmations, drag-and-drop uploads, per-file metadata (caption/description/order), configurable presets, name-conflict strategies, and optional SHA-256 duplicate detection. Drop it in, point it at a model, and you’re shipping in minutes. +Livewire Media Uploader is a reusable Livewire v3 component that integrates seamlessly with Spatie Laravel Media Library. It ships a clean Tailwind Blade view by default (fully publishable), Bootstrap theme as an option, Alpine overlays for previews/confirmations, drag-and-drop uploads, per-file metadata (caption/description/order), configurable presets, name-conflict strategies, and optional SHA-256 duplicate detection. Drop it in, point it at a model, and you’re shipping in minutes. --- @@ -25,7 +25,10 @@ Livewire Media Uploader is a reusable Livewire v3 component that integrates seam ## Features -- ✅ Livewire v3 component with Tailwind-only Blade (no UI dependency) +- ✅ Livewire v3 component with themeable Blade UI + - Tailwind (default) + - Bootstrap (optional) + - Fully publishable and overridable - ✅ Spatie Media Library integration (attach, list, edit meta, delete) - ✅ **Publishable view** for per-project customization - ✅ Drag & drop uploads + progress bar @@ -50,6 +53,9 @@ Livewire Media Uploader is a reusable Livewire v3 component that integrates seam - spatie/laravel-medialibrary **^10.12** - TailwindCSS (optional but recommended for the default view) - Alpine.js (used by overlays/progress; see [Overlays & UX Notes](#overlays--ux-notes)) +- CSS depending on theme: + - Tailwind theme → TailwindCSS (recommended) + - Bootstrap theme → Bootstrap CSS (no Bootstrap JS required; Alpine drives modals) --- @@ -90,10 +96,34 @@ php artisan vendor:publish --tag=media-uploader-views After publishing, customize the Blade at: ```html -resources/views/vendor/media-uploader/livewire/media-uploader.blade.php +resources/views/vendor/media-uploader/themes/tailwind/media-uploader.blade.php +resources/views/vendor/media-uploader/themes/bootstrap/media-uploader.blade.php ``` - - +## Theme System (Tailwind + Bootstrap + custom) +Select the theme in config/media-uploader.php: +```php +// config/media-uploader.php +return [ + 'theme' => 'tailwind', // 'tailwind' (default) or 'bootstrap' + 'themes' => [ + 'tailwind' => 'media-uploader::themes.tailwind.media-uploader', + 'bootstrap' => 'media-uploader::themes.bootstrap.media-uploader', + ], + // ... +]; +``` +### Custom themes +- Copy an existing theme directory (e.g. themes/tailwind) to themes/custom and edit the Blade. +- Register it in the map and select it: + ```php + 'theme' => 'custom', + 'themes' => [ + 'tailwind' => 'media-uploader::themes.tailwind.media-uploader', + 'bootstrap' => 'media-uploader::themes.bootstrap.media-uploader', + 'custom' => 'media-uploader::themes.custom.media-uploader', + ], + ``` + > Note: The component’s Livewire + Alpine behavior is identical across themes. Only classes/markup differ. If you use the Bootstrap theme, make sure your layout includes Bootstrap CSS. ## Environment variables (optional) You can override preset limits and accepted types/mimes via .env. These map directly to config/media-uploader.php: