From b14b368e3bff3d4b4eff15a485c89208f85fce05 Mon Sep 17 00:00:00 2001 From: Randall Wilk Date: Wed, 14 Oct 2020 10:41:22 -0500 Subject: [PATCH 1/4] Add file upload component --- config/form-components.php | 5 + resources/sass/form-components.scss | 1 + resources/sass/utils/_files.scss | 44 ++ .../components/files/file-upload.blade.php | 74 ++++ src/Components/Files/FileUpload.php | 97 +++++ tests/Components/Files/FileUploadTest.php | 377 ++++++++++++++++++ 6 files changed, 598 insertions(+) create mode 100644 resources/sass/utils/_files.scss create mode 100644 resources/views/components/files/file-upload.blade.php create mode 100644 src/Components/Files/FileUpload.php create mode 100644 tests/Components/Files/FileUploadTest.php diff --git a/config/form-components.php b/config/form-components.php index 64f7d39..e2925ce 100644 --- a/config/form-components.php +++ b/config/form-components.php @@ -109,6 +109,11 @@ 'clear_icon' => 'heroicon-o-x-circle', ], + 'file-upload' => [ + 'class' => Components\Files\FileUpload::class, + 'view' => 'form-components::components.files.file-upload', + ], + ], /* diff --git a/resources/sass/form-components.scss b/resources/sass/form-components.scss index ce9b1f9..0365873 100644 --- a/resources/sass/form-components.scss +++ b/resources/sass/form-components.scss @@ -7,3 +7,4 @@ @import 'utils/choice'; @import 'utils/addon'; @import 'utils/flatpickr'; +@import 'utils/files'; diff --git a/resources/sass/utils/_files.scss b/resources/sass/utils/_files.scss new file mode 100644 index 0000000..86d54eb --- /dev/null +++ b/resources/sass/utils/_files.scss @@ -0,0 +1,44 @@ +.file-upload { + @apply flex; + @apply items-center; +} + +.file-upload__input { + @apply rounded-md; +} + +.file-upload__label { + @apply cursor-pointer; + @apply py-2; + @apply px-3; + @apply border; + @apply border-gray-300; + @apply rounded-md; + @apply text-sm; + @apply leading-4; + @apply font-medium; + @apply text-cool-gray-700; + @apply transition; + @apply duration-150; + @apply ease-in-out; + @apply shadow-sm; + + &:hover { + @apply text-cool-gray-500; + } + + &:active { + @apply bg-gray-50; + @apply text-cool-gray-800; + } + + [role="button"] { + @apply outline-none; + } +} + +.file-upload__label--focused { + @apply outline-none; + @apply border-blue-300; + @apply shadow-outline-blue; +} diff --git a/resources/views/components/files/file-upload.blade.php b/resources/views/components/files/file-upload.blade.php new file mode 100644 index 0000000..153220a --- /dev/null +++ b/resources/views/components/files/file-upload.blade.php @@ -0,0 +1,74 @@ +
+ {{ $slot }} + +
+ + offsetExists('aria-describedby')) + aria-describedby="{{ $id }}-error" + @endif + @endif + + {{ $attributes->except('class') }} + /> + + + + + {{-- Upload progress --}} + @if ($canShowUploadProgress($attributes)) +
+
+
+ {{ __('Processing...') }} +
+ +
+ + +
+
+ +
+
+
+
+
+ @endif +
+ + {{ $after ?? '' }} +
diff --git a/src/Components/Files/FileUpload.php b/src/Components/Files/FileUpload.php new file mode 100644 index 0000000..ed88ebb --- /dev/null +++ b/src/Components/Files/FileUpload.php @@ -0,0 +1,97 @@ +name = $name; + $this->id = $id ?? $name; + $this->multiple = $multiple; + $this->label = $label; + $this->displayUploadProgress = $displayUploadProgress; + $this->showErrors = $showErrors; + $this->type = $type; + } + + public function canShowUploadProgress($attributes) + { + if (! is_null($this->canShowUploadProgress)) { + return $this->canShowUploadProgress; + } + + if (! $this->displayUploadProgress) { + return $this->canShowUploadProgress = false; + } + + if (! $attributes->whereStartsWith('wire:model')->first()) { + return $this->canShowUploadProgress = false; + } + + return $this->canShowUploadProgress = true; + } + + public function accepts(): ?string + { + if (! $this->type) { + return null; + } + + $excelTypes = '.csv,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; + + return [ + 'audio' => 'audio/*', + 'image' => 'image/*', + 'video' => 'video/*', + 'pdf' => '.pdf', + 'csv' => '.csv', + 'spreadsheet' => $excelTypes, + 'excel' => $excelTypes, + 'text' => 'text/plain', + 'html' => 'text/html', + ][$this->type] ?? null; + } +} diff --git a/tests/Components/Files/FileUploadTest.php b/tests/Components/Files/FileUploadTest.php new file mode 100644 index 0000000..5cf9626 --- /dev/null +++ b/tests/Components/Files/FileUploadTest.php @@ -0,0 +1,377 @@ +withViewErrors([]); + + $expected = << +
+ + + + + +
+ + HTML; + + $this->assertComponentRenders( + $expected, + '' + ); + } + + /** @test */ + public function can_show_file_upload_progress_if_wire_model_is_set(): void + { + $this->withViewErrors([]); + + $expected = << +
+ + + + + + +
+
+
+ Processing... +
+ +
+ + +
+
+ +
+
+
+
+
+ + HTML; + + $this->assertComponentRenders( + $expected, + '' + ); + } + + /** @test */ + public function can_have_wire_model_without_upload_progress(): void + { + $this->withViewErrors([]); + + $expected = << +
+ + + + + +
+ + HTML; + + $this->assertComponentRenders( + $expected, + '', + ['show' => false], + ); + } + + /** @test */ + public function can_have_an_after_slot(): void + { + $this->withViewErrors([]); + + $template = << + +
After slot content...
+
+
+ HTML; + + $expected = << +
+ + + + + +
+ +
After slot content...
+ + HTML; + + $this->assertComponentRenders($expected, $template); + } + + /** @test */ + public function can_have_default_slotted_content(): void + { + $this->withViewErrors([]); + + $template = << +
Default slot content...
+
+ HTML; + + $expected = << +
Default slot content...
+ +
+ + + + + +
+ + HTML; + + $this->assertComponentRenders($expected, $template); + } + + /** @test */ + public function adds_class_attribute_to_root_element(): void + { + $this->withViewErrors([]); + + $expected = << +
+ + + + + +
+ + HTML; + + $this->assertComponentRenders( + $expected, + '' + ); + } + + /** @test */ + public function shows_aria_attributes_on_error(): void + { + $this->withViewErrors(['file' => 'required']); + + $expected = << +
+ + + + + +
+ + HTML; + + $this->assertComponentRenders( + $expected, + '' + ); + } + + /** + * @test + * @dataProvider acceptsTypes + * @param string $type + * @param string $shouldAccept + */ + public function can_be_told_to_accept_certain_preset_types(string $type, string $shouldAccept): void + { + $this->withViewErrors([]); + + $expected = << +
+ + + + + +
+ + HTML; + + $this->assertComponentRenders( + $expected, + '', + compact('type') + ); + } + + public function acceptsTypes(): array + { + $excelTypes = '.csv,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; + + return [ + ['audio', 'audio/*'], + ['image', 'image/*'], + ['video', 'video/*'], + ['pdf', '.pdf'], + ['csv', '.csv'], + ['spreadsheet', $excelTypes], + ['excel', $excelTypes], + ['text', 'text/plain'], + ['html', 'text/html'], + ]; + } +} From 5bcbaf8b660d5dda6c7984ef5c9f72541d73f8a6 Mon Sep 17 00:00:00 2001 From: rawilk Date: Wed, 14 Oct 2020 15:41:59 +0000 Subject: [PATCH 2/4] Fix styling --- src/Components/Files/FileUpload.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Components/Files/FileUpload.php b/src/Components/Files/FileUpload.php index ed88ebb..c4f361d 100644 --- a/src/Components/Files/FileUpload.php +++ b/src/Components/Files/FileUpload.php @@ -46,8 +46,7 @@ public function __construct( string $type = null, bool $displayUploadProgress = true, bool $showErrors = true - ) - { + ) { $this->name = $name; $this->id = $id ?? $name; $this->multiple = $multiple; From 1f534118671b1c99e64bccf77d5b376c979577e7 Mon Sep 17 00:00:00 2001 From: Randall Wilk Date: Wed, 14 Oct 2020 10:46:47 -0500 Subject: [PATCH 3/4] Add alpine to required assets for file upload component --- src/Components/Files/FileUpload.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Components/Files/FileUpload.php b/src/Components/Files/FileUpload.php index c4f361d..8041371 100644 --- a/src/Components/Files/FileUpload.php +++ b/src/Components/Files/FileUpload.php @@ -11,6 +11,8 @@ class FileUpload extends BladeComponent { use HandlesValidationErrors; + protected static array $assets = ['alpine']; + /** @var string */ public $name; From e1f88ecf1c3ee80452a17ca8621aeee567e1c5e7 Mon Sep 17 00:00:00 2001 From: Randall Wilk Date: Wed, 14 Oct 2020 13:46:43 -0500 Subject: [PATCH 4/4] Add FilePond input --- config/form-components.php | 10 +++ resources/sass/utils/_files.scss | 39 +++++++++ .../components/files/file-pond.blade.php | 30 +++++++ src/Components/Files/FilePond.php | 84 +++++++++++++++++++ src/Components/Files/FileUpload.php | 31 +------ src/Concerns/AcceptsFiles.php | 35 ++++++++ 6 files changed, 200 insertions(+), 29 deletions(-) create mode 100644 resources/views/components/files/file-pond.blade.php create mode 100644 src/Components/Files/FilePond.php create mode 100644 src/Concerns/AcceptsFiles.php diff --git a/config/form-components.php b/config/form-components.php index e2925ce..91f9f36 100644 --- a/config/form-components.php +++ b/config/form-components.php @@ -114,6 +114,11 @@ 'view' => 'form-components::components.files.file-upload', ], + 'file-pond' => [ + 'class' => Components\Files\FilePond::class, + 'view' => 'form-components::components.files.file-pond', + ], + ], /* @@ -176,6 +181,11 @@ 'https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css', 'https://cdnjs.cloudflare.com/ajax/libs/flatpickr/4.6.3/flatpickr.min.js', ], + + 'filepond' => [ + 'https://unpkg.com/filepond/dist/filepond.css', + 'https://unpkg.com/filepond/dist/filepond.js', + ], ], ]; diff --git a/resources/sass/utils/_files.scss b/resources/sass/utils/_files.scss index 86d54eb..c6966eb 100644 --- a/resources/sass/utils/_files.scss +++ b/resources/sass/utils/_files.scss @@ -42,3 +42,42 @@ @apply border-blue-300; @apply shadow-outline-blue; } + +// FilePond style overrides +/* purgecss start ignore */ +.filepond--panel-root { + @apply border-dashed; + @apply border-2; + @apply border-cool-gray-200; + @apply rounded-md; +} + +.filepond--panel-root { + @apply bg-transparent; + @apply max-w-lg; + @apply transition; + @apply duration-150; + @apply ease-in-out; +} + +.filepond--label-action { + @apply text-blue-600; + text-decoration-color: theme('colors.blue.600'); + @apply transition; + @apply duration-100; + @apply ease-in-out; + + &:hover, + &:focus { + @apply opacity-75; + } +} + +.fc-filepond--desc { + @apply text-cool-gray-500; +} + +.fc-filepond--sub-desc { + @apply text-xs #{!important}; +} +/* purgecss end ignore */ diff --git a/resources/views/components/files/file-pond.blade.php b/resources/views/components/files/file-pond.blade.php new file mode 100644 index 0000000..0a6c239 --- /dev/null +++ b/resources/views/components/files/file-pond.blade.php @@ -0,0 +1,30 @@ +
+ except('wire:model') }} + /> +
diff --git a/src/Components/Files/FilePond.php b/src/Components/Files/FilePond.php new file mode 100644 index 0000000..05bed39 --- /dev/null +++ b/src/Components/Files/FilePond.php @@ -0,0 +1,84 @@ +multiple = $multiple; + $this->allowDrop = $allowDrop; + $this->name = $name; + $this->disabled = $disabled; + $this->maxFiles = $maxFiles; + $this->type = $type; + $this->options = $options; + $this->description = $description; + } + + public function options(): array + { + $label = array_filter([ + 'Upload a file or drag and drop', + $this->description, + ]); + + if (isset($label[1])) { + $label[1] = '' . $label[1] . ''; + } + + $defaultOptions = [ + 'allowMultiple' => $this->multiple, + 'allowDrop' => $this->allowDrop, + 'disabled' => $this->disabled, + ] + array_filter([ + 'maxFiles' => $this->multiple && $this->maxFiles ? $this->maxFiles : null, + 'name' => $this->name, + 'labelIdle' => '' . implode('
', $label) . '
', + ]); + + return array_merge($defaultOptions, $this->options); + } + + public function jsonOptions(): string + { + if (empty($this->options())) { + return ''; + } + + return '...' . json_encode((object) $this->options()) . ','; + } +} diff --git a/src/Components/Files/FileUpload.php b/src/Components/Files/FileUpload.php index 8041371..ff1b571 100644 --- a/src/Components/Files/FileUpload.php +++ b/src/Components/Files/FileUpload.php @@ -5,11 +5,13 @@ namespace Rawilk\FormComponents\Components\Files; use Rawilk\FormComponents\Components\BladeComponent; +use Rawilk\FormComponents\Concerns\AcceptsFiles; use Rawilk\FormComponents\Concerns\HandlesValidationErrors; class FileUpload extends BladeComponent { use HandlesValidationErrors; + use AcceptsFiles; protected static array $assets = ['alpine']; @@ -30,14 +32,6 @@ class FileUpload extends BladeComponent */ public bool $displayUploadProgress; - /** - * If specified, the component will fill out the "accept" property depending on - * which type is requested. - * - * @var string - */ - public $type; - protected ?bool $canShowUploadProgress = null; public function __construct( @@ -74,25 +68,4 @@ public function canShowUploadProgress($attributes) return $this->canShowUploadProgress = true; } - - public function accepts(): ?string - { - if (! $this->type) { - return null; - } - - $excelTypes = '.csv,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; - - return [ - 'audio' => 'audio/*', - 'image' => 'image/*', - 'video' => 'video/*', - 'pdf' => '.pdf', - 'csv' => '.csv', - 'spreadsheet' => $excelTypes, - 'excel' => $excelTypes, - 'text' => 'text/plain', - 'html' => 'text/html', - ][$this->type] ?? null; - } } diff --git a/src/Concerns/AcceptsFiles.php b/src/Concerns/AcceptsFiles.php new file mode 100644 index 0000000..cc02c5b --- /dev/null +++ b/src/Concerns/AcceptsFiles.php @@ -0,0 +1,35 @@ +type) { + return null; + } + + $excelTypes = '.csv,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; + + return [ + 'audio' => 'audio/*', + 'image' => 'image/*', + 'video' => 'video/*', + 'pdf' => '.pdf', + 'csv' => '.csv', + 'spreadsheet' => $excelTypes, + 'excel' => $excelTypes, + 'text' => 'text/plain', + 'html' => 'text/html', + ][$this->type] ?? null; + } +}