This commit is contained in:
Dan Harrin
2023-07-15 23:40:45 +01:00
parent c75fabee75
commit 3ca11a956f
13 changed files with 552 additions and 398 deletions
+35 -27
View File
@@ -34,35 +34,43 @@ const defaultOptions = {
'.woff2': 'file',
'.data': 'base64',
},
plugins: [{
name: 'watchPlugin',
setup: function (build) {
build.onStart(() => {
console.log(`Build started at ${new Date(Date.now()).toLocaleTimeString()}: ${build.initialOptions.outfile}`)
})
plugins: [
{
name: 'watchPlugin',
setup: function (build) {
build.onStart(() => {
console.log(
`Build started at ${new Date(
Date.now(),
).toLocaleTimeString()}: ${
build.initialOptions.outfile
}`,
)
})
build.onEnd((result) => {
if (result.errors.length > 0) {
console.log(
`Build failed at ${new Date(
Date.now(),
).toLocaleTimeString()}: ${
build.initialOptions.outfile
}`,
result.errors,
)
} else {
console.log(
`Build finished at ${new Date(
Date.now(),
).toLocaleTimeString()}: ${
build.initialOptions.outfile
}`,
)
}
})
build.onEnd((result) => {
if (result.errors.length > 0) {
console.log(
`Build failed at ${new Date(
Date.now(),
).toLocaleTimeString()}: ${
build.initialOptions.outfile
}`,
result.errors,
)
} else {
console.log(
`Build finished at ${new Date(
Date.now(),
).toLocaleTimeString()}: ${
build.initialOptions.outfile
}`,
)
}
})
},
},
}],
],
}
const corePackages = ['forms', 'notifications', 'panels', 'support', 'tables']
+26 -9
View File
@@ -23,6 +23,7 @@
"chart.js": "^3.6.0",
"choices.js": "^10.1.0",
"codemirror": "^5.65.8",
"cropperjs": "^1.5.13",
"dayjs": "^1.10.6",
"easymde": "^2.18.0",
"esbuild": "^0.17.18",
@@ -31,6 +32,7 @@
"filepond-plugin-file-validate-size": "^2.2.4",
"filepond-plugin-file-validate-type": "^1.2.6",
"filepond-plugin-image-crop": "^2.0.6",
"filepond-plugin-image-edit": "^1.6.3",
"filepond-plugin-image-exif-orientation": "^1.0.11",
"filepond-plugin-image-preview": "^4.6.7",
"filepond-plugin-image-resize": "^2.0.10",
@@ -3758,9 +3760,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001515",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001515.tgz",
"integrity": "sha512-eEFDwUOZbE24sb+Ecsx3+OvNETqjWIdabMy52oOkIgcUtAsQifjUG9q4U9dgTHJM2mfk4uEPxc0+xuFdJ629QA==",
"version": "1.0.30001516",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001516.tgz",
"integrity": "sha512-Wmec9pCBY8CWbmI4HsjBeQLqDTqV91nFVR83DnZpYyRnPI1wePDsTg0bGLPC5VU/3OIZV1fmxEea1b+tFKe86g==",
"dev": true,
"funding": [
{
@@ -4211,6 +4213,12 @@
"sha.js": "^2.4.8"
}
},
"node_modules/cropperjs": {
"version": "1.5.13",
"resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.5.13.tgz",
"integrity": "sha512-by7jKAo73y5/Do0K6sxdTKHgndY0NMjG2bEdgeJxycbcmHuCiMXqw8sxy5C5Y5WTOTcDGmbT7Sr5CgKOXR06OA==",
"dev": true
},
"node_modules/cross-spawn": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
@@ -4784,9 +4792,9 @@
"dev": true
},
"node_modules/electron-to-chromium": {
"version": "1.4.460",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.460.tgz",
"integrity": "sha512-kKiHnbrHME7z8E6AYaw0ehyxY5+hdaRmeUbjBO22LZMdqTYCO29EvF0T1cQ3pJ1RN5fyMcHl1Lmcsdt9WWJpJQ==",
"version": "1.4.461",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.461.tgz",
"integrity": "sha512-1JkvV2sgEGTDXjdsaQCeSwYYuhLRphRpc+g6EHTFELJXEiznLt3/0pZ9JuAOQ5p2rI3YxKTbivtvajirIfhrEQ==",
"dev": true
},
"node_modules/elliptic": {
@@ -5421,6 +5429,15 @@
"filepond": ">=3.x <5.x"
}
},
"node_modules/filepond-plugin-image-edit": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/filepond-plugin-image-edit/-/filepond-plugin-image-edit-1.6.3.tgz",
"integrity": "sha512-5q3RDaVlfvyI346ckF1DfKw4uN5rfAmUCv7HCG30jBZcGmepg8hFyjVM71uZXYeb4AgLhpCkSPP8Immwig8bzw==",
"dev": true,
"peerDependencies": {
"filepond": ">=3.7.2 <5.x"
}
},
"node_modules/filepond-plugin-image-exif-orientation": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/filepond-plugin-image-exif-orientation/-/filepond-plugin-image-exif-orientation-1.0.11.tgz",
@@ -10105,9 +10122,9 @@
}
},
"node_modules/sucrase": {
"version": "3.32.0",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz",
"integrity": "sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==",
"version": "3.33.0",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.33.0.tgz",
"integrity": "sha512-ARGC7vbufOHfpvyGcZZXFaXCMZ9A4fffOGC5ucOW7+WHDGlAe8LJdf3Jts1sWhDeiI1RSWrKy5Hodl+JWGdW2A==",
"dev": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.2",
+2
View File
@@ -28,6 +28,7 @@
"chart.js": "^3.6.0",
"choices.js": "^10.1.0",
"codemirror": "^5.65.8",
"cropperjs": "^1.5.13",
"dayjs": "^1.10.6",
"easymde": "^2.18.0",
"esbuild": "^0.17.18",
@@ -36,6 +37,7 @@
"filepond-plugin-file-validate-size": "^2.2.4",
"filepond-plugin-file-validate-type": "^1.2.6",
"filepond-plugin-image-crop": "^2.0.6",
"filepond-plugin-image-edit": "^1.6.3",
"filepond-plugin-image-exif-orientation": "^1.0.11",
"filepond-plugin-image-preview": "^4.6.7",
"filepond-plugin-image-resize": "^2.0.10",
File diff suppressed because one or more lines are too long
+19 -1
View File
File diff suppressed because one or more lines are too long
@@ -24,12 +24,12 @@ window.FilePond = FilePond
export default function fileUploadFormComponent({
acceptedFileTypes,
cropperEmptyFillColor,
cropperMode,
cropperViewportHeight,
cropperViewportWidth,
imageCropperEmptyFillColor,
imageCropperMode,
imageCropperViewportHeight,
imageCropperViewportWidth,
deleteUploadedFileUsing,
disabled,
isDisabled,
getUploadedFilesUsing,
imageCropAspectRatio,
imagePreviewHeight,
@@ -38,7 +38,7 @@ export default function fileUploadFormComponent({
imageResizeTargetWidth,
imageResizeUpscale,
isAvatar,
isCroppable,
hasCroppableImages,
isDownloadable,
isOpenable,
isPreviewable,
@@ -74,7 +74,7 @@ export default function fileUploadFormComponent({
uploadedFileIndex: {},
showCropper: false,
isCropperOpen: false,
editingFile: {},
@@ -174,12 +174,12 @@ export default function fileUploadFormComponent({
load()
},
},
allowImageEdit: isCroppable,
allowImageEdit: hasCroppableImages,
imageEditEditor: {
open: (file) => this.loadCropper(file),
onconfirm: () => {},
oncancel: () => this.cancelCropper(),
onclose: () => this.cancelCropper(),
oncancel: () => this.closeCropper(),
onclose: () => this.closeCropper(),
},
})
@@ -413,46 +413,59 @@ export default function fileUploadFormComponent({
},
initCropper: function () {
if (disabled) return
if (isDisabled) {
return
}
if (!isCroppable) return
if (!hasCroppableImages) {
return
}
this.cropper = new Cropper(this.$refs.cropper, {
aspectRatio: cropperViewportWidth / cropperViewportHeight,
aspectRatio:
imageCropperViewportWidth / imageCropperViewportHeight,
autoCropArea: 1,
center: true,
crop: (event) => {
this.$refs.inputX.value = Math.round(event.detail.x)
this.$refs.inputY.value = Math.round(event.detail.y)
this.$refs.inputHeight.value = Math.round(
this.$refs.xPositionInput.value = Math.round(event.detail.x)
this.$refs.yPositionInput.value = Math.round(event.detail.y)
this.$refs.heightInput.value = Math.round(
event.detail.height,
)
this.$refs.inputWidth.value = Math.round(event.detail.width)
this.$refs.inputRotate.value = event.detail.rotate
this.$refs.widthInput.value = Math.round(event.detail.width)
this.$refs.rotationInput.value = event.detail.rotate
},
cropBoxResizable: true,
guides: true,
highlight: true,
responsive: true,
toggleDragModeOnDblclick: true,
viewMode: cropperMode,
viewMode: imageCropperMode,
wheelZoomRatio: 0.02,
})
},
cancelCropper: function () {
closeCropper: function () {
this.editingFile = {}
this.showCropper = false
this.isCropperOpen = false
this.cropper.destroy()
this.cropper = null
},
loadCropper: function (file) {
if (disabled) return
if (isDisabled) {
return
}
if (!isCroppable) return
if (!hasCroppableImages) {
return
}
if (!file) return
if (!file) {
return
}
this.editingFile = file
@@ -460,7 +473,7 @@ export default function fileUploadFormComponent({
const reader = new FileReader()
reader.onload = (event) => {
this.showCropper = true
this.isCropperOpen = true
setTimeout(() => this.cropper.replace(event.target.result), 200)
}
@@ -469,13 +482,17 @@ export default function fileUploadFormComponent({
},
saveCropper: function () {
if (disabled) return
if (isDisabled) {
return
}
if (!isCroppable) return
if (!hasCroppableImages) {
return
}
this.cropper
.getCroppedCanvas({
fillColor: cropperEmptyFillColor,
fillColor: imageCropperEmptyFillColor,
height: imageResizeTargetHeight,
imageSmoothingEnabled: true,
imageSmoothingQuality: 'high',
@@ -493,6 +510,8 @@ export default function fileUploadFormComponent({
)
this.$nextTick(() => {
this.shouldUpdateState = false
this.pond
.addFile(
new File(
@@ -509,7 +528,7 @@ export default function fileUploadFormComponent({
),
)
.then(() => {
this.cancelCropper()
this.closeCropper()
})
})
}, this.editingFile.type)
+43 -24
View File
@@ -96,14 +96,6 @@ return [
'label' => 'Flip image vertical',
],
'free' => [
'label' => 'Free',
],
'height' => [
'label' => 'Height',
],
'move_down' => [
'label' => 'Move image down',
],
@@ -116,10 +108,6 @@ return [
'label' => 'Move image to right',
],
'move_top_left' => [
'label' => 'Move image to top/left corner of the container',
],
'move_up' => [
'label' => 'Move image up',
],
@@ -128,14 +116,6 @@ return [
'label' => 'Reset',
],
'rotate' => [
'label' => 'Rotate',
],
'rotate_180' => [
'label' => 'Rotate image 180°',
],
'rotate_left' => [
'label' => 'Rotate image to left',
],
@@ -144,12 +124,12 @@ return [
'label' => 'Rotate image to right',
],
'save' => [
'label' => 'Save',
'set_aspect_ratio' => [
'label' => 'Set aspect ratio to :ratio',
],
'width' => [
'label' => 'Width',
'save' => [
'label' => 'Save',
],
'zoom_100' => [
@@ -166,6 +146,45 @@ return [
],
'fields' => [
'height' => [
'label' => 'Height',
'unit' => 'px',
],
'rotation' => [
'label' => 'Rotation',
'unit' => 'deg',
],
'width' => [
'label' => 'Width',
'unit' => 'px',
],
'x_position' => [
'label' => 'X',
'unit' => 'px',
],
'y_position' => [
'label' => 'Y',
'unit' => 'px',
],
],
'aspect_ratios' => [
'label' => 'Aspect ratios',
'no_fixed' => [
'label' => 'Free',
],
],
],
],
+26 -24
View File
@@ -96,14 +96,6 @@ return [
'label' => 'Vänd bilden vertikalt',
],
'free' => [
'label' => 'Fritt',
],
'height' => [
'label' => 'Höjd',
],
'move_down' => [
'label' => 'Flytta bilden nedåt',
],
@@ -116,10 +108,6 @@ return [
'label' => 'Flytta bilden åt höger',
],
'move_top_left' => [
'label' => 'Flytta bilden till vänstra hörnet',
],
'move_up' => [
'label' => 'Flytta bilden uppåt',
],
@@ -128,14 +116,6 @@ return [
'label' => 'Reset',
],
'rotate' => [
'label' => 'Rotera',
],
'rotate_180' => [
'label' => 'Rotera bilden 180°',
],
'rotate_left' => [
'label' => 'Rotera bilden åt vänster',
],
@@ -148,10 +128,6 @@ return [
'label' => 'Spara',
],
'width' => [
'label' => 'Bredd',
],
'zoom_100' => [
'label' => 'Zooma bilden till 100%',
],
@@ -166,6 +142,32 @@ return [
],
'fields' => [
'height' => [
'label' => 'Höjd',
],
'rotation' => [
'label' => 'Rotera',
],
'width' => [
'label' => 'Bredd',
],
],
'aspect_ratios' => [
'label' => 'Aspect ratios',
'no_fixed' => [
'label' => 'Fritt',
],
],
],
],
@@ -10,64 +10,69 @@
$isAvatar = $isAvatar();
$statePath = $getStatePath();
$isDisabled = $isDisabled();
$isCroppable = $isCroppable();
$hasCroppableImages = $hasCroppableImages();
@endphp
<div
ax-load
ax-load-src="{{ \Filament\Support\Facades\FilamentAsset::getAlpineComponentSrc('file-upload', 'filament/forms') }}"
x-data="fileUploadFormComponent({
acceptedFileTypes: @js($getAcceptedFileTypes()),
deleteUploadedFileUsing: async (fileKey) => {
return await $wire.deleteUploadedFile(@js($statePath), fileKey)
},
getUploadedFilesUsing: async () => {
return await $wire.getFormUploadedFiles(@js($statePath))
},
imageCropAspectRatio: @js($imageCropAspectRatio),
imagePreviewHeight: @js($getImagePreviewHeight()),
imageResizeMode: @js($getImageResizeMode()),
imageResizeTargetHeight: @js($imageResizeTargetHeight),
imageResizeTargetWidth: @js($imageResizeTargetWidth),
imageResizeUpscale: @js($getImageResizeUpscale()),
isAvatar: {{ $isAvatar ? 'true' : 'false' }},
isCroppable: @js($isCroppable),
isDownloadable: @js($isDownloadable()),
isOpenable: @js($isOpenable()),
isPreviewable: @js($isPreviewable()),
isReorderable: @js($isReorderable()),
loadingIndicatorPosition: @js($getLoadingIndicatorPosition()),
locale: @js(app()->getLocale()),
panelAspectRatio: @js($getPanelAspectRatio()),
panelLayout: @js($getPanelLayout()),
placeholder: @js($getPlaceholder()),
maxSize: {{ ($size = $getMaxSize()) ? "'{$size} KB'" : 'null' }},
minSize: {{ ($size = $getMinSize()) ? "'{$size} KB'" : 'null' }},
removeUploadedFileUsing: async (fileKey) => {
return await $wire.removeFormUploadedFile(@js($statePath), fileKey)
},
removeUploadedFileButtonPosition: @js($getRemoveUploadedFileButtonPosition()),
reorderUploadedFilesUsing: async (files) => {
return await $wire.reorderFormUploadedFiles(@js($statePath), files)
},
shouldAppendFiles: @js($shouldAppendFiles()),
shouldOrientImageFromExif: @js($shouldOrientImagesFromExif()),
shouldTransformImage: @js($imageCropAspectRatio || $imageResizeTargetHeight || $imageResizeTargetWidth),
state: $wire.{{ $applyStateBindingModifiers("entangle('{$statePath}')") }},
uploadButtonPosition: @js($getUploadButtonPosition()),
uploadProgressIndicatorPosition: @js($getUploadProgressIndicatorPosition()),
uploadUsing: (fileKey, file, success, error, progress) => {
$wire.upload(
`{{ $statePath }}.${fileKey}`,
file,
() => {
success(fileKey)
acceptedFileTypes: @js($getAcceptedFileTypes()),
imageCropperEmptyFillColor: '{{ $getImageCropperEmptyFillColor() }}',
imageCropperMode: {{ $getImageCropperMode() }},
imageCropperViewportHeight: @js($getImageCropperViewportHeight()),
imageCropperViewportWidth: @js($getImageCropperViewportWidth()),
deleteUploadedFileUsing: async (fileKey) => {
return await $wire.deleteUploadedFile(@js($statePath), fileKey)
},
error,
progress,
)
},
})"
getUploadedFilesUsing: async () => {
return await $wire.getFormUploadedFiles(@js($statePath))
},
hasCroppableImages: @js($hasCroppableImages),
imageCropAspectRatio: @js($imageCropAspectRatio),
imagePreviewHeight: @js($getImagePreviewHeight()),
imageResizeMode: @js($getImageResizeMode()),
imageResizeTargetHeight: @js($imageResizeTargetHeight),
imageResizeTargetWidth: @js($imageResizeTargetWidth),
imageResizeUpscale: @js($getImageResizeUpscale()),
isAvatar: @js($isAvatar),
isDisabled: @js($isDisabled),
isDownloadable: @js($isDownloadable()),
isOpenable: @js($isOpenable()),
isPreviewable: @js($isPreviewable()),
isReorderable: @js($isReorderable()),
loadingIndicatorPosition: @js($getLoadingIndicatorPosition()),
locale: @js(app()->getLocale()),
panelAspectRatio: @js($getPanelAspectRatio()),
panelLayout: @js($getPanelLayout()),
placeholder: @js($getPlaceholder()),
maxSize: {{ ($size = $getMaxSize()) ? "'{$size} KB'" : 'null' }},
minSize: {{ ($size = $getMinSize()) ? "'{$size} KB'" : 'null' }},
removeUploadedFileUsing: async (fileKey) => {
return await $wire.removeFormUploadedFile(@js($statePath), fileKey)
},
removeUploadedFileButtonPosition: @js($getRemoveUploadedFileButtonPosition()),
reorderUploadedFilesUsing: async (files) => {
return await $wire.reorderFormUploadedFiles(@js($statePath), files)
},
shouldAppendFiles: @js($shouldAppendFiles()),
shouldOrientImageFromExif: @js($shouldOrientImagesFromExif()),
shouldTransformImage: @js($imageCropAspectRatio || $imageResizeTargetHeight || $imageResizeTargetWidth),
state: $wire.{{ $applyStateBindingModifiers("entangle('{$statePath}')") }},
uploadButtonPosition: @js($getUploadButtonPosition()),
uploadProgressIndicatorPosition: @js($getUploadProgressIndicatorPosition()),
uploadUsing: (fileKey, file, success, error, progress) => {
$wire.upload(
`{{ $statePath }}.${fileKey}`,
file,
() => {
success(fileKey)
},
error,
progress,
)
},
})"
wire:ignore
x-ignore
{{
@@ -109,151 +114,163 @@
/>
</div>
@if ($isCroppable && (! $isDisabled))
@if ($hasCroppableImages && (! $isDisabled))
<div
x-show="showCropper"
x-show="isCropperOpen"
x-cloak
x-on:click.stop
x-trap.noscroll="showCropper"
x-on:keydown.escape.window="cancelCropper"
class="fixed inset-0 h-screen w-screen p-2 sm:p-10 md:p-20 z-50 isolate"
x-trap.noscroll="isCropperOpen"
x-on:keydown.escape.window="closeCropper"
class="fixed inset-0 isolate z-50 h-screen w-screen p-2 sm:p-10 md:p-20"
>
<div
x-on:click="cancelCropper"
aria-hidden="true"
class="filament-modal-close-overlay fixed inset-0 w-full h-full bg-black/50 cursor-pointer"
style="will-change: transform;"
class="fixed inset-0 h-full w-full cursor-pointer bg-black/50"
style="will-change: transform"
></div>
<div class="w-full h-full flex justify-center items-center isolate z-10">
<div class="mx-auto h-full w-full flex flex-col lg:flex-row overflow-hidden bg-white dark:bg-gray-800 dark:ring-gray-50/10 ring-1 ring-gray-900/10 rounded-xl">
<div class="flex-1 w-full lg:h-full overflow-auto p-4">
<div
class="isolate z-10 flex h-full w-full items-center justify-center"
>
<div
class="mx-auto flex h-full w-full flex-col overflow-hidden rounded-xl bg-white ring-1 ring-gray-900/10 dark:bg-gray-800 dark:ring-gray-50/10 lg:flex-row"
>
<div class="w-full flex-1 overflow-auto p-4 lg:h-full">
<div class="h-full w-full">
<img x-ref="cropper" src="" class="h-full w-auto"/>
<img x-ref="cropper" class="h-full w-auto" />
</div>
</div>
<div class="w-full h-96 lg:h-full lg:max-w-xs overflow-auto bg-gray-50 dark:bg-gray-900/30 flex flex-col shadow-top lg:shadow-none z-[1]">
<div
class="shadow-top z-[1] flex h-96 w-full flex-col overflow-auto bg-gray-50 dark:bg-gray-900/30 lg:h-full lg:max-w-xs lg:shadow-none"
>
<div class="flex-1 overflow-hidden">
<div class="flex flex-col h-full overflow-y-auto">
<div
class="flex h-full flex-col overflow-y-auto"
>
<div class="flex-1 overflow-auto">
<div class="space-y-3 p-4">
<div class="w-full space-y-2">
<div class="space-y-6 p-4">
<div class="w-full space-y-3">
@foreach ([
[
'label' => 'X',
'name' => 'inputX',
'placeholder' => 'x',
'unit' => 'px',
'onEnter' => 'cropper.setData({...cropper.getData(true), x: +$el.value})',
'label' => __('filament-forms::components.file_upload.cropper.fields.x_position.label'),
'ref' => 'xPositionInput',
'unit' => __('filament-forms::components.file_upload.cropper.fields.x_position.unit'),
'alpineSaveHandler' => 'cropper.setData({...cropper.getData(true), x: +$el.value})',
],
[
'label' => 'Y',
'name' => 'inputY',
'placeholder' => 'y',
'unit' => 'px',
'onEnter' => 'cropper.setData({...cropper.getData(true), y: +$el.value})',
'label' => __('filament-forms::components.file_upload.cropper.fields.y_position.label'),
'ref' => 'yPositionInput',
'unit' => __('filament-forms::components.file_upload.cropper.fields.y_position.unit'),
'alpineSaveHandler' => 'cropper.setData({...cropper.getData(true), y: +$el.value})',
],
[
'label' => __('filament-forms::components.file_upload.cropper.actions.width.label'),
'name' => 'inputWidth',
'placeholder' => 'width',
'unit' => 'px',
'onEnter' => 'cropper.setData({...cropper.getData(true), width: +$el.value})',
'label' => __('filament-forms::components.file_upload.cropper.fields.width.label'),
'ref' => 'widthInput',
'unit' => __('filament-forms::components.file_upload.cropper.fields.width.unit'),
'alpineSaveHandler' => 'cropper.setData({...cropper.getData(true), width: +$el.value})',
],
[
'label' => __('filament-forms::components.file_upload.cropper.actions.height.label'),
'name' => 'inputHeight',
'placeholder' => 'height',
'unit' => 'px',
'onEnter' => 'cropper.setData({...cropper.getData(true), height: +$el.value})',
'label' => __('filament-forms::components.file_upload.cropper.fields.height.label'),
'ref' => 'heightInput',
'unit' => __('filament-forms::components.file_upload.cropper.fields.height.unit'),
'alpineSaveHandler' => 'cropper.setData({...cropper.getData(true), height: +$el.value})',
],
[
'label' => __('filament-forms::components.file_upload.cropper.actions.rotate.label'),
'name' => 'inputRotate',
'placeholder' => 'rotate',
'unit' => 'deg',
'onEnter' => 'cropper.rotateTo(+$el.value)',
'label' => __('filament-forms::components.file_upload.cropper.fields.rotation.label'),
'ref' => 'rotationInput',
'unit' => __('filament-forms::components.file_upload.cropper.fields.rotation.unit'),
'alpineSaveHandler' => 'cropper.rotateTo(+$el.value)',
],
] as $input)
<label class="flex items-center w-full text-sm border border-gray-300 dark:border-gray-700 rounded-lg shadow-sm bg-gray-100 dark:bg-gray-800">
<span class="w-20 flex-shrink-0 self-stretch flex items-center justify-center px-2 border-e border-gray-300 dark:border-gray-700">{{ $input['label'] }}</span>
<label
class="flex w-full items-center rounded-lg border border-gray-300 bg-gray-100 text-sm shadow-sm dark:border-gray-700 dark:bg-gray-800"
>
<span
class="flex w-20 flex-shrink-0 items-center justify-center self-stretch border-e border-gray-300 px-2 dark:border-gray-700"
>
{{ $input['label'] }}
</span>
<input
@class([
"text-sm block w-full transition duration-75 border-none focus:border-primary-500 focus:ring-1 focus:ring-inset focus:ring-primary-500 disabled:opacity-70 dark:bg-gray-700 dark:text-white dark:focus:border-primary-500"
'block w-full border-none text-sm transition duration-75 focus:border-primary-500 focus:ring-1 focus:ring-inset focus:ring-primary-500 disabled:opacity-70 dark:bg-gray-700 dark:text-white dark:focus:border-primary-500',
])
x-on:keyup.enter.stop.prevent="{{ $input['onEnter'] }}"
x-on:blur="{{ $input['onEnter'] }}"
x-ref="{{ $input['name'] }}"
name="@js($statePath . $input['name'])"
placeholder="{{ $input['placeholder'] }}"
x-on:keyup.enter.stop.prevent="{{ $input['alpineSaveHandler'] }}"
x-on:blur="{{ $input['alpineSaveHandler'] }}"
x-ref="{{ $input['ref'] }}"
x-on:keydown.enter.prevent
type="text"
/>
<span class="w-16 self-stretch flex items-center justify-center px-2 border-s border-gray-300 dark:border-gray-700">{{ $input['unit'] }}</span>
</label>
<span
class="flex w-16 items-center justify-center self-stretch border-s border-gray-300 px-2 dark:border-gray-700"
>
{{ $input['unit'] }}
</span>
</label>
@endforeach
</div>
@foreach ($getCropperActions() as $buttonGroups)
<div class="flex items-center w-full isolate rounded-lg shadow">
@foreach ($buttonGroups as $button)
<x-filament::button
type="button"
x-tooltip.raw="{{ $button['tooltip'] }}"
x-on:click.stop.prevent="{{ $button['click'] }}"
color="gray"
@class([
'!rounded-none !shadow-none flex-1 focus:z-10 active:z-10',
'!rounded-l-lg' => $loop->first,
'!rounded-r-lg' => $loop->last,
'-ml-px' => ! $loop->first,
])
>
{!! $button['icon'] !!}
</x-filament::button>
@endforeach
</div>
@endforeach
<div class="space-y-3">
@foreach ($getImageCropperActions(iconSizeClasses: 'h-5 w-5 mx-auto') as $groupedActions)
<x-filament::button.group
class="w-full"
>
@foreach ($groupedActions as $action)
<x-filament::button
x-tooltip.raw="{{ $action['label'] }}"
x-on:click.stop.prevent="{{ $action['alpineClickHandler'] }}"
color="gray"
grouped
>
{!! $action['iconHtml'] !!}
@foreach (collect($getCropperAspectRatios())->chunk(5) as $chunk)
<div class="flex items-center w-full isolate rounded-lg shadow">
@foreach ($chunk as $label => $ratio)
<x-filament::button
type="button"
x-tooltip.raw="Set aspect ratio: {{ $label }}"
x-on:click.stop.prevent="currentRatio = '{{ $label }}'; cropper.setAspectRatio({{ $ratio }})"
color="gray"
x-bind:class="{'!bg-gray-50 dark:!bg-gray-700': currentRatio === '{{ $label }}'}"
@class([
'!rounded-none !shadow-none flex-1 focus:z-10 active:z-10',
'!rounded-l-lg !rounded-r-none' => $loop->first,
'!rounded-r-lg !rounded-l-none' => $loop->last,
'-ml-px' => ! $loop->first,
])
<span
class="sr-only"
>
{{ $action['label'] }}
</span>
</x-filament::button>
@endforeach
</x-filament::button.group>
@endforeach
</div>
@if (count($aspectRatios = $getImageCropperAspectRatiosForJs()))
<div class="space-y-3">
<div
class="text-xs text-gray-950 dark:text-white"
>
{{ __('filament-forms::components.file_upload.cropper.aspect_ratios.label') }}
</div>
@foreach (collect($aspectRatios)->chunk(5) as $ratiosChunk)
<x-filament::button.group
class="w-full"
>
{{ $label }}
</x-filament::button>
@foreach ($ratiosChunk as $label => $ratio)
<x-filament::button
:x-tooltip.raw="__('filament-forms::components.file_upload.cropper.actions.set_aspect_ratio.label', ['ratio' => $label])"
x-on:click.stop.prevent="currentRatio = '{{ $label }}'; cropper.setAspectRatio({{ $ratio }})"
color="gray"
x-bind:class="{'!bg-gray-50 dark:!bg-gray-700': currentRatio === '{{ $label }}'}"
grouped
>
{{ $label }}
</x-filament::button>
@endforeach
</x-filament::button.group>
@endforeach
</div>
@endforeach
@endif
</div>
</div>
<div class="flex items-center gap-3 py-3 px-4 border-t border-gray-200 dark:border-gray-800 bg-gray-200 dark:bg-black/10">
<div
class="flex items-center gap-3 px-4 py-3"
>
<x-filament::button
size="sm"
color="warning"
x-on:click.stop.prevent="cropper.reset()"
>
{{ __('filament-forms::components.file_upload.cropper.actions.reset.label') }}
</x-filament::button>
<x-filament::button
size="sm"
color="gray"
x-on:click.prevent="pond.imageEditEditor.oncancel"
>
@@ -261,10 +278,16 @@
</x-filament::button>
<x-filament::button
size="sm"
color="warning"
x-on:click.stop.prevent="cropper.reset()"
class="ml-auto"
>
{{ __('filament-forms::components.file_upload.cropper.actions.reset.label') }}
</x-filament::button>
<x-filament::button
color="success"
x-on:click.prevent="saveCropper"
class="ml-auto"
>
{{ __('filament-forms::components.file_upload.cropper.actions.save.label') }}
</x-filament::button>
+123 -113
View File
@@ -3,8 +3,10 @@
namespace Filament\Forms\Components;
use Closure;
use Exception;
use Filament\Support\Concerns\HasAlignment;
use Filament\Support\Concerns\HasExtraAlpineAttributes;
use Illuminate\Support\Collection;
use Illuminate\Support\HtmlString;
class FileUpload extends BaseFileUpload
@@ -49,17 +51,20 @@ class FileUpload extends BaseFileUpload
protected string | Closure $uploadProgressIndicatorPosition = 'right';
protected bool | Closure $isCroppable = false;
protected bool | Closure $hasCroppableImages = false;
protected ?int $cropperViewportWidth = null;
protected int | Closure | null $imageCropperViewportWidth = null;
protected ?int $cropperViewportHeight = null;
protected int | Closure | null $imageCropperViewportHeight = null;
protected int $cropperMode = 1;
protected int $imageCropperMode = 1;
protected string $cropperEmptyFillColor = 'transparent';
protected string | Closure $imageCropperEmptyFillColor = 'transparent';
protected array | Closure $cropperAspectRatios = [];
/**
* @var array<?string> | Closure
*/
protected array | Closure $imageCropperAspectRatios = [];
public function appendFiles(bool | Closure $condition = true): static
{
@@ -81,7 +86,6 @@ class FileUpload extends BaseFileUpload
$this->removeUploadedFileButtonPosition('center bottom');
$this->uploadButtonPosition('center bottom');
$this->uploadProgressIndicatorPosition('center bottom');
$this->circularCropper();
return $this;
}
@@ -281,81 +285,83 @@ class FileUpload extends BaseFileUpload
return (bool) $this->evaluate($this->shouldOrientImagesFromExif);
}
public function croppable(bool | Closure $condition = true): static
public function croppableImages(bool | Closure $condition = true): static
{
$this->isCroppable = $condition;
$this->hasCroppableImages = $condition;
return $this;
}
public function cropperViewportWidth(int $width): static
public function imageCropperViewportWidth(int | Closure | null $width): static
{
$this->cropperViewportWidth = $width;
$this->imageCropperViewportWidth = $width;
return $this;
}
public function cropperViewportHeight(int $height): static
public function imageCropperViewportHeight(int | Closure | null $height): static
{
$this->cropperViewportHeight = $height;
$this->imageCropperViewportHeight = $height;
return $this;
}
// https://github.com/fengyuanchen/cropperjs#viewmode
public function cropperMode(int $mode): static
public function imageCropperMode(int $mode): static
{
//setting data via inputs won't work with viewMode 0 src: https://github.com/fengyuanchen/cropperjs#setdatadata, see "note"
if (in_array($mode, [1, 2, 3])) {
$this->cropperMode = $mode;
if (! in_array($mode, [1, 2, 3])) {
throw new Exception("The file upload cropper mode must be either 1, 2 or 3. [{$mode}] given, which is unsupported. See https://github.com/fengyuanchen/cropperjs#viewmode for more information on the available modes. Mode 0 is not supported, as it does not allow configuration via manual inputs.");
}
$this->imageCropperMode = $mode;
return $this;
}
// https://github.com/fengyuanchen/cropperjs#getcroppedcanvasoptions
public function cropperEmptyFillColor(string $color): static
public function imageCropperEmptyFillColor(string | Closure $color): static
{
$this->cropperEmptyFillColor = $color;
$this->imageCropperEmptyFillColor = $color;
return $this;
}
public function cropperAspectRatios(array $ratios): static
/**
* @param array<?string> | Closure $ratios
*/
public function imageCropperAspectRatios(array | Closure $ratios): static
{
$this->cropperAspectRatios = $ratios;
$this->imageCropperAspectRatios = $ratios;
return $this;
}
public function getCropperViewportHeight(): ?int
public function getImageCropperViewportHeight(): ?int
{
if (($targetHeight = (int) $this->getImageResizeTargetHeight()) > 1) {
return (int) round($targetHeight * $this->getParentTargetSizes($targetHeight), 0);
return (int) round($targetHeight * $this->getParentTargetSizes($targetHeight), precision: 0);
}
if (is_string($ratio = $this->getImageCropAspectRatio())) {
if (filled($ratio = $this->getImageCropAspectRatio())) {
[$numerator, $denominator] = explode(':', $ratio);
return $denominator;
return (int) $denominator;
}
return $this->cropperViewportHeight;
return $this->evaluate($this->imageCropperViewportHeight);
}
public function getCropperViewportWidth(): ?int
public function getImageCropperViewportWidth(): ?int
{
if (($targetWidth = (int) $this->getImageResizeTargetWidth()) > 1) {
return (int) round($targetWidth * $this->getParentTargetSizes($targetWidth), 0);
return (int) round($targetWidth * $this->getParentTargetSizes($targetWidth), precision: 0);
}
if (is_string($ratio = $this->getImageCropAspectRatio())) {
if (filled($ratio = $this->getImageCropAspectRatio())) {
[$numerator, $denominator] = explode(':', $ratio);
return $numerator;
return (int) $numerator;
}
return $this->cropperViewportWidth;
return $this->evaluate($this->imageCropperViewportWidth);
}
protected function getParentTargetSizes(int $withOrHeight): float
@@ -363,143 +369,147 @@ class FileUpload extends BaseFileUpload
return $withOrHeight > 1 ? 360 / (int) $this->getImageResizeTargetWidth() : 1;
}
public function getCropperShape(): string
public function getImageCropperMode(): int
{
return $this->isAvatar ? 'circle' : $this->cropperShape;
return $this->imageCropperMode;
}
public function getCropperMode(): int
public function getImageCropperEmptyFillColor(): string
{
return $this->isAvatar ? 1 : $this->cropperMode;
return $this->evaluate($this->imageCropperEmptyFillColor);
}
public function getCropperEmptyFillColor(): string
public function hasCroppableImages(): bool
{
return $this->cropperEmptyFillColor;
return (bool) $this->evaluate($this->hasCroppableImages);
}
public function isCroppable(): bool
/**
* @return array<string, float | string>
*/
public function getImageCropperAspectRatiosForJs(): array
{
return (bool) $this->evaluate($this->isCroppable);
return collect($this->evaluate($this->imageCropperAspectRatios) ?? [])
->when(
filled($imageCropAspectRatio = $this->getImageCropAspectRatio()),
fn (Collection $ratios): Collection => $ratios->push($imageCropAspectRatio),
)
->unique()
->mapWithKeys(fn (?string $ratio): array => [
$ratio ?? __('filament-forms::components.file_upload.cropper.aspect_ratios.no_fixed.label') => $this->normalizeImageCroppingRatioForJs($ratio),
])
->filter(fn (float | string | false $ratio): bool => $ratio !== false)
->when(
fn (Collection $ratios): bool => $ratios->count() < 2,
fn (Collection $ratios): Collection => $ratios->take(0),
)
->all();
}
public function getCropperAspectRatios(): array
{
$ratios = $this->evaluate($this->cropperAspectRatios);
$ratios[] = $this->getImageCropAspectRatio();
if (is_array($ratios)) {
$compiled = [];
foreach (array_unique($ratios) as $ratio) {
if ($validRatio = $this->makeRatio($ratio)) {
$compiled[$ratio ?? __('filament-forms::components.file_upload.cropper.actions.free.label')] = $validRatio;
}
}
//don't output any aspect ratio buttons if there is only one, nothing to toggle...
return count($compiled) > 1 ? $compiled : [];
}
return [];
}
protected function makeRatio(null | string | int $ratio): float | int | bool | string
protected function normalizeImageCroppingRatioForJs(?string $ratio): float | string | false
{
if ($ratio === null) {
return 'NaN';
}
$parts = explode(':', $ratio);
$ratioParts = explode(':', $ratio);
if (count($parts) !== 2) {
if (count($ratioParts) !== 2) {
return false;
}
[$numerator, $denominator] = $parts;
[$numerator, $denominator] = $ratioParts;
if ($denominator == 0 || ! is_numeric($numerator) || ! is_numeric($denominator)) {
if (! $denominator) {
return false;
}
if (! is_numeric($numerator)) {
return false;
}
if (! is_numeric($denominator)) {
return false;
}
return $numerator / $denominator;
}
public function getCropperActions(): array
/**
* @return array<array<array<string, mixed>>>
*/
public function getImageCropperActions(string $iconSizeClasses): array
{
$size = 'h-5 w-5 mx-auto';
return [
'zoom' => [
[
'tooltip' => __('filament-forms::components.file_upload.cropper.actions.drag_move.label'),
'icon' => new HtmlString('<svg class="' . $size . '" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M13 6v5h5V7.75L22.25 12L18 16.25V13h-5v5h3.25L12 22.25L7.75 18H11v-5H6v3.25L1.75 12L6 7.75V11h5V6H7.75L12 1.75L16.25 6H13Z"/></svg>'),
'click' => "cropper.setDragMode('move')",
'label' => __('filament-forms::components.file_upload.cropper.actions.drag_move.label'),
'iconHtml' => new HtmlString('<svg class="' . $iconSizeClasses . '" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M13 6v5h5V7.75L22.25 12L18 16.25V13h-5v5h3.25L12 22.25L7.75 18H11v-5H6v3.25L1.75 12L6 7.75V11h5V6H7.75L12 1.75L16.25 6H13Z"/></svg>'),
'alpineClickHandler' => "cropper.setDragMode('move')",
],
[
'tooltip' => __('filament-forms::components.file_upload.cropper.actions.drag_crop.label'),
'icon' => new HtmlString('<svg class="' . $size . '" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M17 23v-4H7q-.825 0-1.412-.587Q5 17.825 5 17V7H1V5h4V1h2v16h16v2h-4v4Zm0-8V7H9V5h8q.825 0 1.413.588Q19 6.175 19 7v8Z"/></svg>'),
'click' => "cropper.setDragMode('crop')",
'label' => __('filament-forms::components.file_upload.cropper.actions.drag_crop.label'),
'iconHtml' => new HtmlString('<svg class="' . $iconSizeClasses . '" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M17 23v-4H7q-.825 0-1.412-.587Q5 17.825 5 17V7H1V5h4V1h2v16h16v2h-4v4Zm0-8V7H9V5h8q.825 0 1.413.588Q19 6.175 19 7v8Z"/></svg>'),
'alpineClickHandler' => "cropper.setDragMode('crop')",
],
[
'tooltip' => __('filament-forms::components.file_upload.cropper.actions.zoom_in.label'),
'icon' => svg('heroicon-o-magnifying-glass-plus', $size)->toHtml(),
'click' => 'cropper.zoom(0.1)',
'label' => __('filament-forms::components.file_upload.cropper.actions.zoom_in.label'),
'iconHtml' => svg('heroicon-o-magnifying-glass-plus', $iconSizeClasses)->toHtml(),
'alpineClickHandler' => 'cropper.zoom(0.1)',
],
[
'tooltip' => __('filament-forms::components.file_upload.cropper.actions.zoom_out.label'),
'icon' => svg('heroicon-o-magnifying-glass-minus', $size)->toHtml(),
'click' => 'cropper.zoom(-0.1)',
'label' => __('filament-forms::components.file_upload.cropper.actions.zoom_out.label'),
'iconHtml' => svg('heroicon-o-magnifying-glass-minus', $iconSizeClasses)->toHtml(),
'alpineClickHandler' => 'cropper.zoom(-0.1)',
],
[
'label' => __('filament-forms::components.file_upload.cropper.actions.zoom_100.label'),
'iconHtml' => svg('heroicon-o-arrows-pointing-out', $iconSizeClasses)->toHtml(),
'alpineClickHandler' => 'cropper.zoomTo(1)',
],
// [
// 'tooltip' => __('filament-forms::components.file_upload.cropper.actions.zoom_100.label'),
// 'icon' => svg('heroicon-o-arrows-pointing-out', $size)->toHtml(),
// 'click' => 'cropper.zoomTo(1)',
// ],
],
'move' => [
[
'tooltip' => __('filament-forms::components.file_upload.cropper.actions.move_left.label'),
'icon' => svg('heroicon-o-arrow-left-circle', $size)->toHtml(),
'click' => 'cropper.move(-10, 0)',
'label' => __('filament-forms::components.file_upload.cropper.actions.move_left.label'),
'iconHtml' => svg('heroicon-o-arrow-left-circle', $iconSizeClasses)->toHtml(),
'alpineClickHandler' => 'cropper.move(-10, 0)',
],
[
'tooltip' => __('filament-forms::components.file_upload.cropper.actions.move_right.label'),
'icon' => svg('heroicon-o-arrow-right-circle', $size)->toHtml(),
'click' => 'cropper.move(10, 0)',
'label' => __('filament-forms::components.file_upload.cropper.actions.move_right.label'),
'iconHtml' => svg('heroicon-o-arrow-right-circle', $iconSizeClasses)->toHtml(),
'alpineClickHandler' => 'cropper.move(10, 0)',
],
[
'tooltip' => __('filament-forms::components.file_upload.cropper.actions.move_up.label'),
'icon' => svg('heroicon-o-arrow-up-circle', $size)->toHtml(),
'click' => 'cropper.move(0, -10)',
'label' => __('filament-forms::components.file_upload.cropper.actions.move_up.label'),
'iconHtml' => svg('heroicon-o-arrow-up-circle', $iconSizeClasses)->toHtml(),
'alpineClickHandler' => 'cropper.move(0, -10)',
],
[
'tooltip' => __('filament-forms::components.file_upload.cropper.actions.move_down.label'),
'icon' => svg('heroicon-o-arrow-down-circle', $size)->toHtml(),
'click' => 'cropper.move(0, 10)',
'label' => __('filament-forms::components.file_upload.cropper.actions.move_down.label'),
'iconHtml' => svg('heroicon-o-arrow-down-circle', $iconSizeClasses)->toHtml(),
'alpineClickHandler' => 'cropper.move(0, 10)',
],
],
'rotate_flip' => [
'transform' => [
[
'tooltip' => __('filament-forms::components.file_upload.cropper.actions.rotate_left.label'),
'icon' => svg('heroicon-o-arrow-uturn-left', $size)->toHtml(),
'click' => 'cropper.rotate(-90)',
'label' => __('filament-forms::components.file_upload.cropper.actions.rotate_left.label'),
'iconHtml' => svg('heroicon-o-arrow-uturn-left', $iconSizeClasses)->toHtml(),
'alpineClickHandler' => 'cropper.rotate(-90)',
],
[
'tooltip' => __('filament-forms::components.file_upload.cropper.actions.rotate_right.label'),
'icon' => svg('heroicon-o-arrow-uturn-right', $size)->toHtml(),
'click' => 'cropper.rotate(90)',
'label' => __('filament-forms::components.file_upload.cropper.actions.rotate_right.label'),
'iconHtml' => svg('heroicon-o-arrow-uturn-right', $iconSizeClasses)->toHtml(),
'alpineClickHandler' => 'cropper.rotate(90)',
],
[
'tooltip' => __('filament-forms::components.file_upload.cropper.actions.flip_horizontal.label'),
'icon' => new HtmlString('<svg class="' . $size . '" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m17 3l-5 5l-5-5h10m0 18l-5-5l-5 5h10M4 12H2m8 0H8m8 0h-2m8 0h-2"/></svg>'),
'click' => 'cropper.scaleX(-cropper.getData().scaleX || -1)',
'label' => __('filament-forms::components.file_upload.cropper.actions.flip_horizontal.label'),
'iconHtml' => new HtmlString('<svg class="' . $iconSizeClasses . '" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m17 3l-5 5l-5-5h10m0 18l-5-5l-5 5h10M4 12H2m8 0H8m8 0h-2m8 0h-2"/></svg>'),
'alpineClickHandler' => 'cropper.scaleX(-cropper.getData().scaleX || -1)',
],
[
'tooltip' => __('filament-forms::components.file_upload.cropper.actions.flip_vertical.label'),
'icon' => new HtmlString('<svg class="' . $size . '" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m3 7l5 5l-5 5V7m18 0l-5 5l5 5V7m-9 13v2m0-8v2m0-8v2m0-8v2"/></svg>'),
'click' => 'cropper.scaleY(-cropper.getData().scaleY || -1)',
'label' => __('filament-forms::components.file_upload.cropper.actions.flip_vertical.label'),
'iconHtml' => new HtmlString('<svg class="' . $iconSizeClasses . '" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m3 7l5 5l-5 5V7m18 0l-5 5l5 5V7m-9 13v2m0-8v2m0-8v2m0-8v2"/></svg>'),
'alpineClickHandler' => 'cropper.scaleY(-cropper.getData().scaleY || -1)',
],
],
];
+20 -2
View File
File diff suppressed because one or more lines are too long
@@ -0,0 +1,9 @@
<div
{{
$attributes->class([
'flex items-center divide-x divide-gray-950/10 overflow-hidden rounded-lg shadow ring-1 ring-gray-950/10 dark:divide-white/20 dark:ring-white/20',
])
}}
>
{{ $slot }}
</div>
@@ -2,6 +2,7 @@
'color' => 'primary',
'disabled' => false,
'form' => null,
'grouped' => false,
'icon' => null,
'iconAlias' => null,
'iconPosition' => 'before',
@@ -21,7 +22,9 @@
@php
$buttonClasses = \Illuminate\Support\Arr::toCssClasses([
...[
"fi-btn fi-btn-size-{$size} relative grid-flow-col items-center justify-center rounded-lg font-medium outline-none transition duration-75 focus:ring-2 disabled:pointer-events-none disabled:opacity-70",
"fi-btn fi-btn-size-{$size} relative grid-flow-col items-center justify-center font-medium outline-none transition duration-75 focus:ring-2 disabled:pointer-events-none disabled:opacity-70",
'flex-1' => $grouped,
'rounded-lg' => ! $grouped,
is_string($color) ? "fi-btn-color-{$color}" : null,
match ($size) {
'xs' => 'gap-1 px-2 py-1.5 text-xs',
@@ -41,19 +44,25 @@
},
],
...(
$outlined
? [
'fi-btn-outlined ring-1 ',
match ($color) {
'gray' => 'ring-gray-300 text-gray-950 hover:bg-gray-400/10 focus:bg-gray-400/10 focus:ring-gray-400/40 dark:ring-gray-700 dark:text-white',
default => 'ring-custom-600 text-custom-600 hover:bg-custom-400/10 focus:bg-custom-400/10 focus:ring-custom-500/50 dark:ring-custom-400 dark:text-custom-400 dark:focus:ring-custom-400/70',
},
]
: [
'shadow',
$outlined ?
[
'fi-btn-outlined ring-1',
match ($color) {
'gray' => 'ring-gray-950/10 ring-1 bg-white text-gray-950 hover:bg-gray-50 focus:bg-gray-50 dark:ring-white/20 dark:bg-white/10 dark:text-white dark:hover:bg-white/20 dark:hover:ring-white/30 dark:focus:bg-white/20 dark:focus:ring-white/30',
default => 'bg-custom-600 text-white hover:bg-custom-500 focus:bg-custom-500 focus:ring-custom-400/50 dark:bg-custom-500 dark:hover:bg-custom-400 dark:focus:bg-custom-400',
'gray' => 'ring-gray-300 text-gray-950 hover:bg-gray-400/10 focus:bg-gray-400/10 focus:ring-gray-400/40 dark:ring-gray-700 dark:text-white',
default => 'ring-custom-600 text-custom-600 hover:bg-custom-400/10 focus:bg-custom-400/10 focus:ring-custom-500/50 dark:ring-custom-400 dark:text-custom-400 dark:focus:ring-custom-400/70',
},
] :
[
'shadow' => ! $grouped,
...match ($color) {
'gray' => [
'bg-white text-gray-950 hover:bg-gray-50 focus:bg-gray-50 dark:bg-white/10 dark:text-white dark:hover:bg-white/20 dark:focus:bg-white/20',
'ring-gray-950/10 ring-1 dark:ring-white/20 dark:hover:ring-white/30 dark:focus:ring-white/30' => ! $grouped,
],
default => [
'bg-custom-600 text-white hover:bg-custom-500 focus:bg-custom-500 dark:bg-custom-500 dark:hover:bg-custom-400 dark:focus:bg-custom-400',
'focus:ring-custom-400/50' => ! $grouped,
],
},
]
),