generated from boboko/starter
product custom fields
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
{{--
|
||||
<x-product-custom-fields :fields="$product['custom_fields']" />
|
||||
|
||||
The product's custom fields (boboko-core's Product::$custom_fields), rendered
|
||||
inside the add-to-cart <form> so their values travel with it as
|
||||
custom_fields[key]. text → input, textarea → textarea, file → photo upload.
|
||||
|
||||
A photo is uploaded as soon as it's picked (custom-field-upload-controller.js)
|
||||
and only the returned reference is submitted with the form — the file input
|
||||
itself has no name. It still carries `required`, so the browser's own
|
||||
validation blocks add-to-cart until a photo is picked, and the controller
|
||||
marks it invalid (setCustomValidity) while the upload is in flight.
|
||||
--}}
|
||||
@props(['fields' => []])
|
||||
|
||||
@use('App\Http\Controllers\CustomFieldUploadController')
|
||||
|
||||
<div class="flex flex-col gap-6">
|
||||
@foreach ($fields as $field)
|
||||
@php
|
||||
$id = 'custom-field-'.$field['key'];
|
||||
$name = 'custom_fields['.$field['key'].']';
|
||||
$required = (bool) ($field['required'] ?? false);
|
||||
@endphp
|
||||
|
||||
@switch($field['type'])
|
||||
@case('textarea')
|
||||
<x-ui.field :label="$field['label']" :for="$id" :required="$required">
|
||||
<x-ui.textarea :id="$id" :name="$name" :required="$required" :rows="4" maxlength="2000" />
|
||||
</x-ui.field>
|
||||
@break
|
||||
|
||||
@case('file')
|
||||
<x-ui.field
|
||||
:label="$field['label']"
|
||||
:for="$id"
|
||||
:required="$required"
|
||||
:description="__('storefront.product.custom_field_photo_hint', ['size' => CustomFieldUploadController::MAX_KILOBYTES / 1024])"
|
||||
data-controller="custom-field-upload"
|
||||
data-custom-field-upload-url-value="{{ route('custom-field-upload.store') }}"
|
||||
data-custom-field-upload-label-value="{{ $field['label'] }}"
|
||||
data-custom-field-upload-uploading-message-value="{{ __('storefront.product.custom_field_uploading') }}"
|
||||
data-custom-field-upload-failed-message-value="{{ __('storefront.product.custom_field_upload_failed') }}"
|
||||
>
|
||||
<input type="hidden" name="{{ $name }}" data-custom-field-upload-target="reference">
|
||||
|
||||
<x-ui.file-input
|
||||
:id="$id"
|
||||
:accept="CustomFieldUploadController::accept()"
|
||||
:required="$required"
|
||||
aria-describedby="{{ $id }}-description {{ $id }}-error"
|
||||
data-custom-field-upload-target="file"
|
||||
data-action="change->custom-field-upload#upload"
|
||||
/>
|
||||
|
||||
<img
|
||||
alt=""
|
||||
width="96"
|
||||
height="96"
|
||||
hidden
|
||||
class="w-24 h-24 object-cover border border-black"
|
||||
data-custom-field-upload-target="preview"
|
||||
data-action="error->custom-field-upload#hidePreview"
|
||||
>
|
||||
|
||||
<p id="{{ $id }}-error" class="text-sm text-red-600" role="alert" hidden data-custom-field-upload-target="error"></p>
|
||||
</x-ui.field>
|
||||
@break
|
||||
|
||||
@default
|
||||
<x-ui.field :label="$field['label']" :for="$id" :required="$required">
|
||||
<x-ui.input :id="$id" :name="$name" :required="$required" maxlength="255" />
|
||||
</x-ui.field>
|
||||
@endswitch
|
||||
@endforeach
|
||||
</div>
|
||||
@@ -25,6 +25,7 @@
|
||||
:image="$product['image'] ?? null"
|
||||
:href="$product['href'] ?? '#'"
|
||||
:variant-id="$product['variantId'] ?? null"
|
||||
:has-custom-fields="$product['hasCustomFields'] ?? false"
|
||||
/>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
@props([
|
||||
'accept' => null,
|
||||
'required' => false,
|
||||
'disabled' => false,
|
||||
])
|
||||
|
||||
<input
|
||||
type="file"
|
||||
@if ($accept) accept="{{ $accept }}" @endif
|
||||
@required($required)
|
||||
@disabled($disabled)
|
||||
{{ $attributes->merge([
|
||||
'class' => 'w-full py-2 text-sm
|
||||
file:mr-4 file:py-2 file:px-4 file:rounded-none file:border file:border-black file:bg-transparent
|
||||
file:font-semibold file:cursor-pointer file:transition-colors
|
||||
hover:file:bg-black hover:file:text-neutral-200
|
||||
focus:outline-none focus-visible:ring-2 focus-visible:ring-black
|
||||
disabled:cursor-not-allowed disabled:opacity-50',
|
||||
]) }}
|
||||
/>
|
||||
@@ -4,6 +4,7 @@
|
||||
'image' => null,
|
||||
'href' => '#',
|
||||
'variantId' => null,
|
||||
'hasCustomFields' => false,
|
||||
])
|
||||
|
||||
{{-- data-turbo-frame="_top" on the links: this card renders inside the
|
||||
@@ -28,7 +29,18 @@ class="w-full h-auto block"
|
||||
@endif
|
||||
</a>
|
||||
|
||||
@if ($variantId)
|
||||
@if ($hasCustomFields)
|
||||
{{-- Custom fields have to be filled in on the product page. --}}
|
||||
<x-ui.button
|
||||
:href="$href"
|
||||
size="md"
|
||||
position="absolute"
|
||||
data-turbo-frame="_top"
|
||||
class="opacity-0 group-hover:opacity-100 focus-visible:opacity-100 transition-opacity duration-100"
|
||||
>
|
||||
{{ __('storefront.product.personalize') }}
|
||||
</x-ui.button>
|
||||
@elseif ($variantId)
|
||||
{{-- has-[...] forces the button visible while an add-to-cart error
|
||||
is showing, so it isn't only readable on hover — a shopper who
|
||||
already moved off the card (mouse or the click itself) must
|
||||
|
||||
Reference in New Issue
Block a user