affiliations
This commit is contained in:
1 parent
4f3f9c9aad
commit
0b5b3dd3ee
177 files changed
+9660
-8531
No files matched your search
@@ -0,0 +1,131 @@
|
||||
@props([
|
||||
'name' => 'date',
|
||||
'id' => 'date',
|
||||
'value' => '',
|
||||
'required' => false,
|
||||
'error' => null,
|
||||
'label' => 'Date',
|
||||
'minYear' => null,
|
||||
'maxYear' => null,
|
||||
'minAge' => null,
|
||||
'maxAge' => null
|
||||
])
|
||||
|
||||
@php
|
||||
$currentYear = date('Y');
|
||||
|
||||
// Calculate year range - support both direct year values and age-based calculation
|
||||
if ($minYear !== null && $maxYear !== null) {
|
||||
// Direct year range specified
|
||||
$startYear = $maxYear;
|
||||
$endYear = $minYear;
|
||||
} elseif ($minAge !== null && $maxAge !== null) {
|
||||
// Age-based calculation (for birthdates)
|
||||
$startYear = $currentYear - $minAge;
|
||||
$endYear = $currentYear - $maxAge;
|
||||
} else {
|
||||
// Default: current year to 100 years ago
|
||||
$startYear = $currentYear;
|
||||
$endYear = $currentYear - 100;
|
||||
}
|
||||
|
||||
// Parse existing value
|
||||
$selectedDay = '';
|
||||
$selectedMonth = '';
|
||||
$selectedYear = '';
|
||||
if ($value) {
|
||||
$parts = explode('-', $value);
|
||||
if (count($parts) === 3) {
|
||||
$selectedYear = $parts[0];
|
||||
$selectedMonth = $parts[1];
|
||||
$selectedDay = $parts[2];
|
||||
}
|
||||
}
|
||||
@endphp
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{{ $label }}@if($required) <span class="text-danger">*</span>@endif</label>
|
||||
<div class="row g-2">
|
||||
<div class="col-4">
|
||||
<select id="{{ $id }}_day"
|
||||
class="form-select @if($error) is-invalid @endif"
|
||||
{{ $required ? 'required' : '' }}
|
||||
onchange="updateDate_{{ $id }}()">
|
||||
<option value="">Day</option>
|
||||
@for($day = 1; $day <= 31; $day++)
|
||||
@php $dayPadded = str_pad($day, 2, '0', STR_PAD_LEFT); @endphp
|
||||
<option value="{{ $dayPadded }}" {{ $selectedDay == $dayPadded ? 'selected' : '' }}>
|
||||
{{ $day }}
|
||||
</option>
|
||||
@endfor
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<select id="{{ $id }}_month"
|
||||
class="form-select @if($error) is-invalid @endif"
|
||||
{{ $required ? 'required' : '' }}
|
||||
onchange="updateDate_{{ $id }}()">
|
||||
<option value="">Month</option>
|
||||
<option value="01" {{ $selectedMonth == '01' ? 'selected' : '' }}>January</option>
|
||||
<option value="02" {{ $selectedMonth == '02' ? 'selected' : '' }}>February</option>
|
||||
<option value="03" {{ $selectedMonth == '03' ? 'selected' : '' }}>March</option>
|
||||
<option value="04" {{ $selectedMonth == '04' ? 'selected' : '' }}>April</option>
|
||||
<option value="05" {{ $selectedMonth == '05' ? 'selected' : '' }}>May</option>
|
||||
<option value="06" {{ $selectedMonth == '06' ? 'selected' : '' }}>June</option>
|
||||
<option value="07" {{ $selectedMonth == '07' ? 'selected' : '' }}>July</option>
|
||||
<option value="08" {{ $selectedMonth == '08' ? 'selected' : '' }}>August</option>
|
||||
<option value="09" {{ $selectedMonth == '09' ? 'selected' : '' }}>September</option>
|
||||
<option value="10" {{ $selectedMonth == '10' ? 'selected' : '' }}>October</option>
|
||||
<option value="11" {{ $selectedMonth == '11' ? 'selected' : '' }}>November</option>
|
||||
<option value="12" {{ $selectedMonth == '12' ? 'selected' : '' }}>December</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<select id="{{ $id }}_year"
|
||||
class="form-select @if($error) is-invalid @endif"
|
||||
{{ $required ? 'required' : '' }}
|
||||
onchange="updateDate_{{ $id }}()">
|
||||
<option value="">Year</option>
|
||||
@for($year = $startYear; $year >= $endYear; $year--)
|
||||
<option value="{{ $year }}" {{ $selectedYear == $year ? 'selected' : '' }}>
|
||||
{{ $year }}
|
||||
</option>
|
||||
@endfor
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" id="{{ $id }}" name="{{ $name }}" value="{{ $value }}">
|
||||
@if($error)
|
||||
<span class="invalid-feedback d-block" role="alert">
|
||||
<strong>{{ $error }}</strong>
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@once
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize all date dropdown components
|
||||
document.querySelectorAll('[id$="_day"]').forEach(function(daySelect) {
|
||||
const baseId = daySelect.id.replace('_day', '');
|
||||
if (document.getElementById(baseId + '_month') && document.getElementById(baseId + '_year')) {
|
||||
// Component exists, initialize it
|
||||
window['updateDate_' + baseId] = function() {
|
||||
const day = document.getElementById(baseId + '_day').value;
|
||||
const month = document.getElementById(baseId + '_month').value;
|
||||
const year = document.getElementById(baseId + '_year').value;
|
||||
const hidden = document.getElementById(baseId);
|
||||
|
||||
if (day && month && year) {
|
||||
hidden.value = `${year}-${month}-${day}`;
|
||||
} else {
|
||||
hidden.value = '';
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
@endonce
|
||||
@@ -22,7 +22,7 @@
|
||||
onkeydown="event.stopPropagation()"
|
||||
onkeyup="event.stopPropagation()">
|
||||
|
||||
<div class="country-list" id="{{ $id }}List" style="max-height: 300px; overflow-y: auto;">
|
||||
<div class="country-list country-code-dropdown-list" id="{{ $id }}List" data-component-id="{{ $id }}" style="max-height: 300px; overflow-y: auto;">
|
||||
<!-- Countries will be populated by JavaScript -->
|
||||
</div>
|
||||
</div>
|
||||
@@ -162,9 +162,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize only country code dropdowns (not nationality dropdowns)
|
||||
document.querySelectorAll('[id$="country_codeList"]').forEach(function(listElement) {
|
||||
const componentId = listElement.id.replace('List', '');
|
||||
// Initialize all country code dropdowns
|
||||
document.querySelectorAll('.country-code-dropdown-list').forEach(function(listElement) {
|
||||
const componentId = listElement.getAttribute('data-component-id');
|
||||
initializeCountryDropdown(componentId, countries);
|
||||
});
|
||||
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
<div class="mb-3">
|
||||
<label for="{{ $id }}" class="form-label">Currency</label>
|
||||
<select id="{{ $id }}"
|
||||
class="form-select currency-select @error($name) is-invalid @enderror"
|
||||
class="form-select currency-dropdown-select @error($name) is-invalid @enderror"
|
||||
name="{{ $name }}"
|
||||
data-component-id="{{ $id }}"
|
||||
data-initial-value="{{ $value }}"
|
||||
{{ $required ? 'required' : '' }}>
|
||||
<option value="">Select Currency</option>
|
||||
</select>
|
||||
@@ -23,60 +25,60 @@
|
||||
fetch('/data/countries.json')
|
||||
.then(response => response.json())
|
||||
.then(countries => {
|
||||
const selectElement = document.getElementById('{{ $id }}');
|
||||
if (!selectElement) return;
|
||||
// Initialize all currency dropdowns
|
||||
document.querySelectorAll('.currency-dropdown-select').forEach(function(selectElement) {
|
||||
const initialValue = selectElement.getAttribute('data-initial-value');
|
||||
|
||||
// Get unique currencies
|
||||
const uniqueCurrencies = {};
|
||||
countries.forEach(country => {
|
||||
if (!uniqueCurrencies[country.currency]) {
|
||||
uniqueCurrencies[country.currency] = {
|
||||
currency: country.currency,
|
||||
currency_symbol: country.currency_symbol,
|
||||
flag: country.flag,
|
||||
name: country.name
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Populate dropdown
|
||||
Object.values(uniqueCurrencies).forEach(currencyData => {
|
||||
const option = document.createElement('option');
|
||||
option.value = currencyData.currency;
|
||||
option.textContent = `${currencyData.name} (${currencyData.currency} ${currencyData.currency_symbol})`;
|
||||
option.setAttribute('data-flag', currencyData.flag);
|
||||
selectElement.appendChild(option);
|
||||
});
|
||||
|
||||
// Set initial value if provided
|
||||
const initialValue = '{{ $value }}';
|
||||
if (initialValue) {
|
||||
selectElement.value = initialValue;
|
||||
}
|
||||
|
||||
// Initialize Select2 for searchable dropdown
|
||||
if (typeof $ !== 'undefined' && $.fn.select2) {
|
||||
$(selectElement).select2({
|
||||
templateResult: function(state) {
|
||||
if (!state.id) {
|
||||
return state.text;
|
||||
}
|
||||
const option = $(state.element);
|
||||
const flagCode = option.data('flag');
|
||||
return $(`<span><span class="fi fi-${flagCode} me-2"></span>${state.text}</span>`);
|
||||
},
|
||||
templateSelection: function(state) {
|
||||
if (!state.id) {
|
||||
return state.text;
|
||||
}
|
||||
const option = $(state.element);
|
||||
const flagCode = option.data('flag');
|
||||
return $(`<span><span class="fi fi-${flagCode} me-2"></span>${state.text}</span>`);
|
||||
},
|
||||
width: '100%'
|
||||
// Get unique currencies
|
||||
const uniqueCurrencies = {};
|
||||
countries.forEach(country => {
|
||||
if (country.currency && !uniqueCurrencies[country.currency]) {
|
||||
uniqueCurrencies[country.currency] = {
|
||||
currency: country.currency,
|
||||
currency_symbol: country.currency_symbol || '',
|
||||
flag: country.flag,
|
||||
name: country.name
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Populate dropdown
|
||||
Object.values(uniqueCurrencies).forEach(currencyData => {
|
||||
const option = document.createElement('option');
|
||||
option.value = currencyData.currency;
|
||||
option.textContent = `${currencyData.currency} - ${currencyData.name} ${currencyData.currency_symbol}`;
|
||||
option.setAttribute('data-flag', currencyData.flag);
|
||||
selectElement.appendChild(option);
|
||||
});
|
||||
|
||||
// Set initial value if provided
|
||||
if (initialValue) {
|
||||
selectElement.value = initialValue;
|
||||
}
|
||||
|
||||
// Initialize Select2 for searchable dropdown
|
||||
if (typeof $ !== 'undefined' && $.fn.select2) {
|
||||
$(selectElement).select2({
|
||||
templateResult: function(state) {
|
||||
if (!state.id) {
|
||||
return state.text;
|
||||
}
|
||||
const option = $(state.element);
|
||||
const flagCode = option.data('flag');
|
||||
return $(`<span><span class="fi fi-${flagCode} me-2"></span>${state.text}</span>`);
|
||||
},
|
||||
templateSelection: function(state) {
|
||||
if (!state.id) {
|
||||
return state.text;
|
||||
}
|
||||
const option = $(state.element);
|
||||
const flagCode = option.data('flag');
|
||||
return $(`<span><span class="fi fi-${flagCode} me-2"></span>${state.text}</span>`);
|
||||
},
|
||||
width: '100%'
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(error => console.error('Error loading countries:', error));
|
||||
});
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
@props(['name' => 'gender', 'id' => 'gender', 'value' => '', 'required' => false, 'error' => null])
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="{{ $id }}" class="form-label">Gender</label>
|
||||
<select id="{{ $id }}"
|
||||
class="form-select @if($error) is-invalid @endif"
|
||||
name="{{ $name }}"
|
||||
{{ $required ? 'required' : '' }}>
|
||||
<option value="">Select Gender</option>
|
||||
<option value="m" {{ $value == 'm' ? 'selected' : '' }}>Male</option>
|
||||
<option value="f" {{ $value == 'f' ? 'selected' : '' }}>Female</option>
|
||||
</select>
|
||||
@if($error)
|
||||
<span class="invalid-feedback" role="alert">
|
||||
<strong>{{ $error }}</strong>
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
@@ -0,0 +1,84 @@
|
||||
@props([
|
||||
'id' => 'image_upload_' . uniqid(),
|
||||
'name' => 'image',
|
||||
'width' => 300,
|
||||
'height' => 300,
|
||||
'shape' => 'square',
|
||||
'folder' => 'uploads',
|
||||
'filename' => 'image_' . time(),
|
||||
'uploadUrl' => '',
|
||||
'currentImage' => '',
|
||||
'placeholder' => 'No image',
|
||||
'placeholderIcon' => 'bi-image',
|
||||
'buttonText' => 'Change Photo',
|
||||
'buttonClass' => 'btn btn-success',
|
||||
'previewClass' => '',
|
||||
'showPreview' => true,
|
||||
'rounded' => false
|
||||
])
|
||||
|
||||
@php
|
||||
$cacheBuster = $currentImage ? '?v=' . time() : '';
|
||||
$imageUrl = $currentImage ? $currentImage . $cacheBuster : '';
|
||||
$borderRadius = $rounded ? '50%' : '8px';
|
||||
$previewWidth = $width . 'px';
|
||||
$previewHeight = $height . 'px';
|
||||
@endphp
|
||||
|
||||
<div class="image-upload-component text-center {{ $previewClass }}">
|
||||
@if($showPreview)
|
||||
<div class="image-preview mb-3">
|
||||
@if($imageUrl)
|
||||
<img src="{{ $imageUrl }}"
|
||||
alt="Preview"
|
||||
id="{{ $id }}_preview"
|
||||
class="image-upload-preview"
|
||||
style="width: {{ $previewWidth }}; height: {{ $previewHeight }}; object-fit: cover; border: 3px solid #dee2e6; border-radius: {{ $borderRadius }};">
|
||||
@else
|
||||
<div class="image-placeholder"
|
||||
id="{{ $id }}_placeholder"
|
||||
style="width: {{ $previewWidth }}; height: {{ $previewHeight }}; background-color: #f0f0f0; border: 3px solid #dee2e6; border-radius: {{ $borderRadius }}; display: flex; align-items: center; justify-content: center; margin: 0 auto;">
|
||||
<div class="text-center">
|
||||
<i class="bi {{ $placeholderIcon }}" style="font-size: 60px; color: #dee2e6;"></i>
|
||||
<p class="text-muted mt-2 mb-0">{{ $placeholder }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<x-takeone-cropper
|
||||
:id="$id"
|
||||
:width="$width"
|
||||
:height="$height"
|
||||
:shape="$shape"
|
||||
:folder="$folder"
|
||||
:filename="$filename"
|
||||
:uploadUrl="$uploadUrl"
|
||||
:currentImage="$currentImage"
|
||||
:buttonText="$buttonText"
|
||||
:buttonClass="$buttonClass"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@once
|
||||
@push('styles')
|
||||
<style>
|
||||
.image-upload-component {
|
||||
display: inline-block;
|
||||
}
|
||||
.image-upload-preview {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
.image-upload-preview:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.image-placeholder {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.image-placeholder:hover {
|
||||
background-color: #e9ecef !important;
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
@endonce
|
||||
@@ -26,7 +26,7 @@
|
||||
onkeydown="event.stopPropagation()"
|
||||
onkeyup="event.stopPropagation()">
|
||||
|
||||
<div class="country-list" id="{{ $id }}List" style="max-height: 300px; overflow-y: auto;">
|
||||
<div class="country-list nationality-dropdown-list" id="{{ $id }}List" data-component-id="{{ $id }}" style="max-height: 300px; overflow-y: auto;">
|
||||
<!-- Countries will be populated by JavaScript -->
|
||||
</div>
|
||||
</div>
|
||||
@@ -72,9 +72,9 @@
|
||||
fetch('/data/countries.json')
|
||||
.then(response => response.json())
|
||||
.then(countries => {
|
||||
// Initialize only nationality dropdowns (not country code dropdowns)
|
||||
document.querySelectorAll('[id$="nationalityList"]').forEach(function(listElement) {
|
||||
const componentId = listElement.id.replace('List', '');
|
||||
// Initialize all nationality/country dropdowns by finding elements with data attribute
|
||||
document.querySelectorAll('.nationality-dropdown-list').forEach(function(listElement) {
|
||||
const componentId = listElement.getAttribute('data-component-id');
|
||||
initializeNationalityDropdown(componentId, countries);
|
||||
});
|
||||
})
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
<div class="mb-3">
|
||||
<label for="{{ $id }}" class="form-label">Timezone</label>
|
||||
<select id="{{ $id }}"
|
||||
class="form-select timezone-select @error($name) is-invalid @enderror"
|
||||
class="form-select timezone-dropdown-select @error($name) is-invalid @enderror"
|
||||
name="{{ $name }}"
|
||||
data-component-id="{{ $id }}"
|
||||
data-initial-value="{{ $value }}"
|
||||
{{ $required ? 'required' : '' }}>
|
||||
<option value="">Select Timezone</option>
|
||||
</select>
|
||||
@@ -23,59 +25,59 @@
|
||||
fetch('/data/countries.json')
|
||||
.then(response => response.json())
|
||||
.then(countries => {
|
||||
const selectElement = document.getElementById('{{ $id }}');
|
||||
if (!selectElement) return;
|
||||
// Initialize all timezone dropdowns
|
||||
document.querySelectorAll('.timezone-dropdown-select').forEach(function(selectElement) {
|
||||
const initialValue = selectElement.getAttribute('data-initial-value');
|
||||
|
||||
// Get unique timezones
|
||||
const uniqueTimezones = {};
|
||||
countries.forEach(country => {
|
||||
if (!uniqueTimezones[country.timezone]) {
|
||||
uniqueTimezones[country.timezone] = {
|
||||
timezone: country.timezone,
|
||||
flag: country.flag,
|
||||
name: country.name
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Populate dropdown
|
||||
Object.values(uniqueTimezones).forEach(timezoneData => {
|
||||
const option = document.createElement('option');
|
||||
option.value = timezoneData.timezone;
|
||||
option.textContent = `${timezoneData.name} (${timezoneData.timezone})`;
|
||||
option.setAttribute('data-flag', timezoneData.flag);
|
||||
selectElement.appendChild(option);
|
||||
});
|
||||
|
||||
// Set initial value if provided
|
||||
const initialValue = '{{ $value }}';
|
||||
if (initialValue) {
|
||||
selectElement.value = initialValue;
|
||||
}
|
||||
|
||||
// Initialize Select2 for searchable dropdown
|
||||
if (typeof $ !== 'undefined' && $.fn.select2) {
|
||||
$(selectElement).select2({
|
||||
templateResult: function(state) {
|
||||
if (!state.id) {
|
||||
return state.text;
|
||||
}
|
||||
const option = $(state.element);
|
||||
const flagCode = option.data('flag');
|
||||
return $(`<span><span class="fi fi-${flagCode} me-2"></span>${state.text}</span>`);
|
||||
},
|
||||
templateSelection: function(state) {
|
||||
if (!state.id) {
|
||||
return state.text;
|
||||
}
|
||||
const option = $(state.element);
|
||||
const flagCode = option.data('flag');
|
||||
return $(`<span><span class="fi fi-${flagCode} me-2"></span>${state.text}</span>`);
|
||||
},
|
||||
width: '100%'
|
||||
// Get unique timezones
|
||||
const uniqueTimezones = {};
|
||||
countries.forEach(country => {
|
||||
if (country.timezone && !uniqueTimezones[country.timezone]) {
|
||||
uniqueTimezones[country.timezone] = {
|
||||
timezone: country.timezone,
|
||||
flag: country.flag,
|
||||
name: country.name
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Populate dropdown
|
||||
Object.values(uniqueTimezones).forEach(timezoneData => {
|
||||
const option = document.createElement('option');
|
||||
option.value = timezoneData.timezone;
|
||||
option.textContent = `${timezoneData.name} (${timezoneData.timezone})`;
|
||||
option.setAttribute('data-flag', timezoneData.flag);
|
||||
selectElement.appendChild(option);
|
||||
});
|
||||
|
||||
// Set initial value if provided
|
||||
if (initialValue) {
|
||||
selectElement.value = initialValue;
|
||||
}
|
||||
|
||||
// Initialize Select2 for searchable dropdown
|
||||
if (typeof $ !== 'undefined' && $.fn.select2) {
|
||||
$(selectElement).select2({
|
||||
templateResult: function(state) {
|
||||
if (!state.id) {
|
||||
return state.text;
|
||||
}
|
||||
const option = $(state.element);
|
||||
const flagCode = option.data('flag');
|
||||
return $(`<span><span class="fi fi-${flagCode} me-2"></span>${state.text}</span>`);
|
||||
},
|
||||
templateSelection: function(state) {
|
||||
if (!state.id) {
|
||||
return state.text;
|
||||
}
|
||||
const option = $(state.element);
|
||||
const flagCode = option.data('flag');
|
||||
return $(`<span><span class="fi fi-${flagCode} me-2"></span>${state.text}</span>`);
|
||||
},
|
||||
width: '100%'
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(error => console.error('Error loading countries:', error));
|
||||
});
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
@props(['position' => 'top-right'])
|
||||
|
||||
@once
|
||||
@push('styles')
|
||||
<style>
|
||||
/* Toast Notification Styles */
|
||||
.toast-container {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
pointer-events: none;
|
||||
}
|
||||
.toast-container.top-right {
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
}
|
||||
.toast-container.top-left {
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
}
|
||||
.toast-container.top-center {
|
||||
top: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
.toast-container.bottom-right {
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
}
|
||||
.toast-container.bottom-left {
|
||||
bottom: 20px;
|
||||
left: 20px;
|
||||
}
|
||||
.toast-container.bottom-center {
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
.toast-notification {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 16px 20px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
|
||||
min-width: 300px;
|
||||
max-width: 400px;
|
||||
margin-bottom: 10px;
|
||||
pointer-events: auto;
|
||||
animation: toastSlideIn 0.3s ease-out;
|
||||
}
|
||||
.toast-notification.hiding {
|
||||
animation: toastFadeOut 0.3s ease-in forwards;
|
||||
}
|
||||
.toast-notification.success {
|
||||
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
||||
color: white;
|
||||
}
|
||||
.toast-notification.error {
|
||||
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
||||
color: white;
|
||||
}
|
||||
.toast-notification.warning {
|
||||
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
|
||||
color: white;
|
||||
}
|
||||
.toast-notification.info {
|
||||
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
||||
color: white;
|
||||
}
|
||||
.toast-notification .toast-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.toast-notification .toast-content {
|
||||
flex: 1;
|
||||
}
|
||||
.toast-notification .toast-title {
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.toast-notification .toast-message {
|
||||
font-size: 13px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
.toast-notification .toast-close {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: none;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
padding: 4px 8px;
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
border-radius: 6px;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.toast-notification .toast-close:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
.toast-notification .toast-progress {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 3px;
|
||||
background: rgba(255, 255, 255, 0.4);
|
||||
border-radius: 0 0 12px 12px;
|
||||
animation: toastProgress 3s linear forwards;
|
||||
}
|
||||
@keyframes toastSlideIn {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes toastFadeOut {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
}
|
||||
@keyframes toastProgress {
|
||||
from { width: 100%; }
|
||||
to { width: 0%; }
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
const Toast = {
|
||||
container: null,
|
||||
position: '{{ $position }}',
|
||||
|
||||
init() {
|
||||
if (!this.container) {
|
||||
this.container = document.createElement('div');
|
||||
this.container.className = `toast-container ${this.position}`;
|
||||
document.body.appendChild(this.container);
|
||||
}
|
||||
return this.container;
|
||||
},
|
||||
|
||||
show(type, title, message, duration = 3000) {
|
||||
const container = this.init();
|
||||
|
||||
const icons = {
|
||||
success: '<i class="bi bi-check-lg"></i>',
|
||||
error: '<i class="bi bi-x-lg"></i>',
|
||||
warning: '<i class="bi bi-exclamation-lg"></i>',
|
||||
info: '<i class="bi bi-info-lg"></i>'
|
||||
};
|
||||
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast-notification ${type}`;
|
||||
toast.style.position = 'relative';
|
||||
toast.innerHTML = `
|
||||
<span class="toast-icon">${icons[type] || icons.info}</span>
|
||||
<div class="toast-content">
|
||||
<div class="toast-title">${title}</div>
|
||||
<div class="toast-message">${message}</div>
|
||||
</div>
|
||||
<button class="toast-close" aria-label="Close">×</button>
|
||||
<div class="toast-progress" style="animation-duration: ${duration}ms"></div>
|
||||
`;
|
||||
|
||||
// Close button handler
|
||||
toast.querySelector('.toast-close').addEventListener('click', () => {
|
||||
this.hide(toast);
|
||||
});
|
||||
|
||||
container.appendChild(toast);
|
||||
|
||||
// Auto remove
|
||||
if (duration > 0) {
|
||||
setTimeout(() => this.hide(toast), duration);
|
||||
}
|
||||
|
||||
return toast;
|
||||
},
|
||||
|
||||
hide(toast) {
|
||||
if (!toast || !toast.parentElement) return;
|
||||
toast.classList.add('hiding');
|
||||
setTimeout(() => {
|
||||
if (toast.parentElement) toast.remove();
|
||||
}, 300);
|
||||
},
|
||||
|
||||
success(title, message, duration) {
|
||||
return this.show('success', title, message, duration);
|
||||
},
|
||||
|
||||
error(title, message, duration) {
|
||||
return this.show('error', title, message, duration);
|
||||
},
|
||||
|
||||
warning(title, message, duration) {
|
||||
return this.show('warning', title, message, duration);
|
||||
},
|
||||
|
||||
info(title, message, duration) {
|
||||
return this.show('info', title, message, duration);
|
||||
}
|
||||
};
|
||||
|
||||
// Global function for backward compatibility
|
||||
function showToast(type, title, message, duration = 3000) {
|
||||
return Toast.show(type, title, message, duration);
|
||||
}
|
||||
</script>
|
||||
@endpush
|
||||
@endonce
|
||||
Reference in new issue
Block a user