Skip to content
This repository was archived by the owner on Mar 18, 2025. It is now read-only.

Commit dadace7

Browse files
authored
Feature/files (#8)
1 parent 0bb9f96 commit dadace7

File tree

9 files changed

+770
-0
lines changed

9 files changed

+770
-0
lines changed

config/form-components.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,16 @@
109109
'clear_icon' => 'heroicon-o-x-circle',
110110
],
111111

112+
'file-upload' => [
113+
'class' => Components\Files\FileUpload::class,
114+
'view' => 'form-components::components.files.file-upload',
115+
],
116+
117+
'file-pond' => [
118+
'class' => Components\Files\FilePond::class,
119+
'view' => 'form-components::components.files.file-pond',
120+
],
121+
112122
],
113123

114124
/*
@@ -171,6 +181,11 @@
171181
'https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css',
172182
'https://cdnjs.cloudflare.com/ajax/libs/flatpickr/4.6.3/flatpickr.min.js',
173183
],
184+
185+
'filepond' => [
186+
'https://unpkg.com/filepond/dist/filepond.css',
187+
'https://unpkg.com/filepond/dist/filepond.js',
188+
],
174189
],
175190

176191
];

resources/sass/form-components.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@
77
@import 'utils/choice';
88
@import 'utils/addon';
99
@import 'utils/flatpickr';
10+
@import 'utils/files';

resources/sass/utils/_files.scss

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
.file-upload {
2+
@apply flex;
3+
@apply items-center;
4+
}
5+
6+
.file-upload__input {
7+
@apply rounded-md;
8+
}
9+
10+
.file-upload__label {
11+
@apply cursor-pointer;
12+
@apply py-2;
13+
@apply px-3;
14+
@apply border;
15+
@apply border-gray-300;
16+
@apply rounded-md;
17+
@apply text-sm;
18+
@apply leading-4;
19+
@apply font-medium;
20+
@apply text-cool-gray-700;
21+
@apply transition;
22+
@apply duration-150;
23+
@apply ease-in-out;
24+
@apply shadow-sm;
25+
26+
&:hover {
27+
@apply text-cool-gray-500;
28+
}
29+
30+
&:active {
31+
@apply bg-gray-50;
32+
@apply text-cool-gray-800;
33+
}
34+
35+
[role="button"] {
36+
@apply outline-none;
37+
}
38+
}
39+
40+
.file-upload__label--focused {
41+
@apply outline-none;
42+
@apply border-blue-300;
43+
@apply shadow-outline-blue;
44+
}
45+
46+
// FilePond style overrides
47+
/* purgecss start ignore */
48+
.filepond--panel-root {
49+
@apply border-dashed;
50+
@apply border-2;
51+
@apply border-cool-gray-200;
52+
@apply rounded-md;
53+
}
54+
55+
.filepond--panel-root {
56+
@apply bg-transparent;
57+
@apply max-w-lg;
58+
@apply transition;
59+
@apply duration-150;
60+
@apply ease-in-out;
61+
}
62+
63+
.filepond--label-action {
64+
@apply text-blue-600;
65+
text-decoration-color: theme('colors.blue.600');
66+
@apply transition;
67+
@apply duration-100;
68+
@apply ease-in-out;
69+
70+
&:hover,
71+
&:focus {
72+
@apply opacity-75;
73+
}
74+
}
75+
76+
.fc-filepond--desc {
77+
@apply text-cool-gray-500;
78+
}
79+
80+
.fc-filepond--sub-desc {
81+
@apply text-xs #{!important};
82+
}
83+
/* purgecss end ignore */
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<div wire:ignore
2+
x-data
3+
x-cloak
4+
x-init="
5+
{{ $plugins ?? '' }}
6+
FilePond.setOptions({
7+
{{ $jsonOptions() }}
8+
{{-- Enhance for livewire support --}}
9+
@if ($attributes->whereStartsWith('wire:model')->first())
10+
server: {
11+
process: (fieldName, file, metadata, load, error, progress, abort, transfer, options) => {
12+
@this.upload('{{ $attributes['wire:model'] }}', file, load, error, progress);
13+
},
14+
revert: (filename, load) => {
15+
@this.removeUpload('{{ $attributes['wire:model'] }}', filename, load);
16+
},
17+
},
18+
@endif
19+
{{ $optionsSlot ?? '' }}
20+
});
21+
FilePond.create($refs.input);
22+
"
23+
>
24+
<input x-ref="input"
25+
type="file"
26+
style="display:none;"
27+
@if ($accepts()) accept="{{ $accepts() }}" @endif
28+
{{ $attributes->except('wire:model') }}
29+
/>
30+
</div>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<div class="file-upload space-x-5 {{ $attributes['class'] ?? '' }}">
2+
{{ $slot }}
3+
4+
<div x-data="{ focused: false, isUploading: false, progress: 0 }"
5+
@if ($canShowUploadProgress($attributes))
6+
x-on:livewire-upload-start="isUploading = true"
7+
x-on:livewire-upload-finish="isUploading = false"
8+
x-on:livewire-upload-error="isUploading = false"
9+
x-on:livewire-upload-progress="progress = $event.detail.progress"
10+
@endif
11+
class="space-y-4 w-full"
12+
>
13+
<span class="file-upload__input">
14+
<input x-on:focus="focused = true"
15+
x-on:blur="focused = false"
16+
class="sr-only"
17+
type="file"
18+
@if ($multiple) multiple @endif
19+
name="{{ $name }}"
20+
@if ($id) id="{{ $id }}" @endif
21+
@if ($accepts()) accept="{{ $accepts() }}" @endif
22+
23+
@if ($hasErrorsAndShow($name))
24+
aria-invalid="true"
25+
26+
@if (! $attributes->offsetExists('aria-describedby'))
27+
aria-describedby="{{ $id }}-error"
28+
@endif
29+
@endif
30+
31+
{{ $attributes->except('class') }}
32+
/>
33+
34+
<label for="{{ $id }}"
35+
x-bind:class="{ 'file-upload__label--focused': focused }"
36+
class="file-upload__label"
37+
>
38+
<span role="button"
39+
aria-controls="{{ $id }}"
40+
tabindex="0"
41+
>
42+
{{ $label }}
43+
</span>
44+
</label>
45+
</span>
46+
47+
{{-- Upload progress --}}
48+
@if ($canShowUploadProgress($attributes))
49+
<div class="relative" x-show.transition.opacity.duration.150ms="isUploading" x-cloak>
50+
<div class="flex mb-2 items-center justify-between">
51+
<div class="file-upload__badge inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium leading-4 bg-green-100 text-green-800">
52+
{{ __('Processing...') }}
53+
</div>
54+
55+
<div class="text-right">
56+
<span class="text-xs font-semibold inline-block text-green-600"
57+
x-text="progress + '%'"
58+
>
59+
</span>
60+
</div>
61+
</div>
62+
63+
<div class="file-upload__progress overflow-hidden h-2 mb-4 text-xs flex rounded bg-green-200">
64+
<div class="shadow-none flex flex-col text-center whitespace-nowrap text-white justify-center bg-green-500"
65+
x-bind:style="'width: ' + progress + '%;'"
66+
>
67+
</div>
68+
</div>
69+
</div>
70+
@endif
71+
</div>
72+
73+
{{ $after ?? '' }}
74+
</div>

src/Components/Files/FilePond.php

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rawilk\FormComponents\Components\Files;
6+
7+
use Rawilk\FormComponents\Components\BladeComponent;
8+
use Rawilk\FormComponents\Concerns\AcceptsFiles;
9+
use Rawilk\FormComponents\Concerns\HandlesValidationErrors;
10+
11+
class FilePond extends BladeComponent
12+
{
13+
use HandlesValidationErrors;
14+
use AcceptsFiles;
15+
16+
protected static array $assets = ['alpine', 'filepond'];
17+
18+
public bool $multiple;
19+
public bool $allowDrop;
20+
public bool $disabled;
21+
public array $options;
22+
23+
/** @var string|null */
24+
public $name;
25+
26+
/** @var int|null */
27+
public $maxFiles;
28+
29+
/** @var string|null */
30+
public $description;
31+
32+
public function __construct(
33+
bool $multiple = false,
34+
bool $allowDrop = true,
35+
string $name = null,
36+
array $options = [],
37+
bool $disabled = false,
38+
int $maxFiles = null,
39+
string $type = null,
40+
string $description = null
41+
) {
42+
$this->multiple = $multiple;
43+
$this->allowDrop = $allowDrop;
44+
$this->name = $name;
45+
$this->disabled = $disabled;
46+
$this->maxFiles = $maxFiles;
47+
$this->type = $type;
48+
$this->options = $options;
49+
$this->description = $description;
50+
}
51+
52+
public function options(): array
53+
{
54+
$label = array_filter([
55+
'<span class="filepond--label-action">Upload a file</span> or drag and drop',
56+
$this->description,
57+
]);
58+
59+
if (isset($label[1])) {
60+
$label[1] = '<span class="fc-filepond--sub-desc">' . $label[1] . '</span>';
61+
}
62+
63+
$defaultOptions = [
64+
'allowMultiple' => $this->multiple,
65+
'allowDrop' => $this->allowDrop,
66+
'disabled' => $this->disabled,
67+
] + array_filter([
68+
'maxFiles' => $this->multiple && $this->maxFiles ? $this->maxFiles : null,
69+
'name' => $this->name,
70+
'labelIdle' => '<span class="fc-filepond--desc">' . implode('<br>', $label) . '</span>',
71+
]);
72+
73+
return array_merge($defaultOptions, $this->options);
74+
}
75+
76+
public function jsonOptions(): string
77+
{
78+
if (empty($this->options())) {
79+
return '';
80+
}
81+
82+
return '...' . json_encode((object) $this->options()) . ',';
83+
}
84+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Rawilk\FormComponents\Components\Files;
6+
7+
use Rawilk\FormComponents\Components\BladeComponent;
8+
use Rawilk\FormComponents\Concerns\AcceptsFiles;
9+
use Rawilk\FormComponents\Concerns\HandlesValidationErrors;
10+
11+
class FileUpload extends BladeComponent
12+
{
13+
use HandlesValidationErrors;
14+
use AcceptsFiles;
15+
16+
protected static array $assets = ['alpine'];
17+
18+
/** @var string */
19+
public $name;
20+
21+
/** @var string */
22+
public $id;
23+
24+
/** @var string */
25+
public $label;
26+
27+
public bool $multiple;
28+
29+
/*
30+
* Display the file upload progress if using livewire.
31+
* Only applies if a "wire:model" attribute is set.
32+
*/
33+
public bool $displayUploadProgress;
34+
35+
protected ?bool $canShowUploadProgress = null;
36+
37+
public function __construct(
38+
string $name = null,
39+
string $id = null,
40+
string $label = 'Select File',
41+
bool $multiple = false,
42+
string $type = null,
43+
bool $displayUploadProgress = true,
44+
bool $showErrors = true
45+
) {
46+
$this->name = $name;
47+
$this->id = $id ?? $name;
48+
$this->multiple = $multiple;
49+
$this->label = $label;
50+
$this->displayUploadProgress = $displayUploadProgress;
51+
$this->showErrors = $showErrors;
52+
$this->type = $type;
53+
}
54+
55+
public function canShowUploadProgress($attributes)
56+
{
57+
if (! is_null($this->canShowUploadProgress)) {
58+
return $this->canShowUploadProgress;
59+
}
60+
61+
if (! $this->displayUploadProgress) {
62+
return $this->canShowUploadProgress = false;
63+
}
64+
65+
if (! $attributes->whereStartsWith('wire:model')->first()) {
66+
return $this->canShowUploadProgress = false;
67+
}
68+
69+
return $this->canShowUploadProgress = true;
70+
}
71+
}

0 commit comments

Comments
 (0)